@pascal-app/viewer 0.5.1 → 0.7.0

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 (121) hide show
  1. package/dist/components/error-boundary.d.ts +7 -3
  2. package/dist/components/error-boundary.d.ts.map +1 -1
  3. package/dist/components/error-boundary.js +3 -1
  4. package/dist/components/renderers/ceiling/ceiling-renderer.d.ts.map +1 -1
  5. package/dist/components/renderers/ceiling/ceiling-renderer.js +21 -4
  6. package/dist/components/renderers/column/column-renderer.d.ts +5 -0
  7. package/dist/components/renderers/column/column-renderer.d.ts.map +1 -0
  8. package/dist/components/renderers/column/column-renderer.js +594 -0
  9. package/dist/components/renderers/door/door-renderer.d.ts.map +1 -1
  10. package/dist/components/renderers/door/door-renderer.js +8 -10
  11. package/dist/components/renderers/fence/fence-renderer.d.ts.map +1 -1
  12. package/dist/components/renderers/fence/fence-renderer.js +16 -2
  13. package/dist/components/renderers/guide/guide-renderer.js +1 -1
  14. package/dist/components/renderers/item/item-renderer.d.ts.map +1 -1
  15. package/dist/components/renderers/item/item-renderer.js +2 -1
  16. package/dist/components/renderers/node-renderer.d.ts.map +1 -1
  17. package/dist/components/renderers/node-renderer.js +3 -1
  18. package/dist/components/renderers/roof/roof-renderer.d.ts.map +1 -1
  19. package/dist/components/renderers/roof/roof-renderer.js +19 -9
  20. package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts.map +1 -1
  21. package/dist/components/renderers/roof-segment/roof-segment-renderer.js +27 -8
  22. package/dist/components/renderers/site/site-renderer.d.ts.map +1 -1
  23. package/dist/components/renderers/site/site-renderer.js +7 -1
  24. package/dist/components/renderers/slab/slab-renderer.d.ts.map +1 -1
  25. package/dist/components/renderers/slab/slab-renderer.js +48 -7
  26. package/dist/components/renderers/spawn/spawn-renderer.d.ts +5 -0
  27. package/dist/components/renderers/spawn/spawn-renderer.d.ts.map +1 -0
  28. package/dist/components/renderers/spawn/spawn-renderer.js +32 -0
  29. package/dist/components/renderers/stair/stair-renderer.d.ts.map +1 -1
  30. package/dist/components/renderers/stair/stair-renderer.js +154 -39
  31. package/dist/components/renderers/stair-segment/stair-segment-renderer.d.ts.map +1 -1
  32. package/dist/components/renderers/stair-segment/stair-segment-renderer.js +22 -10
  33. package/dist/components/renderers/wall/wall-renderer.d.ts.map +1 -1
  34. package/dist/components/renderers/wall/wall-renderer.js +4 -9
  35. package/dist/components/renderers/window/window-renderer.d.ts.map +1 -1
  36. package/dist/components/renderers/window/window-renderer.js +5 -2
  37. package/dist/components/viewer/frame-limiter.d.ts +6 -0
  38. package/dist/components/viewer/frame-limiter.d.ts.map +1 -0
  39. package/dist/components/viewer/frame-limiter.js +31 -0
  40. package/dist/components/viewer/ground-occluder.d.ts.map +1 -1
  41. package/dist/components/viewer/ground-occluder.js +5 -1
  42. package/dist/components/viewer/index.d.ts +2 -0
  43. package/dist/components/viewer/index.d.ts.map +1 -1
  44. package/dist/components/viewer/index.js +90 -20
  45. package/dist/components/viewer/post-processing.d.ts +13 -1
  46. package/dist/components/viewer/post-processing.d.ts.map +1 -1
  47. package/dist/components/viewer/post-processing.js +98 -44
  48. package/dist/components/viewer/selection-manager.d.ts.map +1 -1
  49. package/dist/components/viewer/selection-manager.js +31 -3
  50. package/dist/hooks/use-node-events.d.ts +9 -1
  51. package/dist/hooks/use-node-events.d.ts.map +1 -1
  52. package/dist/hooks/use-node-events.js +2 -0
  53. package/dist/index.d.ts +6 -2
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +5 -2
  56. package/dist/lib/materials.d.ts +7 -1
  57. package/dist/lib/materials.d.ts.map +1 -1
  58. package/dist/lib/materials.js +192 -2
  59. package/dist/lib/merged-outline-node.d.ts +2 -0
  60. package/dist/lib/merged-outline-node.d.ts.map +1 -1
  61. package/dist/lib/merged-outline-node.js +36 -9
  62. package/dist/store/use-viewer.d.ts +6 -6
  63. package/dist/store/use-viewer.d.ts.map +1 -1
  64. package/dist/store/use-viewer.js +2 -2
  65. package/dist/systems/ceiling/ceiling-system.d.ts +8 -0
  66. package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -0
  67. package/dist/systems/ceiling/ceiling-system.js +91 -0
  68. package/dist/systems/column/column-geometry.d.ts +22 -0
  69. package/dist/systems/column/column-geometry.d.ts.map +1 -0
  70. package/dist/systems/column/column-geometry.js +108 -0
  71. package/dist/systems/door/door-animation-system.d.ts +2 -0
  72. package/dist/systems/door/door-animation-system.d.ts.map +1 -0
  73. package/dist/systems/door/door-animation-system.js +53 -0
  74. package/dist/systems/door/door-system.d.ts +2 -0
  75. package/dist/systems/door/door-system.d.ts.map +1 -0
  76. package/dist/systems/door/door-system.js +867 -0
  77. package/dist/systems/fence/fence-system.d.ts +2 -0
  78. package/dist/systems/fence/fence-system.d.ts.map +1 -0
  79. package/dist/systems/fence/fence-system.js +185 -0
  80. package/dist/systems/item/item-system.d.ts +2 -0
  81. package/dist/systems/item/item-system.d.ts.map +1 -0
  82. package/dist/systems/item/item-system.js +44 -0
  83. package/dist/systems/item-mesh-metadata/compute-item-mesh-metadata.d.ts +14 -0
  84. package/dist/systems/item-mesh-metadata/compute-item-mesh-metadata.d.ts.map +1 -0
  85. package/dist/systems/item-mesh-metadata/compute-item-mesh-metadata.js +155 -0
  86. package/dist/systems/item-mesh-metadata/item-mesh-metadata-system.d.ts +6 -0
  87. package/dist/systems/item-mesh-metadata/item-mesh-metadata-system.d.ts.map +1 -0
  88. package/dist/systems/item-mesh-metadata/item-mesh-metadata-system.js +75 -0
  89. package/dist/systems/item-mesh-metadata/sync-request.d.ts +7 -0
  90. package/dist/systems/item-mesh-metadata/sync-request.d.ts.map +1 -0
  91. package/dist/systems/item-mesh-metadata/sync-request.js +25 -0
  92. package/dist/systems/roof/roof-materials.d.ts +5 -0
  93. package/dist/systems/roof/roof-materials.d.ts.map +1 -0
  94. package/dist/systems/roof/roof-materials.js +46 -0
  95. package/dist/systems/roof/roof-system.d.ts +16 -0
  96. package/dist/systems/roof/roof-system.d.ts.map +1 -0
  97. package/dist/systems/roof/roof-system.js +797 -0
  98. package/dist/systems/slab/slab-system.d.ts +8 -0
  99. package/dist/systems/slab/slab-system.d.ts.map +1 -0
  100. package/dist/systems/slab/slab-system.js +155 -0
  101. package/dist/systems/stair/stair-materials.d.ts +7 -0
  102. package/dist/systems/stair/stair-materials.d.ts.map +1 -0
  103. package/dist/systems/stair/stair-materials.js +56 -0
  104. package/dist/systems/stair/stair-system.d.ts +2 -0
  105. package/dist/systems/stair/stair-system.d.ts.map +1 -0
  106. package/dist/systems/stair/stair-system.js +772 -0
  107. package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
  108. package/dist/systems/wall/wall-cutout.js +4 -123
  109. package/dist/systems/wall/wall-materials.d.ts +16 -0
  110. package/dist/systems/wall/wall-materials.d.ts.map +1 -0
  111. package/dist/systems/wall/wall-materials.js +148 -0
  112. package/dist/systems/wall/wall-system.d.ts +11 -0
  113. package/dist/systems/wall/wall-system.d.ts.map +1 -0
  114. package/dist/systems/wall/wall-system.js +592 -0
  115. package/dist/systems/window/window-animation-system.d.ts +2 -0
  116. package/dist/systems/window/window-animation-system.d.ts.map +1 -0
  117. package/dist/systems/window/window-animation-system.js +135 -0
  118. package/dist/systems/window/window-system.d.ts +12 -0
  119. package/dist/systems/window/window-system.d.ts.map +1 -0
  120. package/dist/systems/window/window-system.js +1430 -0
  121. package/package.json +6 -4
