@pascal-app/viewer 0.1.10 → 0.1.12

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.
@@ -1 +1 @@
1
- {"version":3,"file":"ceiling-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/ceiling/ceiling-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAe,MAAM,kBAAkB,CAAA;AAoBhE,eAAO,MAAM,eAAe,GAAI,UAAU;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,4CAe9D,CAAA"}
1
+ {"version":3,"file":"ceiling-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/ceiling/ceiling-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAe,MAAM,kBAAkB,CAAA;AAuChE,eAAO,MAAM,eAAe,GAAI,UAAU;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,4CAe9D,CAAA"}
@@ -1,21 +1,35 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useRegistry } from '@pascal-app/core';
3
3
  import { useRef } from 'react';
4
- import { faceDirection, float, mix } from 'three/tsl';
5
- import { DoubleSide, MeshStandardNodeMaterial } from 'three/webgpu';
4
+ import { faceDirection, float, mix, positionWorld, smoothstep } from 'three/tsl';
5
+ import { DoubleSide, MeshBasicNodeMaterial } from 'three/webgpu';
6
6
  import { useNodeEvents } from '../../../hooks/use-node-events';
7
7
  import { NodeRenderer } from '../node-renderer';
8
8
  // TSL material that renders differently based on face direction:
9
9
  // - Back face (looking up at ceiling from below): solid
10
10
  // - Front face (looking down at ceiling from above): 30% opacity
