@pascal-app/viewer 0.9.2 → 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -5
- package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
- package/dist/components/viewer/frame-limiter.js +25 -5
- package/dist/components/viewer/glb-scene.js +2 -2
- package/dist/components/viewer/glb-walkthrough-controller.d.ts +10 -0
- package/dist/components/viewer/glb-walkthrough-controller.d.ts.map +1 -1
- package/dist/components/viewer/glb-walkthrough-controller.js +96 -8
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +9 -0
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +1 -0
- package/dist/components/viewer/selection-manager.js +5 -2
- package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -1
- package/dist/hooks/use-gltf-ktx2.js +2 -5
- package/dist/index.d.ts +8 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -8
- package/dist/lib/hero-pose.d.ts +47 -0
- package/dist/lib/hero-pose.d.ts.map +1 -0
- package/dist/lib/hero-pose.js +227 -0
- package/dist/lib/ktx2-loader.d.ts +3 -0
- package/dist/lib/ktx2-loader.d.ts.map +1 -1
- package/dist/lib/ktx2-loader.js +6 -0
- package/dist/lib/materials.d.ts +2 -0
- package/dist/lib/materials.d.ts.map +1 -1
- package/dist/lib/materials.js +56 -9
- package/dist/lib/materials.test.d.ts +2 -0
- package/dist/lib/materials.test.d.ts.map +1 -0
- package/dist/lib/materials.test.js +25 -0
- package/dist/lib/merged-outline-node.test.d.ts +2 -0
- package/dist/lib/merged-outline-node.test.d.ts.map +1 -0
- package/dist/lib/merged-outline-node.test.js +22 -0
- package/dist/lib/snapshot-pipeline.d.ts +42 -0
- package/dist/lib/snapshot-pipeline.d.ts.map +1 -0
- package/dist/lib/snapshot-pipeline.js +254 -0
- package/dist/lib/terrain-perimeter-fill.d.ts +7 -0
- package/dist/lib/terrain-perimeter-fill.d.ts.map +1 -0
- package/dist/lib/terrain-perimeter-fill.js +75 -0
- package/dist/lib/texture-node-guard.d.ts +13 -0
- package/dist/lib/texture-node-guard.d.ts.map +1 -0
- package/dist/lib/texture-node-guard.js +39 -0
- package/dist/lib/texture-reference.d.ts +31 -0
- package/dist/lib/texture-reference.d.ts.map +1 -0
- package/dist/lib/texture-reference.js +139 -0
- package/dist/lib/texture-reference.test.d.ts +2 -0
- package/dist/lib/texture-reference.test.d.ts.map +1 -0
- package/dist/lib/texture-reference.test.js +86 -0
- package/dist/store/use-viewer.d.ts +13 -0
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +16 -1
- package/dist/store/use-viewer.test.js +19 -0
- package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
- package/dist/systems/ceiling/ceiling-system.js +8 -4
- package/dist/systems/floor-elevation/floor-elevation-system.d.ts.map +1 -1
- package/dist/systems/floor-elevation/floor-elevation-system.js +30 -7
- package/dist/systems/level/level-system.d.ts.map +1 -1
- package/dist/systems/level/level-system.js +8 -7
- package/dist/systems/level/level-utils.d.ts.map +1 -1
- package/dist/systems/level/level-utils.js +7 -6
- package/dist/systems/slab/slab-system.d.ts +5 -4
- package/dist/systems/slab/slab-system.d.ts.map +1 -1
- package/dist/systems/slab/slab-system.js +24 -94
- package/dist/systems/slab/slab-system.test.js +51 -12
- package/dist/systems/stair/stair-system.js +1 -1
- package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout.js +5 -2
- package/dist/systems/wall/wall-support-extension.test.js +69 -1
- package/dist/systems/wall/wall-system.d.ts +3 -7
- package/dist/systems/wall/wall-system.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.js +87 -22
- package/dist/systems/wall/wall-terrain-bottom.test.d.ts +2 -0
- package/dist/systems/wall/wall-terrain-bottom.test.d.ts.map +1 -0
- package/dist/systems/wall/wall-terrain-bottom.test.js +34 -0
- package/package.json +2 -2
- package/dist/components/viewer/walkthrough-controls.d.ts +0 -3
- package/dist/components/viewer/walkthrough-controls.d.ts.map +0 -1
- package/dist/components/viewer/walkthrough-controls.js +0 -151
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// @ts-expect-error — bun:test is provided by the Bun runtime; viewer does not
|
|
2
|
+
// depend on @types/bun so the import type is unresolved at compile time.
|
|
3
|
+
import { describe, expect, test } from 'bun:test';
|
|
4
|
+
import { calculateLevelMiters, WallNode } from '@pascal-app/core';
|
|
5
|
+
import { generateExtrudedWall } from './wall-system';
|
|
6
|
+
describe('wall terrain bottom', () => {
|
|
7
|
+
test('samples both faces independently without changing the authored body height', () => {
|
|
8
|
+
const wall = WallNode.parse({
|
|
9
|
+
start: [0, 0],
|
|
10
|
+
end: [4, 0],
|
|
11
|
+
height: 2.5,
|
|
12
|
+
thickness: 1,
|
|
13
|
+
supportSlabId: 'ground',
|
|
14
|
+
fillToTerrain: true,
|
|
15
|
+
});
|
|
16
|
+
const geometry = generateExtrudedWall(wall, [], calculateLevelMiters([wall]), 1.5, 1.5, undefined, 4, (_x, z) => (z >= 0 ? 0.2 : 0.8));
|
|
17
|
+
geometry.computeBoundingBox();
|
|
18
|
+
const position = geometry.getAttribute('position');
|
|
19
|
+
let positiveFaceMinY = Number.POSITIVE_INFINITY;
|
|
20
|
+
let negativeFaceMinY = Number.POSITIVE_INFINITY;
|
|
21
|
+
for (let index = 0; index < position.count; index += 1) {
|
|
22
|
+
const z = position.getZ(index);
|
|
23
|
+
const y = position.getY(index);
|
|
24
|
+
if (z > 0.49)
|
|
25
|
+
positiveFaceMinY = Math.min(positiveFaceMinY, y);
|
|
26
|
+
if (z < -0.49)
|
|
27
|
+
negativeFaceMinY = Math.min(negativeFaceMinY, y);
|
|
28
|
+
}
|
|
29
|
+
expect(positiveFaceMinY).toBeCloseTo(-1.3);
|
|
30
|
+
expect(negativeFaceMinY).toBeCloseTo(-0.7);
|
|
31
|
+
expect(geometry.boundingBox?.max.y).toBeCloseTo(2.5);
|
|
32
|
+
geometry.dispose();
|
|
33
|
+
});
|
|
34
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pascal-app/viewer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0-beta.1",
|
|
4
4
|
"description": "3D viewer component for Pascal building editor",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"prepublishOnly": "npm run build"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
|
-
"@pascal-app/core": "^0.
|
|
25
|
+
"@pascal-app/core": "^1.0.0-beta.1",
|
|
26
26
|
"@react-three/drei": "^10",
|
|
27
27
|
"@react-three/fiber": "^9",
|
|
28
28
|
"react": "^18 || ^19",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"walkthrough-controls.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/walkthrough-controls.tsx"],"names":[],"mappings":"AAeA,eAAO,MAAM,eAAe,KAAK,CAAA;AAMjC,eAAO,MAAM,mBAAmB,0CAsI/B,CAAA"}
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { PointerLockControls } from '@react-three/drei';
|
|
4
|
-
import { useFrame, useThree } from '@react-three/fiber';
|
|
5
|
-
import { useCallback, useEffect, useRef } from 'react';
|
|
6
|
-
import { Vector3 } from 'three';
|
|
7
|
-
import useViewer from '../../store/use-viewer';
|
|
8
|
-
const MOVE_SPEED = 5;
|
|
9
|
-
const EYE_HEIGHT = 1.6;
|
|
10
|
-
// First-person FOV. The orbit camera is 50° (set on the Canvas), which feels
|
|
11
|
-
// cramped on foot; ~60° vertical (~90° horizontal at 16:9) restores peripheral
|
|
12
|
-
// awareness without wide-angle distortion. Applied only while walking — both
|
|
13
|
-
// walkthrough controllers read this and restore the orbit FOV on exit.
|
|
14
|
-
export const WALKTHROUGH_FOV = 60;
|
|
15
|
-
const _direction = new Vector3();
|
|
16
|
-
const _forward = new Vector3();
|
|
17
|
-
const _right = new Vector3();
|
|
18
|
-
export const WalkthroughControls = () => {
|
|
19
|
-
const controlsRef = useRef(null);
|
|
20
|
-
const walkthroughMode = useViewer((s) => s.walkthroughMode);
|
|
21
|
-
const keys = useRef({ w: false, a: false, s: false, d: false });
|
|
22
|
-
const camera = useThree((s) => s.camera);
|
|
23
|
-
// Set initial eye height
|
|
24
|
-
useEffect(() => {
|
|
25
|
-
if (walkthroughMode) {
|
|
26
|
-
camera.position.y = EYE_HEIGHT;
|
|
27
|
-
}
|
|
28
|
-
}, [walkthroughMode, camera]);
|
|
29
|
-
// Widen FOV while walking; restore the orbit FOV on exit.
|
|
30
|
-
useEffect(() => {
|
|
31
|
-
if (!walkthroughMode)
|
|
32
|
-
return;
|
|
33
|
-
const cam = camera;
|
|
34
|
-
if (!cam.isPerspectiveCamera)
|
|
35
|
-
return;
|
|
36
|
-
const prevFov = cam.fov;
|
|
37
|
-
cam.fov = WALKTHROUGH_FOV;
|
|
38
|
-
cam.updateProjectionMatrix();
|
|
39
|
-
return () => {
|
|
40
|
-
cam.fov = prevFov;
|
|
41
|
-
cam.updateProjectionMatrix();
|
|
42
|
-
};
|
|
43
|
-
}, [walkthroughMode, camera]);
|
|
44
|
-
// Keyboard handlers
|
|
45
|
-
useEffect(() => {
|
|
46
|
-
if (!walkthroughMode)
|
|
47
|
-
return;
|
|
48
|
-
const onKeyDown = (e) => {
|
|
49
|
-
if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement)
|
|
50
|
-
return;
|
|
51
|
-
const key = e.key.toLowerCase();
|
|
52
|
-
// ESC exits walkthrough mode completely
|
|
53
|
-
if (e.key === 'Escape') {
|
|
54
|
-
e.preventDefault();
|
|
55
|
-
e.stopPropagation();
|
|
56
|
-
useViewer.getState().setWalkthroughMode(false);
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
if (key === 'w' || key === 'arrowup')
|
|
60
|
-
keys.current.w = true;
|
|
61
|
-
if (key === 'a' || key === 'arrowleft')
|
|
62
|
-
keys.current.a = true;
|
|
63
|
-
if (key === 's' || key === 'arrowdown')
|
|
64
|
-
keys.current.s = true;
|
|
65
|
-
if (key === 'd' || key === 'arrowright')
|
|
66
|
-
keys.current.d = true;
|
|
67
|
-
};
|
|
68
|
-
const onKeyUp = (e) => {
|
|
69
|
-
const key = e.key.toLowerCase();
|
|
70
|
-
if (key === 'w' || key === 'arrowup')
|
|
71
|
-
keys.current.w = false;
|
|
72
|
-
if (key === 'a' || key === 'arrowleft')
|
|
73
|
-
keys.current.a = false;
|
|
74
|
-
if (key === 's' || key === 'arrowdown')
|
|
75
|
-
keys.current.s = false;
|
|
76
|
-
if (key === 'd' || key === 'arrowright')
|
|
77
|
-
keys.current.d = false;
|
|
78
|
-
};
|
|
79
|
-
window.addEventListener('keydown', onKeyDown);
|
|
80
|
-
window.addEventListener('keyup', onKeyUp);
|
|
81
|
-
return () => {
|
|
82
|
-
window.removeEventListener('keydown', onKeyDown);
|
|
83
|
-
window.removeEventListener('keyup', onKeyUp);
|
|
84
|
-
// Reset keys on cleanup
|
|
85
|
-
keys.current = { w: false, a: false, s: false, d: false };
|
|
86
|
-
};
|
|
87
|
-
}, [walkthroughMode]);
|
|
88
|
-
// Release pointer lock when walkthrough mode is turned off
|
|
89
|
-
useEffect(() => {
|
|
90
|
-
if (!walkthroughMode && document.pointerLockElement) {
|
|
91
|
-
document.exitPointerLock();
|
|
92
|
-
}
|
|
93
|
-
}, [walkthroughMode]);
|
|
94
|
-
// Movement loop
|
|
95
|
-
useFrame((_, delta) => {
|
|
96
|
-
if (!(walkthroughMode && controlsRef.current))
|
|
97
|
-
return;
|
|
98
|
-
_direction.set(0, 0, 0);
|
|
99
|
-
// Get camera forward and right vectors (XZ plane only)
|
|
100
|
-
camera.getWorldDirection(_forward);
|
|
101
|
-
_forward.y = 0;
|
|
102
|
-
_forward.normalize();
|
|
103
|
-
_right.crossVectors(_forward, camera.up).normalize();
|
|
104
|
-
if (keys.current.w)
|
|
105
|
-
_direction.add(_forward);
|
|
106
|
-
if (keys.current.s)
|
|
107
|
-
_direction.sub(_forward);
|
|
108
|
-
if (keys.current.d)
|
|
109
|
-
_direction.add(_right);
|
|
110
|
-
if (keys.current.a)
|
|
111
|
-
_direction.sub(_right);
|
|
112
|
-
if (_direction.lengthSq() > 0) {
|
|
113
|
-
_direction.normalize().multiplyScalar(MOVE_SPEED * delta);
|
|
114
|
-
camera.position.add(_direction);
|
|
115
|
-
// Keep eye height constant
|
|
116
|
-
camera.position.y = EYE_HEIGHT;
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
const handleClick = useCallback(() => {
|
|
120
|
-
if (walkthroughMode && controlsRef.current) {
|
|
121
|
-
// Feature detection: some browsers (Facebook/Instagram in-app, older Safari)
|
|
122
|
-
// don't support pointer lock on the canvas element
|
|
123
|
-
if (typeof controlsRef.current.lock === 'function') {
|
|
124
|
-
try {
|
|
125
|
-
controlsRef.current.lock();
|
|
126
|
-
}
|
|
127
|
-
catch {
|
|
128
|
-
// Silently ignore — pointer lock unavailable in this browser context
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}, [walkthroughMode]);
|
|
133
|
-
// Click to lock
|
|
134
|
-
useEffect(() => {
|
|
135
|
-
if (!walkthroughMode)
|
|
136
|
-
return;
|
|
137
|
-
const canvas = document.querySelector('canvas');
|
|
138
|
-
if (!canvas)
|
|
139
|
-
return;
|
|
140
|
-
canvas.addEventListener('click', handleClick);
|
|
141
|
-
return () => canvas.removeEventListener('click', handleClick);
|
|
142
|
-
}, [walkthroughMode, handleClick]);
|
|
143
|
-
if (!walkthroughMode)
|
|
144
|
-
return null;
|
|
145
|
-
// Skip PointerLockControls on browsers that don't support pointer lock
|
|
146
|
-
// (Facebook/Instagram in-app browsers, some iOS WebViews)
|
|
147
|
-
if (typeof document !== 'undefined' && !('requestPointerLock' in HTMLElement.prototype)) {
|
|
148
|
-
return null;
|
|
149
|
-
}
|
|
150
|
-
return _jsx(PointerLockControls, { ref: controlsRef });
|
|
151
|
-
};
|