Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions packages/engine/Source/Core/Math.js
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,64 @@ CesiumMath.lerp = function (p, q, time) {
return (1.0 - time) * p + time * q;
};

/** @typedef {Object} SmoothDampResult
* @property {number} value The new value after applying the smooth damp.
* @property {number} velocity The updated current velocity.
*/

/**
* Gradually changes a value towards a target value over time. The smoothing function uses a spring-damping algorithm based on Game Programming Gems 4 Chapter 1.10.
* @param {number} p The current value.
* @param {number} q The target value.
* @param {number} velocity The current velocity.
* @param {number} [deltaTime=0.0] The time since the last call to this function. Value must be greater than or equal to 0.0.
* @param {number} [maximumSpeed=Number.POSITIVE_INFINITY] Optionally allows clamping to the specified maximum speed.
* @param {number} [smoothTime=0.0001] Approximately the time it will take to reach the target. A smaller value will reach the target faster. This value must be greater than or equal to 0.0001.
* @param {SmoothDampResult} [result] An object to store the result. If not provided, a new object will be created and returned.
* @returns {SmoothDampResult} An object containing the new value and the updated current velocity.
*/
CesiumMath.smoothDamp = function (
p,
q,
velocity,
deltaTime = 0.0,
maximumSpeed = Number.POSITIVE_INFINITY,
smoothTime = 0.0001,
result = {},
) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.number("p", p);
Check.typeOf.number("q", q);
Check.typeOf.number("velocity", velocity);
Check.typeOf.number.greaterThanOrEquals("deltaTime", deltaTime, 0.0);
Check.typeOf.number.greaterThanOrEquals("maximumSpeed", maximumSpeed, 0.0);
Check.typeOf.number.greaterThanOrEquals("smoothTime", smoothTime, 0.0001);
Check.typeOf.object("result", result);
//>>includeEnd('debug');

// As a fallback, prevent crashes even if smoothTime is too small
smoothTime = Math.max(0.0001, smoothTime);
const omega = 2.0 / smoothTime;

const x = omega * deltaTime;
const exp = 1.0 / (1.0 + x + 0.48 * x * x + 0.235 * x * x * x);

const maxChange = maximumSpeed * smoothTime;
let change = p - q;
change = CesiumMath.clamp(change, -maxChange, maxChange);

const target = p - change;

const temp = (velocity + omega * change) * deltaTime;

velocity = (velocity - omega * temp) * exp;

result.value = target + (change + temp) * exp;
result.velocity = velocity;

return result;
};

/**
* pi
*
Expand Down Expand Up @@ -1122,4 +1180,5 @@ CesiumMath.fastApproximateAtan2 = function (x, y) {
t = y < 0.0 ? -t : t;
return t;
};

export default CesiumMath;
6 changes: 4 additions & 2 deletions packages/engine/Source/Core/getTimestamp.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// @ts-check

/**
* Gets a timestamp that can be used in measuring the time between events. Timestamps
* are expressed in milliseconds, but it is not specified what the milliseconds are
* measured from. This function uses performance.now() if it is available, or Date.now()
* otherwise.
*
* @type {Function}
* @function getTimestamp
*
* @returns {number} The timestamp in milliseconds since some unspecified reference time.
*/
let getTimestamp;
Expand All @@ -23,4 +24,5 @@ if (
return Date.now();
};
}

export default getTimestamp;
76 changes: 71 additions & 5 deletions packages/engine/Source/Scene/Camera.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Cartesian2 from "../Core/Cartesian2.js";
import Cartesian3 from "../Core/Cartesian3.js";
import Cartesian4 from "../Core/Cartesian4.js";
import Cartographic from "../Core/Cartographic.js";
import Check from "../Core/Check.js";
import Frozen from "../Core/Frozen.js";
import defined from "../Core/defined.js";
import DeveloperError from "../Core/DeveloperError.js";
Expand Down Expand Up @@ -2356,7 +2357,7 @@ const scratchLookAtHeadingPitchRangeQuaternion1 = new Quaternion();
const scratchLookAtHeadingPitchRangeQuaternion2 = new Quaternion();
const scratchHeadingPitchRangeMatrix3 = new Matrix3();

