From a915bdb3e51feb8b11b1860a352d951006bd8c44 Mon Sep 17 00:00:00 2001 From: German Khokhlov Date: Thu, 15 Oct 2026 00:18:35 +0400 Subject: [PATCH] refactor(launches,cctv,alpr): take vector maths from src/geo Launches selection/replay/panel/policy/policyHelpers/state/camera, CCTV geometry/model/projection/navigation/cards/geometryQueue/state and ALPR model/visuals now use src/geo for Math, Cartesian2/3, Cartographic, Matrix3/4, Quaternion and ENU frames. launches/paths.js keeps Cesium (EllipsoidGeodesic). In camera.js, EllipsoidGeodesic, BoundingSphere.fromPoints/union and the BoundingSphere constructor stay on Cesium; the geo Cartographics passed into EllipsoidGeodesic are only read field by field and cloned there (computeProperties). The CCTV plane orientation stays a Cesium Quaternion and plane dimensions a Cesium Cartesian2 (entity ConstantProperty values). launches-layer and alpr-cameras own the src/geo files. --- scripts/package-boundaries.json | 32 +++++- src/layers/alpr/model.js | 12 +-- src/layers/alpr/visuals.js | 12 +-- src/layers/cctv/cards.js | 6 +- src/layers/cctv/geometry.js | 29 +++-- src/layers/cctv/geometryQueue.js | 6 +- src/layers/cctv/model.js | 43 ++++---- src/layers/cctv/navigation.js | 5 +- src/layers/cctv/projection.js | 11 +- src/layers/cctv/state.js | 4 +- src/layers/launches/camera.js | 156 +++++++++++++-------------- src/layers/launches/panel.js | 3 +- src/layers/launches/policy.js | 8 +- src/layers/launches/policyHelpers.js | 9 +- src/layers/launches/replay.js | 27 +++-- src/layers/launches/selection.js | 63 +++++------ src/layers/launches/state.js | 3 +- 17 files changed, 215 insertions(+), 214 deletions(-) diff --git a/scripts/package-boundaries.json b/scripts/package-boundaries.json index ead88cb35..a61b3f82d 100644 --- a/scripts/package-boundaries.json +++ b/scripts/package-boundaries.json @@ -1145,7 +1145,21 @@ "src/layers/launches/source.js", "src/layers/launches/state.js", "src/layers/launches/testing.js", - "src/data/inputOwnership.js" + "src/data/inputOwnership.js", + "src/geo/index.js", + "src/geo/math.js", + "src/geo/ellipsoid.js", + "src/geo/cartographic.js", + "src/geo/cartesian2.js", + "src/geo/cartesian3.js", + "src/geo/matrix3.js", + "src/geo/matrix4.js", + "src/geo/quaternion.js", + "src/geo/transforms.js", + "src/geo/scaleToGeodeticSurface.js", + "src/geo/check.js", + "src/geo/debug.js", + "src/geo/developerError.js" ], "external": ["cesium"] }, @@ -1406,7 +1420,21 @@ "src/sources/httpBody.js", "src/sources/vectorTiles.js", "src/sources/phaseTiming.js", - "src/layers/alpr/floors.js" + "src/layers/alpr/floors.js", + "src/geo/index.js", + "src/geo/math.js", + "src/geo/ellipsoid.js", + "src/geo/cartographic.js", + "src/geo/cartesian2.js", + "src/geo/cartesian3.js", + "src/geo/matrix3.js", + "src/geo/matrix4.js", + "src/geo/quaternion.js", + "src/geo/transforms.js", + "src/geo/scaleToGeodeticSurface.js", + "src/geo/check.js", + "src/geo/debug.js", + "src/geo/developerError.js" ], "external": ["cesium", "@mapbox/vector-tile", "pbf"] }, diff --git a/src/layers/alpr/model.js b/src/layers/alpr/model.js index 5966cad01..701cb649b 100644 --- a/src/layers/alpr/model.js +++ b/src/layers/alpr/model.js @@ -1,4 +1,4 @@ -import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; import { EARTH_MEAN_RADIUS_M } from './policy.js'; export * from './records.js'; @@ -12,9 +12,9 @@ export * from './records.js'; */ export function destinationPointDeg(latDeg, lonDeg, bearingDeg, distanceM) { const angularDistance = distanceM / EARTH_MEAN_RADIUS_M; - const bearing = Cesium.Math.toRadians(bearingDeg); - const lat1 = Cesium.Math.toRadians(latDeg); - const lon1 = Cesium.Math.toRadians(lonDeg); + const bearing = geo.math.toRadians(bearingDeg); + const lat1 = geo.math.toRadians(latDeg); + const lon1 = geo.math.toRadians(lonDeg); const lat2 = Math.asin( Math.sin(lat1) * Math.cos(angularDistance) + Math.cos(lat1) * Math.sin(angularDistance) * Math.cos(bearing), @@ -26,8 +26,8 @@ export function destinationPointDeg(latDeg, lonDeg, bearingDeg, distanceM) { Math.cos(angularDistance) - Math.sin(lat1) * Math.sin(lat2), ); return { - latitude: Cesium.Math.toDegrees(lat2), - longitude: Cesium.Math.toDegrees(lon2), + latitude: geo.math.toDegrees(lat2), + longitude: geo.math.toDegrees(lon2), }; } diff --git a/src/layers/alpr/visuals.js b/src/layers/alpr/visuals.js index 3a3d3fc5f..b9d93857b 100644 --- a/src/layers/alpr/visuals.js +++ b/src/layers/alpr/visuals.js @@ -1,4 +1,4 @@ -import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; import { destinationPointDeg } from './model.js'; import { DIRECTION_CONE_M, @@ -95,13 +95,9 @@ export function directionWedgePositions(record, heightM = 0, heightAt = null) { return Number.isFinite(height) ? height : heightM; }; return [ - Cesium.Cartesian3.fromDegrees(record.longitude, record.latitude, heightM), - Cesium.Cartesian3.fromDegrees(left.longitude, left.latitude, corner(left)), - Cesium.Cartesian3.fromDegrees( - right.longitude, - right.latitude, - corner(right), - ), + geo.cartesian3.fromDegrees(record.longitude, record.latitude, heightM), + geo.cartesian3.fromDegrees(left.longitude, left.latitude, corner(left)), + geo.cartesian3.fromDegrees(right.longitude, right.latitude, corner(right)), ]; } diff --git a/src/layers/cctv/cards.js b/src/layers/cctv/cards.js index 72f0b88a4..e432938e5 100644 --- a/src/layers/cctv/cards.js +++ b/src/layers/cctv/cards.js @@ -8,7 +8,7 @@ import { CCTV_FRAME_CANVAS_H, applyFrameResult, } from '../../data/cctvCards.js'; -import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; import { horizonOccluder } from '../../data/iconOrientation.js'; import { selectCctvLod, applyEvictionGrace } from '../../data/cctvLod.js'; import { @@ -85,8 +85,8 @@ export function createCards({ state: layerState, services, parts, source }) { } const scene = layerState._viewer.scene; const carto = layerState._viewer.camera.positionCartographic; - const viewerLat = carto ? Cesium.Math.toDegrees(carto.latitude) : 0; - const viewerLon = carto ? Cesium.Math.toDegrees(carto.longitude) : 0; + const viewerLat = carto ? geo.math.toDegrees(carto.latitude) : 0; + const viewerLon = carto ? geo.math.toDegrees(carto.longitude) : 0; // The active camera is excluded from ambient selection ALWAYS (not just // after its async activation settles). It is published separately in the // protected lane below, and grace never applies to it. diff --git a/src/layers/cctv/geometry.js b/src/layers/cctv/geometry.js index 797d1706b..b01cb1307 100644 --- a/src/layers/cctv/geometry.js +++ b/src/layers/cctv/geometry.js @@ -1,4 +1,5 @@ import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; import { createFrustumVolumePrimitive } from '../../data/cctvViewshed.js'; import { PROJECTION_VERT_ASPECT, @@ -272,7 +273,7 @@ export function createGeometry({ state: layerState, services, parts, source }) { */ function frustumCartesians(geometry) { - const at = (p) => Cesium.Cartesian3.fromDegrees(p.lon, p.lat, p.alt); + const at = (p) => geo.cartesian3.fromDegrees(p.lon, p.lat, p.alt); return { mount: at(geometry.mount), capCenter: at(geometry.capCenter), @@ -280,7 +281,7 @@ export function createGeometry({ state: layerState, services, parts, source }) { tr: at(geometry.corners.tr), br: at(geometry.corners.br), bl: at(geometry.corners.bl), - label: Cesium.Cartesian3.fromDegrees( + label: geo.cartesian3.fromDegrees( geometry.topCenter.lon, geometry.topCenter.lat, geometry.topCenter.alt + 1.2, @@ -301,26 +302,22 @@ export function createGeometry({ state: layerState, services, parts, source }) { function frustumFrameEcef(camera, atPos) { const h = parts.model.toRad(camera.headingDeg); const p = parts.model.toRad(camera.pitchDeg); - const enu = Cesium.Transforms.eastNorthUpToFixedFrame(atPos); - const rot = Cesium.Matrix4.getMatrix3(enu, new Cesium.Matrix3()); + const enu = geo.transforms.eastNorthUpToFixedFrame(atPos); + const rot = geo.matrix4.getMatrix3(enu, geo.matrix3.create()); // ENU components: view axis + the perpendicular "up" of the pitched cap. - const dirEnu = new Cesium.Cartesian3( + const dirEnu = geo.cartesian3.create( Math.sin(h) * Math.cos(p), Math.cos(h) * Math.cos(p), Math.sin(p), ); - const upEnu = new Cesium.Cartesian3( + const upEnu = geo.cartesian3.create( -Math.sin(p) * Math.sin(h), -Math.sin(p) * Math.cos(h), Math.cos(p), ); return { - dir: Cesium.Matrix3.multiplyByVector( - rot, - dirEnu, - new Cesium.Cartesian3(), - ), - up: Cesium.Matrix3.multiplyByVector(rot, upEnu, new Cesium.Cartesian3()), + dir: geo.matrix3.multiplyByVector(rot, dirEnu, geo.cartesian3.create()), + up: geo.matrix3.multiplyByVector(rot, upEnu, geo.cartesian3.create()), }; } @@ -462,10 +459,10 @@ export function createGeometry({ state: layerState, services, parts, source }) { sampleMeshFloorCells(layerState._viewer?.scene, [point], { excludeObjects: excludeObjects.filter(Boolean), viewerLat: viewerCarto - ? Cesium.Math.toDegrees(viewerCarto.latitude) + ? geo.math.toDegrees(viewerCarto.latitude) : undefined, viewerLon: viewerCarto - ? Cesium.Math.toDegrees(viewerCarto.longitude) + ? geo.math.toDegrees(viewerCarto.longitude) : undefined, }); } @@ -623,7 +620,7 @@ export function createGeometry({ state: layerState, services, parts, source }) { if (Math.abs(rangeScale - 1) > 0.0001) return; // slider overrides the clamp try { const mountAlt = parts.ground.groundAltFor(record) + camera.mountHeightM; - const mountPos = Cesium.Cartesian3.fromDegrees( + const mountPos = geo.cartesian3.fromDegrees( camera.lon, camera.lat, mountAlt, @@ -637,7 +634,7 @@ export function createGeometry({ state: layerState, services, parts, source }) { } const hit = scene.pickFromRay(new Cesium.Ray(mountPos, dir), exclude); if (!hit?.position) return; - const dist = Cesium.Cartesian3.distance(mountPos, hit.position); + const dist = geo.cartesian3.distance(mountPos, hit.position); record.probeClampRangeM = activationProbeClampRange(camera.rangeM, dist); } catch { // probe failure → keep the unclamped range diff --git a/src/layers/cctv/geometryQueue.js b/src/layers/cctv/geometryQueue.js index 5583e36af..dc68d139a 100644 --- a/src/layers/cctv/geometryQueue.js +++ b/src/layers/cctv/geometryQueue.js @@ -1,4 +1,4 @@ -import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; import { GEO_PROGRESS_NOTIFY_INTERVAL_MS, GEO_PROGRESS_NOTIFY_BATCH_LIMIT, @@ -298,10 +298,10 @@ export function createGeometryQueue({ const active = parts.selection.getActiveRecord(); const carto = layerState._viewer?.camera?.positionCartographic; const refLat = carto - ? Cesium.Math.toDegrees(carto.latitude) + ? geo.math.toDegrees(carto.latitude) : (active?.camera.lat ?? 0); const refLon = carto - ? Cesium.Math.toDegrees(carto.longitude) + ? geo.math.toDegrees(carto.longitude) : (active?.camera.lon ?? 0); const pending = layerState._records .filter((record) => record !== active) diff --git a/src/layers/cctv/model.js b/src/layers/cctv/model.js index 48c8cddd4..c1c9682fb 100644 --- a/src/layers/cctv/model.js +++ b/src/layers/cctv/model.js @@ -1,4 +1,5 @@ import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; import { staticFrameRefreshMs } from '../../data/cctvLod.js'; import { frameFetchDue, cardFetchPolicy } from '../../data/cctvCards.js'; import { DEFAULT_CAMERA_CALIBRATION } from './policy.js'; @@ -13,7 +14,7 @@ export function createModel({ state: layerState, services, parts, source }) { */ function toRad(deg) { - return Cesium.Math.toRadians(deg); + return geo.math.toRadians(deg); } /** @@ -283,8 +284,8 @@ export function createModel({ state: layerState, services, parts, source }) { const lon2 = lon1 + Math.atan2(y, x); return { - lat: Cesium.Math.toDegrees(lat2), - lon: Cesium.Math.toDegrees(lon2), + lat: geo.math.toDegrees(lat2), + lon: geo.math.toDegrees(lon2), }; } @@ -328,8 +329,8 @@ export function createModel({ state: layerState, services, parts, source }) { function currentViewContext() { const carto = layerState._viewer?.camera?.positionCartographic; if (!carto) return 'none'; - const lat = Cesium.Math.toDegrees(carto.latitude); - const lon = Cesium.Math.toDegrees(carto.longitude); + const lat = geo.math.toDegrees(carto.latitude); + const lon = geo.math.toDegrees(carto.longitude); const alt = carto.height || 0; const zoomBucket = alt < 1500 @@ -404,24 +405,26 @@ export function createModel({ state: layerState, services, parts, source }) { function planeOrientationFor(camera, capCenterPos) { const frame = parts.geometry.frustumFrameEcef(camera, capCenterPos); - const right = Cesium.Cartesian3.cross( + const right = geo.cartesian3.cross( frame.dir, frame.up, - new Cesium.Cartesian3(), + geo.cartesian3.create(), ); - const normal = Cesium.Cartesian3.negate(frame.dir, new Cesium.Cartesian3()); - const m = new Cesium.Matrix3( - right.x, - frame.up.x, - normal.x, - right.y, - frame.up.y, - normal.y, - right.z, - frame.up.z, - normal.z, - ); - return Cesium.Quaternion.fromRotationMatrix(m); + const normal = geo.cartesian3.negate(frame.dir, geo.cartesian3.create()); + // Column-major rotation whose columns are right, up and normal. + const m = geo.matrix3.create(); + m[0] = right.x; + m[1] = right.y; + m[2] = right.z; + m[3] = frame.up.x; + m[4] = frame.up.y; + m[5] = frame.up.z; + m[6] = normal.x; + m[7] = normal.y; + m[8] = normal.z; + // The quaternion becomes an entity's ConstantProperty value, so it stays a + // Cesium Quaternion there (that property clones and compares its value). + return geo.quaternion.fromRotationMatrix(m, new Cesium.Quaternion()); } /** diff --git a/src/layers/cctv/navigation.js b/src/layers/cctv/navigation.js index 6c576bb4f..a1f41682c 100644 --- a/src/layers/cctv/navigation.js +++ b/src/layers/cctv/navigation.js @@ -1,4 +1,5 @@ import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; import { CCTV_FOCUS_RESULT } from './policy.js'; export function createNavigation({ @@ -15,8 +16,8 @@ export function createNavigation({ function nearestCameraIdToViewer() { const carto = layerState._viewer?.camera?.positionCartographic; if (!carto || !layerState._records.length) return null; - const lat = Cesium.Math.toDegrees(carto.latitude); - const lon = Cesium.Math.toDegrees(carto.longitude); + const lat = geo.math.toDegrees(carto.latitude); + const lon = geo.math.toDegrees(carto.longitude); let best = null; for (const record of layerState._records) { diff --git a/src/layers/cctv/projection.js b/src/layers/cctv/projection.js index e32db4fd4..b7349171a 100644 --- a/src/layers/cctv/projection.js +++ b/src/layers/cctv/projection.js @@ -1,5 +1,6 @@ import { attachCctvVideo } from './videoPlayback.js'; import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; import { CCTV_PROJECTION_OVERLAY_SOURCE_ID, CCTV_PROJECTION_OVERLAY_SOURCE_OPTIONS, @@ -77,7 +78,7 @@ export function createProjection({ ); } if (runtime.labelPosition) { - Cesium.Cartesian3.clone(positions.label, runtime.labelPosition); + geo.cartesian3.clone(positions.label, runtime.labelPosition); } } @@ -124,8 +125,8 @@ export function createProjection({ /** Create the native monitor plane plus its cached host-label presentation. */ function createProjectionPlane(record, runtime, geometry, positions) { - runtime.labelPosition ||= new Cesium.Cartesian3(); - Cesium.Cartesian3.clone(positions.label, runtime.labelPosition); + runtime.labelPosition ||= geo.cartesian3.create(); + geo.cartesian3.clone(positions.label, runtime.labelPosition); runtime.cameraId = String(record.camera.id); runtime.overlayEntry = createCctvProjectionOverlayEntry({ cameraId: runtime.cameraId, @@ -142,7 +143,7 @@ export function createProjection({ positions.capCenter, ), plane: { - plane: new Cesium.Plane(Cesium.Cartesian3.UNIT_Z, 0.0), + plane: new Cesium.Plane(geo.cartesian3.UNIT_Z, 0.0), dimensions: new Cesium.Cartesian2( geometry.halfW * 2, geometry.halfH * 2, @@ -186,7 +187,7 @@ export function createProjection({ video: null, planeEntity: null, cameraId: String(record.camera.id), - labelPosition: new Cesium.Cartesian3(), + labelPosition: geo.cartesian3.create(), overlayEntry: null, planeMaterial: null, buffers: null, diff --git a/src/layers/cctv/state.js b/src/layers/cctv/state.js index b78d18b73..725d77e4f 100644 --- a/src/layers/cctv/state.js +++ b/src/layers/cctv/state.js @@ -1,4 +1,4 @@ -import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; export function createState({ services }) { const { @@ -74,7 +74,7 @@ export function createState({ services }) { layerState._activeFocusStyleCount = 0; - layerState._scratchFocusScreen = new Cesium.Cartesian2(); + layerState._scratchFocusScreen = geo.cartesian2.create(); // Staggered geometry-load queue state (see startGeometryLoadQueue). diff --git a/src/layers/launches/camera.js b/src/layers/launches/camera.js index e6a1ef8c9..6a1f37e8f 100644 --- a/src/layers/launches/camera.js +++ b/src/layers/launches/camera.js @@ -1,4 +1,5 @@ import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; import { REPLAY_ASCENT_CAMERA_OFFSET_RAD, REPLAY_ORBIT_CAMERA_OFFSET_RAD, @@ -18,12 +19,12 @@ export function createCamera({ state: layerState, services, parts, source }) { if (!path?.length) return fallback; const current = parts.paths.samplePath(path, progress); const currentCartographic = - Cesium.Ellipsoid.WGS84.cartesianToCartographic(current); + geo.ellipsoid.WGS84.cartesianToCartographic(current); if (!currentCartographic) return fallback; for (const step of [0.01, 0.025, 0.05, 0.1, 0.2]) { const next = parts.paths.samplePath(path, Math.min(1, progress + step)); const nextCartographic = - Cesium.Ellipsoid.WGS84.cartesianToCartographic(next); + geo.ellipsoid.WGS84.cartesianToCartographic(next); if (!nextCartographic) continue; const geodesic = new Cesium.EllipsoidGeodesic( currentCartographic, @@ -36,7 +37,7 @@ export function createCamera({ state: layerState, services, parts, source }) { // HeadingPitchRange positions the camera opposite its heading vector. // Passing the forward path heading therefore keeps the camera behind // the vehicle, with the remaining ascent receding into the scene. - return Cesium.Math.zeroToTwoPi(geodesic.startHeading); + return geo.math.zeroToTwoPi(geodesic.startHeading); } } return fallback; @@ -49,8 +50,8 @@ export function createCamera({ state: layerState, services, parts, source }) { */ function replayInitialCameraHeading(path) { - return Cesium.Math.zeroToTwoPi( - cameraHeadingForPath(path, 0, Math.PI) + Cesium.Math.PI_OVER_TWO, + return geo.math.zeroToTwoPi( + cameraHeadingForPath(path, 0, Math.PI) + geo.math.PI_OVER_TWO, ); } @@ -63,10 +64,10 @@ export function createCamera({ state: layerState, services, parts, source }) { */ function replayChaseCameraHeading(pathHeading, orbitBlend = 0) { - const blend = Cesium.Math.clamp(Number(orbitBlend) || 0, 0, 1); - return Cesium.Math.zeroToTwoPi( + const blend = geo.math.clamp(Number(orbitBlend) || 0, 0, 1); + return geo.math.zeroToTwoPi( pathHeading + - Cesium.Math.lerp( + geo.math.lerp( REPLAY_ASCENT_CAMERA_OFFSET_RAD, REPLAY_ORBIT_CAMERA_OFFSET_RAD, blend, @@ -86,17 +87,17 @@ export function createCamera({ state: layerState, services, parts, source }) { function smoothReplayCameraHeading( previous, desired, - maxStepRad = Cesium.Math.toRadians(2), + maxStepRad = geo.math.toRadians(2), ) { - if (!Number.isFinite(previous)) return Cesium.Math.zeroToTwoPi(desired); - if (!Number.isFinite(desired)) return Cesium.Math.zeroToTwoPi(previous); - const delta = Cesium.Math.negativePiToPi(desired - previous); - const step = Cesium.Math.clamp( + if (!Number.isFinite(previous)) return geo.math.zeroToTwoPi(desired); + if (!Number.isFinite(desired)) return geo.math.zeroToTwoPi(previous); + const delta = geo.math.negativePiToPi(desired - previous); + const step = geo.math.clamp( delta, -Math.abs(maxStepRad), Math.abs(maxStepRad), ); - return Cesium.Math.zeroToTwoPi(previous + step); + return geo.math.zeroToTwoPi(previous + step); } /** @@ -108,7 +109,7 @@ export function createCamera({ state: layerState, services, parts, source }) { function missionZoomPitch(rangeM) { const range = Math.max(0, Number(rangeM) || 0); - const blend = Cesium.Math.clamp( + const blend = geo.math.clamp( (Math.log(Math.max(range, MISSION_CLOSE_VIEW_RANGE_M)) - Math.log(MISSION_CLOSE_VIEW_RANGE_M)) / (Math.log(MISSION_GLOBE_VIEW_RANGE_M) - @@ -116,11 +117,7 @@ export function createCamera({ state: layerState, services, parts, source }) { 0, 1, ); - return Cesium.Math.lerp( - Cesium.Math.toRadians(-42), - -Cesium.Math.PI_OVER_TWO, - blend, - ); + return geo.math.lerp(geo.math.toRadians(-42), -geo.math.PI_OVER_TWO, blend); } /** @@ -133,48 +130,44 @@ export function createCamera({ state: layerState, services, parts, source }) { function replayCameraView(state, altitudeM) { const altitude = Math.max(0, Number(altitudeM) || 0); - const localRange = Cesium.Math.clamp( + const localRange = geo.math.clamp( REPLAY_INITIAL_RANGE_M + altitude * 0.7, REPLAY_INITIAL_RANGE_M, REPLAY_LOCAL_MAX_RANGE_M, ); - const rawContextBlend = Cesium.Math.clamp( + const rawContextBlend = geo.math.clamp( (altitude - 20000) / (REPLAY_CONTEXT_ALTITUDE_END_M - 20000), 0, 1, ); const contextBlend = rawContextBlend * rawContextBlend * (3 - 2 * rawContextBlend); - const contextRange = Cesium.Math.clamp( + const contextRange = geo.math.clamp( 180000 + altitude * 3.8, MISSION_CLOSE_VIEW_RANGE_M, REPLAY_CONTEXT_MAX_RANGE_M, ); - const ascentRange = Cesium.Math.lerp( - localRange, - contextRange, - contextBlend, - ); - const ascentPitch = Cesium.Math.lerp( - Cesium.Math.toRadians(-20), - Cesium.Math.toRadians(-34), + const ascentRange = geo.math.lerp(localRange, contextRange, contextBlend); + const ascentPitch = geo.math.lerp( + geo.math.toRadians(-20), + geo.math.toRadians(-34), contextBlend, ); if (state?.ascending) { return { range: ascentRange, pitch: ascentPitch }; } - const rawBlend = Cesium.Math.clamp( + const rawBlend = geo.math.clamp( (Number(state?.phaseProgress) || 0) / REPLAY_ORBIT_PULLBACK_FRACTION, 0, 1, ); const blend = rawBlend * rawBlend * (3 - 2 * rawBlend); return { - range: Cesium.Math.lerp(ascentRange, REPLAY_ORBIT_GLOBE_RANGE_M, blend), + range: geo.math.lerp(ascentRange, REPLAY_ORBIT_GLOBE_RANGE_M, blend), // Keep an oblique tactical view of the complete orbit rather than ending // in a nadir view. This also prevents the camera from appearing to // return toward the launch site after insertion. - pitch: Cesium.Math.lerp(ascentPitch, Cesium.Math.toRadians(-45), blend), + pitch: geo.math.lerp(ascentPitch, geo.math.toRadians(-45), blend), }; } @@ -188,16 +181,15 @@ export function createCamera({ state: layerState, services, parts, source }) { function replayOrbitGlobeAnchor(position, orbitBlend = 0) { if (!position) return position; - const blend = Cesium.Math.clamp(Number(orbitBlend) || 0, 0, 1); - if (blend <= 0) return Cesium.Cartesian3.clone(position); - const cartographic = - Cesium.Ellipsoid.WGS84.cartesianToCartographic(position); - if (!cartographic) return Cesium.Cartesian3.clone(position); + const blend = geo.math.clamp(Number(orbitBlend) || 0, 0, 1); + if (blend <= 0) return geo.cartesian3.clone(position); + const cartographic = geo.ellipsoid.WGS84.cartesianToCartographic(position); + if (!cartographic) return geo.cartesian3.clone(position); const altitude = Math.max(0, cartographic.height || 0); - return Cesium.Cartesian3.fromRadians( + return geo.cartesian3.fromRadians( cartographic.longitude, cartographic.latitude, - Cesium.Math.lerp(altitude, altitude * 0.1, blend), + geo.math.lerp(altitude, altitude * 0.1, blend), ); } @@ -216,15 +208,15 @@ export function createCamera({ state: layerState, services, parts, source }) { function replayOrbitCameraTarget(vehicleAnchor, frameCenter, orbitBlend = 0) { if (!vehicleAnchor) return vehicleAnchor; - if (!frameCenter) return Cesium.Cartesian3.clone(vehicleAnchor); + if (!frameCenter) return geo.cartesian3.clone(vehicleAnchor); const blend = - Cesium.Math.clamp(Number(orbitBlend) || 0, 0, 1) * + geo.math.clamp(Number(orbitBlend) || 0, 0, 1) * REPLAY_ORBIT_FRAME_CENTER_BLEND; - return Cesium.Cartesian3.lerp( + return geo.cartesian3.lerp( vehicleAnchor, frameCenter, blend, - new Cesium.Cartesian3(), + geo.cartesian3.create(), ); } @@ -249,63 +241,59 @@ export function createCamera({ state: layerState, services, parts, source }) { pitch, ) { if (!position || !tangentPosition || !target) return null; - const radial = Cesium.Cartesian3.normalize( - Cesium.Cartesian3.clone(position), - new Cesium.Cartesian3(), + const radial = geo.cartesian3.normalize( + geo.cartesian3.clone(position), + geo.cartesian3.create(), ); - const tangent = Cesium.Cartesian3.subtract( + const tangent = geo.cartesian3.subtract( tangentPosition, position, - new Cesium.Cartesian3(), + geo.cartesian3.create(), ); - if (Cesium.Cartesian3.magnitudeSquared(tangent) < 1) return null; - Cesium.Cartesian3.normalize(tangent, tangent); - const orbitNormal = Cesium.Cartesian3.cross( + if (geo.cartesian3.magnitudeSquared(tangent) < 1) return null; + geo.cartesian3.normalize(tangent, tangent); + const orbitNormal = geo.cartesian3.cross( radial, tangent, - new Cesium.Cartesian3(), + geo.cartesian3.create(), ); - if (Cesium.Cartesian3.magnitudeSquared(orbitNormal) < 1e-12) return null; - Cesium.Cartesian3.normalize(orbitNormal, orbitNormal); + if (geo.cartesian3.magnitudeSquared(orbitNormal) < 1e-12) return null; + geo.cartesian3.normalize(orbitNormal, orbitNormal); const distance = Math.max(1, Number(range) || 1); - const elevation = Cesium.Math.clamp( + const elevation = geo.math.clamp( Math.abs(Number(pitch) || 0), - Cesium.Math.toRadians(5), - Cesium.Math.toRadians(80), + geo.math.toRadians(5), + geo.math.toRadians(80), ); - const planeOffset = Cesium.Cartesian3.multiplyByScalar( + const planeOffset = geo.cartesian3.multiplyByScalar( orbitNormal, Math.cos(elevation) * distance, - new Cesium.Cartesian3(), + geo.cartesian3.create(), ); - const radialOffset = Cesium.Cartesian3.multiplyByScalar( + const radialOffset = geo.cartesian3.multiplyByScalar( radial, Math.sin(elevation) * distance, - new Cesium.Cartesian3(), + geo.cartesian3.create(), ); - const destination = Cesium.Cartesian3.add( + const destination = geo.cartesian3.add( target, planeOffset, - new Cesium.Cartesian3(), + geo.cartesian3.create(), ); - Cesium.Cartesian3.add(destination, radialOffset, destination); - const direction = Cesium.Cartesian3.normalize( - Cesium.Cartesian3.subtract(target, destination, new Cesium.Cartesian3()), - new Cesium.Cartesian3(), + geo.cartesian3.add(destination, radialOffset, destination); + const direction = geo.cartesian3.normalize( + geo.cartesian3.subtract(target, destination, geo.cartesian3.create()), + geo.cartesian3.create(), ); - const radialAlongView = Cesium.Cartesian3.multiplyByScalar( + const radialAlongView = geo.cartesian3.multiplyByScalar( direction, - Cesium.Cartesian3.dot(radial, direction), - new Cesium.Cartesian3(), + geo.cartesian3.dot(radial, direction), + geo.cartesian3.create(), ); - const up = Cesium.Cartesian3.normalize( - Cesium.Cartesian3.subtract( - radial, - radialAlongView, - new Cesium.Cartesian3(), - ), - new Cesium.Cartesian3(), + const up = geo.cartesian3.normalize( + geo.cartesian3.subtract(radial, radialAlongView, geo.cartesian3.create()), + geo.cartesian3.create(), ); return { destination, direction, up }; } @@ -321,8 +309,8 @@ export function createCamera({ state: layerState, services, parts, source }) { function replayOrbitFrameSphere(orbitPath = []) { const earth = new Cesium.BoundingSphere( - Cesium.Cartesian3.ZERO, - Cesium.Ellipsoid.WGS84.maximumRadius, + geo.cartesian3.ZERO, + geo.ellipsoid.WGS84.maximumRadius, ); if (!Array.isArray(orbitPath) || orbitPath.length < 2) return earth; const orbit = Cesium.BoundingSphere.fromPoints(orbitPath); @@ -351,13 +339,13 @@ export function createCamera({ state: layerState, services, parts, source }) { const range = Math.max(0, Number(baseRange) || 0); const altitude = Math.max(0, Number(altitudeM) || 0); const frameRadius = Math.max(0, Number(frameRadiusM) || 0); - const blend = Cesium.Math.clamp(Number(orbitBlend) || 0, 0, 1); + const blend = geo.math.clamp(Number(orbitBlend) || 0, 0, 1); const globeAndVehicleRange = Math.max( range, - altitude + Cesium.Ellipsoid.WGS84.maximumRadius * 2.4, + altitude + geo.ellipsoid.WGS84.maximumRadius * 2.4, frameRadius * 3, ); - return Cesium.Math.lerp(range, globeAndVehicleRange, blend); + return geo.math.lerp(range, globeAndVehicleRange, blend); } return { cameraHeadingForPath, diff --git a/src/layers/launches/panel.js b/src/layers/launches/panel.js index c97077d42..20b275d35 100644 --- a/src/layers/launches/panel.js +++ b/src/layers/launches/panel.js @@ -1,4 +1,5 @@ import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; import { MISSION_CLOSE_VIEW_RANGE_M, MISSION_GLOBE_VIEW_RANGE_M, @@ -360,7 +361,7 @@ export function createPanel({ state: layerState, services, parts, source }) { Cesium.JulianDate.now(layerState._declutterTime), ); const altitudeM = position - ? Cesium.Cartographic.fromCartesian(position)?.height + ? geo.cartographic.fromCartesian(position)?.height : null; setMissionPanelField( '[data-mission-distance]', diff --git a/src/layers/launches/policy.js b/src/layers/launches/policy.js index 786aed79c..07a27f688 100644 --- a/src/layers/launches/policy.js +++ b/src/layers/launches/policy.js @@ -1,4 +1,4 @@ -import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; export const WINDOW_DAYS = 30; @@ -48,9 +48,9 @@ export const REPLAY_ORBIT_GLOBE_RANGE_M = 18000000; export const REPLAY_ORBIT_PULLBACK_FRACTION = 0.2; -export const REPLAY_ASCENT_CAMERA_OFFSET_RAD = Cesium.Math.toRadians(30); +export const REPLAY_ASCENT_CAMERA_OFFSET_RAD = geo.math.toRadians(30); -export const REPLAY_ORBIT_CAMERA_OFFSET_RAD = Cesium.Math.toRadians(45); +export const REPLAY_ORBIT_CAMERA_OFFSET_RAD = geo.math.toRadians(45); export const REPLAY_ORBIT_FRAME_CENTER_BLEND = 0.45; @@ -64,7 +64,7 @@ export const MAX_POST_TLE_RETRIES = 1; export const POST_TLE_RETRY_DELAY_MS = 1500; -export const EARTH_ROTATION_RAD_PER_SEC = Cesium.Math.TWO_PI / 86164.0905; +export const EARTH_ROTATION_RAD_PER_SEC = geo.math.TWO_PI / 86164.0905; export const PROJECTED_ASCENT_ROTATION_SEC = 600; diff --git a/src/layers/launches/policyHelpers.js b/src/layers/launches/policyHelpers.js index c3cf77c07..3af60771a 100644 --- a/src/layers/launches/policyHelpers.js +++ b/src/layers/launches/policyHelpers.js @@ -1,4 +1,5 @@ import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; import { SATELLITE_STANDALONE_DEFAULTS, LAUNCH_PAD_ZONE_MAX_CAMERA_HEIGHT_M, @@ -226,15 +227,11 @@ export function createPolicyHelpers({ const range = parts.panel.missionHoverPreviewRange( layerState._viewer.camera.positionCartographic?.height, ); - const position = Cesium.Cartesian3.fromDegrees(launch.lon, launch.lat); + const position = geo.cartesian3.fromDegrees(launch.lon, launch.lat); layerState._viewer.camera.flyToBoundingSphere( new Cesium.BoundingSphere(position, 0), { - offset: new Cesium.HeadingPitchRange( - 0, - -Cesium.Math.PI_OVER_TWO, - range, - ), + offset: new Cesium.HeadingPitchRange(0, -geo.math.PI_OVER_TWO, range), duration: 0.8, easingFunction: Cesium.EasingFunction.CUBIC_IN_OUT, }, diff --git a/src/layers/launches/replay.js b/src/layers/launches/replay.js index e6cbff98b..0faf7d28e 100644 --- a/src/layers/launches/replay.js +++ b/src/layers/launches/replay.js @@ -1,4 +1,5 @@ import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; import { REPLAY_ASCENT_FALLBACK_SEC, REPLAY_ASCENT_MIN_SEC, @@ -52,10 +53,10 @@ export function createReplay({ state: layerState, services, parts, source }) { if (!previous || !next) return next; const distance = Math.hypot(next.x - previous.x, next.y - previous.y); if (!Number.isFinite(distance) || distance > snapDistance) return next; - const amount = Cesium.Math.clamp(Number(alpha) || 0, 0, 1); + const amount = geo.math.clamp(Number(alpha) || 0, 0, 1); return { - x: Cesium.Math.lerp(previous.x, next.x, amount), - y: Cesium.Math.lerp(previous.y, next.y, amount), + x: geo.math.lerp(previous.x, next.x, amount), + y: geo.math.lerp(previous.y, next.y, amount), }; } @@ -77,13 +78,13 @@ export function createReplay({ state: layerState, services, parts, source }) { .slice(1) .reduce( (total, point, index) => - total + Cesium.Cartesian3.distance(ascentPath[index], point), + total + geo.cartesian3.distance(ascentPath[index], point), 0, ); realAscentSeconds = Math.max(180, pathLength / 9000); } if (!realAscentSeconds) realAscentSeconds = REPLAY_ASCENT_FALLBACK_SEC * 50; - return Cesium.Math.clamp( + return geo.math.clamp( REPLAY_ASCENT_FALLBACK_SEC * (realAscentSeconds / 600), REPLAY_ASCENT_MIN_SEC, REPLAY_ASCENT_MAX_SEC, @@ -99,11 +100,7 @@ export function createReplay({ state: layerState, services, parts, source }) { function normalizeReplaySpeed(value) { const numeric = Number(value); if (!Number.isFinite(numeric)) return 1; - const clamped = Cesium.Math.clamp( - numeric, - REPLAY_SPEED_MIN, - REPLAY_SPEED_MAX, - ); + const clamped = geo.math.clamp(numeric, REPLAY_SPEED_MIN, REPLAY_SPEED_MAX); return Math.round(clamped / REPLAY_SPEED_STEP) * REPLAY_SPEED_STEP; } @@ -414,7 +411,7 @@ export function createReplay({ state: layerState, services, parts, source }) { track.lastCameraHeading, ); const orbitBlend = !state.ascending - ? Cesium.Math.clamp( + ? geo.math.clamp( (Number(state.phaseProgress) || 0) / REPLAY_ORBIT_PULLBACK_FRACTION, 0, @@ -428,16 +425,16 @@ export function createReplay({ state: layerState, services, parts, source }) { if (state.ascending) track.orbitCameraWorldFrame = false; const nowMs = performance.now(); const frameDurationMs = Number.isFinite(lastCameraUpdateMs) - ? Cesium.Math.clamp(nowMs - lastCameraUpdateMs, 4, 50) + ? geo.math.clamp(nowMs - lastCameraUpdateMs, 4, 50) : 1000 / 60; lastCameraUpdateMs = nowMs; track.lastCameraHeading = parts.camera.smoothReplayCameraHeading( track.lastCameraHeading, desiredHeading, - (Cesium.Math.toRadians(2) * frameDurationMs) / (1000 / 60), + (geo.math.toRadians(2) * frameDurationMs) / (1000 / 60), ); const cartographic = - Cesium.Ellipsoid.WGS84.cartesianToCartographic(position); + geo.ellipsoid.WGS84.cartesianToCartographic(position); const altitude = Math.max(0, cartographic?.height || 0); const cameraView = parts.camera.replayCameraView(state, altitude); const defaultOrbitTarget = !state.ascending @@ -518,7 +515,7 @@ export function createReplay({ state: layerState, services, parts, source }) { { offset: new Cesium.HeadingPitchRange( initialHeading, - Cesium.Math.toRadians(-20), + geo.math.toRadians(-20), REPLAY_INITIAL_RANGE_M, ), duration: 1.4, diff --git a/src/layers/launches/selection.js b/src/layers/launches/selection.js index 813b7875f..5bbd30145 100644 --- a/src/layers/launches/selection.js +++ b/src/layers/launches/selection.js @@ -1,4 +1,5 @@ import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; import { MISSION_GLOBE_VIEW_RANGE_M, MISSION_FOCUS_RANGE_M } from './policy.js'; export function createSelection({ @@ -24,7 +25,7 @@ export function createSelection({ fovy >= Math.PI ) return; - const earthRadius = Cesium.Ellipsoid.WGS84.maximumRadius; + const earthRadius = geo.ellipsoid.WGS84.maximumRadius; const keyholeRadius = getKeyholeGeometry(width, height).radius; const targetScreenRadius = keyholeRadius * 0.61; const angularRadius = Math.atan( @@ -33,14 +34,14 @@ export function createSelection({ const distance = earthRadius / Math.max(Math.sin(angularRadius), 1e-4); const altitude = Math.max(earthRadius * 1.55, distance - earthRadius); viewer.camera.flyTo({ - destination: Cesium.Cartesian3.fromRadians( + destination: geo.cartesian3.fromRadians( cartographic.longitude, cartographic.latitude, altitude, ), orientation: { heading: viewer.camera.heading, - pitch: -Cesium.Math.PI_OVER_TWO, + pitch: -geo.math.PI_OVER_TWO, roll: 0, }, duration, @@ -99,7 +100,7 @@ export function createSelection({ if (!layerState._viewer || !launch || layerState._replayCameraLaunchId) return; stopMissionZoomAnchor(); - const target = Cesium.Cartesian3.fromDegrees(launch.lon, launch.lat); + const target = geo.cartesian3.fromDegrees(launch.lon, launch.lat); const range = Math.max( 2000, Number(initialRange) || MISSION_GLOBE_VIEW_RANGE_M, @@ -127,15 +128,12 @@ export function createSelection({ const orbitPositions = orbitEntity?.polyline?.positions?.getValue( Cesium.JulianDate.now(), ); - const launchPosition = Cesium.Cartesian3.fromDegrees( - launch.lon, - launch.lat, - ); + const launchPosition = geo.cartesian3.fromDegrees(launch.lon, launch.lat); let range = 18000000; if (orbitPositions?.length > 1) { const canvas = layerState._viewer.scene.canvas; const fovy = - layerState._viewer.camera.frustum?.fovy || Cesium.Math.toRadians(60); + layerState._viewer.camera.frustum?.fovy || geo.math.toRadians(60); const aspect = Math.max( 0.1, (canvas.clientWidth || canvas.width) / @@ -145,7 +143,7 @@ export function createSelection({ const paddedHalfAngle = Math.min(fovy, fovx) * 0.5 * 0.62; const orbitExtent = orbitPositions.reduce( (largest, point) => - Math.max(largest, Cesium.Cartesian3.distance(launchPosition, point)), + Math.max(largest, geo.cartesian3.distance(launchPosition, point)), 0, ); range = Math.max( @@ -153,39 +151,35 @@ export function createSelection({ 18000000, ); } - const targetNormal = Cesium.Cartesian3.normalize( + const targetNormal = geo.cartesian3.normalize( launchPosition, - new Cesium.Cartesian3(), + geo.cartesian3.create(), ); - const destination = Cesium.Cartesian3.add( + const destination = geo.cartesian3.add( launchPosition, - Cesium.Cartesian3.multiplyByScalar( + geo.cartesian3.multiplyByScalar( targetNormal, range, - new Cesium.Cartesian3(), + geo.cartesian3.create(), ), - new Cesium.Cartesian3(), + geo.cartesian3.create(), ); - const direction = Cesium.Cartesian3.negate( + const direction = geo.cartesian3.negate( targetNormal, - new Cesium.Cartesian3(), + geo.cartesian3.create(), ); - let right = Cesium.Cartesian3.cross( + let right = geo.cartesian3.cross( direction, - Cesium.Cartesian3.UNIT_Z, - new Cesium.Cartesian3(), + geo.cartesian3.UNIT_Z, + geo.cartesian3.create(), ); - if (Cesium.Cartesian3.magnitudeSquared(right) < 1e-8) { - right = Cesium.Cartesian3.cross( - direction, - Cesium.Cartesian3.UNIT_Y, - right, - ); + if (geo.cartesian3.magnitudeSquared(right) < 1e-8) { + right = geo.cartesian3.cross(direction, geo.cartesian3.UNIT_Y, right); } - Cesium.Cartesian3.normalize(right, right); - const up = Cesium.Cartesian3.normalize( - Cesium.Cartesian3.cross(right, direction, new Cesium.Cartesian3()), - new Cesium.Cartesian3(), + geo.cartesian3.normalize(right, right); + const up = geo.cartesian3.normalize( + geo.cartesian3.cross(right, direction, geo.cartesian3.create()), + geo.cartesian3.create(), ); layerState._viewer.camera.flyTo({ destination, @@ -202,10 +196,7 @@ export function createSelection({ if (!layerState._viewer || !launch) return; parts.replay.stopMissionReplay(); stopMissionZoomAnchor(); - const launchPosition = Cesium.Cartesian3.fromDegrees( - launch.lon, - launch.lat, - ); + const launchPosition = geo.cartesian3.fromDegrees(launch.lon, launch.lat); layerState._viewer.selectedEntity = layerState._dataSource.entities.getById( `rocket-launch:${launch.id}`, ); @@ -214,7 +205,7 @@ export function createSelection({ { offset: new Cesium.HeadingPitchRange( 0, - Cesium.Math.toRadians(-42), + geo.math.toRadians(-42), MISSION_FOCUS_RANGE_M, ), duration: 1.4, diff --git a/src/layers/launches/state.js b/src/layers/launches/state.js index 0f0194221..2cb1fd26d 100644 --- a/src/layers/launches/state.js +++ b/src/layers/launches/state.js @@ -1,4 +1,5 @@ import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; export function createState({ services }) { const { setOverlayEntries, setOverlaySourceVisible, clearOverlaySource } = @@ -125,7 +126,7 @@ export function createState({ services }) { state._declutterOccluder = new Cesium.EllipsoidalOccluder( Cesium.Ellipsoid.WGS84, - new Cesium.Cartesian3(), + geo.cartesian3.create(), ); state._missionRingDate = new Date(0);