diff --git a/scripts/build-area.js b/scripts/build-area.js index 0961ac1..0567e65 100755 --- a/scripts/build-area.js +++ b/scripts/build-area.js @@ -628,18 +628,85 @@ function cesiumPreviewHtml(glbName, metadataName, routeName, areaId) { viewer.clock.multiplier = value / speed; speedLabel.textContent = value + " m/s"; }); + const follow = createChaseFollow(viewer, positions); toggleFollow.addEventListener("click", () => { - if (viewer.trackedEntity === vehicle) { - viewer.trackedEntity = undefined; + if (follow.enabled) { + follow.stop(); toggleFollow.textContent = "Follow"; } else { - viewer.trackedEntity = vehicle; + follow.start(); toggleFollow.textContent = "Free"; } }); return { segment, vehicle }; } + function createChaseFollow(viewer, positions) { + const scratchPosition = new Cesium.Cartesian3(); + const scratchPrevious = new Cesium.Cartesian3(); + const scratchDirection = new Cesium.Cartesian3(); + const scratchEast = new Cesium.Cartesian3(); + const scratchNorth = new Cesium.Cartesian3(); + const scratchUp = new Cesium.Cartesian3(); + const offset = new Cesium.Cartesian3(); + const state = { enabled: false }; + + function update(clock) { + const time = clock.currentTime; + const position = positions.getValue(time, scratchPosition); + if (!position) return; + const previousTime = Cesium.JulianDate.addSeconds(time, -0.8, new Cesium.JulianDate()); + const previous = positions.getValue(previousTime, scratchPrevious); + if (previous) { + Cesium.Cartesian3.subtract(position, previous, scratchDirection); + } else { + Cesium.Cartesian3.clone(Cesium.Cartesian3.UNIT_X, scratchDirection); + } + if (Cesium.Cartesian3.magnitudeSquared(scratchDirection) < 0.0001) { + Cesium.Cartesian3.clone(Cesium.Cartesian3.UNIT_X, scratchDirection); + } + Cesium.Cartesian3.normalize(scratchDirection, scratchDirection); + Cesium.Cartesian3.normalize(position, scratchUp); + Cesium.Cartesian3.cross(Cesium.Cartesian3.UNIT_Z, scratchUp, scratchEast); + if (Cesium.Cartesian3.magnitudeSquared(scratchEast) < 0.0001) { + Cesium.Cartesian3.clone(Cesium.Cartesian3.UNIT_X, scratchEast); + } else { + Cesium.Cartesian3.normalize(scratchEast, scratchEast); + } + Cesium.Cartesian3.cross(scratchUp, scratchEast, scratchNorth); + Cesium.Cartesian3.normalize(scratchNorth, scratchNorth); + + const eastComponent = Cesium.Cartesian3.dot(scratchDirection, scratchEast); + const northComponent = Cesium.Cartesian3.dot(scratchDirection, scratchNorth); + const heading = Math.atan2(eastComponent, northComponent); + Cesium.Cartesian3.fromElements(0.0, -36.0, 18.0, offset); + const transform = Cesium.Transforms.headingPitchRollToFixedFrame( + position, + new Cesium.HeadingPitchRoll(heading, 0.0, 0.0) + ); + viewer.camera.lookAtTransform(transform, offset); + } + + return { + get enabled() { + return state.enabled; + }, + start() { + if (state.enabled) return; + state.enabled = true; + viewer.trackedEntity = undefined; + viewer.clock.onTick.addEventListener(update); + update(viewer.clock); + }, + stop() { + if (!state.enabled) return; + state.enabled = false; + viewer.clock.onTick.removeEventListener(update); + viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY); + } + }; + } + function distanceMeters(a, b) { const radius = 6371008.8; const lat1 = Cesium.Math.toRadians(a[1]);