| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192 |
- import assert from 'node:assert/strict'
- import * as THREE from 'three'
- import { planPipeEnhancement } from '../../../src/modules/cockpit/domain/pipeEnhancement.js'
- import { planEnhancementDevices } from '../../../src/modules/cockpit/domain/enhancementDevices.js'
- import { planPipeModels } from '../../../src/modules/cockpit/domain/pipeModels.js'
- import { mountPipeEnhancement } from '../../../src/modules/cockpit/services/threePipeEnhancement.js'
- import { pipeSurface } from '../../../src/modules/cockpit/domain/pipePhysical.js'
- import { pipeSurfaceMaterial } from '../../../src/modules/cockpit/services/pipeSurfaceMaterial.js'
- const row = { code: 'P', industry: 'WATER_SUPPLY', diameter: 400, material: 'PE', positions: [[110, 28], [110.001, 28]] }
- const next = { ...row, code: 'N', material: '钢', positions: [[110, 28.00015], [110.001, 28.00015]] }
- const plan = planPipeEnhancement({ selected: { kind: 'pipe', code: 'P' }, visiblePipes: [row, next] })
- function fixture(plan, options = {}) {
- const canvas = new EventTarget()
- canvas.getBoundingClientRect = () => ({ left: 0, top: 0, width: 900, height: 500 })
- const host = { clientWidth: 900, clientHeight: 500, appendChild(n) { n.parentNode = this }, removeChild(n) { n.parentNode = null } }
- let scene, camera
- const viewer = mountPipeEnhancement(host, plan, { ...options, rendererFactory: () => ({ domElement: canvas, setSize() {}, dispose() {}, render(s, c) { scene = s; camera = c; s.updateMatrixWorld(true); c.updateMatrixWorld() } }) })
- return { viewer, canvas, scene: () => scene, camera: () => camera }
- }
- function event(canvas, type, props = {}) { const e = new Event(type, { cancelable: true }); Object.assign(e, props); canvas.dispatchEvent(e) }
- let failed = 0, passed = 0
- function check(name, action) { try { action(); passed++; console.log(`PASS ${name}`) } catch (error) { failed++; console.error(`FAIL ${name}: ${error.message}`) } }
- check('主图与详情恢复8倍显示直径但保留0.4米源径', () => {
- const model = planPipeModels({ pipes: [row] }).instances[0]
- assert.equal(model.diameterM, 0.4)
- assert.equal(model.displayDiameterM, 3.2)
- const f = fixture(plan)
- try { const wall = f.scene().getObjectByName('pipe-wall'); wall.geometry.computeBoundingBox(); assert.ok(Math.abs(wall.geometry.boundingBox.max.x - 1.6) < 1e-6) }
- finally { f.viewer.destroy() }
- })
- check('基础设施模式的管道详情仍可查看真实附近设备,设备位于放大管壁之外', () => {
- const devices = planEnhancementDevices(plan, [{ code: 'D', name: '压力设备', industry: 'WATER_SUPPLY', coordinate: { renderable: true }, longitude: 110.0005, latitude: 28 }], { mode: 'infrastructure' })
- assert.equal(devices.devices.length, 1)
- const f = fixture({ ...plan, ...devices })
- try { const device = f.scene().getObjectByName('device:D'); const pipe = f.scene().getObjectByName('pipe-wall'); const world = device.getWorldPosition(new THREE.Vector3()); const box = new THREE.Box3().setFromObject(pipe); assert.ok(world.y > box.max.y + 0.15) }
- finally { f.viewer.destroy() }
- })
- check('100米管段可缩放到厘米级,近处同像素拖拽速度随视距降低', () => {
- const f = fixture(plan)
- try {
- const pan = () => { const before = f.camera().position.clone(); event(f.canvas, 'pointerdown', { button: 0, pointerId: 1, clientX: -100, clientY: -100 }); event(f.canvas, 'pointermove', { pointerId: 1, clientX: -90, clientY: -100 }); event(f.canvas, 'pointerup', { pointerId: 1 }); return f.camera().position.distanceTo(before) }
- const farPan = pan()
- // Fresh viewer, so pan offset is not mistaken for the camera's orbit range.
- f.viewer.destroy()
- const near = fixture(plan)
- try { for (let i = 0; i < 18; i++) event(near.canvas, 'wheel', { deltaY: -1000, clientX: -100, clientY: -100 }); assert.ok(near.camera().position.distanceTo(new THREE.Box3().setFromObject(near.scene().getObjectByName('pipe-enhancement:P')).getCenter(new THREE.Vector3())) < 0.1, '最小视距不能随整个场景半径放大到米级'); const before = near.camera().position.clone(); event(near.canvas, 'pointerdown', { button: 0, pointerId: 1, clientX: -100, clientY: -100 }); event(near.canvas, 'pointermove', { pointerId: 1, clientX: -90, clientY: -100 }); event(near.canvas, 'pointerup', { pointerId: 1 }); assert.ok(near.camera().position.distanceTo(before) < farPan / 100) }
- finally { near.viewer.destroy() }
- } finally { f.viewer.destroy() }
- })
- check('真实射线可点选邻近管道身份,拖拽不触发', () => {
- let selected
- const f = fixture(plan, { onObjectPick: (hit) => { selected = hit } })
- try {
- const mesh = f.scene().getObjectByName('neighbor-pipe-wall')
- const point = mesh.getWorldPosition(new THREE.Vector3()).project(f.camera())
- const x = (point.x + 1) * 450, y = (1 - point.y) * 250
- event(f.canvas, 'pointerdown', { button: 0, pointerId: 1, clientX: x, clientY: y }); event(f.canvas, 'pointerup', { button: 0, pointerId: 1, clientX: x, clientY: y })
- assert.equal(selected?.code, 'N'); assert.equal(selected?.objectKind, 'pipe')
- selected = null
- event(f.canvas, 'pointerdown', { button: 0, pointerId: 1, clientX: x, clientY: y }); event(f.canvas, 'pointermove', { pointerId: 1, clientX: x + 40, clientY: y }); event(f.canvas, 'pointerup', { button: 0, pointerId: 1, clientX: x + 40, clientY: y }); assert.equal(selected, null)
- } finally { f.viewer.destroy() }
- })
- check('材质有不同表面纹理/凹凸,钢与PE及混凝土不能仅靠微小反射差异', () => {
- const values = ['钢', 'PE', '混凝土'].map((s) => pipeSurfaceMaterial('#168bff', pipeSurface(s)))
- try { assert.ok(values.every((m) => m.bumpMap || m.normalMap)); assert.notEqual(values[0].bumpMap, values[1].bumpMap); assert.notEqual(values[1].bumpScale, values[2].bumpScale) }
- finally { for (const m of values) { m.bumpMap?.dispose(); m.dispose() } }
- })
- check('新设备渐进返回不挪动已聚焦设备,且可从清单聚焦模型', () => {
- const device = { code: 'D', industry: 'WATER_SUPPLY', coordinate: { renderable: true }, longitude: 110.0005, latitude: 28 }
- const first = planEnhancementDevices(plan, [device])
- const second = planEnhancementDevices(plan, [device, { ...device, code: 'later' }])
- assert.deepEqual(first.devices[0].position, second.devices.find((d) => d.code === 'D').position)
- const f = fixture({ ...plan, ...first })
- try { assert.equal(f.viewer.focusDevice('D'), true); const before = f.camera().position.clone(); f.viewer.sync({ ...plan, ...second }); assert.ok(f.camera().position.distanceTo(before) < 1e-6); assert.equal(f.viewer.focusDevice('not-loaded'), false) }
- finally { f.viewer.destroy() }
- })
- check('细管设备不被8倍相邻粗管包住', () => {
- const small = { ...row, code: 'small', diameter: 50, networkId: 'small-id' }
- const large = { ...row, code: 'large', positions: row.positions.map((p) => [p[0], p[1] + 0.5 / 111320]) }
- const local = planPipeEnhancement({ selected: { kind: 'pipe', code: 'small' }, visiblePipes: [small, large] })
- const layout = planEnhancementDevices(local, [{ code: 'D', industry: 'WATER_SUPPLY', networkId: 'small-id' }])
- const f = fixture({ ...local, ...layout })
- try { const device = f.scene().getObjectByName('device:D'); const pipe = f.scene().getObjectByName('neighbor-pipe-wall'); const deviceBox = new THREE.Box3().setFromObject(device.getObjectByName('device-body')); const pipeBox = new THREE.Box3().setFromObject(pipe); assert.ok(deviceBox.min.y > pipeBox.max.y) }
- finally { f.viewer.destroy() }
- })
- check('偏心邻管滚轮沿鼠标射线接近、朝向不变', () => {
- const f = fixture(plan)
- try { const pipe = f.scene().getObjectByName('neighbor-pipe-wall'); const point = pipe.localToWorld(new THREE.Vector3(0, 20, 0)); const before = f.camera().position.clone(); const orientation = f.camera().quaternion.clone(); const projected = point.clone().project(f.camera()); event(f.canvas, 'wheel', { deltaY: -120, clientX: (projected.x + 1) * 450, clientY: (1 - projected.y) * 250 }); const movement = f.camera().position.clone().sub(before).normalize(); const expected = point.sub(before).normalize(); assert.ok(movement.dot(expected) > 0.9999); assert.ok(f.camera().quaternion.angleTo(orientation) < 1e-6) }
- finally { f.viewer.destroy() }
- })
- console.log(`T55 local usability: ${passed} passed, ${failed} failed`)
- if (failed) process.exitCode = 1
|