@pascal-app/viewer 0.9.2 → 1.0.0-beta.2

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.
Files changed (79) hide show
  1. package/README.md +20 -5
  2. package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
  3. package/dist/components/viewer/frame-limiter.js +25 -5
  4. package/dist/components/viewer/glb-scene.js +2 -2
  5. package/dist/components/viewer/glb-walkthrough-controller.d.ts +10 -0
  6. package/dist/components/viewer/glb-walkthrough-controller.d.ts.map +1 -1
  7. package/dist/components/viewer/glb-walkthrough-controller.js +96 -8
  8. package/dist/components/viewer/index.d.ts.map +1 -1
  9. package/dist/components/viewer/index.js +9 -0
  10. package/dist/components/viewer/post-processing.d.ts.map +1 -1
  11. package/dist/components/viewer/post-processing.js +1 -0
  12. package/dist/components/viewer/selection-manager.js +5 -2
  13. package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -1
  14. package/dist/hooks/use-gltf-ktx2.js +2 -5
  15. package/dist/index.d.ts +8 -5
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +10 -8
  18. package/dist/lib/hero-pose.d.ts +47 -0
  19. package/dist/lib/hero-pose.d.ts.map +1 -0
  20. package/dist/lib/hero-pose.js +227 -0
  21. package/dist/lib/ktx2-loader.d.ts +3 -0
  22. package/dist/lib/ktx2-loader.d.ts.map +1 -1
  23. package/dist/lib/ktx2-loader.js +6 -0
  24. package/dist/lib/materials.d.ts +2 -0
  25. package/dist/lib/materials.d.ts.map +1 -1
  26. package/dist/lib/materials.js +56 -9
  27. package/dist/lib/materials.test.d.ts +2 -0
  28. package/dist/lib/materials.test.d.ts.map +1 -0
  29. package/dist/lib/materials.test.js +25 -0
  30. package/dist/lib/merged-outline-node.test.d.ts +2 -0
  31. package/dist/lib/merged-outline-node.test.d.ts.map +1 -0
  32. package/dist/lib/merged-outline-node.test.js +22 -0
  33. package/dist/lib/snapshot-pipeline.d.ts +42 -0
  34. package/dist/lib/snapshot-pipeline.d.ts.map +1 -0
  35. package/dist/lib/snapshot-pipeline.js +254 -0
  36. package/dist/lib/terrain-perimeter-fill.d.ts +7 -0
  37. package/dist/lib/terrain-perimeter-fill.d.ts.map +1 -0
  38. package/dist/lib/terrain-perimeter-fill.js +75 -0
  39. package/dist/lib/texture-node-guard.d.ts +13 -0
  40. package/dist/lib/texture-node-guard.d.ts.map +1 -0
  41. package/dist/lib/texture-node-guard.js +39 -0
  42. package/dist/lib/texture-reference.d.ts +31 -0
  43. package/dist/lib/texture-reference.d.ts.map +1 -0
  44. package/dist/lib/texture-reference.js +139 -0
  45. package/dist/lib/texture-reference.test.d.ts +2 -0
  46. package/dist/lib/texture-reference.test.d.ts.map +1 -0
  47. package/dist/lib/texture-reference.test.js +86 -0
  48. package/dist/store/use-viewer.d.ts +13 -0
  49. package/dist/store/use-viewer.d.ts.map +1 -1
  50. package/dist/store/use-viewer.js +16 -1
  51. package/dist/store/use-viewer.test.js +19 -0
  52. package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
  53. package/dist/systems/ceiling/ceiling-system.js +8 -4
  54. package/dist/systems/floor-elevation/floor-elevation-system.d.ts.map +1 -1
  55. package/dist/systems/floor-elevation/floor-elevation-system.js +30 -7
  56. package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
  57. package/dist/systems/geometry/geometry-system.js +18 -2
  58. package/dist/systems/level/level-system.d.ts.map +1 -1
  59. package/dist/systems/level/level-system.js +8 -7
  60. package/dist/systems/level/level-utils.d.ts.map +1 -1
  61. package/dist/systems/level/level-utils.js +7 -6
  62. package/dist/systems/slab/slab-system.d.ts +5 -4
  63. package/dist/systems/slab/slab-system.d.ts.map +1 -1
  64. package/dist/systems/slab/slab-system.js +24 -94
  65. package/dist/systems/slab/slab-system.test.js +51 -12
  66. package/dist/systems/stair/stair-system.js +1 -1
  67. package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
  68. package/dist/systems/wall/wall-cutout.js +5 -2
  69. package/dist/systems/wall/wall-support-extension.test.js +69 -1
  70. package/dist/systems/wall/wall-system.d.ts +3 -7
  71. package/dist/systems/wall/wall-system.d.ts.map +1 -1
  72. package/dist/systems/wall/wall-system.js +87 -22
  73. package/dist/systems/wall/wall-terrain-bottom.test.d.ts +2 -0
  74. package/dist/systems/wall/wall-terrain-bottom.test.d.ts.map +1 -0
  75. package/dist/systems/wall/wall-terrain-bottom.test.js +34 -0
  76. package/package.json +2 -2
  77. package/dist/components/viewer/walkthrough-controls.d.ts +0 -3
  78. package/dist/components/viewer/walkthrough-controls.d.ts.map +0 -1
  79. 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.9.2",
3
+ "version": "1.0.0-beta.2",
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.9.2",
25
+ "@pascal-app/core": "^1.0.0-beta.2",
26
26
  "@react-three/drei": "^10",
27
27
  "@react-three/fiber": "^9",
28
28
  "react": "^18 || ^19",
@@ -1,3 +0,0 @@
1
- export declare const WALKTHROUGH_FOV = 60;
2
- export declare const WalkthroughControls: () => import("react").JSX.Element | null;
3
- //# sourceMappingURL=walkthrough-controls.d.ts.map
@@ -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
- };