@pascal-app/viewer 0.5.1 → 0.6.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 (53) hide show
  1. package/dist/components/renderers/ceiling/ceiling-renderer.d.ts.map +1 -1
  2. package/dist/components/renderers/ceiling/ceiling-renderer.js +4 -3
  3. package/dist/components/renderers/door/door-renderer.d.ts.map +1 -1
  4. package/dist/components/renderers/door/door-renderer.js +5 -2
  5. package/dist/components/renderers/fence/fence-renderer.d.ts.map +1 -1
  6. package/dist/components/renderers/fence/fence-renderer.js +16 -2
  7. package/dist/components/renderers/roof/roof-renderer.d.ts.map +1 -1
  8. package/dist/components/renderers/roof/roof-renderer.js +31 -9
  9. package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts.map +1 -1
  10. package/dist/components/renderers/roof-segment/roof-segment-renderer.js +42 -8
  11. package/dist/components/renderers/site/site-renderer.d.ts.map +1 -1
  12. package/dist/components/renderers/site/site-renderer.js +7 -1
  13. package/dist/components/renderers/slab/slab-renderer.d.ts.map +1 -1
  14. package/dist/components/renderers/slab/slab-renderer.js +37 -8
  15. package/dist/components/renderers/stair/stair-renderer.d.ts.map +1 -1
  16. package/dist/components/renderers/stair/stair-renderer.js +172 -39
  17. package/dist/components/renderers/stair-segment/stair-segment-renderer.d.ts.map +1 -1
  18. package/dist/components/renderers/stair-segment/stair-segment-renderer.js +37 -8
  19. package/dist/components/renderers/wall/wall-renderer.d.ts.map +1 -1
  20. package/dist/components/renderers/wall/wall-renderer.js +4 -9
  21. package/dist/components/renderers/window/window-renderer.d.ts.map +1 -1
  22. package/dist/components/renderers/window/window-renderer.js +5 -2
  23. package/dist/components/viewer/frame-limiter.d.ts +6 -0
  24. package/dist/components/viewer/frame-limiter.d.ts.map +1 -0
  25. package/dist/components/viewer/frame-limiter.js +31 -0
  26. package/dist/components/viewer/ground-occluder.d.ts.map +1 -1
  27. package/dist/components/viewer/ground-occluder.js +5 -1
  28. package/dist/components/viewer/index.d.ts.map +1 -1
  29. package/dist/components/viewer/index.js +11 -11
  30. package/dist/components/viewer/post-processing.d.ts.map +1 -1
  31. package/dist/components/viewer/post-processing.js +33 -36
  32. package/dist/components/viewer/selection-manager.d.ts.map +1 -1
  33. package/dist/components/viewer/selection-manager.js +12 -1
  34. package/dist/hooks/use-node-events.d.ts.map +1 -1
  35. package/dist/hooks/use-node-events.js +2 -0
  36. package/dist/lib/materials.d.ts +4 -1
  37. package/dist/lib/materials.d.ts.map +1 -1
  38. package/dist/lib/materials.js +178 -2
  39. package/dist/lib/merged-outline-node.d.ts.map +1 -1
  40. package/dist/lib/merged-outline-node.js +11 -4
  41. package/dist/store/use-viewer.d.ts +2 -2
  42. package/dist/systems/roof/roof-materials.d.ts +5 -0
  43. package/dist/systems/roof/roof-materials.d.ts.map +1 -0
  44. package/dist/systems/roof/roof-materials.js +29 -0
  45. package/dist/systems/stair/stair-materials.d.ts +7 -0
  46. package/dist/systems/stair/stair-materials.d.ts.map +1 -0
  47. package/dist/systems/stair/stair-materials.js +33 -0
  48. package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
  49. package/dist/systems/wall/wall-cutout.js +4 -123
  50. package/dist/systems/wall/wall-materials.d.ts +16 -0
  51. package/dist/systems/wall/wall-materials.d.ts.map +1 -0
  52. package/dist/systems/wall/wall-materials.js +148 -0
  53. package/package.json +4 -4
@@ -1,129 +1,12 @@
1
- import { baseMaterial, emitter, sceneRegistry, useScene, } from '@pascal-app/core';
1
+ import { emitter, sceneRegistry, useScene } from '@pascal-app/core';
2
2
  import { useFrame } from '@react-three/fiber';
3
3
  import { useEffect, useRef } from 'react';
4
- import { Color } from 'three';
5
- import { Fn, float, fract, length, mix, positionLocal, smoothstep, step, vec2 } from 'three/tsl';
6
- import { MeshStandardNodeMaterial, Vector3 } from 'three/webgpu';
4
+ import { Vector3 } from 'three/webgpu';
7
5
  import useViewer from '../../store/use-viewer';
