@pascal-app/viewer 0.3.3 → 0.4.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 (42) hide show
  1. package/dist/components/renderers/item/item-renderer.d.ts.map +1 -1
  2. package/dist/components/renderers/item/item-renderer.js +14 -19
  3. package/dist/components/renderers/node-renderer.d.ts.map +1 -1
  4. package/dist/components/renderers/node-renderer.js +3 -1
  5. package/dist/components/renderers/site/site-renderer.d.ts.map +1 -1
  6. package/dist/components/renderers/site/site-renderer.js +59 -15
  7. package/dist/components/renderers/slab/slab-renderer.js +1 -1
  8. package/dist/components/renderers/stair/stair-renderer.d.ts +5 -0
  9. package/dist/components/renderers/stair/stair-renderer.d.ts.map +1 -0
  10. package/dist/components/renderers/stair/stair-renderer.js +21 -0
  11. package/dist/components/renderers/stair-segment/stair-segment-renderer.d.ts +5 -0
  12. package/dist/components/renderers/stair-segment/stair-segment-renderer.d.ts.map +1 -0
  13. package/dist/components/renderers/stair-segment/stair-segment-renderer.js +20 -0
  14. package/dist/components/renderers/wall/wall-renderer.js +1 -1
  15. package/dist/components/viewer/index.d.ts.map +1 -1
  16. package/dist/components/viewer/index.js +2 -3
  17. package/dist/components/viewer/post-processing.d.ts.map +1 -1
  18. package/dist/components/viewer/post-processing.js +33 -45
  19. package/dist/components/viewer/walkthrough-controls.d.ts +2 -0
  20. package/dist/components/viewer/walkthrough-controls.d.ts.map +1 -0
  21. package/dist/components/viewer/walkthrough-controls.js +131 -0
  22. package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -1
  23. package/dist/hooks/use-node-events.d.ts +9 -1
  24. package/dist/hooks/use-node-events.d.ts.map +1 -1
  25. package/dist/index.d.ts +2 -1
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +2 -1
  28. package/dist/lib/materials.d.ts +1 -0
  29. package/dist/lib/materials.d.ts.map +1 -1
  30. package/dist/lib/materials.js +1 -0
  31. package/dist/lib/merged-outline-node.d.ts +88 -0
  32. package/dist/lib/merged-outline-node.d.ts.map +1 -0
  33. package/dist/lib/merged-outline-node.js +450 -0
  34. package/dist/lib/suppress-three-clock-warning.d.ts +25 -0
  35. package/dist/lib/suppress-three-clock-warning.d.ts.map +1 -0
  36. package/dist/lib/suppress-three-clock-warning.js +51 -0
  37. package/dist/store/use-viewer.d.ts +6 -0
  38. package/dist/store/use-viewer.d.ts.map +1 -1
  39. package/dist/store/use-viewer.js +8 -1
  40. package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
  41. package/dist/systems/wall/wall-cutout.js +110 -61
  42. package/package.json +1 -1
@@ -12,6 +12,10 @@ type Outliner = {
12
12
  };
