diff --git a/Apps/Sandcastle/gallery/Callback Position Property.html b/Apps/Sandcastle/gallery/Callback Position Property.html new file mode 100644 index 00000000000..19dab3dadca --- /dev/null +++ b/Apps/Sandcastle/gallery/Callback Position Property.html @@ -0,0 +1,221 @@ + + +
+ + + + + +true
when the callback function returns the same value every time, false
if the value will change.
+ * @param {ReferenceFrame} [referenceFrame=ReferenceFrame.FIXED] The reference frame in which the position is defined.
+ *
+ * @demo {@link https://sandcastle.cesium.com/index.html?src=Callback%20Position%20Property.html|Cesium Sandcastle Callback Position Property Demo}
+ */
+function CallbackPositionProperty(callback, isConstant, referenceFrame) {
+ this._callback = undefined;
+ this._isConstant = undefined;
+ this._referenceFrame = defaultValue(referenceFrame, ReferenceFrame.FIXED);
+ this._definitionChanged = new Event();
+ this.setCallback(callback, isConstant);
+}
+
+Object.defineProperties(CallbackPositionProperty.prototype, {
+ /**
+ * Gets a value indicating if this property is constant.
+ * @memberof CallbackPositionProperty.prototype
+ *
+ * @type {boolean}
+ * @readonly
+ */
+ isConstant: {
+ get: function () {
+ return this._isConstant;
+ },
+ },
+ /**
+ * Gets the event that is raised whenever the definition of this property changes.
+ * The definition is considered to have changed if a call to getValue would return
+ * a different result for the same time.
+ * @memberof CallbackPositionProperty.prototype
+ *
+ * @type {Event}
+ * @readonly
+ */
+ definitionChanged: {
+ get: function () {
+ return this._definitionChanged;
+ },
+ },
+ /**
+ * Gets the reference frame in which the position is defined.
+ * @memberof CallbackPositionProperty.prototype
+ * @type {ReferenceFrame}
+ * @default ReferenceFrame.FIXED;
+ */
+ referenceFrame: {
+ get: function () {
+ return this._referenceFrame;
+ },
+ },
+});
+
+const timeScratch = new JulianDate();
+
+/**
+ * Gets the value of the property at the provided time in the fixed frame.
+ *
+ * @param {JulianDate} [time=JulianDate.now()] The time for which to retrieve the value. If omitted, the current system time is used.
+ * @param {Cartesian3} [result] The object to store the value into, if omitted, a new instance is created and returned.
+ * @returns {Cartesian3 | undefined} The modified result parameter or a new instance if the result parameter was not supplied.
+ */
+CallbackPositionProperty.prototype.getValue = function (time, result) {
+ if (!defined(time)) {
+ time = JulianDate.now(timeScratch);
+ }
+ return this.getValueInReferenceFrame(time, ReferenceFrame.FIXED, result);
+};
+
+/**
+ * Sets the callback to be used.
+ *
+ * @param {CallbackPositionProperty.Callback} callback The function to be called when the property is evaluated.
+ * @param {boolean} isConstant true
when the callback function returns the same value every time, false
if the value will change.
+ */
+CallbackPositionProperty.prototype.setCallback = function (
+ callback,
+ isConstant
+) {
+ //>>includeStart('debug', pragmas.debug);
+ if (!defined(callback)) {
+ throw new DeveloperError("callback is required.");
+ }
+ if (!defined(isConstant)) {
+ throw new DeveloperError("isConstant is required.");
+ }
+ //>>includeEnd('debug');
+
+ const changed =
+ this._callback !== callback || this._isConstant !== isConstant;
+
+ this._callback = callback;
+ this._isConstant = isConstant;
+
+ if (changed) {
+ this._definitionChanged.raiseEvent(this);
+ }
+};
+
+/**
+ * Gets the value of the property at the provided time and in the provided reference frame.
+ *
+ * @param {JulianDate} time The time for which to retrieve the value.
+ * @param {ReferenceFrame} referenceFrame The desired referenceFrame of the result.
+ * @param {Cartesian3} [result] The object to store the value into, if omitted, a new instance is created and returned.
+ * @returns {Cartesian3 | undefined} The modified result parameter or a new instance if the result parameter was not supplied.
+ */
+CallbackPositionProperty.prototype.getValueInReferenceFrame = function (
+ time,
+ referenceFrame,
+ result
+) {
+ //>>includeStart('debug', pragmas.debug);
+ if (!defined(time)) {
+ throw new DeveloperError("time is required.");
+ }
+ if (!defined(referenceFrame)) {
+ throw new DeveloperError("referenceFrame is required.");
+ }
+ //>>includeEnd('debug');
+
+ const value = this._callback(time, result);
+
+ return PositionProperty.convertToReferenceFrame(
+ time,
+ value,
+ this._referenceFrame,
+ referenceFrame,
+ result
+ );
+};
+
+/**
+ * Compares this property to the provided property and returns
+ * true
if they are equal, false
otherwise.
+ *
+ * @param {Property} [other] The other property.
+ * @returns {boolean} true
if left and right are equal, false
otherwise.
+ */
+CallbackPositionProperty.prototype.equals = function (other) {
+ return (
+ this === other ||
+ (other instanceof CallbackPositionProperty &&
+ this._callback === other._callback &&
+ this._isConstant === other._isConstant &&
+ this._referenceFrame === other._referenceFrame)
+ );
+};
+
+/**
+ * A function that returns the value of the position property.
+ * @callback CallbackPositionProperty.Callback
+ *
+ * @param {JulianDate} [time=JulianDate.now()] The time for which to retrieve the value. If omitted, the current system time is used.
+ * @param {Cartesian3} [result] The object to store the value into. If omitted, the function must create and return a new instance.
+ * @returns {Cartesian3 | undefined} The modified result parameter, or a new instance if the result parameter was not supplied or is unsupported.
+ */
+export default CallbackPositionProperty;
diff --git a/packages/engine/Source/DataSources/PathVisualizer.js b/packages/engine/Source/DataSources/PathVisualizer.js
index 8ae9a415fff..6c99fc36815 100644
--- a/packages/engine/Source/DataSources/PathVisualizer.js
+++ b/packages/engine/Source/DataSources/PathVisualizer.js
@@ -11,6 +11,7 @@ import TimeInterval from "../Core/TimeInterval.js";
import Transforms from "../Core/Transforms.js";
import PolylineCollection from "../Scene/PolylineCollection.js";
import SceneMode from "../Scene/SceneMode.js";
+import CallbackPositionProperty from "./CallbackPositionProperty.js";
import CompositePositionProperty from "./CompositePositionProperty.js";
import ConstantPositionProperty from "./ConstantPositionProperty.js";
import MaterialProperty from "./MaterialProperty.js";
@@ -135,6 +136,58 @@ function subSampleSampledProperty(
return r;
}
+function subSampleCallbackPositionProperty(
+ property,
+ start,
+ stop,
+ updateTime,
+ referenceFrame,
+ maximumStep,
+ startingIndex,
+ result
+) {
+ let tmp;
+ let i = 0;
+ let index = startingIndex;
+ let time = start;
+ let steppedOnNow =
+ !defined(updateTime) ||
+ JulianDate.lessThanOrEquals(updateTime, start) ||
+ JulianDate.greaterThanOrEquals(updateTime, stop);
+ while (JulianDate.lessThan(time, stop)) {
+ if (!steppedOnNow && JulianDate.greaterThanOrEquals(time, updateTime)) {
+ steppedOnNow = true;
+ tmp = property.getValueInReferenceFrame(
+ updateTime,
+ referenceFrame,
+ result[index]
+ );
+ if (defined(tmp)) {
+ result[index] = tmp;
+ index++;
+ }
+ }
+ tmp = property.getValueInReferenceFrame(
+ time,
+ referenceFrame,
+ result[index]
+ );
+ if (defined(tmp)) {
+ result[index] = tmp;
+ index++;
+ }
+ i++;
+ time = JulianDate.addSeconds(start, maximumStep * i, new JulianDate());
+ }
+ //Always sample stop.
+ tmp = property.getValueInReferenceFrame(stop, referenceFrame, result[index]);
+ if (defined(tmp)) {
+ result[index] = tmp;
+ index++;
+ }
+ return index;
+}
+
function subSampleGenericProperty(
property,
start,
@@ -339,6 +392,17 @@ function reallySubSample(
index,
result
);
+ } else if (property instanceof CallbackPositionProperty) {
+ index = subSampleCallbackPositionProperty(
+ property,
+ start,
+ stop,
+ updateTime,
+ referenceFrame,
+ maximumStep,
+ index,
+ result
+ );
} else if (property instanceof CompositePositionProperty) {
index = subSampleCompositeProperty(
property,
diff --git a/packages/engine/Source/DataSources/PositionProperty.js b/packages/engine/Source/DataSources/PositionProperty.js
index 66a9c384b67..17bce66ed19 100644
--- a/packages/engine/Source/DataSources/PositionProperty.js
+++ b/packages/engine/Source/DataSources/PositionProperty.js
@@ -14,6 +14,7 @@ import Transforms from "../Core/Transforms.js";
* @constructor
* @abstract
*
+ * @see CallbackPositionProperty
* @see CompositePositionProperty
* @see ConstantPositionProperty
* @see SampledPositionProperty
diff --git a/packages/engine/Specs/DataSources/CallbackPositionPropertySpec.js b/packages/engine/Specs/DataSources/CallbackPositionPropertySpec.js
new file mode 100644
index 00000000000..c9c2470f89a
--- /dev/null
+++ b/packages/engine/Specs/DataSources/CallbackPositionPropertySpec.js
@@ -0,0 +1,196 @@
+import {
+ JulianDate,
+ CallbackPositionProperty,
+ Cartesian3,
+ PositionProperty,
+ ReferenceFrame,
+} from "../../index.js";
+
+describe("DataSources/CallbackPositionProperty", function () {
+ const time = JulianDate.now();
+
+ it("constructor throws with undefined isConstant", function () {
+ expect(function () {
+ return new CallbackPositionProperty(function () {}, undefined);
+ }).toThrowDeveloperError();
+ });
+
+ it("constructor throws with undefined callback", function () {
+ expect(function () {
+ return new CallbackPositionProperty(undefined, true);
+ }).toThrowDeveloperError();
+ });
+
+ it("constructor sets expected defaults", function () {
+ const callback = jasmine.createSpy("callback");
+ let property = new CallbackPositionProperty(callback, true);
+ expect(property.referenceFrame).toBe(ReferenceFrame.FIXED);
+
+ property = new CallbackPositionProperty(
+ callback,
+ true,
+ ReferenceFrame.INERTIAL
+ );
+ expect(property.referenceFrame).toBe(ReferenceFrame.INERTIAL);
+ });
+
+ it("callback received proper parameters", function () {
+ const result = {};
+ const callback = jasmine.createSpy("callback");
+ const property = new CallbackPositionProperty(callback, true);
+ property.getValue(time, result);
+ expect(callback).toHaveBeenCalledWith(time, result);
+ });
+
+ it("getValue returns callback result", function () {
+ const value = new Cartesian3(1, 2, 3);
+ const callback = function (_time, result) {
+ return value.clone(result);
+ };
+ const property = new CallbackPositionProperty(callback, true);
+ const result = property.getValue(time);
+ expect(result).not.toBe(value);
+ expect(result).toEqual(value);
+
+ const value2 = new Cartesian3();
+ expect(property.getValue(time, value2)).toBe(value2);
+ });
+
+ it("getValue returns in fixed frame", function () {
+ const valueInertial = new Cartesian3(1, 2, 3);
+ const valueFixed = PositionProperty.convertToReferenceFrame(
+ time,
+ valueInertial,
+ ReferenceFrame.INERTIAL,
+ ReferenceFrame.FIXED
+ );
+ const callback = function (_time, result) {
+ return valueInertial.clone(result);
+ };
+ const property = new CallbackPositionProperty(
+ callback,
+ true,
+ ReferenceFrame.INERTIAL
+ );
+
+ const result = property.getValue(time);
+ expect(result).toEqual(valueFixed);
+ });
+
+ it("getValue uses JulianDate.now() if time parameter is undefined", function () {
+ spyOn(JulianDate, "now").and.callThrough();
+
+ const value = new Cartesian3(1, 2, 3);
+ const callback = function (_time, result) {
+ return value.clone(result);
+ };
+ const property = new CallbackPositionProperty(callback, true);
+ const actualResult = property.getValue();
+ expect(JulianDate.now).toHaveBeenCalled();
+ expect(actualResult).toEqual(value);
+ });
+
+ it("getValueInReferenceFrame works without a result parameter", function () {
+ const value = new Cartesian3(1, 2, 3);
+ const callback = function (_time, result) {
+ return value.clone(result);
+ };
+ const property = new CallbackPositionProperty(callback, true);
+
+ const result = property.getValueInReferenceFrame(
+ time,
+ ReferenceFrame.INERTIAL
+ );
+ expect(result).not.toBe(value);
+ expect(result).toEqual(
+ PositionProperty.convertToReferenceFrame(
+ time,
+ value,
+ ReferenceFrame.FIXED,
+ ReferenceFrame.INERTIAL
+ )
+ );
+ });
+
+ it("getValueInReferenceFrame works with a result parameter", function () {
+ const value = new Cartesian3(1, 2, 3);
+ const callback = function (_time, result) {
+ return value.clone(result);
+ };
+ const property = new CallbackPositionProperty(
+ callback,
+ true,
+ ReferenceFrame.INERTIAL
+ );
+
+ const expected = new Cartesian3();
+ const result = property.getValueInReferenceFrame(
+ time,
+ ReferenceFrame.FIXED,
+ expected
+ );
+ expect(result).toBe(expected);
+ expect(expected).toEqual(
+ PositionProperty.convertToReferenceFrame(
+ time,
+ value,
+ ReferenceFrame.INERTIAL,
+ ReferenceFrame.FIXED
+ )
+ );
+ });
+
+ it("getValueInReferenceFrame throws with undefined time", function () {
+ const property = new CallbackPositionProperty(function () {}, true);
+
+ expect(function () {
+ property.getValueInReferenceFrame(undefined, ReferenceFrame.FIXED);
+ }).toThrowDeveloperError();
+ });
+
+ it("getValueInReferenceFrame throws with undefined reference frame", function () {
+ const property = new CallbackPositionProperty(function () {}, true);
+
+ expect(function () {
+ property.getValueInReferenceFrame(time, undefined);
+ }).toThrowDeveloperError();
+ });
+
+ it("isConstant returns correct value", function () {
+ const property = new CallbackPositionProperty(function () {}, true);
+ expect(property.isConstant).toBe(true);
+ property.setCallback(function () {}, false);
+ expect(property.isConstant).toBe(false);
+ });
+
+ it("setCallback raises definitionChanged event", function () {
+ const property = new CallbackPositionProperty(function () {}, true);
+ const listener = jasmine.createSpy("listener");
+ property.definitionChanged.addEventListener(listener);
+ property.setCallback(function () {}, false);
+ expect(listener).toHaveBeenCalledWith(property);
+ });
+
+ it("equals works", function () {
+ const callback = function () {};
+ const left = new CallbackPositionProperty(callback, true);
+ let right = new CallbackPositionProperty(callback, true);
+
+ expect(left.equals(right)).toEqual(true);
+
+ right.setCallback(callback, false);
+ expect(left.equals(right)).toEqual(false);
+
+ right.setCallback(function () {
+ return undefined;
+ }, true);
+ expect(left.equals(right)).toEqual(false);
+
+ right = new CallbackPositionProperty(
+ callback,
+ true,
+ ReferenceFrame.INERTIAL
+ );
+ expect(left.equals(right)).toEqual(false);
+ });
+});
diff --git a/packages/engine/Specs/DataSources/ConstantPositionPropertySpec.js b/packages/engine/Specs/DataSources/ConstantPositionPropertySpec.js
index 7c1442cdef9..0d4c734aeef 100644
--- a/packages/engine/Specs/DataSources/ConstantPositionPropertySpec.js
+++ b/packages/engine/Specs/DataSources/ConstantPositionPropertySpec.js
@@ -61,7 +61,7 @@ describe("DataSources/ConstantPositionProperty", function () {
expect(property.getValue(time)).toBeUndefined();
});
- it("getValue work swith undefined inertial value", function () {
+ it("getValue works with undefined inertial value", function () {
const property = new ConstantPositionProperty(
undefined,
ReferenceFrame.INERTIAL
diff --git a/packages/engine/Specs/DataSources/PathVisualizerSpec.js b/packages/engine/Specs/DataSources/PathVisualizerSpec.js
index c34f20ce89d..ce9ecfff278 100644
--- a/packages/engine/Specs/DataSources/PathVisualizerSpec.js
+++ b/packages/engine/Specs/DataSources/PathVisualizerSpec.js
@@ -16,6 +16,8 @@ import {
PolylineOutlineMaterialProperty,
ReferenceProperty,
SampledPositionProperty,
+ CallbackPositionProperty,
+ LinearSpline,
ScaledPositionProperty,
TimeIntervalCollectionPositionProperty,
SceneMode,
@@ -733,6 +735,58 @@ describe(
expect(result).toEqual([new Cartesian3(0, 0, 3)]);
});
+ it("subSample works for callback position properties", function () {
+ const t1 = new JulianDate(0, 0);
+ const t2 = new JulianDate(2, 0);
+ const updateTime = new JulianDate(1, 1);
+ const duration = JulianDate.secondsDifference(t2, t1);
+ const spline = new LinearSpline({
+ times: [0, 1],
+ points: [new Cartesian3(0, 0, 0), new Cartesian3(0, 0, 1)],
+ });
+ const callback = function (time, result) {
+ if (JulianDate.lessThan(time, t1) || JulianDate.greaterThan(time, t2)) {
+ return undefined;
+ }
+ if (result === undefined) {
+ result = new Cartesian3();
+ }
+ const delta = JulianDate.secondsDifference(time, t1);
+ return spline.evaluate(delta / duration, result);
+ };
+
+ const property = new CallbackPositionProperty(callback, false);
+
+ const referenceFrame = ReferenceFrame.FIXED;
+ const maximumStep = 43200;
+ const result = [];
+ PathVisualizer._subSample(
+ property,
+ t1,
+ t2,
+ updateTime,
+ referenceFrame,
+ maximumStep,
+ result
+ );
+ expect(result).toEqual([
+ property.getValue(t1),
+ property.getValue(
+ JulianDate.addSeconds(t1, maximumStep, new JulianDate())
+ ),
+ property.getValue(
+ JulianDate.addSeconds(t1, maximumStep * 2, new JulianDate())
+ ),
+ property.getValue(updateTime),
+ property.getValue(
+ JulianDate.addSeconds(t1, maximumStep * 3, new JulianDate())
+ ),
+ property.getValue(
+ JulianDate.addSeconds(t1, maximumStep * 4, new JulianDate())
+ ),
+ ]);
+ });
+
function CustomPositionProperty(innerProperty) {
this.SampledProperty = innerProperty;
this.isConstant = innerProperty.isConstant;