6
+ import { getMaterialsForWall } from './wall-materials';
8
7
  const tmpVec = new Vector3();
9
8
  const u = new Vector3();
10
9
  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
- };
24
- const dotPattern = Fn(() => {
25
- const scale = float(0.1);
26
- const dotSize = float(0.3);
27
- const uv = vec2(positionLocal.x, positionLocal.y).div(scale);
28
- const gridUV = fract(uv);
29
- const dist = length(gridUV.sub(0.5));
30
- const dots = step(dist, dotSize.mul(0.5));
31
- const fadeHeight = float(2.5);
32
- const yFade = float(1).sub(smoothstep(float(0), fadeHeight, positionLocal.y));
33
- return dots.mul(yFade);
34
- });
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
- };
47
- function getMaterialHash(wallNode) {
48
- if (!wallNode.material)
49
- return 'none';
50
- const mat = wallNode.material;
51
- if (mat.preset && mat.preset !== 'custom') {
52
- return `preset-${mat.preset}`;
53
- }
54
- if (mat.properties) {
55
- return `props-${mat.properties.color}-${mat.properties.roughness}-${mat.properties.metalness}`;
56
- }
57
- return 'default';
58
- }
59
- function getPresetColor(preset) {
60
- return presetColors[preset] ?? '#ffffff';
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
- }
75
- function getMaterialsForWall(wallNode) {
76
- const cacheKey = wallNode.id;
77
- const materialHash = getMaterialHash(wallNode);
78
- const existing = wallMaterialCache.get(cacheKey);
79
- if (existing && existing.materialHash === materialHash) {
80
- return existing;
81
- }
82
- if (existing) {
83
- existing.visible.dispose();
84
- existing.invisible.dispose();
85
- existing.deleteVisible.dispose();
86
- existing.deleteInvisible.dispose();
87
- existing.highlightedVisible.dispose();
88
- existing.highlightedInvisible.dispose();
89
- }
90
- let userColor = DEFAULT_WALL_COLOR;
91
- if (wallNode.material?.properties?.color) {
92
- userColor = wallNode.material.properties.color;
93
- }
94
- else if (wallNode.material?.preset && wallNode.material.preset !== 'custom') {
95
- userColor = getPresetColor(wallNode.material.preset);
96
- }
97
- const visibleMat = wallNode.material
98
- ? new MeshStandardNodeMaterial({
99
- color: userColor,
100
- roughness: 1,
101
- metalness: 0,
102
- })
103
- : baseMaterial.clone();
104
- const invisibleMat = new MeshStandardNodeMaterial({
105
- transparent: true,
106
- opacityNode: mix(float(0.0), float(0.24), dotPattern()),
107
- color: userColor,
108
- depthWrite: false,
109
- emissive: userColor,
110
- });
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
- };
124
- wallMaterialCache.set(cacheKey, result);
125
- return result;
126
- }
127
10
  function getWallHideState(wallNode, wallMesh, wallMode, cameraDir) {
128
11
  let hideWall = wallNode.frontSide === 'interior' && wallNode.backSide === 'interior';
129
12
  if (wallMode === 'up') {
@@ -206,9 +89,7 @@ export const WallCutout = () => {
206
89
  ? materials.deleteVisible
207
90
  : isSelectionHighlighted
208
91
  ? materials.highlightedVisible
209
- : wallNode.material
210
- ? materials.visible
211
- : baseMaterial;
92
+ : materials.visible;
212
93
  }
213
94
  });
214
95
  lastWallMode.current = wallMode;
