| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101 |
- import assert from 'node:assert/strict'
- import { readFileSync } from 'node:fs'
- import vm from 'node:vm'
- import { createCameraControl } from '../../../src/modules/cockpit/services/cameraControl.js'
- function fixture() {
- let now = 0
- let frame = null
- const applied = []
- const control = createCameraControl({ engine: {
- now: () => now,
- requestFrame: (callback) => { frame = callback },
- applyView: (pivot, hpr) => applied.push({ pivot: { ...pivot }, hpr: { ...hpr } }),
- pickGroundAtCenter: () => null,
- viewport: () => ({ width: 1680, height: 900 }),
- fovY: () => Math.PI / 3,
- } })
- const settle = () => {
- for (let n = 0; frame && n < 500; n += 1) {
- const callback = frame; frame = null; now += 16; callback(now)
- }
- assert.equal(control.getState().settled, true)
- }
- control.applyViewPreset({ lon: 110.4, lat: 28.44, height: 320 }, { heading: 0, pitch: -55, range: 150 }, { immediate: true })
- return { control, settle, applied }
- }
- let failed = 0
- function check(name, fn) {
- try { fn(); console.log(`PASS ${name}`) }
- catch (error) { failed += 1; console.error(`FAIL ${name}: ${error.message}`) }
- }
- check('pan then select device preserves sampled ground Z and stays above terrain', () => {
- const { control, settle, applied } = fixture()
- control.beginDrag({ button: 0, x: 500, y: 300 })
- control.moveDrag({ x: 560, y: 320 })
- control.endDrag(); settle()
- const before = control.getState().hpr
- const device = { lon: 110.4001, lat: 28.4401, height: 345 }
- control.setFocus(device); settle()
- const state = control.getState()
- assert.equal(state.pivot.height, device.height, 'device ground height must not be replaced by ellipsoid zero')
- assert.equal(state.focus.height, device.height)
- assert.deepEqual(state.hpr, before)
- const last = applied.at(-1)
- const cameraHeight = last.pivot.height + last.hpr.range * Math.sin(-last.hpr.pitch * Math.PI / 180)
- assert.ok(cameraHeight > device.height + 10, `camera ${cameraHeight} must clear ground ${device.height}`)
- assert.ok(applied.every((view) => view.pivot.height >= 320), 'flight must not interpolate down to zero')
- })
- check('same XY device selection still updates newly sampled Z', () => {
- const { control, settle } = fixture()
- control.setFocus({ lon: 110.4, lat: 28.44, height: 360 }); settle()
- assert.equal(control.getState().pivot.height, 360)
- })
- check('orbit drag pending selection preserves device ground height', () => {
- const { control, settle } = fixture()
- control.beginDrag({ button: 2, x: 500, y: 300 })
- control.setFocus({ lon: 110.401, lat: 28.441, height: 350 })
- control.endDrag(); settle()
- assert.equal(control.getState().pivot.height, 350)
- })
- const service = readFileSync(new URL('../../../src/modules/cockpit/services/mapService.js', import.meta.url), 'utf8')
- const extract = (name, async = false) => service.match(new RegExp(`export ${async ? 'async ' : ''}function ${name}\\([^]*?\\n\\}`))[0].replace(/^export /, '')
- const { control, settle } = fixture()
- control.beginDrag({ button: 0, x: 500, y: 300 })
- control.moveDrag({ x: 580, y: 350 })
- control.endDrag(); settle()
- const pending = []
- const record = { cameraControl: control, viewer: {}, engine: {
- sampleTerrainHeight: () => new Promise((resolve) => pending.push(resolve)),
- groundHeightAt: () => 345,
- } }
- const context = vm.createContext({
- instance: record, pointLocateToken: 0,
- isRenderable: (object) => object.coordinate?.renderable === true,
- isStaleLocate: (mine, current) => mine !== current,
- TERRAIN_STATE: { NONE: 'none' }, terrainState: 'ready',
- })
- for (const name of ['cancelPointLocate', 'setCameraFocus']) vm.runInContext(extract(name), context)
- vm.runInContext(extract('locateMonitoringPoint', true), context)
- context.deviceA = { longitude: 110.4001, latitude: 28.4401, coordinate: { renderable: true } }
- context.deviceB = { longitude: 110.4002, latitude: 28.4402, coordinate: { renderable: true } }
- const first = vm.runInContext("locateMonitoringPoint(deviceA, { tier: 'device', centerOnly: true })", context)
- pending.shift()(345)
- await first; settle()
- check('service terrain sampling survives real setCameraFocus and controller', () => {
- assert.equal(control.getState().pivot.height, 345)
- assert.ok(Math.abs(control.getState().pivot.lon - context.deviceA.longitude) < 1e-9)
- })
- const stale = vm.runInContext("locateMonitoringPoint(deviceA, { tier: 'device', centerOnly: true })", context)
- const latest = vm.runInContext("locateMonitoringPoint(deviceB, { tier: 'device', centerOnly: true })", context)
- const resolveStale = pending.shift()
- const resolveLatest = pending.shift()
- resolveLatest(370); await latest
- resolveStale(100); await stale
- settle()
- check('late terrain response cannot move camera down to previously selected device', () => {
- assert.ok(Math.abs(control.getState().pivot.lon - context.deviceB.longitude) < 1e-9)
- assert.equal(control.getState().pivot.height, 370)
- })
- console.log(`T45 device camera height: ${failed ? 'FAILED' : 'passed'}`)
- process.exitCode = failed ? 1 : 0
|