@@ -1,20 +1,204 @@
1
- import { resolveMaterial } from '@pascal-app/core';
1
+ import { getMaterialPresetByRef, resolveMaterial, } from '@pascal-app/core';
2
2
  import * as THREE from 'three';
3
+ import { MeshStandardNodeMaterial } from 'three/webgpu';
4
+ export const baseMaterial = new MeshStandardNodeMaterial({
5
+ color: '#f2f0ed',
6
+ roughness: 0.5,
7
+ metalness: 0.0,
8
+ });
9
+ export const glassMaterial = new MeshStandardNodeMaterial({
10
+ color: '#e0f2fe',
11
+ roughness: 0.05,
12
+ metalness: 0.0,
13
+ transparent: true,
14
+ opacity: 0.35,
15
+ side: THREE.DoubleSide,
16
+ });
3
17
  const sideMap = {
4
18
  front: THREE.FrontSide,
5
19
  back: THREE.BackSide,
6
20
  double: THREE.DoubleSide,
7
21
  };
8
22
  const materialCache = new Map();
23
+ const textureCache = new Map();
24
+ const textureLoadPromises = new Map();
25
+ const textureLoader = new THREE.TextureLoader();
26
+ const wrapMap = {
27
+ Repeat: THREE.RepeatWrapping,
28
+ ClampToEdge: THREE.ClampToEdgeWrapping,
29
+ MirroredRepeat: THREE.MirroredRepeatWrapping,
30
+ };
31
+ const SRGB_TEXTURE_SLOTS = ['map', 'emissiveMap'];
9
32
  function getCacheKey(props) {
10
33
  return `${props.color}-${props.roughness}-${props.metalness}-${props.opacity}-${props.transparent}-${props.side}`;
11
34
  }