11
- const ceilingMaterial = new MeshStandardNodeMaterial({
12
- color: 0xffffff,
11
+ const ceilingMaterial = new MeshBasicNodeMaterial({
12
+ color: 0x999999,
13
13
  side: DoubleSide,
14
14
  transparent: true,
15
+ depthWrite: false,
15
16
  });
17
+ // Create grid pattern based on local position
18
+ const gridScale = 5; // Grid cells per meter (1 = 1m grid)
19
+ const gridX = positionWorld.x.mul(gridScale).fract();
20
+ const gridY = positionWorld.z.mul(gridScale).fract();
21
+ // Create grid lines - they are at 0 and 1
22
+ const lineWidth = 0.05; // Width of grid lines (0-1 range within cell)
23
+ // Create visible lines at edges (near 0 and near 1)
24
+ const lineX = smoothstep(lineWidth, 0, gridX).add(smoothstep(1.0 - lineWidth, 1.0, gridX));
25
+ const lineY = smoothstep(lineWidth, 0, gridY).add(smoothstep(1.0 - lineWidth, 1.0, gridY));
26
+ // Combine: if either X or Y is a line, show the line
27
+ const gridPattern = lineX.max(lineY);
28
+ // Grid lines at 0.8 opacity, spaces at 0.1 opacity
29
+ const gridOpacity = mix(float(0.1), float(0.8), gridPattern);
16
30
  // faceDirection is 1.0 for front face, -1.0 for back face
17
- // We want: front face (top, looking down) = 0.3 opacity, back face (bottom, looking up) = 1.0 opacity
18
- ceilingMaterial.opacityNode = mix(float(1.0), float(0.3), faceDirection.greaterThan(0.0));
31
+ // Front face (top, looking down): grid pattern, Back face (bottom, looking up): solid
32
+ ceilingMaterial.opacityNode = mix(float(1.0), gridOpacity, faceDirection.greaterThan(0.0));
19
33
  export const CeilingRenderer = ({ node }) => {
20
34
  const ref = useRef(null);
21
35
  useRegistry(node.id, 'ceiling', ref);
@@ -1 +1 @@
1
- {"version":3,"file":"item-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/item/item-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,QAAQ,EAAyB,MAAM,kBAAkB,CAAA;AAkCvF,eAAO,MAAM,YAAY,GAAI,UAAU;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,4CAYxD,CAAA"}
1
+ {"version":3,"file":"item-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/item/item-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,QAAQ,EAAyB,MAAM,kBAAkB,CAAA;AAmCvF,eAAO,MAAM,YAAY,GAAI,UAAU;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,4CAYxD,CAAA"}
@@ -3,9 +3,10 @@ import { useRegistry, useScene } from '@pascal-app/core';
3
3
  import { Clone } from '@react-three/drei/core/Clone';
4
4
  import { useGLTF } from '@react-three/drei/core/Gltf';
5
5
  import { Suspense, useEffect, useMemo, useRef } from 'react';
6
+ import { positionLocal, smoothstep, time } from 'three/tsl';
6
7
  import { DoubleSide, MeshStandardNodeMaterial } from 'three/webgpu';
7
- import { resolveCdnUrl } from '../../../lib/asset-url';
8
8
  import { useNodeEvents } from '../../../hooks/use-node-events';
9
+ import { resolveCdnUrl } from '../../../lib/asset-url';
9
10
  // Shared materials to avoid creating new instances for every mesh
10
11
  const defaultMaterial = new MeshStandardNodeMaterial({
11
12
  color: 0xffffff,
@@ -31,7 +32,19 @@ const getMaterialForOriginal = (original) => {
31
32
  export const ItemRenderer = ({ node }) => {
32
33
  const ref = useRef(null);
33
34
  useRegistry(node.id, node.type, ref);
34
- return (_jsx("group", { position: node.position, rotation: node.rotation, ref: ref, visible: node.visible, children: _jsx(Suspense, { children: _jsx(ModelRenderer, { node: node }) }) }));
35
+ return (_jsx("group", { position: node.position, rotation: node.rotation, ref: ref, visible: node.visible, children: _jsx(Suspense, { fallback: _jsx(PreviewModel, { node: node }), children: _jsx(ModelRenderer, { node: node }) }) }));
36
+ };
37
+ const previewMaterial = new MeshStandardNodeMaterial({
38
+ color: '#cccccc',
39
+ roughness: 1,
40
+ metalness: 0,
41
+ depthTest: false,
42
+ });
43
+ const previewOpacity = smoothstep(0.42, 0.55, positionLocal.y.add(time.mul(-0.2)).mul(10).fract());
44
+ previewMaterial.opacityNode = previewOpacity;
45
+ previewMaterial.transparent = true;
46
+ const PreviewModel = ({ node }) => {
47
+ return (_jsx("mesh", { "position-y": node.asset.dimensions[1] / 2, material: previewMaterial, children: _jsx("boxGeometry", { args: [node.asset.dimensions[0], node.asset.dimensions[1], node.asset.dimensions[2]] }) }));
35
48
  };
36
49
  const ModelRenderer = ({ node }) => {
37
50
  const { scene, nodes } = useGLTF(resolveCdnUrl(node.asset.src) || '');
@@ -1 +1 @@
1
- {"version":3,"file":"node-renderer.d.ts","sourceRoot":"","sources":["../../../src/components/renderers/node-renderer.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,OAAO,EAAY,MAAM,kBAAkB,CAAA;AAYzD,eAAO,MAAM,YAAY,GAAI,YAAY;IAAE,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,mDAmBjE,CAAA"}
1
+ {"version":3,"file":"node-renderer.d.ts","sourceRoot":"","sources":["../../../src/components/renderers/node-renderer.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,OAAO,EAAY,MAAM,kBAAkB,CAAA;AAazD,eAAO,MAAM,YAAY,GAAI,YAAY;IAAE,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,mDAoBjE,CAAA"}
@@ -8,6 +8,7 @@ import { ItemRenderer } from './item/item-renderer';
8
8
  import { LevelRenderer } from './level/level-renderer';
9
9
  import { RoofRenderer } from './roof/roof-renderer';
10
10
  import { ScanRenderer } from './scan/scan-renderer';
11
+ import { SiteRenderer } from './site/site-renderer';
11
12
  import { SlabRenderer } from './slab/slab-renderer';
12
13
  import { WallRenderer } from './wall/wall-renderer';
13
14
  import { ZoneRenderer } from './zone/zone-renderer';
@@ -15,5 +16,5 @@ export const NodeRenderer = ({ nodeId }) => {
15
16
  const node = useScene((state) => state.nodes[nodeId]);
16
17
  if (!node)
17
18
  return null;
18
- return (_jsxs(_Fragment, { children: [node.type === 'building' && _jsx(BuildingRenderer, { node: node }), node.type === 'ceiling' && _jsx(CeilingRenderer, { node: node }), node.type === 'level' && _jsx(LevelRenderer, { node: node }), node.type === 'item' && _jsx(ItemRenderer, { node: node }), node.type === 'slab' && _jsx(SlabRenderer, { node: node }), node.type === 'wall' && _jsx(WallRenderer, { node: node }), node.type === 'zone' && _jsx(ZoneRenderer, { node: node }), node.type === 'roof' && _jsx(RoofRenderer, { node: node }), node.type === 'scan' && _jsx(ScanRenderer, { node: node }), node.type === 'guide' && _jsx(GuideRenderer, { node: node })] }));
19
+ return (_jsxs(_Fragment, { children: [node.type === 'site' && _jsx(SiteRenderer, { node: node }), node.type === 'building' && _jsx(BuildingRenderer, { node: node }), node.type === 'ceiling' && _jsx(CeilingRenderer, { node: node }), node.type === 'level' && _jsx(LevelRenderer, { node: node }), node.type === 'item' && _jsx(ItemRenderer, { node: node }), node.type === 'slab' && _jsx(SlabRenderer, { node: node }), node.type === 'wall' && _jsx(WallRenderer, { node: node }), node.type === 'zone' && _jsx(ZoneRenderer, { node: node }), node.type === 'roof' && _jsx(RoofRenderer, { node: node }), node.type === 'scan' && _jsx(ScanRenderer, { node: node }), node.type === 'guide' && _jsx(GuideRenderer, { node: node })] }));
19
20
  };
@@ -19,10 +19,12 @@ const ScanModel = ({ url, opacity }) => {
19
19
  if (isTransparent) {
20
20
  material.transparent = true;
21
21
  material.opacity = normalizedOpacity;
22
+ material.depthWrite = false;
22
23
  }
23
24
  else {
24
25
  material.transparent = false;
25
26
  material.opacity = 1;
27
+ material.depthWrite = true;
26
28
  }
27
29
  material.needsUpdate = true;
28
30
  };
@@ -0,0 +1,5 @@
1
+ import { type SiteNode } from '@pascal-app/core';
2
+ export declare const SiteRenderer: ({ node }: {
3
+ node: SiteNode;
4
+ }) => import("react/jsx-runtime").JSX.Element | null;
5
+ //# sourceMappingURL=site-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"site-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/site/site-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAe,MAAM,kBAAkB,CAAA;AAiC7D,eAAO,MAAM,YAAY,GAAI,UAAU;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,mDAmGxD,CAAA"}
@@ -0,0 +1,71 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRegistry } from '@pascal-app/core';
3
+ import { Html } from '@react-three/drei';
4
+ import { useMemo, useRef } from 'react';
5
+ import { BufferGeometry, DoubleSide, Float32BufferAttribute, Shape } from 'three';
6
+ import { useNodeEvents } from '../../../hooks/use-node-events';
7
+ import { NodeRenderer } from '../node-renderer';
8
+ const Y_OFFSET = 0.01;
9
+ const LINE_HEIGHT = 0.5;
10
+ /**
11
+ * Creates simple line geometry for site boundary
12
+ * Single horizontal line at ground level
13
+ */
14
+ const createBoundaryLineGeometry = (points) => {
15
+ const geometry = new BufferGeometry();
16
+ if (points.length < 2)
17
+ return geometry;
18
+ const positions = [];
19
+ // Create a simple line loop at ground level
20
+ for (const [x, z] of points) {
21
+ positions.push(x, Y_OFFSET, z);
22
+ }
23
+ // Close the loop
24
+ positions.push(points[0][0], Y_OFFSET, points[0][1]);
25
+ geometry.setAttribute('position', new Float32BufferAttribute(positions, 3));
26
+ return geometry;
27
+ };
28
+ export const SiteRenderer = ({ node }) => {
29
+ const ref = useRef(null);
30
+ useRegistry(node.id, 'site', ref);
31
+ // Create floor shape from polygon points
32
+ const floorShape = useMemo(() => {
33
+ if (!node?.polygon?.points || node.polygon.points.length < 3)
34
+ return null;
35
+ const shape = new Shape();
36
+ const firstPt = node.polygon.points[0];
37
+ // Shape is in X-Y plane, we rotate it to X-Z plane
38
+ // Negate Y (which becomes Z) to get correct orientation
39
+ shape.moveTo(firstPt[0], -firstPt[1]);
40
+ for (let i = 1; i < node.polygon.points.length; i++) {
41
+ const pt = node.polygon.points[i];
42
+ shape.lineTo(pt[0], -pt[1]);
43
+ }
44
+ shape.closePath();
45
+ return shape;
46
+ }, [node?.polygon?.points]);
47
+ // Create boundary line geometry
48
+ const lineGeometry = useMemo(() => {
49
+ if (!node?.polygon?.points || node.polygon.points.length < 2)
50
+ return null;
51
+ return createBoundaryLineGeometry(node.polygon.points);
52
+ }, [node?.polygon?.points]);
53
+ // Edge distances for labels
54
+ const edges = useMemo(() => {
55
+ const polygon = node?.polygon?.points ?? [];
56
+ if (polygon.length < 2)
57
+ return [];
58
+ return polygon.map(([x1, z1], i) => {
59
+ const [x2, z2] = polygon[(i + 1) % polygon.length];
60
+ const midX = (x1 + x2) / 2;
61
+ const midZ = (z1 + z2) / 2;
62
+ const dist = Math.sqrt((x2 - x1) ** 2 + (z2 - z1) ** 2);
63
+ return { midX, midZ, dist };
64
+ });
65
+ }, [node?.polygon?.points]);
66
+ const handlers = useNodeEvents(node, 'site');
67
+ if (!node || !floorShape || !lineGeometry) {
68
+ return null;
69
+ }
70
+ return (_jsxs("group", { ref: ref, ...handlers, children: [node.children.map((child) => (_jsx(NodeRenderer, { nodeId: typeof child === 'string' ? child : child.id }, typeof child === 'string' ? child : child.id))), _jsxs("mesh", { position: [0, Y_OFFSET - 0.005, 0], rotation: [-Math.PI / 2, 0, 0], children: [_jsx("shapeGeometry", { args: [floorShape] }), _jsx("meshBasicMaterial", { color: "#f59e0b", transparent: true, opacity: 0.05, side: DoubleSide, depthWrite: false })] }), _jsx("line", { geometry: lineGeometry, frustumCulled: false, renderOrder: 9, children: _jsx("lineBasicMaterial", { color: "#f59e0b", linewidth: 2, transparent: true, opacity: 0.6 }) }), edges.map((edge, i) => (_jsx(Html, { center: true, position: [edge.midX, 0.5, edge.midZ], style: { pointerEvents: 'none', userSelect: 'none' }, zIndexRange: [10, 0], occlude: true, children: _jsxs("div", { className: "whitespace-nowrap rounded bg-black/75 px-1.5 py-0.5 font-mono text-white text-xs backdrop-blur-sm", children: [edge.dist.toFixed(2), "m"] }) }, `edge-${i}`)))] }));
71
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"zone-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/zone/zone-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAkG7D,eAAO,MAAM,YAAY,GAAI,UAAU;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,mDAmGxD,CAAA"}
1
+ {"version":3,"file":"zone-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/zone/zone-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAwG7D,eAAO,MAAM,YAAY,GAAI,UAAU;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,mDAoGxD,CAAA"}
@@ -3,7 +3,7 @@ import { useRegistry } from '@pascal-app/core';
3
3
  import { Html } from '@react-three/drei';
4
4
  import { useMemo, useRef } from 'react';
5
5
  import { BufferGeometry, Color, DoubleSide, Float32BufferAttribute, Shape } from 'three';
6
- import { color, float, uv } from 'three/tsl';
6
+ import { color, float, uniform, uv } from 'three/tsl';
7
7
  import { MeshBasicNodeMaterial } from 'three/webgpu';
8
8
  import { useNodeEvents } from '../../../hooks/use-node-events';
9
9
  const Y_OFFSET = 0.01;
@@ -16,15 +16,19 @@ const createWallGradientMaterial = (zoneColor) => {
16
16
  const baseColor = color(new Color(zoneColor));
17
17
  // Use UV y coordinate for vertical gradient (0 at bottom, 1 at top)
18
18
  const gradientT = uv().y;
19
+ const opacity = uniform(0);
19
20
  // Fade opacity from 0.6 at bottom to 0 at top
20
- const opacity = float(0.6).mul(float(1).sub(gradientT));
21
+ const finalOpacity = float(0.6).mul(float(1).sub(gradientT)).mul(opacity);
21
22
  return new MeshBasicNodeMaterial({
22
23
  transparent: true,
23
24
  colorNode: baseColor,
24
- opacityNode: opacity,
25
+ opacityNode: finalOpacity,
25
26
  side: DoubleSide,
26
27
  depthWrite: false,
27
28
  depthTest: false,
29
+ userData: {
30
+ uOpacity: opacity,
31
+ }
28
32
  });
29
33
  };
30
34
  /**
@@ -32,12 +36,15 @@ const createWallGradientMaterial = (zoneColor) => {
32
36
  */
33
37
  const createFloorMaterial = (zoneColor) => {
34
38
  const baseColor = color(new Color(zoneColor));
39
+ const opacity = uniform(0);
35
40
  return new MeshBasicNodeMaterial({
36
41
  transparent: true,
37
42
  colorNode: baseColor,
38
- opacityNode: float(0.15),
43
+ opacityNode: float(0.25).mul(opacity),
39
44
  side: DoubleSide,
40
45
  depthWrite: false,
46
+ depthTest: false,
47
+ userData: { uOpacity: opacity }
41
48
  });
42
49
  };
43
50
  /**
@@ -142,7 +149,7 @@ export const ZoneRenderer = ({ node }) => {
142
149
  }
143
150
  return (_jsxs("group", { ref: ref, ...handlers, children: [_jsx(Html, { name: "label", position: [centroid[0], 1, centroid[1]], style: {
144
151
  pointerEvents: 'none'
145
- }, children: _jsx("div", { style: {
152
+ }, zIndexRange: [10, 0], children: _jsx("div", { style: {
146
153
  transform: 'translate3d(-50%, -50%, 0)',
147
154
  width: 'max-content',
148
155
  color: 'white',
@@ -150,5 +157,5 @@ export const ZoneRenderer = ({ node }) => {
150
157
  display: 'flex',
151
158
  gap: '8px',
152
159
  alignItems: 'center',
153
- }, children: node.name }) }), _jsx("mesh", { position: [0, Y_OFFSET, 0], rotation: [-Math.PI / 2, 0, 0], material: floorMaterial, children: _jsx("shapeGeometry", { args: [floorShape] }) }), _jsx("mesh", { geometry: wallGeometry, material: wallMaterial })] }));
160
+ }, children: node.name }) }), _jsx("mesh", { position: [0, Y_OFFSET, 0], rotation: [-Math.PI / 2, 0, 0], material: floorMaterial, name: "floor", children: _jsx("shapeGeometry", { args: [floorShape] }) }), _jsx("mesh", { geometry: wallGeometry, material: wallMaterial, name: "walls" })] }));
154
161
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/index.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAkB,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAC5E,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAWrC,OAAO,QAAQ,oBAAoB,CAAC;IAClC,UAAU,aAAc,SAAQ,kBAAkB,CAAC,OAAO,KAAK,CAAC;KAAG;CACpE;AAID,UAAU,WAAW;IACnB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,gBAAgB,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAA;CACxC;AAED,QAAA,MAAM,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,WAAW,CA4CjC,CAAA;AAED,eAAe,MAAM,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/index.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAkB,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAC5E,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAYrC,OAAO,QAAQ,oBAAoB,CAAC;IAClC,UAAU,aAAc,SAAQ,kBAAkB,CAAC,OAAO,KAAK,CAAC;KAAG;CACpE;AAID,UAAU,WAAW;IACnB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,gBAAgB,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAA;CACxC;AAED,QAAA,MAAM,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,WAAW,CA8CjC,CAAA;AAED,eAAe,MAAM,CAAA"}
@@ -8,6 +8,7 @@ import { GuideSystem } from '../../systems/guide/guide-system';
8
8
  import { LevelSystem } from '../../systems/level/level-system';
9
9
  import { ScanSystem } from '../../systems/scan/scan-system';
10
10
  import { WallCutout } from '../../systems/wall/wall-cutout';
11
+ import { ZoneSystem } from '../../systems/zone/zone-system';
11
12
  import { SceneRenderer } from '../renderers/scene-renderer';
12
13
  import { Lights } from './lights';
13
14
  import PostProcessing from './post-processing';
@@ -15,7 +16,7 @@ import { SelectionManager } from './selection-manager';
15
16
  import { ViewerCamera } from './viewer-camera';
16
17
  extend(THREE);
17
18
  const Viewer = ({ children, selectionManager = 'default' }) => {
18
- return (_jsxs(Canvas, { className: 'bg-[#fafafa]', gl: async (props) => {
19
+ return (_jsxs(Canvas, { dpr: [1, 1.5], className: 'bg-[#fafafa]', gl: async (props) => {
19
20
  const renderer = new THREE.WebGPURenderer(props);
20
21
  await renderer.init();
21
22
  renderer.toneMapping = THREE.ACESFilmicToneMapping;
@@ -24,6 +25,6 @@ const Viewer = ({ children, selectionManager = 'default' }) => {
24
25
  }, shadows: {
25
26
  type: THREE.PCFShadowMap,
26
27
  enabled: true,
27
- }, camera: { position: [50, 50, 50], fov: 50 }, children: [_jsx("color", { attach: "background", args: ['#fafafa'] }), _jsx(ViewerCamera, {}), _jsx(Lights, {}), _jsx(Bvh, { children: _jsx(SceneRenderer, {}) }), _jsx(LevelSystem, {}), _jsx(GuideSystem, {}), _jsx(ScanSystem, {}), _jsx(WallCutout, {}), _jsx(CeilingSystem, {}), _jsx(ItemSystem, {}), _jsx(RoofSystem, {}), _jsx(SlabSystem, {}), _jsx(WallSystem, {}), _jsx(PostProcessing, {}), selectionManager === 'default' && _jsx(SelectionManager, {}), children] }));
28
+ }, camera: { position: [50, 50, 50], fov: 50 }, children: [_jsx("color", { attach: "background", args: ['#fafafa'] }), _jsx(ViewerCamera, {}), _jsx(Lights, {}), _jsx(Bvh, { children: _jsx(SceneRenderer, {}) }), _jsx(LevelSystem, {}), _jsx(GuideSystem, {}), _jsx(ScanSystem, {}), _jsx(WallCutout, {}), _jsx(CeilingSystem, {}), _jsx(ItemSystem, {}), _jsx(RoofSystem, {}), _jsx(SlabSystem, {}), _jsx(WallSystem, {}), _jsx(ZoneSystem, {}), _jsx(PostProcessing, {}), selectionManager === 'default' && _jsx(SelectionManager, {}), children] }));
28
29
  };
29
30
  export default Viewer;
@@ -1 +1 @@
1
- {"version":3,"file":"post-processing.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/post-processing.tsx"],"names":[],"mappings":"AA0BA,eAAO,MAAM,WAAW;;;;;;;;;;;;;CAavB,CAAA;AAED,QAAA,MAAM,oBAAoB,YA8IzB,CAAA;AAED,eAAe,oBAAoB,CAAA"}
1
+ {"version":3,"file":"post-processing.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/post-processing.tsx"],"names":[],"mappings":"AA0BA,eAAO,MAAM,WAAW;;;;;;;;;;;;;CAavB,CAAA;AAED,QAAA,MAAM,oBAAoB,YAgJzB,CAAA;AAED,eAAe,oBAAoB,CAAA"}
@@ -10,10 +10,10 @@ import useViewer from '../../store/use-viewer';
10
10
  // SSGI Parameters - adjust these to fine-tune global illumination and ambient occlusion
11
11
  export const SSGI_PARAMS = {
12
12
  enabled: true,
13
- sliceCount: 2,
13
+ sliceCount: 1,
14
14
  stepCount: 8,
15
- radius: 2,
16
- expFactor: 2,
15
+ radius: 1,
16
+ expFactor: 1.5,
17
17
  thickness: 0.5,
18
18
  backfaceLighting: 0.5,
19
19
  aoIntensity: 1.5,
@@ -1,6 +1,10 @@
1
- import { type BuildingEvent, type BuildingNode, type CeilingEvent, type CeilingNode, type ItemEvent, type ItemNode, type LevelEvent, type LevelNode, type RoofEvent, type RoofNode, type SlabEvent, type SlabNode, type WallEvent, type WallNode, type ZoneEvent, type ZoneNode } from '@pascal-app/core';
1
+ import { type BuildingEvent, type BuildingNode, type CeilingEvent, type CeilingNode, type ItemEvent, type ItemNode, type LevelEvent, type LevelNode, type RoofEvent, type RoofNode, type SiteEvent, type SiteNode, type SlabEvent, type SlabNode, type WallEvent, type WallNode, type ZoneEvent, type ZoneNode } from '@pascal-app/core';
2
2
  import type { ThreeEvent } from '@react-three/fiber';
3
3
  type NodeConfig = {
4
+ site: {
5
+ node: SiteNode;
6
+ event: SiteEvent;
7
+ };
4
8
  item: {
5
9
  node: ItemNode;
6
10
  event: ItemEvent;
@@ -1 +1 @@
1
- {"version":3,"file":"use-node-events.d.ts","sourceRoot":"","sources":["../../src/hooks/use-node-events.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,WAAW,EAGhB,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,QAAQ,EACd,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAEpD,KAAK,UAAU,GAAG;IAChB,IAAI,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAA;IAC1C,IAAI,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAA;IAC1C,QAAQ,EAAE;QAAE,IAAI,EAAE,YAAY,CAAC;QAAC,KAAK,EAAE,aAAa,CAAA;KAAE,CAAA;IACtD,KAAK,EAAE;QAAE,IAAI,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,UAAU,CAAA;KAAE,CAAA;IAC7C,IAAI,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAA;IAC1C,IAAI,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAA;IAC1C,OAAO,EAAE;QAAE,IAAI,EAAE,WAAW,CAAC;QAAC,KAAK,EAAE,YAAY,CAAA;KAAE,CAAA;IACnD,IAAI,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAA;CAC3C,CAAA;AAED,KAAK,QAAQ,GAAG,MAAM,UAAU,CAAA;AAEhC,wBAAgB,aAAa,CAAC,CAAC,SAAS,QAAQ,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC;uBAiB/D,UAAU,CAAC,YAAY,CAAC;qBAI1B,UAAU,CAAC,YAAY,CAAC;iBAI5B,UAAU,CAAC,YAAY,CAAC;wBAIjB,UAAU,CAAC,YAAY,CAAC;wBACxB,UAAU,CAAC,YAAY,CAAC;uBACzB,UAAU,CAAC,YAAY,CAAC;uBACxB,UAAU,CAAC,YAAY,CAAC;uBACxB,UAAU,CAAC,YAAY,CAAC;EAE9C"}
1
+ {"version":3,"file":"use-node-events.d.ts","sourceRoot":"","sources":["../../src/hooks/use-node-events.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,WAAW,EAGhB,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,QAAQ,EACd,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAEpD,KAAK,UAAU,GAAG;IAChB,IAAI,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAA;IAC1C,IAAI,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAA;IAC1C,IAAI,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAA;IAC1C,QAAQ,EAAE;QAAE,IAAI,EAAE,YAAY,CAAC;QAAC,KAAK,EAAE,aAAa,CAAA;KAAE,CAAA;IACtD,KAAK,EAAE;QAAE,IAAI,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,UAAU,CAAA;KAAE,CAAA;IAC7C,IAAI,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAA;IAC1C,IAAI,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAA;IAC1C,OAAO,EAAE;QAAE,IAAI,EAAE,WAAW,CAAC;QAAC,KAAK,EAAE,YAAY,CAAA;KAAE,CAAA;IACnD,IAAI,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAA;CAC3C,CAAA;AAED,KAAK,QAAQ,GAAG,MAAM,UAAU,CAAA;AAEhC,wBAAgB,aAAa,CAAC,CAAC,SAAS,QAAQ,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC;uBAiB/D,UAAU,CAAC,YAAY,CAAC;qBAI1B,UAAU,CAAC,YAAY,CAAC;iBAI5B,UAAU,CAAC,YAAY,CAAC;wBAIjB,UAAU,CAAC,YAAY,CAAC;wBACxB,UAAU,CAAC,YAAY,CAAC;uBACzB,UAAU,CAAC,YAAY,CAAC;uBACxB,UAAU,CAAC,YAAY,CAAC;uBACxB,UAAU,CAAC,YAAY,CAAC;EAE9C"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  export { default as Viewer } from './components/viewer';
2
- export { useGridEvents } from './hooks/use-grid-events';
3
2
  export { default as useViewer } from './store/use-viewer';
4
3
  export { ASSETS_CDN_URL, resolveAssetUrl, resolveCdnUrl } from './lib/asset-url';
5
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAA;AACzD,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAA;AACzD,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA"}
package/dist/index.js CHANGED
@@ -1,4 +1,3 @@
1
1
  export { default as Viewer } from './components/viewer';
2
- export { useGridEvents } from './hooks/use-grid-events';
3
2
  export { default as useViewer } from './store/use-viewer';
4
3
  export { ASSETS_CDN_URL, resolveAssetUrl, resolveCdnUrl } from './lib/asset-url';
@@ -0,0 +1,2 @@
1
+ export declare const ZoneSystem: () => null;
2
+ //# sourceMappingURL=zone-system.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zone-system.d.ts","sourceRoot":"","sources":["../../../src/systems/zone/zone-system.tsx"],"names":[],"mappings":"AASA,eAAO,MAAM,UAAU,YA4DtB,CAAA"}
@@ -0,0 +1,57 @@
1
+ import { sceneRegistry, useScene } from '@pascal-app/core';
2
+ import { useFrame } from '@react-three/fiber';
3
+ import { useRef } from 'react';
4
+ import { MathUtils } from 'three';
5
+ import useViewer from '../../store/use-viewer';
6
+ const TRANSITION_DURATION = 400; // ms
7
+ export const ZoneSystem = () => {
8
+ const lastHighlightedZoneRef = useRef(null);
9
+ const lastChangeTimeRef = useRef(0);
10
+ const isTransitioningRef = useRef(false);
11
+ useFrame(({ clock }, delta) => {
12
+ const hoveredId = useViewer.getState().hoveredId;
13
+ let highlightedZone = null;
14
+ if (hoveredId) {
15
+ const hoveredNode = useScene.getState().nodes[hoveredId];
16
+ if (hoveredNode?.type === 'zone') {
17
+ highlightedZone = hoveredId;
18
+ }
19
+ }
20
+ // Detect zone change
21
+ if (highlightedZone !== lastHighlightedZoneRef.current) {
22
+ lastHighlightedZoneRef.current = highlightedZone;
23
+ lastChangeTimeRef.current = clock.elapsedTime * 1000;
24
+ isTransitioningRef.current = true;
25
+ }
26
+ // Skip frame if not transitioning
27
+ if (!isTransitioningRef.current)
28
+ return;
29
+ const elapsed = clock.elapsedTime * 1000 - lastChangeTimeRef.current;
30
+ // Stop transitioning after duration
31
+ if (elapsed >= TRANSITION_DURATION) {
32
+ isTransitioningRef.current = false;
33
+ }
34
+ // Lerp speed: complete transition in ~400ms
35
+ const lerpSpeed = 10 * delta;
36
+ sceneRegistry.byType.zone.forEach((zoneId) => {
37
+ const zone = sceneRegistry.nodes.get(zoneId);
38
+ if (!zone)
39
+ return;
40
+ const isHighlighted = zoneId === highlightedZone;
41
+ const targetOpacity = isHighlighted ? 1 : 0;
42
+ const walls = zone.getObjectByName('walls');
43
+ if (walls) {
44
+ const material = walls.material;
45
+ const currentOpacity = material.userData.uOpacity.value;
46
+ material.userData.uOpacity.value = MathUtils.lerp(currentOpacity, targetOpacity, lerpSpeed);
47
+ }
48
+ const floor = zone.getObjectByName('floor');
49
+ if (floor) {
50
+ const material = floor.material;
51
+ const currentOpacity = material.userData.uOpacity.value;
52
+ material.userData.uOpacity.value = MathUtils.lerp(currentOpacity, targetOpacity, lerpSpeed);
53
+ }
54
+ });
55
+ });
56
+ return null;
57
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pascal-app/viewer",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "3D viewer component for Pascal building editor",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",