function offsetFromHeadingPitchRange(heading, pitch, range) {
function offsetFromHeadingPitchRange(heading, pitch, range, result) {
pitch = CesiumMath.clamp(
pitch,
-CesiumMath.PI_OVER_TWO,
Expand All @@ -2380,10 +2381,7 @@ function offsetFromHeadingPitchRange(heading, pitch, range) {
scratchHeadingPitchRangeMatrix3,
);

const offset = Cartesian3.clone(
Cartesian3.UNIT_X,
scratchLookAtHeadingPitchRangeOffset,
);
const offset = Cartesian3.clone(Cartesian3.UNIT_X, result);
Matrix3.multiplyByVector(rotMatrix, offset, offset);
Cartesian3.negate(offset, offset);
Cartesian3.multiplyByScalar(offset, range, offset);
Expand Down Expand Up @@ -2441,6 +2439,7 @@ Camera.prototype.lookAtTransform = function (transform, offset) {
offset.heading,
offset.pitch,
offset.range,
scratchLookAtHeadingPitchRangeOffset,
);
} else {
cartesianOffset = offset;
Expand Down Expand Up @@ -2492,6 +2491,72 @@ Camera.prototype.lookAtTransform = function (transform, offset) {
this._adjustOrthographicFrustum(true);
};

const scratchLookAtWorldPositionTransform = new Matrix4();
const scratchLookAtWorldPositionDirection = new Cartesian3();
const scratchLookAtWorldPositionWorldUp = new Cartesian3();
const scratchLookAtWorldPositionRight = new Cartesian3();

/**
* Sets the camera orientation to look at a target position in world coordinates. The camera's up vector will be oriented to the world up vector at the target position.
* If the camera is at the target position, the camera will be oriented to the world up vector at the target position.
* @param {Cartesian3} target The target position in world coordinates.
* @param {Ellipsoid} [ellipsoid=Ellipsoid.default] The ellipsoid to use for determining the world up.
*/
Camera.prototype.lookAtWorldPosition = function (
target,
ellipsoid = Ellipsoid.default,
) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.object("target", target);
Check.typeOf.object("ellipsoid", ellipsoid);
//>>includeEnd('debug');

const transform = Matrix4.clone(
this._transform,
scratchLookAtWorldPositionTransform,
);

this._setTransform(Matrix4.IDENTITY);

// Get direction to look at target
let direction = Cartesian3.subtract(
target,
this.positionWC,
scratchLookAtWorldPositionDirection,
);

// If the camera is at the target position, we can't look at it, but we should still continue to re-orient the camera to the world up vector at the target position.
if (Cartesian3.magnitudeSquared(direction) < CesiumMath.EPSILON8) {
direction = Cartesian3.clone(
this.directionWC,
scratchLookAtWorldPositionDirection,
);
}

direction = Cartesian3.normalize(direction, this.direction);

// Orient the camera to the world up vector at the target position
const worldUp = ellipsoid.geodeticSurfaceNormal(
target,
scratchLookAtWorldPositionWorldUp,
);

let right = Cartesian3.cross(
direction,
worldUp,
scratchLookAtWorldPositionRight,
);
if (Cartesian3.magnitudeSquared(right) < CesiumMath.EPSILON8) {
right = Cartesian3.clone(this.rightWC, scratchLookAtWorldPositionRight);
}
Cartesian3.normalize(right, this.right);

const up = Cartesian3.cross(right, direction, this.up);
Cartesian3.normalize(up, this.up);

this._setTransform(transform);
};

const viewRectangle3DCartographic1 = new Cartographic();
const viewRectangle3DCartographic2 = new Cartographic();
const viewRectangle3DNorthEast = new Cartesian3();
Expand Down Expand Up @@ -3621,6 +3686,7 @@ Camera.prototype.flyToBoundingSphere = function (boundingSphere, options) {
offset.heading,
offset.pitch,
offset.range,
scratchflyToBoundingSphereDestination,
);
}

Expand Down
63 changes: 63 additions & 0 deletions packages/engine/Source/Scene/Controllers/Controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import DeveloperError from "../../Core/DeveloperError.js";

/**
* An interface for a camera controller that can be registered with the scene to handle input events, camera animations, and other interactions. Implementations of this interface are expected to be registered with the scene via a {@link ControllerHost}.
* This type describes an
* interface and is not intended to be instantiated directly.
* @class
* @abstract
* @see {@link HybridScreenSpacePanCameraController}
* @see {@link ScreenSpaceElevatorCameraController}
* @see {@link ScreenSpaceMapCameraController}
* @see {@link ScreenSpaceTiltOrbitCameraController}
*/
class Controller {
/**
* Determines if the controller is enabled and should be updated by the host scene.
* @type {boolean}
*/
get enabled() {
return DeveloperError.throwInstantiationError();
}
set enabled(value) {
DeveloperError.throwInstantiationError();
}

/**
* Invoked when the controller is added to the DOM. Implement <code>connectedCallback</code> to set up any DOM event listeners.
* @param {HTMLElement} element The DOM element containing the Cesium scene.
*/
connectedCallback(element) {
DeveloperError.throwInstantiationError();
}

/**
* Invoked when the controller is removed from the DOM. Implement <code>disconnectedCallback</code> to tear down any DOM event listeners.
* @param {HTMLElement} element The DOM element containing the Cesium scene.
*/
disconnectedCallback(element) {
DeveloperError.throwInstantiationError();
}

/**
* Invoked once per frame. Implement <code>update</code> to modify the camera or other parts of the scene.
* @see {@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/#updaterender-cycle-events|Update/Render Cycle Events}
* @param {Scene} scene
* @param {JulianDate} time The current simulation time.
*/
update(scene, time) {
DeveloperError.throwInstantiationError();
}

/**
* Invoked when the controller is being updated the first time, immediately before <code>update</code> is called. Implement <code>firstUpdate</code> to perform one-time work after the relevant scene has begun its render loop. Some examples might include initializing simulation time values or adding a primitive to the scene.
* @see Controller#update
* @param {Scene} scene
* @param {JulianDate} time The current simulation time.
*/
firstUpdate(scene, time) {
DeveloperError.throwInstantiationError();
}
}

export default Controller;
80 changes: 80 additions & 0 deletions packages/engine/Source/Scene/Controllers/ControllerHost.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/**
* Collects an array of Controller objects that can be registered with the scene to handle input events, camera animations, and other interactions.
* @class
* @see {@link Controller}
* @see {@link Scene#controllerHost}
*/
class ControllerHost {
/**
* Creates an instance of a <code>ControllerHost</code>. Typically, a <code>ControllerHost</code> is created by the Scene constructor and accessed via {@link Scene#controllerHost}.
* @constructor
* @alias ControllerHost
* @see {@link Scene#controllerHost}
*/
constructor() {
/**
* @type {Controller[]}
* @private
*/
this._controllers = [];
this._needsUpdate = new Set();
}

/**
* The number of controllers registered to this host.
* @type {number}
* @readonly
*/
get controllerCount() {
return this._controllers.length;
}

/**
* Registers a controller implementation with this host.
* @param {Controller} controller An implementation of the Controller interface to register with this host.
* @param {HTMLElement} element The DOM element containing the Cesium scene.
* @param {number} [priority=0] An index, less than or equal to the current count of registed controllers, that defines the precedence of the new controller relative to those previously registered. A priority of <code>0</code> would mean the new controller would apply its updates before any other controller. As subsequent controllers are updated, their effects are applied on top of any previous update effects. If omitted, the new controller becomes the highest priority, i.e., its updates are applied after all other controllers.
*/
registerController(controller, element, priority) {
const index = priority ?? this.controllerCount;
this._controllers.splice(index, 0, controller);
this._needsUpdate.add(controller);
controller.connectedCallback(element);
}

/**
* Unregisters a controller implementation from this host.
* @param {Controller} controller An implementation of the Controller interface to unregister from this host.
* @param {HTMLElement} element The DOM element containing the Cesium scene.
*/
unregisterController(controller, element) {
const controllers = this._controllers;
const index = controllers.indexOf(controller);
if (index !== -1) {
controllers.splice(index, 1);
controller.disconnectedCallback(element);
}
}

/**
* Invoked once per frame by the host scene. Updates all registered controllers in order of their priority.
* @param {Scene} scene The host scene.
* @param {JulianDate} time The current simulation time.
*/
update(scene, time) {
for (const controller of this._controllers) {
if (!controller.enabled) {
continue;
}

if (this._needsUpdate.has(controller)) {
controller.firstUpdate(scene, time);
this._needsUpdate.delete(controller);
}

controller.update(scene, time);
}
}
}

export default ControllerHost;
Loading
Loading