Files
gods-eye-view/src/orbit.js
T

95 lines
2.8 KiB
JavaScript

import * as Cesium from 'cesium';
import {
holdContinuousRender,
releaseContinuousRender,
} from './renderGovernor.js';
/**
* OrbitController — smooth orbit around a target point.
* Uses scene.preRender for frame-rate-independent 60fps updates.
* Toggle with O key; auto-stops on POI/city change.
*/
export class OrbitController {
constructor(viewer) {
this.viewer = viewer;
this.active = false;
this.target = null;
this.radius = 500;
this.pitch = -30;
this.speed = Cesium.Math.toRadians(6); // ~6°/sec → full rotation in ~60s
this.angle = 0;
this._removeListener = null;
}
/**
* Start orbiting around a target position.
* @param {Cesium.Cartesian3} targetCartesian - The point to orbit around
* @param {object} options
* @param {number} options.radius - Distance from target in meters
* @param {number} options.pitch - Tilt angle in degrees (negative = looking down)
* @param {number} options.speed - Degrees per second (default 6)
*/
start(targetCartesian, options = {}) {
if (!targetCartesian) return;
this.target = targetCartesian;
this.radius = options.radius || this.radius;
this.pitch = options.pitch || this.pitch;
this.speed = Cesium.Math.toRadians(options.speed || 6);
this.active = true;
// Orbit mutates the camera from preRender — without a hold the loop
// starves after its first idle frame. (perf wave 2)
holdContinuousRender('camera-orbit');
// Start from the camera's current heading for seamless transition
this.angle = this.viewer.camera.heading;
let lastTime = Date.now();
this._removeListener = this.viewer.scene.preRender.addEventListener(() => {
if (!this.active) return;
const now = Date.now();
const dt = (now - lastTime) / 1000;
lastTime = now;
this.angle += this.speed * dt;
const hpr = new Cesium.HeadingPitchRange(
this.angle,
Cesium.Math.toRadians(this.pitch),
this.radius,
);
this.viewer.camera.lookAt(this.target, hpr);
});
}
/**
* Stop orbiting. Camera freezes at current position and user regains control.
*/
stop() {
this.active = false;
releaseContinuousRender('camera-orbit');
if (this._removeListener) {
this._removeListener();
this._removeListener = null;
}
// Unlock camera for free interaction
this.viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);
}
/**
* Toggle orbit on/off.
* @param {Cesium.Cartesian3} targetCartesian - Required when starting
* @param {object} options - Passed to start()
* @returns {boolean} Whether orbit is now active
*/
toggle(targetCartesian, options) {
if (this.active) {
this.stop();
} else {
this.start(targetCartesian, options);
}
return this.active;
}
}