13
13
  type ViewerState = {
14
14
  selection: SelectionPath;
15
+ previewSelectedIds: BaseNode['id'][];
16
+ setPreviewSelectedIds: (ids: BaseNode['id'][]) => void;
17
+ hoverHighlightMode: 'default' | 'delete';
18
+ setHoverHighlightMode: (mode: 'default' | 'delete') => void;
15
19
  hoveredId: AnyNode['id'] | ZoneNode['id'] | null;
16
20
  setHoveredId: (id: AnyNode['id'] | ZoneNode['id'] | null) => void;
17
21
  cameraMode: 'perspective' | 'orthographic';
@@ -44,6 +48,8 @@ type ViewerState = {
44
48
  setExportScene: (fn: ((format?: 'glb' | 'stl' | 'obj') => Promise<void>) | null) => void;
45
49
  debugColors: boolean;
46
50
  setDebugColors: (enabled: boolean) => void;
51
+ walkthroughMode: boolean;
52
+ setWalkthroughMode: (mode: boolean) => void;
47
53
  cameraDragging: boolean;
48
54
  setCameraDragging: (dragging: boolean) => void;
49
55
  };
@@ -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":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC5F,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAKrC,KAAK,aAAa,GAAG;IACnB,UAAU,EAAE,YAAY,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IACrC,OAAO,EAAE,SAAS,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAC/B,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAC7B,WAAW,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAA;CAC9B,CAAA;AAED,KAAK,QAAQ,GAAG;IACd,eAAe,EAAE,QAAQ,EAAE,CAAA;IAC3B,cAAc,EAAE,QAAQ,EAAE,CAAA;CAC3B,CAAA;AAED,KAAK,WAAW,GAAG;IACjB,SAAS,EAAE,aAAa,CAAA;IACxB,kBAAkB,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAA;IACpC,qBAAqB,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,IAAI,CAAA;IACtD,kBAAkB,EAAE,SAAS,GAAG,QAAQ,CAAA;IACxC,qBAAqB,EAAE,CAAC,IAAI,EAAE,SAAS,GAAG,QAAQ,KAAK,IAAI,CAAA;IAC3D,SAAS,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAChD,YAAY,EAAE,CAAC,EAAE,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,KAAK,IAAI,CAAA;IAEjE,UAAU,EAAE,aAAa,GAAG,cAAc,CAAA;IAC1C,aAAa,EAAE,CAAC,IAAI,EAAE,aAAa,GAAG,cAAc,KAAK,IAAI,CAAA;IAE7D,KAAK,EAAE,OAAO,GAAG,MAAM,CAAA;IACvB,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,KAAK,IAAI,CAAA;IAE3C,IAAI,EAAE,QAAQ,GAAG,UAAU,CAAA;IAC3B,OAAO,EAAE,CAAC,IAAI,EAAE,QAAQ,GAAG,UAAU,KAAK,IAAI,CAAA;IAE9C,SAAS,EAAE,SAAS,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,CAAA;IACrD,YAAY,EAAE,CAAC,IAAI,EAAE,SAAS,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,KAAK,IAAI,CAAA;IAExE,QAAQ,EAAE,IAAI,GAAG,SAAS,GAAG,MAAM,CAAA;IACnC,WAAW,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,SAAS,GAAG,MAAM,KAAK,IAAI,CAAA;IAEtD,SAAS,EAAE,OAAO,CAAA;IAClB,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IAErC,UAAU,EAAE,OAAO,CAAA;IACnB,aAAa,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IAEtC,QAAQ,EAAE,OAAO,CAAA;IACjB,WAAW,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IAEpC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IACzC,kBAAkB,EAAE,MAAM,CACxB,MAAM,EACN;QAAE,SAAS,CAAC,EAAE,OAAO,CAAC;QAAC,UAAU,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,CAClE,CAAA;IAGD,YAAY,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,KAAK,IAAI,CAAA;IACvD,cAAc,EAAE,MAAM,IAAI,CAAA;IAE1B,QAAQ,EAAE,QAAQ,CAAA;IAGlB,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAA;IACvE,cAAc,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,KAAK,IAAI,CAAA;IAExF,WAAW,EAAE,OAAO,CAAA;IACpB,cAAc,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IAE1C,eAAe,EAAE,OAAO,CAAA;IACxB,kBAAkB,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IAE3C,cAAc,EAAE,OAAO,CAAA;IACvB,iBAAiB,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAA;CAC/C,CAAA;AAED,QAAA,MAAM,SAAS;;;;;;;;;;;4BAvBG,OAAO;6BAAe,OAAO;2BAAa,OAAO;;;;;;;;;;;;;;;4BAAjD,OAAO;6BAAe,OAAO;2BAAa,OAAO;;;;EAgKlE,CAAA;AAED,eAAe,SAAS,CAAA"}
@@ -3,6 +3,10 @@ import { create } from 'zustand';
3
3
  import { persist } from 'zustand/middleware';