35
+ function getTextureKey(material) {
36
+ const texture = material?.texture;
37
+ if (!texture)
38
+ return 'none';
39
+ const repeat = texture.repeat?.join('x') ?? 'default';
40
+ const scale = texture.scale ?? 'default';
41
+ return `${texture.url}-${repeat}-${scale}`;
42
+ }
43
+ function getTexture(material) {
44
+ const textureConfig = material?.texture;
45
+ if (!textureConfig?.url)
46
+ return undefined;
47
+ const cacheKey = getTextureKey(material);
48
+ const cached = textureCache.get(cacheKey);
49
+ if (cached)
50
+ return cached;
51
+ const texture = textureLoader.load(textureConfig.url);
52
+ texture.wrapS = THREE.RepeatWrapping;
53
+ texture.wrapT = THREE.RepeatWrapping;
54
+ const repeatX = textureConfig.repeat?.[0] ?? textureConfig.scale ?? 1;
55
+ const repeatY = textureConfig.repeat?.[1] ?? textureConfig.scale ?? 1;
56
+ texture.repeat.set(repeatX, repeatY);
57
+ texture.colorSpace = THREE.SRGBColorSpace;
58
+ textureCache.set(cacheKey, texture);
59
+ return texture;
60
+ }
61
+ function isStandardMaterial(material) {
62
+ return (material instanceof THREE.MeshStandardMaterial ||
63
+ material instanceof THREE.MeshPhysicalMaterial);
64
+ }
65
+ function applyTextureProperties(texture, props, slot) {
66
+ texture.wrapS = wrapMap[props.wrapS];
67
+ texture.wrapT = wrapMap[props.wrapT];
68
+ texture.repeat.set(props.repeatX, props.repeatY);
69
+ texture.rotation = props.rotation;
70
+ texture.flipY = props.flipY;
71
+ texture.colorSpace = SRGB_TEXTURE_SLOTS.includes(slot ?? 'map')
72
+ ? THREE.SRGBColorSpace
73
+ : THREE.NoColorSpace;
74
+ texture.needsUpdate = true;
75
+ return texture;
76
+ }
77
+ function getPresetTextureCacheKey(path, props, slot) {
78
+ return `${path}-${props.repeatX}-${props.repeatY}-${props.rotation}-${props.wrapS}-${props.wrapT}-${props.flipY}-${slot ?? 'map'}`;
79
+ }
80
+ function getPresetTexture(path, props, slot) {
81
+ const cacheKey = getPresetTextureCacheKey(path, props, slot);
82
+ const cached = textureCache.get(cacheKey);
83
+ if (cached)
84
+ return cached;
85
+ const texture = textureLoader.load(path);
86
+ applyTextureProperties(texture, props, slot);
87
+ textureCache.set(cacheKey, texture);
88
+ return texture;
89
+ }
90
+ async function loadPresetTexture(path, props, slot) {
91
+ const cacheKey = getPresetTextureCacheKey(path, props, slot);
92
+ const cached = textureCache.get(cacheKey);
93
+ if (cached)
94
+ return cached;
95
+ const existingPromise = textureLoadPromises.get(cacheKey);
96
+ if (existingPromise)
97
+ return existingPromise;
98
+ const promise = textureLoader
99
+ .loadAsync(path)
100
+ .then((texture) => {
101
+ applyTextureProperties(texture, props, slot);
102
+ textureCache.set(cacheKey, texture);
103
+ textureLoadPromises.delete(cacheKey);
104
+ return texture;
105
+ })
106
+ .catch((error) => {
107
+ console.warn('[viewer] Failed to load material texture', path, error);
108
+ textureLoadPromises.delete(cacheKey);
109
+ return null;
110
+ });
111
+ textureLoadPromises.set(cacheKey, promise);
112
+ return promise;
113
+ }
114
+ function queueTextureAssignment(material, slot, path, props) {
115
+ if (!path) {
116
+ material[slot] = null;
117
+ return;
118
+ }
119
+ const cacheKey = getPresetTextureCacheKey(path, props, slot);
120
+ const cached = textureCache.get(cacheKey);
121
+ if (cached) {
122
+ material[slot] = cached;
123
+ return;
124
+ }
125
+ material[slot] = null;
126
+ void loadPresetTexture(path, props, slot).then((texture) => {
127
+ if (!texture)
128
+ return;
129
+ material[slot] = texture;
130
+ material.needsUpdate = true;
131
+ });
132
+ }
133
+ function applyMaterialMapProperties(material, mapProperties) {
134
+ material.color.set(mapProperties.color);
135
+ material.roughness = mapProperties.roughness;
136
+ material.metalness = mapProperties.metalness;
137
+ material.emissiveIntensity = mapProperties.emissiveIntensity;
138
+ material.emissive.set(mapProperties.emissiveColor);
139
+ material.displacementScale = mapProperties.displacementScale;
140
+ material.bumpScale = mapProperties.bumpScale;
141
+ material.aoMapIntensity = mapProperties.aoMapIntensity;
142
+ material.lightMapIntensity = mapProperties.lightMapIntensity;
143
+ material.transparent = mapProperties.transparent;
144
+ material.opacity = mapProperties.opacity;
145
+ material.side =
146
+ mapProperties.side === 0
147
+ ? THREE.FrontSide
148
+ : mapProperties.side === 1
149
+ ? THREE.BackSide
150
+ : THREE.DoubleSide;
151
+ material.normalScale.set(mapProperties.normalScaleX, mapProperties.normalScaleY);
152
+ material.needsUpdate = true;
153
+ }
154
+ function applyMaterialPresetTextures(material, preset) {
155
+ const { maps, mapProperties } = preset;
156
+ queueTextureAssignment(material, 'map', maps.albedoMap, mapProperties);
157
+ queueTextureAssignment(material, 'normalMap', maps.normalMap, mapProperties);
158
+ queueTextureAssignment(material, 'roughnessMap', maps.roughnessMap, mapProperties);
159
+ queueTextureAssignment(material, 'metalnessMap', maps.metalnessMap, mapProperties);
160
+ queueTextureAssignment(material, 'displacementMap', maps.displacementMap, mapProperties);
161
+ queueTextureAssignment(material, 'aoMap', maps.aoMap, mapProperties);
162
+ queueTextureAssignment(material, 'bumpMap', maps.bumpMap, mapProperties);
163
+ queueTextureAssignment(material, 'alphaMap', maps.alphaMap, mapProperties);
164
+ queueTextureAssignment(material, 'lightMap', maps.lightMap, mapProperties);
165
+ queueTextureAssignment(material, 'emissiveMap', maps.emissiveMap, mapProperties);
166
+ material.needsUpdate = true;
167
+ }
168
+ export function applyMaterialPresetToMaterials(materialInput, preset) {
169
+ if (!preset)
170
+ return;
171
+ const materials = (Array.isArray(materialInput) ? materialInput : [materialInput]).filter(isStandardMaterial);
172
+ if (materials.length === 0)
173
+ return;
174
+ for (const material of materials) {
175
+ applyMaterialMapProperties(material, preset.mapProperties);
176
+ applyMaterialPresetTextures(material, preset);
177
+ }
178
+ }
179
+ export function createMaterialFromPreset(preset) {
180
+ const cacheKey = JSON.stringify(preset);
181
+ if (materialCache.has(cacheKey)) {
182
+ return materialCache.get(cacheKey);
183
+ }
184
+ const material = new THREE.MeshStandardMaterial();
185
+ applyMaterialPresetToMaterials(material, preset);
186
+ materialCache.set(cacheKey, material);
187
+ return material;
188
+ }
189
+ export function createMaterialFromPresetRef(materialPreset) {
190
+ const preset = getMaterialPresetByRef(materialPreset);
191
+ if (!preset)
192
+ return null;
193
+ return createMaterialFromPreset(preset);
194
+ }
12
195
  export function createMaterial(material) {
13
196
  const props = resolveMaterial(material);
14
- const cacheKey = getCacheKey(props);
197
+ const cacheKey = `${getCacheKey(props)}-${getTextureKey(material)}`;
15
198
  if (materialCache.has(cacheKey)) {
16
199
  return materialCache.get(cacheKey);
17
200
  }
201
+ const map = getTexture(material);
18
202
  const threeMaterial = new THREE.MeshStandardMaterial({
19
203
  color: props.color,
20
204
  roughness: props.roughness,
@@ -22,6 +206,7 @@ export function createMaterial(material) {
22
206
  opacity: props.opacity,
23
207
  transparent: props.transparent,
24
208
  side: sideMap[props.side],
209
+ map,
25
210
  });
26
211
  materialCache.set(cacheKey, threeMaterial);
27
212
  return threeMaterial;
@@ -56,4 +241,9 @@ export function clearMaterialCache() {
56
241
  material.dispose();
57
242
  }
58
243
  materialCache.clear();
244
+ for (const texture of textureCache.values()) {
245
+ texture.dispose();
246
+ }
247
+ textureCache.clear();
248
+ textureLoadPromises.clear();
59
249
  }
@@ -62,6 +62,8 @@ export declare class MergedOutlineNode extends TempNode {
62
62
  private readonly _compositeMatB;
63
63
  private readonly _cacheA;
64
64
  private readonly _cacheB;
65
+ private _wroteGroupALastFrame;
66
+ private _wroteGroupBLastFrame;
65
67
  private readonly _textureNodeA;
66
68
  private readonly _textureNodeB;
67
69
  constructor(scene: any, camera: any, params?: {
@@ -1 +1 @@
1
- {"version":3,"file":"merged-outline-node.d.ts","sourceRoot":"","sources":["../../src/lib/merged-outline-node.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"merged-outline-node.d.ts","sourceRoot":"","sources":["../../src/lib/merged-outline-node.ts"],"names":[],"mappings":"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"}
@@ -120,6 +120,13 @@ export class MergedOutlineNode extends TempNode {
120
120
  _compositeMatB;
121
121
  _cacheA = new Set();
122
122
  _cacheB = new Set();
123
+ // Tracks whether either group rendered last frame. We use this to decide
124
+ // when it's safe to skip renderer state manipulation entirely — touching
125
+ // the renderer (resetRendererAndSceneState + setRenderTarget + clearColor)
126
+ // corrupts the FBO state on the WebGL2 backend (iOS Chrome fallback) and
127
+ // the subsequent scene render comes out blank.
128
+ _wroteGroupALastFrame = false;
129
+ _wroteGroupBLastFrame = false;
123
130
  _textureNodeA;
124
131
  _textureNodeB;
125
132
  constructor(scene, camera, params = {}) {
@@ -213,27 +220,40 @@ export class MergedOutlineNode extends TempNode {
213
220
  updateBefore(frame) {
214
221
  const hasPrimary = this.primaryObjects.length > 0;
215
222
  const hasSecondary = this.secondaryObjects.length > 0;
223
+ const hasAny = hasPrimary || hasSecondary;
224
+ // Fast-path: nothing to render and nothing was rendered last frame either,
225
+ // so there are no stale composites to clear. Touch nothing — on the WebGL2
226
+ // backend (iOS Chrome fallback) even an empty reset/setRenderTarget cycle
227
+ // corrupts the framebuffer state and the next scene render goes blank.
228
+ const needsCleanupA = !hasPrimary && this._wroteGroupALastFrame;
229
+ const needsCleanupB = !hasSecondary && this._wroteGroupBLastFrame;
230
+ if (!(hasAny || needsCleanupA || needsCleanupB)) {
231
+ return;
232
+ }
216
233
  const { renderer } = frame;
217
234
  const { camera, scene } = this;
218
235
  _rendererState = RendererUtils.resetRendererAndSceneState(renderer, scene, _rendererState);
219
236
  const size = renderer.getDrawingBufferSize(_size);
220
237
  this.setSize(size.width, size.height);
221
- // Clear composites for inactive groups so stale outlines don't persist on GPU.
222
- // Must happen inside resetRendererAndSceneState to avoid MSAA state corruption.
223
- if (!hasPrimary) {
238
+ // Clear composites for groups that just transitioned from "has content"
239
+ // to "empty" — without this, the previous outline lingers on the GPU.
240
+ if (needsCleanupA) {
224
241
  renderer.setRenderTarget(this._groupA.composite);
225
242
  renderer.clearColor();
243
+ this._wroteGroupALastFrame = false;
226
244
  }
227
- if (!hasSecondary) {
245
+ if (needsCleanupB) {
228
246
  renderer.setRenderTarget(this._groupB.composite);
229
247
  renderer.clearColor();
248
+ this._wroteGroupBLastFrame = false;
230
249
  }
231
- const hasAny = hasPrimary || hasSecondary;
232
250
  if (!hasAny) {
233
251
  RendererUtils.restoreRendererAndSceneState(renderer, scene, _rendererState);
234
252
  return;
235
253
  }
236
254
  renderer.setClearColor(0xff_ff_ff, 1);
255
+ this._wroteGroupALastFrame = hasPrimary;
256
+ this._wroteGroupBLastFrame = hasSecondary;
237
257
  if (hasPrimary)
238
258
  this._buildCache(this.primaryObjects, this._cacheA);
239
259
  if (hasSecondary)
@@ -440,10 +460,17 @@ export class MergedOutlineNode extends TempNode {
440
460
  }
441
461
  _buildCache(objects, cache) {
442
462
  for (const obj of objects) {
443
- obj.traverse((child) => {
444
- if (child.isMesh || child.isSprite)
445
- cache.add(child);
446
- });
463
+ if (!obj || !obj.traverse)
464
+ continue;
465
+ try {
466
+ obj.traverse((child) => {
467
+ if (child.isMesh || child.isSprite)
468
+ cache.add(child);
469
+ });
470
+ }
471
+ catch {
472
+ // Skip objects that were disposed or removed from the scene graph
473
+ }
447
474
  }
448
475
  }
449
476
  }
@@ -14,8 +14,8 @@ type ViewerState = {
14
14
  selection: SelectionPath;
15
15
  previewSelectedIds: BaseNode['id'][];
16
16
  setPreviewSelectedIds: (ids: BaseNode['id'][]) => void;
17
- hoverHighlightMode: 'default' | 'delete';
18
- setHoverHighlightMode: (mode: 'default' | 'delete') => void;
17
+ hoverHighlightMode: string;
18
+ setHoverHighlightMode: (mode: string) => void;
19
19
  hoveredId: AnyNode['id'] | ZoneNode['id'] | null;
20
20
  setHoveredId: (id: AnyNode['id'] | ZoneNode['id'] | null) => void;
21
21
  cameraMode: 'perspective' | 'orthographic';
@@ -61,8 +61,8 @@ declare const useViewer: import("zustand").UseBoundStore<Omit<import("zustand").
61
61
  cameraMode: "perspective" | "orthographic";
62
62
  theme: "light" | "dark";
63
63
  unit: "metric" | "imperial";
64
- levelMode: "stacked" | "exploded" | "solo" | "manual";
65
- wallMode: "up" | "cutaway" | "down";
64
+ levelMode: "manual" | "stacked" | "exploded" | "solo";
65
+ wallMode: "up" | "down" | "cutaway";
66
66
  projectPreferences: Record<string, {
67
67
  showScans?: boolean;
68
68
  showGuides?: boolean;
@@ -78,8 +78,8 @@ declare const useViewer: import("zustand").UseBoundStore<Omit<import("zustand").
78
78
  cameraMode: "perspective" | "orthographic";
79
79
  theme: "light" | "dark";
80
80
  unit: "metric" | "imperial";
81
- levelMode: "stacked" | "exploded" | "solo" | "manual";
82
- wallMode: "up" | "cutaway" | "down";
81
+ levelMode: "manual" | "stacked" | "exploded" | "solo";
82
+ wallMode: "up" | "down" | "cutaway";
83
83
  projectPreferences: Record<string, {
84
84
  showScans?: boolean;
85
85
  showGuides?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"use-viewer.d.ts","sourceRoot":"","sources":["../../src/store/use-viewer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"use-viewer.d.ts","sourceRoot":"","sources":["../../src/store/use-viewer.ts"],"names":[],"mappings":"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"}
@@ -6,9 +6,9 @@ const useViewer = create()(persist((set) => ({
6
6
  previewSelectedIds: [],
7
7
  setPreviewSelectedIds: (ids) => set({ previewSelectedIds: ids }),
8
8
  hoverHighlightMode: 'default',
9
- setHoverHighlightMode: (mode) => set({ hoverHighlightMode: mode }),
9
+ setHoverHighlightMode: (mode) => set((state) => (state.hoverHighlightMode === mode ? state : { hoverHighlightMode: mode })),
10
10
  hoveredId: null,
11
- setHoveredId: (id) => set({ hoveredId: id }),
11
+ setHoveredId: (id) => set((state) => (state.hoveredId === id ? state : { hoveredId: id })),
12
12
  cameraMode: 'perspective',
13
13
  setCameraMode: (mode) => set({ cameraMode: mode }),
14
14
  theme: 'light',
@@ -0,0 +1,8 @@
1
+ import { type CeilingNode } from '@pascal-app/core';
2
+ import * as THREE from 'three';
3
+ export declare const CeilingSystem: () => null;
4
+ /**
5
+ * Generates flat ceiling geometry from polygon (no extrusion)
6
+ */
7
+ export declare function generateCeilingGeometry(ceilingNode: CeilingNode): THREE.BufferGeometry;
8
+ //# sourceMappingURL=ceiling-system.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ceiling-system.d.ts","sourceRoot":"","sources":["../../../src/systems/ceiling/ceiling-system.tsx"],"names":[],"mappings":"AACA,OAAO,EAAkB,KAAK,WAAW,EAA2B,MAAM,kBAAkB,CAAA;AAC5F,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAa9B,eAAO,MAAM,aAAa,YAuBzB,CAAA;AAqBD;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,WAAW,GAAG,KAAK,CAAC,cAAc,CAgDtF"}
@@ -0,0 +1,91 @@
1
+ import { useFrame } from '@react-three/fiber';
2
+ import { sceneRegistry, useScene } from '@pascal-app/core';
3
+ import * as THREE from 'three';
4
+ function ensureUv2Attribute(geometry) {
5
+ const uv = geometry.getAttribute('uv');
6
+ if (!uv)
7
+ return;
8
+ geometry.setAttribute('uv2', new THREE.Float32BufferAttribute(Array.from(uv.array), 2));
9
+ }
10
+ // ============================================================================
11
+ // CEILING SYSTEM
12
+ // ============================================================================
13
+ export const CeilingSystem = () => {
14
+ const dirtyNodes = useScene((state) => state.dirtyNodes);
15
+ const clearDirty = useScene((state) => state.clearDirty);
16
+ useFrame(() => {
17
+ if (dirtyNodes.size === 0)
18
+ return;
19
+ const nodes = useScene.getState().nodes;
20
+ // Process dirty ceilings
21
+ dirtyNodes.forEach((id) => {
22
+ const node = nodes[id];
23
+ if (!node || node.type !== 'ceiling')
24
+ return;
25
+ const mesh = sceneRegistry.nodes.get(id);
26
+ if (mesh) {
27
+ updateCeilingGeometry(node, mesh);
28
+ clearDirty(id);
29
+ }
30
+ // If mesh not found, keep it dirty for next frame
31
+ });
32
+ });
33
+ return null;
34
+ };
35
+ /**
36
+ * Updates the geometry for a single ceiling
37
+ */
38
+ function updateCeilingGeometry(node, mesh) {
39
+ const newGeo = generateCeilingGeometry(node);
40
+ mesh.geometry.dispose();
41
+ mesh.geometry = newGeo;
42
+ const gridMesh = mesh.getObjectByName('ceiling-grid');
43
+ if (gridMesh) {
44
+ gridMesh.geometry.dispose();
45
+ gridMesh.geometry = newGeo;
46
+ }
47
+ // Position at the ceiling height
48
+ mesh.position.y = (node.height ?? 2.5) - 0.01; // Slight offset to avoid z-fighting with upper-level slabs
49
+ }
50
+ /**
51
+ * Generates flat ceiling geometry from polygon (no extrusion)
52
+ */
53
+ export function generateCeilingGeometry(ceilingNode) {
54
+ const polygon = ceilingNode.polygon;
55
+ if (polygon.length < 3) {
56
+ return new THREE.BufferGeometry();
57
+ }
58
+ // Create shape from polygon
59
+ // Shape is in X-Y plane, we'll rotate to X-Z plane
60
+ const shape = new THREE.Shape();
61
+ const firstPt = polygon[0];
62
+ // Negate Y (which becomes Z) to get correct orientation after rotation
63
+ shape.moveTo(firstPt[0], -firstPt[1]);
64
+ for (let i = 1; i < polygon.length; i++) {
65
+ const pt = polygon[i];
66
+ shape.lineTo(pt[0], -pt[1]);
67
+ }
68
+ shape.closePath();
69
+ // Add holes to the shape
70
+ const holes = ceilingNode.holes || [];
71
+ for (const holePolygon of holes) {
72
+ if (holePolygon.length < 3)
73
+ continue;
74
+ const holePath = new THREE.Path();
75
+ const holeFirstPt = holePolygon[0];
76
+ holePath.moveTo(holeFirstPt[0], -holeFirstPt[1]);
77
+ for (let i = 1; i < holePolygon.length; i++) {
78
+ const pt = holePolygon[i];
79
+ holePath.lineTo(pt[0], -pt[1]);
80
+ }
81
+ holePath.closePath();
82
+ shape.holes.push(holePath);
83
+ }
84
+ // Create flat shape geometry (no extrusion)
85
+ const geometry = new THREE.ShapeGeometry(shape);
86
+ // Rotate so the shape lies flat in X-Z plane
87
+ geometry.rotateX(-Math.PI / 2);
88
+ geometry.computeVertexNormals();
89
+ ensureUv2Attribute(geometry);
90
+ return geometry;
91
+ }
@@ -0,0 +1,22 @@
1
+ import { type BufferGeometry } from 'three';
2
+ export declare function createColumnBoxGeometry(width: number, height: number, depth: number, bevelRadius?: number): BufferGeometry<import("three").NormalBufferAttributes, import("three").BufferGeometryEventMap>;
3
+ export declare function createColumnCylinderGeometry({ height, radiusBottom, radiusTop, radiusX, radiusZ, segments, }: {
4
+ height: number;
5
+ radiusBottom: number;
6
+ radiusTop?: number;
7
+ radiusX?: number;
8
+ radiusZ?: number;
9
+ segments?: number;
10
+ }): BufferGeometry<import("three").NormalBufferAttributes, import("three").BufferGeometryEventMap>;
11
+ export declare function createColumnSphereGeometry(radius: number, widthSegments?: number, heightSegments?: number): BufferGeometry<import("three").NormalBufferAttributes, import("three").BufferGeometryEventMap>;
12
+ export declare function createColumnTorusGeometry({ arc, radialSegments, ringRadius, scaleX, scaleY, scaleZ, tubeRadius, tubularSegments, }: {
13
+ arc?: number;
14
+ radialSegments?: number;
15
+ ringRadius: number;
16
+ scaleX?: number;
17
+ scaleY?: number;
18
+ scaleZ?: number;
19
+ tubeRadius: number;
20
+ tubularSegments?: number;
21
+ }): BufferGeometry<import("three").NormalBufferAttributes, import("three").BufferGeometryEventMap>;
22
+ //# sourceMappingURL=column-geometry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"column-geometry.d.ts","sourceRoot":"","sources":["../../../src/systems/column/column-geometry.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,cAAc,EAKpB,MAAM,OAAO,CAAA;AAkHd,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,WAAW,SAAI,kGAOhB;AAED,wBAAgB,4BAA4B,CAAC,EAC3C,MAAM,EACN,YAAY,EACZ,SAAwB,EACxB,OAAW,EACX,OAAW,EACX,QAAa,GACd,EAAE;IACD,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,kGASA;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,SAAK,EAAE,cAAc,SAAI,kGAEhG;AAED,wBAAgB,yBAAyB,CAAC,EACxC,GAAiB,EACjB,cAAmB,EACnB,UAAU,EACV,MAAmB,EACnB,MAAmB,EACnB,MAAU,EACV,UAAU,EACV,eAAoB,GACrB,EAAE;IACD,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB,kGAIA"}
@@ -0,0 +1,108 @@
1
+ import { BoxGeometry, CylinderGeometry, Float32BufferAttribute, SphereGeometry, TorusGeometry, } from 'three';
2
+ import { RoundedBoxGeometry } from 'three/examples/jsm/geometries/RoundedBoxGeometry.js';
3
+ const COLUMN_UV_SCALE = 1;
4
+ function setUvAttributes(geometry, uvs) {
5
+ geometry.setAttribute('uv', new Float32BufferAttribute(uvs, 2));
6
+ geometry.setAttribute('uv2', new Float32BufferAttribute(uvs.slice(), 2));
7
+ return geometry;
8
+ }
9
+ function toUvReadyGeometry(geometry) {
10
+ return geometry.index ? geometry.toNonIndexed() : geometry;
11
+ }
12
+ function applyPlanarColumnUvs(geometry) {
13
+ const mappedGeometry = toUvReadyGeometry(geometry);
14
+ const positions = mappedGeometry.getAttribute('position');
15
+ const normals = mappedGeometry.getAttribute('normal');
16
+ const uvs = [];
17
+ for (let index = 0; index < positions.count; index += 1) {
18
+ const x = positions.getX(index);
19
+ const y = positions.getY(index);
20
+ const z = positions.getZ(index);
21
+ const normalX = normals ? Math.abs(normals.getX(index)) : 0;
22
+ const normalY = normals ? Math.abs(normals.getY(index)) : 1;
23
+ const normalZ = normals ? Math.abs(normals.getZ(index)) : 0;
24
+ if (normalY >= normalX && normalY >= normalZ) {
25
+ uvs.push(x * COLUMN_UV_SCALE, z * COLUMN_UV_SCALE);
26
+ }
27
+ else if (normalX >= normalZ) {
28
+ uvs.push(z * COLUMN_UV_SCALE, y * COLUMN_UV_SCALE);
29
+ }
30
+ else {
31
+ uvs.push(x * COLUMN_UV_SCALE, y * COLUMN_UV_SCALE);
32
+ }
33
+ }
34
+ return setUvAttributes(mappedGeometry, uvs);
35
+ }
36
+ function ellipseCircumference(radiusX, radiusZ) {
37
+ const a = Math.max(0.001, Math.abs(radiusX));
38
+ const b = Math.max(0.001, Math.abs(radiusZ));
39
+ return Math.PI * (3 * (a + b) - Math.sqrt((3 * a + b) * (a + 3 * b)));
40
+ }
41
+ function applyCylindricalColumnUvs(geometry, sideCircumference, height) {
42
+ const mappedGeometry = toUvReadyGeometry(geometry);
43
+ const positions = mappedGeometry.getAttribute('position');
44
+ const normals = mappedGeometry.getAttribute('normal');
45
+ const defaultUvs = mappedGeometry.getAttribute('uv');
46
+ const halfHeight = height / 2;
47
+ const uvs = [];
48
+ for (let index = 0; index < positions.count; index += 1) {
49
+ const x = positions.getX(index);
50
+ const y = positions.getY(index);
51
+ const z = positions.getZ(index);
52
+ const normalY = normals ? Math.abs(normals.getY(index)) : 0;
53
+ if (normalY > 0.65) {
54
+ uvs.push(x * COLUMN_UV_SCALE, z * COLUMN_UV_SCALE);
55
+ }
56
+ else {
57
+ const defaultU = defaultUvs ? defaultUvs.getX(index) : 0;
58
+ uvs.push(defaultU * sideCircumference * COLUMN_UV_SCALE, (y + halfHeight) * COLUMN_UV_SCALE);
59
+ }
60
+ }
61
+ return setUvAttributes(mappedGeometry, uvs);
62
+ }
63
+ function applySphericalColumnUvs(geometry, radius) {
64
+ const mappedGeometry = toUvReadyGeometry(geometry);
65
+ const defaultUvs = mappedGeometry.getAttribute('uv');
66
+ if (!defaultUvs)
67
+ return mappedGeometry;
68
+ const uvs = [];
69
+ const circumference = Math.PI * 2 * radius;
70
+ const arcHeight = Math.PI * radius;
71
+ for (let index = 0; index < defaultUvs.count; index += 1) {
72
+ uvs.push(defaultUvs.getX(index) * circumference * COLUMN_UV_SCALE, defaultUvs.getY(index) * arcHeight * COLUMN_UV_SCALE);
73
+ }
74
+ return setUvAttributes(mappedGeometry, uvs);
75
+ }
76
+ function applyTorusColumnUvs(geometry, ringRadius, tubeRadius) {
77
+ const mappedGeometry = toUvReadyGeometry(geometry);
78
+ const defaultUvs = mappedGeometry.getAttribute('uv');
79
+ if (!defaultUvs)
80
+ return mappedGeometry;
81
+ const uvs = [];
82
+ const ringLength = Math.PI * 2 * Math.max(0.001, ringRadius);
83
+ const tubeLength = Math.PI * 2 * Math.max(0.001, tubeRadius);
84
+ for (let index = 0; index < defaultUvs.count; index += 1) {
85
+ uvs.push(defaultUvs.getX(index) * ringLength * COLUMN_UV_SCALE, defaultUvs.getY(index) * tubeLength * COLUMN_UV_SCALE);
86
+ }
87
+ return setUvAttributes(mappedGeometry, uvs);
88
+ }
89
+ export function createColumnBoxGeometry(width, height, depth, bevelRadius = 0) {
90
+ const geometry = bevelRadius > 0.001
91
+ ? new RoundedBoxGeometry(width, height, depth, 3, bevelRadius)
92
+ : new BoxGeometry(width, height, depth);
93
+ return applyPlanarColumnUvs(geometry);
94
+ }
95
+ export function createColumnCylinderGeometry({ height, radiusBottom, radiusTop = radiusBottom, radiusX = 1, radiusZ = 1, segments = 32, }) {
96
+ const geometry = new CylinderGeometry(radiusTop, radiusBottom, height, segments);
97
+ geometry.scale(radiusX, 1, radiusZ);
98
+ const sideRadius = Math.max(radiusTop, radiusBottom);
99
+ return applyCylindricalColumnUvs(geometry, ellipseCircumference(sideRadius * radiusX, sideRadius * radiusZ), height);
100
+ }
101
+ export function createColumnSphereGeometry(radius, widthSegments = 10, heightSegments = 8) {
102
+ return applySphericalColumnUvs(new SphereGeometry(radius, widthSegments, heightSegments), radius);
103
+ }
104
+ export function createColumnTorusGeometry({ arc = Math.PI * 2, radialSegments = 10, ringRadius, scaleX = ringRadius, scaleY = ringRadius, scaleZ = 1, tubeRadius, tubularSegments = 24, }) {
105
+ const geometry = new TorusGeometry(1, 0.18, radialSegments, tubularSegments, arc);
106
+ geometry.scale(scaleX, scaleY, scaleZ);
107
+ return applyTorusColumnUvs(geometry, ringRadius, tubeRadius);
108
+ }
@@ -0,0 +1,2 @@
1
+ export declare const DoorAnimationSystem: () => null;
2
+ //# sourceMappingURL=door-animation-system.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"door-animation-system.d.ts","sourceRoot":"","sources":["../../../src/systems/door/door-animation-system.tsx"],"names":[],"mappings":"AAYA,eAAO,MAAM,mBAAmB,YA8C/B,CAAA"}