@@ -0,0 +1,16 @@
1
+ import { type WallNode } from '@pascal-app/core';
2
+ import { type Material } from 'three';
3
+ export type WallMaterialArray = [Material, Material, Material];
4
+ export interface WallMaterials {
5
+ visible: WallMaterialArray;
6
+ invisible: WallMaterialArray;
7
+ deleteVisible: WallMaterialArray;
8
+ deleteInvisible: WallMaterialArray;
9
+ highlightedVisible: WallMaterialArray;
10
+ highlightedInvisible: WallMaterialArray;
11
+ materialHash: string;
12
+ }
13
+ export declare function getWallMaterialHash(wallNode: WallNode): string;
14
+ export declare function getMaterialsForWall(wallNode: WallNode): WallMaterials;
15
+ export declare function getVisibleWallMaterials(wallNode: WallNode): WallMaterialArray;
16
+ //# sourceMappingURL=wall-materials.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wall-materials.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-materials.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,QAAQ,EAEd,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAS,KAAK,QAAQ,EAAE,MAAM,OAAO,CAAA;AAwB5C,MAAM,MAAM,iBAAiB,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAA;AAE9D,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,iBAAiB,CAAA;IAC1B,SAAS,EAAE,iBAAiB,CAAA;IAC5B,aAAa,EAAE,iBAAiB,CAAA;IAChC,eAAe,EAAE,iBAAiB,CAAA;IAClC,kBAAkB,EAAE,iBAAiB,CAAA;IACrC,oBAAoB,EAAE,iBAAiB,CAAA;IACvC,YAAY,EAAE,MAAM,CAAA;CACrB;AA4GD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAS9D;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,GAAG,aAAa,CA2DrE;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,QAAQ,GAAG,iBAAiB,CAE7E"}
@@ -0,0 +1,148 @@
1
+ import { baseMaterial, getEffectiveWallSurfaceMaterial, getMaterialPresetByRef, getWallSurfaceMaterialSignature, resolveMaterial, } from '@pascal-app/core';
2
+ import { Color } from 'three';
3
+ import { Fn, float, fract, length, mix, positionLocal, smoothstep, step, vec2 } from 'three/tsl';
4
+ import { MeshStandardNodeMaterial } from 'three/webgpu';
5
+ import { createMaterial, createMaterialFromPresetRef } from '../../lib/materials';
6
+ const DEFAULT_WALL_COLOR = '#f2f0ed';
7
+ const WALL_HIGHLIGHT_PROFILES = {
8
+ delete: {
9
+ color: new Color('#dc2626'),
10
+ blend: 0.76,
11
+ emissiveBlend: 0.92,
12
+ emissiveIntensity: 0.46,
13
+ },
14
+ selection: {
15
+ color: new Color('#818cf8'),
16
+ blend: 0.32,
17
+ emissiveBlend: 0.7,
18
+ emissiveIntensity: 0.42,
19
+ },
20
+ };
21
+ const wallMaterialCache = new Map();
22
+ const dotPattern = Fn(() => {
23
+ const scale = float(0.1);
24
+ const dotSize = float(0.3);
25
+ const uv = vec2(positionLocal.x, positionLocal.y).div(scale);
26
+ const gridUV = fract(uv);
27
+ const dist = length(gridUV.sub(0.5));
28
+ const dots = step(dist, dotSize.mul(0.5));
29
+ const fadeHeight = float(2.5);
30
+ const yFade = float(1).sub(smoothstep(float(0), fadeHeight, positionLocal.y));
31
+ return dots.mul(yFade);
32
+ });
33
+ function getSurfaceVisibleMaterial(spec) {
34
+ if (spec.materialPreset) {
35
+ return createMaterialFromPresetRef(spec.materialPreset) ?? baseMaterial;
36
+ }
37
+ if (spec.material) {
38
+ return createMaterial(spec.material);
39
+ }
40
+ return baseMaterial;
41
+ }
42
+ function getSurfaceColor(spec, fallback = DEFAULT_WALL_COLOR) {
43
+ const preset = getMaterialPresetByRef(spec.materialPreset);
44
+ if (preset?.mapProperties?.color) {
45
+ return preset.mapProperties.color;
46
+ }
47
+ if (spec.material) {
48
+ return resolveMaterial(spec.material).color;
49
+ }
50
+ return fallback;
51
+ }
52
+ function getHighlightedColor(color, kind) {
53
+ const profile = WALL_HIGHLIGHT_PROFILES[kind];
54
+ return color.clone().lerp(profile.color, profile.blend);
55
+ }
56
+ function createHighlightedWallMaterial(material, kind) {
57
+ const highlightedMaterial = material.clone();
58
+ const profile = WALL_HIGHLIGHT_PROFILES[kind];
59
+ if ('color' in highlightedMaterial && highlightedMaterial.color) {
60
+ highlightedMaterial.color = getHighlightedColor(highlightedMaterial.color, kind);
61
+ }
62
+ if ('emissive' in highlightedMaterial && highlightedMaterial.emissive) {
63
+ highlightedMaterial.emissive = highlightedMaterial.emissive
64
+ .clone()
65
+ .lerp(profile.color, profile.emissiveBlend);
66
+ }
67
+ if ('emissiveIntensity' in highlightedMaterial) {
68
+ highlightedMaterial.emissiveIntensity = Math.max(highlightedMaterial.emissiveIntensity ?? 0, profile.emissiveIntensity);
69
+ }
70
+ highlightedMaterial.needsUpdate = true;
71
+ return highlightedMaterial;
72
+ }
73
+ function createInvisibleWallMaterial(color) {
74
+ return new MeshStandardNodeMaterial({
75
+ transparent: true,
76
+ opacityNode: mix(float(0.0), float(0.24), dotPattern()),
77
+ color,
78
+ depthWrite: false,
79
+ emissive: color,
80
+ });
81
+ }
82
+ function mapWallMaterialArray(materials, iteratee) {
83
+ return materials.map(iteratee);
84
+ }
85
+ function disposeOwnedMaterials(materials) {
86
+ const owned = new Set();
87
+ materials.forEach((entry) => {
88
+ entry.forEach((material) => {
89
+ owned.add(material);
90
+ });
91
+ });
92
+ owned.forEach((material) => {
93
+ material.dispose();
94
+ });
95
+ }
96
+ export function getWallMaterialHash(wallNode) {
97
+ return JSON.stringify({
98
+ interior: getWallSurfaceMaterialSignature(getEffectiveWallSurfaceMaterial(wallNode, 'interior')),
99
+ exterior: getWallSurfaceMaterialSignature(getEffectiveWallSurfaceMaterial(wallNode, 'exterior')),
100
+ });
101
+ }
102
+ export function getMaterialsForWall(wallNode) {
103
+ const cacheKey = wallNode.id;
104
+ const materialHash = getWallMaterialHash(wallNode);
105
+ const existing = wallMaterialCache.get(cacheKey);
106
+ if (existing && existing.materialHash === materialHash) {
107
+ return existing;
108
+ }
109
+ if (existing) {
110
+ disposeOwnedMaterials([
111
+ existing.invisible,
112
+ existing.deleteVisible,
113
+ existing.deleteInvisible,
114
+ existing.highlightedVisible,
115
+ existing.highlightedInvisible,
116
+ ]);
117
+ }
118
+ const interiorSpec = getEffectiveWallSurfaceMaterial(wallNode, 'interior');
119
+ const exteriorSpec = getEffectiveWallSurfaceMaterial(wallNode, 'exterior');
120
+ const visible = [
121
+ baseMaterial,
122
+ getSurfaceVisibleMaterial(interiorSpec),
123
+ getSurfaceVisibleMaterial(exteriorSpec),
124
+ ];
125
+ const invisible = [
126
+ createInvisibleWallMaterial(DEFAULT_WALL_COLOR),
127
+ createInvisibleWallMaterial(getSurfaceColor(interiorSpec, DEFAULT_WALL_COLOR)),
128
+ createInvisibleWallMaterial(getSurfaceColor(exteriorSpec, DEFAULT_WALL_COLOR)),
129
+ ];
130
+ const highlightedVisible = mapWallMaterialArray(visible, (material) => createHighlightedWallMaterial(material, 'selection'));
131
+ const highlightedInvisible = mapWallMaterialArray(invisible, (material) => createHighlightedWallMaterial(material, 'selection'));
132
+ const deleteVisible = mapWallMaterialArray(visible, (material) => createHighlightedWallMaterial(material, 'delete'));
133
+ const deleteInvisible = mapWallMaterialArray(invisible, (material) => createHighlightedWallMaterial(material, 'delete'));
134
+ const result = {
135
+ visible,
136
+ invisible,
137
+ deleteVisible,
138
+ deleteInvisible,
139
+ highlightedVisible,
140
+ highlightedInvisible,
141
+ materialHash,
142
+ };
143
+ wallMaterialCache.set(cacheKey, result);
144
+ return result;
145
+ }
146
+ export function getVisibleWallMaterials(wallNode) {
147
+ return getMaterialsForWall(wallNode).visible;
148
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pascal-app/viewer",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "3D viewer component for Pascal building editor",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -22,11 +22,11 @@
22
22
  "prepublishOnly": "npm run build"
23
23
  },
24
24
  "peerDependencies": {
25
- "@pascal-app/core": "^0.5.1",
25
+ "@pascal-app/core": "^0.6.0",
26
26
  "@react-three/drei": "^10",
27
27
  "@react-three/fiber": "^9",
28
28
  "react": "^18 || ^19",
29
- "three": "^0.183"
29
+ "three": "^0.184"
30
30
  },
31
31
  "dependencies": {
32
32
  "polygon-clipping": "^0.15.7",
@@ -36,7 +36,7 @@
36
36
  "@pascal/typescript-config": "*",
37
37
  "@types/node": "^25.5.0",
38
38
  "@types/react": "^19.2.2",
39
- "@types/three": "^0.183.0",
39
+ "@types/three": "^0.184.0",
40
40
  "typescript": "5.9.3"
41
41
  },
42
42
  "keywords": [