UNPKG

@babylonjs/core

Version:

Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.

186 lines 9.76 kB
import { Vector3 } from "../Maths/math.vector.js"; import { Epsilon } from "../Maths/math.constants.js"; const FrameDurationAt60FPS = 1000 / 60; /** * Holds all logic related to converting input pixel deltas into current frame deltas, taking speed / framerate into account * to ensure smooth frame-rate-independent movement */ export class CameraMovement { constructor(scene, _cameraPosition, _behavior) { this._cameraPosition = _cameraPosition; this._behavior = _behavior; /** * Should be set by input classes to indicates whether there is active input this frame * This helps us differentiate between 0 pixel delta due to no input vs user actively holding still */ this.activeInput = false; /** * ------------ Speed ---------------- * Speed defines the amount of camera movement expected per input pixel movement * ----------------------------------- */ /** * Desired coordinate unit movement per input pixel when zooming */ this.zoomSpeed = 1; /** * Desired coordinate unit movement per input pixel when panning */ this.panSpeed = 1; /** * Desired radians movement per input pixel when rotating along x axis */ this.rotationXSpeed = 1; /** * Desired radians movement per input pixel when rotating along y axis */ this.rotationYSpeed = 1; /** * ----------- Speed multipliers --------------- * Multipliers allow movement classes to modify the effective speed dynamically per-frame * (ex: scale zoom based on distance from target) * ----------------------------------- */ /** * Multiplied atop zoom speed. Used to dynamically adjust zoom speed based on per-frame context (ex: zoom faster when further from target) */ this._zoomSpeedMultiplier = 1; /** * Multiplied atop pan speed. Used to dynamically adjust pan speed based on per-frame context (ex: pan slowly when close to target) */ this._panSpeedMultiplier = 1; /** * ---------- Inertia ---------------- * Inertia represents the decay factor per-frame applied to the velocity when there is no user input. * 0 = No inertia, instant stop (velocity immediately becomes 0) * 0.5 = Strong decay, velocity halves every frame at 60fps * 0.9 = Moderate inertia, velocity retains 90% per frame at 60fps * 0.95 = High inertia, smooth glide, velocity retains 95% per frame at 60fps * 1 = Infinite inertia, never stops (velocity never decays) * ----------------------------------- */ /** * Inertia applied to the zoom velocity when there is no user input. * Higher inertia === slower decay, velocity retains more of its value each frame */ this.zoomInertia = 0.9; /** * Inertia applied to the panning velocity when there is no user input. * Higher inertia === slower decay, velocity retains more of its value each frame */ this.panInertia = 0.9; /** * Inertia applied to the rotation velocity when there is no user input. * Higher inertia === slower decay, velocity retains more of its value each frame */ this.rotationInertia = 0.9; /** * ---------- Accumulated Pixel Deltas ----------- * Pixel inputs accumulated throughout the frame by input classes (reset each frame after processing) * ----------------------------------- */ /** * Accumulated pixel delta (by input classes) for zoom this frame * Read by computeCurrentFrameDeltas() function and converted into zoomDeltaCurrentFrame (taking speed into account) * Reset to zero after each frame */ this.zoomAccumulatedPixels = 0; /** * Accumulated pixel delta (by input classes) for panning this frame * Read by computeCurrentFrameDeltas() function and converted into panDeltaCurrentFrame (taking speed into account) * Reset to zero after each frame */ this.panAccumulatedPixels = new Vector3(); /** * Accumulated pixel delta (by input classes) for rotation this frame * Read by computeCurrentFrameDeltas() function and converted into rotationDeltaCurrentFrame (taking speed into account) * Reset to zero after each frame */ this.rotationAccumulatedPixels = new Vector3(); /** * ---------- Current Frame Movement Deltas ----------- * Deltas read on each frame by camera class in order to move the camera * ----------------------------------- */ /** * Zoom delta to apply to camera this frame, computed by computeCurrentFrameDeltas() from zoomPixelDelta (taking speed into account) */ this.zoomDeltaCurrentFrame = 0; /** * Pan delta to apply to camera this frame, computed by computeCurrentFrameDeltas() from panPixelDelta (taking speed into account) */ this.panDeltaCurrentFrame = Vector3.Zero(); /** * Rotation delta to apply to camera this frame, computed by computeCurrentFrameDeltas() from rotationPixelDelta (taking speed into account) */ this.rotationDeltaCurrentFrame = Vector3.Zero(); /** * ---------- Velocity ----------- * Used to track velocity between frames for inertia calculation * ----------------------------------- */ /** * Zoom pixel velocity used for inertia calculations (pixels / ms). */ this._zoomVelocity = 0; /** * Pan velocity used for inertia calculations (movement / time) */ this._panVelocity = new Vector3(); /** * Rotation velocity used for inertia calculations (movement / time) */ this._rotationVelocity = new Vector3(); /** * Used when calculating inertial decay. Default to 60fps */ this._prevFrameTimeMs = FrameDurationAt60FPS; this._scene = scene; } /** * When called, will take the accumulated pixel deltas set by input classes and convert them into current frame deltas, stored in currentFrameMovementDelta properties * Takes speed, scaling, inertia, and framerate into account to ensure smooth movement * Zeros out pixelDeltas before returning */ computeCurrentFrameDeltas() { const deltaTimeMs = this._scene.getEngine().getDeltaTime(); this.panDeltaCurrentFrame.setAll(0); this.rotationDeltaCurrentFrame.setAll(0); this.zoomDeltaCurrentFrame = 0; const hasUserInput = this.panAccumulatedPixels.lengthSquared() > 0 || this.rotationAccumulatedPixels.lengthSquared() > 0 || this.zoomAccumulatedPixels !== 0; if (hasUserInput && this._behavior?.isInterpolating) { this._behavior.stopAllAnimations(); } this._panVelocity.copyFromFloats(this._calculateCurrentVelocity(this._panVelocity.x, this.panAccumulatedPixels.x, this.panInertia), this._calculateCurrentVelocity(this._panVelocity.y, this.panAccumulatedPixels.y, this.panInertia), this._calculateCurrentVelocity(this._panVelocity.z, this.panAccumulatedPixels.z, this.panInertia)); this._panVelocity.scaleToRef(this.panSpeed * this._panSpeedMultiplier * deltaTimeMs, this.panDeltaCurrentFrame); this._rotationVelocity.copyFromFloats(this._calculateCurrentVelocity(this._rotationVelocity.x, this.rotationAccumulatedPixels.x, this.rotationInertia), this._calculateCurrentVelocity(this._rotationVelocity.y, this.rotationAccumulatedPixels.y, this.rotationInertia), this._calculateCurrentVelocity(this._rotationVelocity.z, this.rotationAccumulatedPixels.z, this.rotationInertia)); this.rotationDeltaCurrentFrame.copyFromFloats(this._rotationVelocity.x * this.rotationXSpeed * deltaTimeMs, this._rotationVelocity.y * this.rotationYSpeed * deltaTimeMs, this._rotationVelocity.z * this.rotationYSpeed * deltaTimeMs); this._zoomVelocity = this._calculateCurrentVelocity(this._zoomVelocity, this.zoomAccumulatedPixels, this.zoomInertia); this.zoomDeltaCurrentFrame = this._zoomVelocity * (this.zoomSpeed * this._zoomSpeedMultiplier) * deltaTimeMs; this._prevFrameTimeMs = deltaTimeMs; this.zoomAccumulatedPixels = 0; this.panAccumulatedPixels.setAll(0); this.rotationAccumulatedPixels.setAll(0); } get isInterpolating() { return !!this._behavior?.isInterpolating; } _calculateCurrentVelocity(velocityRef, pixelDelta, inertialDecayFactor) { let inputVelocity = velocityRef; const deltaTimeMs = this._scene.getEngine().getDeltaTime(); // If we are actively receiving input or have accumulated some pixel delta since last frame, calculate inputVelocity (inertia doesn't kick in yet) if (pixelDelta !== 0 || this.activeInput) { inputVelocity = pixelDelta / deltaTimeMs; } else if (!this.activeInput && inputVelocity !== 0) { // If we are not receiving input and velocity isn't already zero, apply inertial decay to decelerate velocity const frameIndependentDecay = Math.pow(inertialDecayFactor, this._prevFrameTimeMs / FrameDurationAt60FPS); inputVelocity *= frameIndependentDecay; if (Math.abs(inputVelocity) <= Epsilon) { inputVelocity = 0; } } return inputVelocity; } } //# sourceMappingURL=cameraMovement.js.map