import * as THREE from 'three'; import { describe, expect, it } from 'vitest'; import { buildPier } from '../src/core/geometry'; import { buildPierScene, frameMatrix } from '../src/view/scene'; import { loadP1 } from './helpers'; const model = buildPier(loadP1()); const M = frameMatrix(model); /** Scene coordinates of a national-grid point at EL 0 (x = E − pivot E, z = −(N − pivot N)). */ function sceneOf(e: number, n: number, el: number): THREE.Vector3 { return new THREE.Vector3(e - model.pivot.e, el, -(n - model.pivot.n)); } describe('3D frame (view/scene.ts)', () => { it('is a proper rotation (determinant +1) so faces keep their winding', () => { expect(M.determinant()).toBeCloseTo(1, 9); }); it('maps local (along-axis s, across n, EL) to the national-grid position', () => { // Block 1 on the front row: its centre in grid coordinates must match the local-to-grid formula. const b = model.blocks[0]; const grid = { e: model.bp1.e + model.u.e * b.s + model.nrm.e * b.n, n: model.bp1.n + model.u.n * b.s + model.nrm.n * b.n, }; const mapped = new THREE.Vector3(b.s, b.bottom, b.n).applyMatrix4(M); const expected = sceneOf(grid.e, grid.n, b.bottom); expect(mapped.distanceTo(expected)).toBeLessThan(1e-6); }); it('bounding box spans pile tip (2.208 m) to the highest solplate top (back bearing 5: pad top 43.898 m + h3 0.186 m + T1 0.0275 m)', () => { const scene = buildPierScene(model); const box = new THREE.Box3().setFromObject(scene.root); expect(box.min.y).toBeCloseTo(2.208, 2); expect(box.max.y).toBeCloseTo(43.898 + 0.186 + 0.0275, 3); }); it('builds the expected part counts for P1', () => { const scene = buildPierScene(model); const count = (key: keyof typeof scene.parts) => scene.parts[key].children.length; expect(count('coping')).toBe(1); expect(count('columns')).toBe(1); expect(count('footings')).toBe(1); expect(count('piles')).toBe(20); expect(count('blocks')).toBe(10); expect(count('pads')).toBe(10); expect(count('blinding')).toBe(1); expect(count('ground')).toBe(1); }); });