4
4
  const useViewer = create()(persist((set) => ({
5
5
  selection: { buildingId: null, levelId: null, zoneId: null, selectedIds: [] },
6
+ previewSelectedIds: [],
7
+ setPreviewSelectedIds: (ids) => set({ previewSelectedIds: ids }),
8
+ hoverHighlightMode: 'default',
9
+ setHoverHighlightMode: (mode) => set({ hoverHighlightMode: mode }),
6
10
  hoveredId: null,
7
11
  setHoveredId: (id) => set({ hoveredId: id }),
8
12
  cameraMode: 'perspective',
@@ -82,7 +86,7 @@ const useViewer = create()(persist((set) => ({
82
86
  if (updates.selectedIds === undefined)
83
87
  newSelection.selectedIds = [];
84
88
  }
85
- return { selection: newSelection };
89
+ return { selection: newSelection, previewSelectedIds: [] };
86
90
  }),
87
91
  resetSelection: () => set({
88
92
  selection: {
@@ -91,12 +95,15 @@ const useViewer = create()(persist((set) => ({
91
95
  zoneId: null,
92
96
  selectedIds: [],
93
97
  },
98
+ previewSelectedIds: [],
94
99
  }),
95
100
  outliner: { selectedObjects: [], hoveredObjects: [] },
96
101
  exportScene: null,
97
102
  setExportScene: (fn) => set({ exportScene: fn }),
98
103
  debugColors: false,
99
104
  setDebugColors: (enabled) => set({ debugColors: enabled }),
105
+ walkthroughMode: false,
106
+ setWalkthroughMode: (mode) => set({ walkthroughMode: mode }),
100
107
  cameraDragging: false,
101
108
  setCameraDragging: (dragging) => set({ cameraDragging: dragging }),
102
109
  }), {
@@ -1 +1 @@
1
- {"version":3,"file":"wall-cutout.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-cutout.tsx"],"names":[],"mappings":"AAmIA,eAAO,MAAM,UAAU,YAiFtB,CAAA"}
1
+ {"version":3,"file":"wall-cutout.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-cutout.tsx"],"names":[],"mappings":"AAoMA,eAAO,MAAM,UAAU,YAgFtB,CAAA"}
@@ -1,12 +1,26 @@
1
- import { sceneRegistry, useScene } from '@pascal-app/core';
1
+ import { baseMaterial, sceneRegistry, useScene, } from '@pascal-app/core';
2
2
  import { useFrame } from '@react-three/fiber';
3
3
  import { useRef } from 'react';
4
+ import { Color } from 'three';
4
5
  import { Fn, float, fract, length, mix, positionLocal, smoothstep, step, vec2 } from 'three/tsl';
5
6
  import { MeshStandardNodeMaterial, Vector3 } from 'three/webgpu';
6
7
  import useViewer from '../../store/use-viewer';
7
8
  const tmpVec = new Vector3();
8
9
  const u = new Vector3();
9
10
  const v = new Vector3();
11
+ const DEFAULT_WALL_COLOR = '#f2f0ed';
12
+ const WALL_HIGHLIGHT_PROFILES = {
13
+ delete: {
14
+ color: new Color('#dc2626'),
15
+ blend: 0.78,
16
+ emissiveIntensity: 0.46,
17
+ },
18
+ selection: {
19
+ color: new Color('#818cf8'),
20
+ blend: 0.32,
21
+ emissiveIntensity: 0.42,
22
+ },
23
+ };
10
24
  const dotPattern = Fn(() => {
11
25
  const scale = float(0.1);
12
26
  const dotSize = float(0.3);
@@ -19,6 +33,17 @@ const dotPattern = Fn(() => {
19
33
  return dots.mul(yFade);
20
34
  });
21
35
  const wallMaterialCache = new Map();
36
+ const presetColors = {
37
+ white: '#ffffff',
38
+ brick: '#8b4513',
39
+ concrete: '#808080',
40
+ wood: '#deb887',
41
+ glass: '#87ceeb',
42
+ metal: '#c0c0c0',
43
+ plaster: '#f5f5dc',
44
+ tile: '#dcdcdc',
45
+ marble: '#f5f5f5',
46
+ };
22
47
  function getMaterialHash(wallNode) {
23
48
  if (!wallNode.material)
24
49
  return 'none';
@@ -31,20 +56,22 @@ function getMaterialHash(wallNode) {
31
56
  }
32
57
  return 'default';
33
58
  }
34
- const presetColors = {
35
- white: '#ffffff',
36
- brick: '#8b4513',
37
- concrete: '#808080',
38
- wood: '#deb887',
39
- glass: '#87ceeb',
40
- metal: '#c0c0c0',
41
- plaster: '#f5f5dc',
42
- tile: '#dcdcdc',
43
- marble: '#f5f5f5',
44
- };
45
59
  function getPresetColor(preset) {
46
60
  return presetColors[preset] ?? '#ffffff';
47
61
  }
62
+ function getHighlightedColor(color, kind) {
63
+ const profile = WALL_HIGHLIGHT_PROFILES[kind];
64
+ return new Color(color).lerp(profile.color, profile.blend);
65
+ }
66
+ function createHighlightedWallMaterial(material, baseColor, kind) {
67
+ const highlightedMaterial = material.clone();
68
+ const highlightedColor = getHighlightedColor(baseColor, kind);
69
+ const profile = WALL_HIGHLIGHT_PROFILES[kind];
70
+ highlightedMaterial.color = highlightedColor;
71
+ highlightedMaterial.emissive = highlightedColor.clone();
72
+ highlightedMaterial.emissiveIntensity = profile.emissiveIntensity;
73
+ return highlightedMaterial;
74
+ }
48
75
  function getMaterialsForWall(wallNode) {
49
76
  const cacheKey = wallNode.id;
50
77
  const materialHash = getMaterialHash(wallNode);
@@ -55,19 +82,25 @@ function getMaterialsForWall(wallNode) {
55
82
  if (existing) {
56
83
  existing.visible.dispose();
57
84
  existing.invisible.dispose();
85
+ existing.deleteVisible.dispose();
86
+ existing.deleteInvisible.dispose();
87
+ existing.highlightedVisible.dispose();
88
+ existing.highlightedInvisible.dispose();
58
89
  }
59
- let userColor = '#ffffff';
90
+ let userColor = DEFAULT_WALL_COLOR;
60
91
  if (wallNode.material?.properties?.color) {
61
92
  userColor = wallNode.material.properties.color;
62
93
  }
63
94
  else if (wallNode.material?.preset && wallNode.material.preset !== 'custom') {
64
95
  userColor = getPresetColor(wallNode.material.preset);
65
96
  }
66
- const visibleMat = new MeshStandardNodeMaterial({
67
- color: userColor,
68
- roughness: 1,
69
- metalness: 0,
70
- });
97
+ const visibleMat = wallNode.material
98
+ ? new MeshStandardNodeMaterial({
99
+ color: userColor,
100
+ roughness: 1,
101
+ metalness: 0,
102
+ })
103
+ : baseMaterial.clone();
71
104
  const invisibleMat = new MeshStandardNodeMaterial({
72
105
  transparent: true,
73
106
  opacityNode: mix(float(0.0), float(0.24), dotPattern()),
@@ -75,7 +108,19 @@ function getMaterialsForWall(wallNode) {
75
108
  depthWrite: false,
76
109
  emissive: userColor,
77
110
  });
78
- const result = { visible: visibleMat, invisible: invisibleMat, materialHash };
111
+ const highlightedVisible = createHighlightedWallMaterial(visibleMat, userColor, 'selection');
112
+ const highlightedInvisible = createHighlightedWallMaterial(invisibleMat, userColor, 'selection');
113
+ const deleteVisible = createHighlightedWallMaterial(visibleMat, userColor, 'delete');
114
+ const deleteInvisible = createHighlightedWallMaterial(invisibleMat, userColor, 'delete');
115
+ const result = {
116
+ visible: visibleMat,
117
+ invisible: invisibleMat,
118
+ deleteVisible,
119
+ deleteInvisible,
120
+ highlightedVisible,
121
+ highlightedInvisible,
122
+ materialHash,
123
+ };
79
124
  wallMaterialCache.set(cacheKey, result);
80
125
  return result;
81
126
  }
@@ -106,65 +151,69 @@ export const WallCutout = () => {
106
151
  const lastUpdateTime = useRef(0);
107
152
  const lastWallMode = useRef(useViewer.getState().wallMode);
108
153
  const lastNumberOfWalls = useRef(0);
109
- const lastWallMaterials = useRef(new Map());
154
+ const lastHighlightKey = useRef('');
110
155
  useFrame(({ camera, clock }) => {
111
156
  const wallMode = useViewer.getState().wallMode;
157
+ const selectedIds = useViewer.getState().selection.selectedIds;
158
+ const previewSelectedIds = useViewer.getState().previewSelectedIds;
159
+ const hoveredId = useViewer.getState().hoveredId;
160
+ const hoverHighlightMode = useViewer.getState().hoverHighlightMode;
112
161
  const currentTime = clock.elapsedTime;
113
162
  const currentCameraPosition = camera.position;
114
163
  camera.getWorldDirection(tmpVec);
115
164
  tmpVec.add(currentCameraPosition);
165
+ const highlightedWallIds = new Set([...selectedIds, ...previewSelectedIds].filter((id) => useScene.getState().nodes[id]?.type === 'wall'));
166
+ const deleteHoveredWallId = hoverHighlightMode === 'delete' &&
167
+ hoveredId &&
168
+ useScene.getState().nodes[hoveredId]?.type === 'wall'
169
+ ? hoveredId
170
+ : null;
171
+ const highlightKey = `${Array.from(highlightedWallIds).sort().join('|')}::${deleteHoveredWallId ?? ''}`;
116
172
  const distanceMoved = currentCameraPosition.distanceTo(lastCameraPosition.current);
117
173
  const directionChanged = tmpVec.distanceTo(lastCameraTarget.current);
118
174
  const timeSinceUpdate = currentTime - lastUpdateTime.current;
119
- const shouldUpdate = ((distanceMoved > 0.5 || directionChanged > 0.3) && timeSinceUpdate > 0.1) ||
175
+ if (((distanceMoved > 0.5 || directionChanged > 0.3) && timeSinceUpdate > 0.1) ||
120
176
  lastWallMode.current !== wallMode ||
121
- sceneRegistry.byType.wall.size !== lastNumberOfWalls.current;
122
- const walls = sceneRegistry.byType.wall;
123
- const currentWallIds = new Set();
124
- walls.forEach((wallId) => {
125
- const wallMesh = sceneRegistry.nodes.get(wallId);
126
- if (!wallMesh)
127
- return;
128
- const wallNode = useScene.getState().nodes[wallId];
129
- if (!wallNode || wallNode.type !== 'wall')
130
- return;
131
- currentWallIds.add(wallId);
132
- const hideWall = getWallHideState(wallNode, wallMesh, wallMode, u);
133
- if (shouldUpdate) {
134
- const materials = getMaterialsForWall(wallNode);
135
- wallMesh.material = hideWall ? materials.invisible : materials.visible;
136
- }
137
- else {
138
- const currentMaterial = wallMesh.material;
139
- const materials = wallMaterialCache.get(wallId);
140
- if (!materials ||
141
- currentMaterial !== (hideWall ? materials.invisible : materials.visible)) {
142
- const newMaterials = getMaterialsForWall(wallNode);
143
- wallMesh.material = hideWall ? newMaterials.invisible : newMaterials.visible;
144
- }
145
- }
146
- });
147
- if (shouldUpdate) {
177
+ sceneRegistry.byType.wall.size !== lastNumberOfWalls.current ||
178
+ lastHighlightKey.current !== highlightKey) {
148
179
  lastCameraPosition.current.copy(currentCameraPosition);
149
180
  lastCameraTarget.current.copy(tmpVec);
150
181
  lastUpdateTime.current = currentTime;
151
182
  camera.getWorldDirection(u);
152
- if (lastWallMode.current !== wallMode) {
153
- wallMaterialCache.clear();
154
- }
155
- for (const [wallId, mats] of lastWallMaterials.current) {
156
- if (!currentWallIds.has(wallId)) {
157
- mats.visible.dispose();
158
- mats.invisible.dispose();
159
- wallMaterialCache.delete(wallId);
183
+ const walls = sceneRegistry.byType.wall;
184
+ walls.forEach((wallId) => {
185
+ const wallMesh = sceneRegistry.nodes.get(wallId);
186
+ if (!wallMesh)
187
+ return;
188
+ const wallNode = useScene.getState().nodes[wallId];
189
+ if (!wallNode || wallNode.type !== 'wall')
190
+ return;
191
+ const hideWall = getWallHideState(wallNode, wallMesh, wallMode, u);
192
+ const isDeleteHighlighted = deleteHoveredWallId === wallId;
193
+ const isSelectionHighlighted = !isDeleteHighlighted && highlightedWallIds.has(wallId);
194
+ const materials = getMaterialsForWall(wallNode);
195
+ if (hideWall) {
196
+ ;
197
+ wallMesh.material = isDeleteHighlighted
198
+ ? materials.deleteInvisible
199
+ : isSelectionHighlighted
200
+ ? materials.highlightedInvisible
201
+ : materials.invisible;
160
202
  }
161
- }
162
- lastWallMaterials.current.clear();
163
- for (const [wallId, mats] of wallMaterialCache) {
164
- lastWallMaterials.current.set(wallId, mats);
165
- }
203
+ else {
204
+ ;
205
+ wallMesh.material = isDeleteHighlighted
206
+ ? materials.deleteVisible
207
+ : isSelectionHighlighted
208
+ ? materials.highlightedVisible
209
+ : wallNode.material
210
+ ? materials.visible
211
+ : baseMaterial;
212
+ }
213
+ });
166
214
  lastWallMode.current = wallMode;
167
215
  lastNumberOfWalls.current = sceneRegistry.byType.wall.size;
216
+ lastHighlightKey.current = highlightKey;
168
217
  }
169
218
  });
170
219
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pascal-app/viewer",
3
- "version": "0.3.3",
3
+ "version": "0.4.0",
4
4
  "description": "3D viewer component for Pascal building editor",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",