@pascal-app/viewer 0.1.2 → 0.1.3

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 (84) hide show
  1. package/dist/components/renderers/building/building-renderer.d.ts +5 -0
  2. package/dist/components/renderers/building/building-renderer.d.ts.map +1 -0
  3. package/dist/components/renderers/building/building-renderer.js +11 -0
  4. package/dist/components/renderers/ceiling/ceiling-renderer.d.ts +5 -0
  5. package/dist/components/renderers/ceiling/ceiling-renderer.d.ts.map +1 -0
  6. package/dist/components/renderers/ceiling/ceiling-renderer.js +24 -0
  7. package/dist/components/renderers/guide/guide-renderer.d.ts +5 -0
  8. package/dist/components/renderers/guide/guide-renderer.d.ts.map +1 -0
  9. package/dist/components/renderers/guide/guide-renderer.js +36 -0
  10. package/dist/components/renderers/item/item-renderer.d.ts +5 -0
  11. package/dist/components/renderers/item/item-renderer.d.ts.map +1 -0
  12. package/dist/components/renderers/item/item-renderer.js +70 -0
  13. package/dist/components/renderers/level/level-renderer.d.ts +5 -0
  14. package/dist/components/renderers/level/level-renderer.d.ts.map +1 -0
  15. package/dist/components/renderers/level/level-renderer.js +11 -0
  16. package/dist/components/renderers/node-renderer.d.ts +5 -0
  17. package/dist/components/renderers/node-renderer.d.ts.map +1 -0
  18. package/dist/components/renderers/node-renderer.js +19 -0
  19. package/dist/components/renderers/roof/roof-renderer.d.ts +5 -0
  20. package/dist/components/renderers/roof/roof-renderer.d.ts.map +1 -0
  21. package/dist/components/renderers/roof/roof-renderer.js +10 -0
  22. package/dist/components/renderers/scan/scan-renderer.d.ts +5 -0
  23. package/dist/components/renderers/scan/scan-renderer.d.ts.map +1 -0
  24. package/dist/components/renderers/scan/scan-renderer.js +50 -0
  25. package/dist/components/renderers/scene-renderer.d.ts +2 -0
  26. package/dist/components/renderers/scene-renderer.d.ts.map +1 -0
  27. package/dist/components/renderers/scene-renderer.js +8 -0
  28. package/dist/components/renderers/slab/slab-renderer.d.ts +5 -0
  29. package/dist/components/renderers/slab/slab-renderer.d.ts.map +1 -0
  30. package/dist/components/renderers/slab/slab-renderer.js +10 -0
  31. package/dist/components/renderers/wall/wall-renderer.d.ts +5 -0
  32. package/dist/components/renderers/wall/wall-renderer.d.ts.map +1 -0
  33. package/dist/components/renderers/wall/wall-renderer.js +11 -0
  34. package/dist/components/renderers/zone/zone-renderer.d.ts +5 -0
  35. package/dist/components/renderers/zone/zone-renderer.d.ts.map +1 -0
  36. package/dist/components/renderers/zone/zone-renderer.js +154 -0
  37. package/dist/components/viewer/index.d.ts +13 -0
  38. package/dist/components/viewer/index.d.ts.map +1 -0
  39. package/dist/components/viewer/index.js +29 -0
  40. package/dist/components/viewer/lights.d.ts +2 -0
  41. package/dist/components/viewer/lights.d.ts.map +1 -0
  42. package/dist/components/viewer/lights.js +10 -0
  43. package/dist/components/viewer/post-processing.d.ts +17 -0
  44. package/dist/components/viewer/post-processing.d.ts.map +1 -0
  45. package/dist/components/viewer/post-processing.js +139 -0
  46. package/dist/components/viewer/selection-manager.d.ts +2 -0
  47. package/dist/components/viewer/selection-manager.d.ts.map +1 -0
  48. package/dist/components/viewer/selection-manager.js +279 -0
  49. package/dist/components/viewer/viewer-camera.d.ts +2 -0
  50. package/dist/components/viewer/viewer-camera.d.ts.map +1 -0
  51. package/dist/components/viewer/viewer-camera.js +7 -0
  52. package/dist/hooks/use-asset-url.d.ts +6 -0
  53. package/dist/hooks/use-asset-url.d.ts.map +1 -0
  54. package/dist/hooks/use-asset-url.js +21 -0
  55. package/dist/hooks/use-gltf-ktx2.d.ts +4 -0
  56. package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -0
  57. package/dist/hooks/use-gltf-ktx2.js +16 -0
  58. package/dist/hooks/use-grid-events.d.ts +12 -0
  59. package/dist/hooks/use-grid-events.d.ts.map +1 -0
  60. package/dist/hooks/use-grid-events.js +33 -0
  61. package/dist/hooks/use-node-events.d.ts +49 -0
  62. package/dist/hooks/use-node-events.d.ts.map +1 -0
  63. package/dist/hooks/use-node-events.js +38 -0
  64. package/dist/index.d.ts +4 -81
  65. package/dist/index.d.ts.map +1 -0
  66. package/dist/index.js +3 -46669
  67. package/dist/store/use-viewer.d.ts +35 -0
  68. package/dist/store/use-viewer.d.ts.map +1 -0
  69. package/dist/store/use-viewer.js +46 -0
  70. package/dist/systems/guide/guide-system.d.ts +2 -0
  71. package/dist/systems/guide/guide-system.d.ts.map +1 -0
  72. package/dist/systems/guide/guide-system.js +16 -0
  73. package/dist/systems/level/level-system.d.ts +2 -0
  74. package/dist/systems/level/level-system.d.ts.map +1 -0
  75. package/dist/systems/level/level-system.js +23 -0
  76. package/dist/systems/scan/scan-system.d.ts +2 -0
  77. package/dist/systems/scan/scan-system.d.ts.map +1 -0
  78. package/dist/systems/scan/scan-system.js +16 -0
  79. package/dist/systems/wall/wall-cutout.d.ts +2 -0
  80. package/dist/systems/wall/wall-cutout.d.ts.map +1 -0
  81. package/dist/systems/wall/wall-cutout.js +103 -0
  82. package/package.json +36 -32
  83. package/dist/index.js.map +0 -1
  84. package/types.d.ts +0 -81
@@ -0,0 +1,5 @@
1
+ import { type BuildingNode } from '@pascal-app/core';
2
+ export declare const BuildingRenderer: ({ node }: {
3
+ node: BuildingNode;
4
+ }) => import("react/jsx-runtime").JSX.Element;
5
+ //# sourceMappingURL=building-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"building-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/building/building-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAe,MAAM,kBAAkB,CAAA;AAMjE,eAAO,MAAM,gBAAgB,GAAI,UAAU;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,4CAYhE,CAAA"}
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useRegistry } from '@pascal-app/core';
3
+ import { useRef } from 'react';
4
+ import { useNodeEvents } from '../../../hooks/use-node-events';
5
+ import { NodeRenderer } from '../node-renderer';
6
+ export const BuildingRenderer = ({ node }) => {
7
+ const ref = useRef(null);
8
+ useRegistry(node.id, node.type, ref);
9
+ const handlers = useNodeEvents(node, 'building');
10
+ return (_jsx("group", { ref: ref, ...handlers, children: node.children.map((childId) => (_jsx(NodeRenderer, { nodeId: childId }, childId))) }));
11
+ };
@@ -0,0 +1,5 @@
1
+ import { type CeilingNode } from '@pascal-app/core';
2
+ export declare const CeilingRenderer: ({ node }: {
3
+ node: CeilingNode;
4
+ }) => import("react/jsx-runtime").JSX.Element;
5
+ //# sourceMappingURL=ceiling-renderer.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,24 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRegistry } from '@pascal-app/core';
3
+ import { useRef } from 'react';
4
+ import { faceDirection, float, mix } from 'three/tsl';
5
+ import { DoubleSide, MeshStandardNodeMaterial } from 'three/webgpu';
6
+ import { useNodeEvents } from '../../../hooks/use-node-events';
7
+ import { NodeRenderer } from '../node-renderer';
8
+ // TSL material that renders differently based on face direction:
9
+ // - Back face (looking up at ceiling from below): solid
10
+ // - Front face (looking down at ceiling from above): 30% opacity
11
+ const ceilingMaterial = new MeshStandardNodeMaterial({
12
+ color: 0xffffff,
13
+ side: DoubleSide,
14
+ transparent: true,
15
+ });
16
+ // 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));
19
+ export const CeilingRenderer = ({ node }) => {
20
+ const ref = useRef(null);
21
+ useRegistry(node.id, 'ceiling', ref);
22
+ const handlers = useNodeEvents(node, 'ceiling');
23
+ return (_jsxs("mesh", { ref: ref, material: ceilingMaterial, ...handlers, children: [_jsx("boxGeometry", { args: [0, 0, 0] }), node.children.map((childId) => (_jsx(NodeRenderer, { nodeId: childId }, childId)))] }));
24
+ };
@@ -0,0 +1,5 @@
1
+ import { type GuideNode } from '@pascal-app/core';
2
+ export declare const GuideRenderer: ({ node }: {
3
+ node: GuideNode;
4
+ }) => import("react/jsx-runtime").JSX.Element;
5
+ //# sourceMappingURL=guide-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guide-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/guide/guide-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAe,MAAM,kBAAkB,CAAA;AAQ9D,eAAO,MAAM,aAAa,GAAI,UAAU;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,4CAe1D,CAAA"}
@@ -0,0 +1,36 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useRegistry } from '@pascal-app/core';
3
+ import { useLoader } from '@react-three/fiber';
4
+ import { Suspense, useMemo, useRef } from 'react';
5
+ import { DoubleSide, TextureLoader } from 'three';
6
+ import { float, texture } from 'three/tsl';
7
+ import { MeshBasicNodeMaterial } from 'three/webgpu';
8
+ import { useAssetUrl } from '../../../hooks/use-asset-url';
9
+ export const GuideRenderer = ({ node }) => {
10
+ const ref = useRef(null);
11
+ useRegistry(node.id, 'guide', ref);
12
+ const resolvedUrl = useAssetUrl(node.url);
13
+ return (_jsx("group", { ref: ref, position: node.position, rotation: [0, node.rotation[1], 0], children: resolvedUrl && (_jsx(Suspense, { children: _jsx(GuidePlane, { url: resolvedUrl, scale: node.scale, opacity: node.opacity }) })) }));
14
+ };
15
+ const GuidePlane = ({ url, scale, opacity }) => {
16
+ const tex = useLoader(TextureLoader, url);
17
+ const { width, height, material } = useMemo(() => {
18
+ const img = tex.image;
19
+ const w = img.width || 1;
20
+ const h = img.height || 1;
21
+ const aspect = w / h;
22
+ // Default: 10 meters wide, height from aspect ratio
23
+ const planeWidth = 10 * scale;
24
+ const planeHeight = (10 / aspect) * scale;
25
+ const normalizedOpacity = opacity / 100;
26
+ const mat = new MeshBasicNodeMaterial({
27
+ transparent: true,
28
+ colorNode: texture(tex),
29
+ opacityNode: float(normalizedOpacity),
30
+ side: DoubleSide,
31
+ depthWrite: false,
32
+ });
33
+ return { width: planeWidth, height: planeHeight, material: mat };
34
+ }, [tex, scale, opacity]);
35
+ return (_jsx("mesh", { rotation: [-Math.PI / 2, 0, 0], material: material, raycast: () => { }, frustumCulled: false, children: _jsx("planeGeometry", { args: [width, height], boundingBox: null, boundingSphere: null }) }));
36
+ };
@@ -0,0 +1,5 @@
1
+ import { type ItemNode } from '@pascal-app/core';
2
+ export declare const ItemRenderer: ({ node }: {
3
+ node: ItemNode;
4
+ }) => import("react/jsx-runtime").JSX.Element;
5
+ //# sourceMappingURL=item-renderer.d.ts.map
@@ -0,0 +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;AAiCvF,eAAO,MAAM,YAAY,GAAI,UAAU;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,4CAYxD,CAAA"}
@@ -0,0 +1,70 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useRegistry, useScene } from '@pascal-app/core';
3
+ import { Clone } from '@react-three/drei/core/Clone';
4
+ import { useGLTF } from '@react-three/drei/core/Gltf';
5
+ import { Suspense, useEffect, useMemo, useRef } from 'react';
6
+ import { DoubleSide, MeshStandardNodeMaterial } from 'three/webgpu';
7
+ import { useNodeEvents } from '../../../hooks/use-node-events';
8
+ // Shared materials to avoid creating new instances for every mesh
9
+ const defaultMaterial = new MeshStandardNodeMaterial({
10
+ color: 0xffffff,
11
+ roughness: 1,
12
+ metalness: 0,
13
+ });
14
+ const glassMaterial = new MeshStandardNodeMaterial({
15
+ name: 'glass',
16
+ color: 'lightgray',
17
+ roughness: 0.8,
18
+ metalness: 0,
19
+ transparent: true,
20
+ opacity: 0.35,
21
+ side: DoubleSide,
22
+ depthWrite: false,
23
+ });
24
+ const getMaterialForOriginal = (original) => {
25
+ if (original.name.toLowerCase() === 'glass') {
26
+ return glassMaterial;
27
+ }
28
+ return defaultMaterial;
29
+ };
30
+ export const ItemRenderer = ({ node }) => {
31
+ const ref = useRef(null);
32
+ useRegistry(node.id, node.type, ref);
33
+ return (_jsx("group", { position: node.position, rotation: node.rotation, ref: ref, visible: node.visible, children: _jsx(Suspense, { children: _jsx(ModelRenderer, { node: node }) }) }));
34
+ };
35
+ const ModelRenderer = ({ node }) => {
36
+ const { scene, nodes } = useGLTF(node.asset.src);
37
+ if (nodes.cutout) {
38
+ nodes.cutout.visible = false;
39
+ }
40
+ const handlers = useNodeEvents(node, 'item');
41
+ useEffect(() => {
42
+ if (!node.parentId)
43
+ return;
44
+ useScene.getState().dirtyNodes.add(node.parentId);
45
+ }, [node.parentId]);
46
+ useMemo(() => {
47
+ scene.traverse((child) => {
48
+ if (child.isMesh) {
49
+ const mesh = child;
50
+ if (mesh.name === 'cutout') {
51
+ child.visible = false;
52
+ return;
53
+ }
54
+ let hasGlass = false;
55
+ // Handle both single material and material array cases
56
+ if (Array.isArray(mesh.material)) {
57
+ mesh.material = mesh.material.map((mat) => getMaterialForOriginal(mat));
58
+ hasGlass = mesh.material.some(mat => mat.name === 'glass');
59
+ }
60
+ else {
61
+ mesh.material = getMaterialForOriginal(mesh.material);
62
+ hasGlass = mesh.material.name === 'glass';
63
+ }
64
+ mesh.castShadow = !hasGlass;
65
+ mesh.receiveShadow = !hasGlass;
66
+ }
67
+ });
68
+ }, [scene]);
69
+ return (_jsx(Clone, { object: scene, scale: node.asset.scale, position: node.asset.offset, rotation: node.asset.rotation, ...handlers }));
70
+ };
@@ -0,0 +1,5 @@
1
+ import { type LevelNode } from '@pascal-app/core';
2
+ export declare const LevelRenderer: ({ node }: {
3
+ node: LevelNode;
4
+ }) => import("react/jsx-runtime").JSX.Element;
5
+ //# sourceMappingURL=level-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"level-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/level/level-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAe,MAAM,kBAAkB,CAAA;AAM9D,eAAO,MAAM,aAAa,GAAI,UAAU;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,4CAa1D,CAAA"}
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useRegistry } from '@pascal-app/core';
3
+ import { useRef } from 'react';
4
+ import { useNodeEvents } from '../../../hooks/use-node-events';
5
+ import { NodeRenderer } from '../node-renderer';
6
+ export const LevelRenderer = ({ node }) => {
7
+ const ref = useRef(null);
8
+ useRegistry(node.id, node.type, ref);
9
+ const handlers = useNodeEvents(node, 'level');
10
+ return (_jsx("group", { ref: ref, ...handlers, children: node.children.map((childId) => (_jsx(NodeRenderer, { nodeId: childId }, childId))) }));
11
+ };
@@ -0,0 +1,5 @@
1
+ import { type AnyNode } from '@pascal-app/core';
2
+ export declare const NodeRenderer: ({ nodeId }: {
3
+ nodeId: AnyNode["id"];
4
+ }) => import("react/jsx-runtime").JSX.Element | null;
5
+ //# sourceMappingURL=node-renderer.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,19 @@
1
+ 'use client';
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useScene } from '@pascal-app/core';
4
+ import { BuildingRenderer } from './building/building-renderer';
5
+ import { CeilingRenderer } from './ceiling/ceiling-renderer';
6
+ import { GuideRenderer } from './guide/guide-renderer';
7
+ import { ItemRenderer } from './item/item-renderer';
8
+ import { LevelRenderer } from './level/level-renderer';
9
+ import { RoofRenderer } from './roof/roof-renderer';
10
+ import { ScanRenderer } from './scan/scan-renderer';
11
+ import { SlabRenderer } from './slab/slab-renderer';
12
+ import { WallRenderer } from './wall/wall-renderer';
13
+ import { ZoneRenderer } from './zone/zone-renderer';
14
+ export const NodeRenderer = ({ nodeId }) => {
15
+ const node = useScene((state) => state.nodes[nodeId]);
16
+ if (!node)
17
+ 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
+ };
@@ -0,0 +1,5 @@
1
+ import { type RoofNode } from '@pascal-app/core';
2
+ export declare const RoofRenderer: ({ node }: {
3
+ node: RoofNode;
4
+ }) => import("react/jsx-runtime").JSX.Element;
5
+ //# sourceMappingURL=roof-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roof-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/roof/roof-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAe,MAAM,kBAAkB,CAAA;AAK7D,eAAO,MAAM,YAAY,GAAI,UAAU;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,4CAsBxD,CAAA"}
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRegistry } from '@pascal-app/core';
3
+ import { useRef } from 'react';
4
+ import { useNodeEvents } from '../../../hooks/use-node-events';
5
+ export const RoofRenderer = ({ node }) => {
6
+ const ref = useRef(null);
7
+ useRegistry(node.id, 'roof', ref);
8
+ const handlers = useNodeEvents(node, 'roof');
9
+ return (_jsxs("mesh", { ref: ref, castShadow: true, receiveShadow: true, position: node.position, "rotation-y": node.rotation, visible: node.visible, ...handlers, children: [_jsx("boxGeometry", { args: [0, 0, 0] }), _jsx("meshStandardMaterial", { color: "white" })] }));
10
+ };
@@ -0,0 +1,5 @@
1
+ import { type ScanNode } from '@pascal-app/core';
2
+ export declare const ScanRenderer: ({ node }: {
3
+ node: ScanNode;
4
+ }) => import("react/jsx-runtime").JSX.Element;
5
+ //# sourceMappingURL=scan-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/scan/scan-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAe,MAAM,kBAAkB,CAAA;AAM7D,eAAO,MAAM,YAAY,GAAI,UAAU;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,4CAoBxD,CAAA"}
@@ -0,0 +1,50 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useRegistry } from '@pascal-app/core';
3
+ import { Suspense, useMemo, useRef } from 'react';
4
+ import { useAssetUrl } from '../../../hooks/use-asset-url';
5
+ import { useGLTFKTX2 } from '../../../hooks/use-gltf-ktx2';
6
+ export const ScanRenderer = ({ node }) => {
7
+ const ref = useRef(null);
8
+ useRegistry(node.id, 'scan', ref);
9
+ const resolvedUrl = useAssetUrl(node.url);
10
+ return (_jsx("group", { ref: ref, position: node.position, rotation: node.rotation, scale: [node.scale, node.scale, node.scale], children: resolvedUrl && (_jsx(Suspense, { children: _jsx(ScanModel, { url: resolvedUrl, opacity: node.opacity }) })) }));
11
+ };
12
+ const ScanModel = ({ url, opacity }) => {
13
+ const gltf = useGLTFKTX2(url);
14
+ const scene = gltf.scene;
15
+ useMemo(() => {
16
+ const normalizedOpacity = opacity / 100;
17
+ const isTransparent = normalizedOpacity < 1;
18
+ const updateMaterial = (material) => {
19
+ if (isTransparent) {
20
+ material.transparent = true;
21
+ material.opacity = normalizedOpacity;
22
+ }
23
+ else {
24
+ material.transparent = false;
25
+ material.opacity = 1;
26
+ }
27
+ material.needsUpdate = true;
28
+ };
29
+ scene.traverse((child) => {
30
+ if (child.isMesh) {
31
+ const mesh = child;
32
+ // Disable raycasting
33
+ mesh.raycast = () => { };
34
+ // Exclude from bounding box calculations
35
+ mesh.geometry.boundingBox = null;
36
+ mesh.geometry.boundingSphere = null;
37
+ mesh.frustumCulled = false;
38
+ if (Array.isArray(mesh.material)) {
39
+ mesh.material.forEach((material) => {
40
+ updateMaterial(material);
41
+ });
42
+ }
43
+ else {
44
+ updateMaterial(mesh.material);
45
+ }
46
+ }
47
+ });
48
+ }, [scene, opacity]);
49
+ return _jsx("primitive", { object: scene });
50
+ };
@@ -0,0 +1,2 @@
1
+ export declare const SceneRenderer: () => import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=scene-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scene-renderer.d.ts","sourceRoot":"","sources":["../../../src/components/renderers/scene-renderer.tsx"],"names":[],"mappings":"AAKA,eAAO,MAAM,aAAa,+CAUzB,CAAC"}
@@ -0,0 +1,8 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useScene } from "@pascal-app/core";
4
+ import { NodeRenderer } from "./node-renderer";
5
+ export const SceneRenderer = () => {
6
+ const rootNodes = useScene((state) => state.rootNodeIds);
7
+ return (_jsx("group", { name: "scene-renderer", children: rootNodes.map((nodeId) => (_jsx(NodeRenderer, { nodeId: nodeId }, nodeId))) }));
8
+ };
@@ -0,0 +1,5 @@
1
+ import { type SlabNode } from '@pascal-app/core';
2
+ export declare const SlabRenderer: ({ node }: {
3
+ node: SlabNode;
4
+ }) => import("react/jsx-runtime").JSX.Element;
5
+ //# sourceMappingURL=slab-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slab-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/slab/slab-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAe,MAAM,kBAAkB,CAAA;AAK7D,eAAO,MAAM,YAAY,GAAI,UAAU;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,4CAcxD,CAAA"}
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRegistry } from '@pascal-app/core';
3
+ import { useRef } from 'react';
4
+ import { useNodeEvents } from '../../../hooks/use-node-events';
5
+ export const SlabRenderer = ({ node }) => {
6
+ const ref = useRef(null);
7
+ useRegistry(node.id, 'slab', ref);
8
+ const handlers = useNodeEvents(node, 'slab');
9
+ return (_jsxs("mesh", { ref: ref, castShadow: true, receiveShadow: true, ...handlers, visible: node.visible, children: [_jsx("boxGeometry", { args: [0, 0, 0] }), _jsx("meshStandardMaterial", { color: "#e5e5e5" })] }));
10
+ };
@@ -0,0 +1,5 @@
1
+ import { type WallNode } from '@pascal-app/core';
2
+ export declare const WallRenderer: ({ node }: {
3
+ node: WallNode;
4
+ }) => import("react/jsx-runtime").JSX.Element;
5
+ //# sourceMappingURL=wall-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wall-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/wall/wall-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAM7D,eAAO,MAAM,YAAY,GAAI,UAAU;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,4CAoBxD,CAAA"}
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRegistry } from '@pascal-app/core';
3
+ import { useRef } from 'react';
4
+ import { useNodeEvents } from '../../../hooks/use-node-events';
5
+ import { NodeRenderer } from '../node-renderer';
6
+ export const WallRenderer = ({ node }) => {
7
+ const ref = useRef(null);
8
+ useRegistry(node.id, 'wall', ref);
9
+ const handlers = useNodeEvents(node, 'wall');
10
+ return (_jsxs("mesh", { ref: ref, castShadow: true, receiveShadow: true, visible: node.visible, children: [_jsx("boxGeometry", { args: [0, 0, 0] }), _jsx("mesh", { name: "collision-mesh", ...handlers, visible: false, children: _jsx("boxGeometry", { args: [0, 0, 0] }) }), node.children.map((childId) => (_jsx(NodeRenderer, { nodeId: childId }, childId)))] }));
11
+ };
@@ -0,0 +1,5 @@
1
+ import { type ZoneNode } from '@pascal-app/core';
2
+ export declare const ZoneRenderer: ({ node }: {
3
+ node: ZoneNode;
4
+ }) => import("react/jsx-runtime").JSX.Element | null;
5
+ //# sourceMappingURL=zone-renderer.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,154 @@
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, Color, DoubleSide, Float32BufferAttribute, Shape } from 'three';
6
+ import { color, float, uv } from 'three/tsl';
7
+ import { MeshBasicNodeMaterial } from 'three/webgpu';
8
+ import { useNodeEvents } from '../../../hooks/use-node-events';
9
+ const Y_OFFSET = 0.01;
10
+ const WALL_HEIGHT = 2.3;
11
+ /**
12
+ * Creates a gradient material for zone walls using TSL
13
+ * Gradient goes from zone color at bottom to transparent at top
14
+ */
15
+ const createWallGradientMaterial = (zoneColor) => {
16
+ const baseColor = color(new Color(zoneColor));
17
+ // Use UV y coordinate for vertical gradient (0 at bottom, 1 at top)
18
+ const gradientT = uv().y;
19
+ // Fade opacity from 0.6 at bottom to 0 at top
20
+ const opacity = float(0.6).mul(float(1).sub(gradientT));
21
+ return new MeshBasicNodeMaterial({
22
+ transparent: true,
23
+ colorNode: baseColor,
24
+ opacityNode: opacity,
25
+ side: DoubleSide,
26
+ depthWrite: false,
27
+ depthTest: false,
28
+ });
29
+ };
30
+ /**
31
+ * Creates a floor material for zones using TSL
32
+ */
33
+ const createFloorMaterial = (zoneColor) => {
34
+ const baseColor = color(new Color(zoneColor));
35
+ return new MeshBasicNodeMaterial({
36
+ transparent: true,
37
+ colorNode: baseColor,
38
+ opacityNode: float(0.15),
39
+ side: DoubleSide,
40
+ depthWrite: false,
41
+ });
42
+ };
43
+ /**
44
+ * Creates wall geometry for zone borders
45
+ * Each wall segment is a vertical quad from one polygon point to the next
46
+ */
47
+ const createWallGeometry = (polygon) => {
48
+ const geometry = new BufferGeometry();
49
+ if (polygon.length < 2)
50
+ return geometry;
51
+ const positions = [];
52
+ const uvs = [];
53
+ const indices = [];
54
+ // Create a wall segment for each edge of the polygon
55
+ for (let i = 0; i < polygon.length; i++) {
56
+ const current = polygon[i];
57
+ const next = polygon[(i + 1) % polygon.length];
58
+ const baseIndex = i * 4;
59
+ // Four vertices per wall segment (two triangles forming a quad)
60
+ // Bottom-left
61
+ positions.push(current[0], Y_OFFSET, current[1]);
62
+ uvs.push(0, 0);
63
+ // Bottom-right
64
+ positions.push(next[0], Y_OFFSET, next[1]);
65
+ uvs.push(1, 0);
66
+ // Top-right
67
+ positions.push(next[0], Y_OFFSET + WALL_HEIGHT, next[1]);
68
+ uvs.push(1, 1);
69
+ // Top-left
70
+ positions.push(current[0], Y_OFFSET + WALL_HEIGHT, current[1]);
71
+ uvs.push(0, 1);
72
+ // Two triangles for the quad
73
+ indices.push(baseIndex, baseIndex + 1, baseIndex + 2, baseIndex, baseIndex + 2, baseIndex + 3);
74
+ }
75
+ geometry.setAttribute('position', new Float32BufferAttribute(positions, 3));
76
+ geometry.setAttribute('uv', new Float32BufferAttribute(uvs, 2));
77
+ geometry.setIndex(indices);
78
+ geometry.computeVertexNormals();
79
+ return geometry;
80
+ };
81
+ export const ZoneRenderer = ({ node }) => {
82
+ const ref = useRef(null);
83
+ useRegistry(node.id, 'zone', ref);
84
+ // Create floor shape from polygon
85
+ const floorShape = useMemo(() => {
86
+ if (!node?.polygon || node.polygon.length < 3)
87
+ return null;
88
+ const shape = new Shape();
89
+ const firstPt = node.polygon[0];
90
+ // Shape is in X-Y plane, we rotate it to X-Z plane
91
+ // Negate Y (which becomes Z) to get correct orientation
92
+ shape.moveTo(firstPt[0], -firstPt[1]);
93
+ for (let i = 1; i < node.polygon.length; i++) {
94
+ const pt = node.polygon[i];
95
+ shape.lineTo(pt[0], -pt[1]);
96
+ }
97
+ shape.closePath();
98
+ return shape;
99
+ }, [node?.polygon]);
100
+ // Create wall geometry from polygon
101
+ const wallGeometry = useMemo(() => {
102
+ if (!node?.polygon || node.polygon.length < 2)
103
+ return null;
104
+ return createWallGeometry(node.polygon);
105
+ }, [node?.polygon]);
106
+ // Calculate polygon centroid for label positioning using the geometric centroid formula
107
+ // This correctly handles polygons regardless of vertex distribution along edges
108
+ const centroid = useMemo(() => {
109
+ if (!node?.polygon || node.polygon.length < 3)
110
+ return [0, 0];
111
+ const polygon = node.polygon;
112
+ let signedArea = 0;
113
+ let cx = 0;
114
+ let cz = 0;
115
+ for (let i = 0; i < polygon.length; i++) {
116
+ const [x0, z0] = polygon[i];
117
+ const [x1, z1] = polygon[(i + 1) % polygon.length];
118
+ // Cross product for signed area
119
+ const cross = x0 * z1 - x1 * z0;
120
+ signedArea += cross;
121
+ cx += (x0 + x1) * cross;
122
+ cz += (z0 + z1) * cross;
123
+ }
124
+ signedArea /= 2;
125
+ const factor = 1 / (6 * signedArea);
126
+ return [cx * factor, cz * factor];
127
+ }, [node?.polygon]);
128
+ // Create materials
129
+ const floorMaterial = useMemo(() => {
130
+ if (!node?.color)
131
+ return null;
132
+ return createFloorMaterial(node.color);
133
+ }, [node?.color]);
134
+ const wallMaterial = useMemo(() => {
135
+ if (!node?.color)
136
+ return null;
137
+ return createWallGradientMaterial(node.color);
138
+ }, [node?.color]);
139
+ const handlers = useNodeEvents(node, 'zone');
140
+ if (!node || !floorShape || !wallGeometry || !floorMaterial || !wallMaterial) {
141
+ return null;
142
+ }
143
+ return (_jsxs("group", { ref: ref, ...handlers, children: [_jsx(Html, { name: "label", position: [centroid[0], 1, centroid[1]], style: {
144
+ pointerEvents: 'none'
145
+ }, children: _jsx("div", { style: {
146
+ transform: 'translate3d(-50%, -50%, 0)',
147
+ width: 'max-content',
148
+ color: 'white',
149
+ textShadow: `-1px -1px 0 ${node.color}, 1px -1px 0 ${node.color}, -1px 1px 0 ${node.color}, 1px 1px 0 ${node.color}`,
150
+ display: 'flex',
151
+ gap: '8px',
152
+ 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 })] }));
154
+ };
@@ -0,0 +1,13 @@
1
+ import { type ThreeToJSXElements } from '@react-three/fiber';
2
+ import * as THREE from 'three/webgpu';
3
+ declare module '@react-three/fiber' {
4
+ interface ThreeElements extends ThreeToJSXElements<typeof THREE> {
5
+ }
6
+ }
7
+ interface ViewerProps {
8
+ children?: React.ReactNode;
9
+ selectionManager?: 'default' | 'custom';
10
+ }
11
+ declare const Viewer: React.FC<ViewerProps>;
12
+ export default Viewer;
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,29 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { CeilingSystem, ItemSystem, RoofSystem, SlabSystem, WallSystem } from '@pascal-app/core';
4
+ import { Bvh } from '@react-three/drei';
5
+ import { Canvas, extend } from '@react-three/fiber';
6
+ import * as THREE from 'three/webgpu';
7
+ import { GuideSystem } from '../../systems/guide/guide-system';
8
+ import { LevelSystem } from '../../systems/level/level-system';
9
+ import { ScanSystem } from '../../systems/scan/scan-system';
10
+ import { WallCutout } from '../../systems/wall/wall-cutout';
11
+ import { SceneRenderer } from '../renderers/scene-renderer';
12
+ import { Lights } from './lights';
13
+ import PostProcessing from './post-processing';
14
+ import { SelectionManager } from './selection-manager';
15
+ import { ViewerCamera } from './viewer-camera';
16
+ extend(THREE);
17
+ const Viewer = ({ children, selectionManager = 'default' }) => {
18
+ return (_jsxs(Canvas, { className: 'bg-[#fafafa]', gl: async (props) => {
19
+ const renderer = new THREE.WebGPURenderer(props);
20
+ await renderer.init();
21
+ renderer.toneMapping = THREE.ACESFilmicToneMapping;
22
+ renderer.toneMappingExposure = 0.9;
23
+ return renderer;
24
+ }, shadows: {
25
+ type: THREE.PCFShadowMap,
26
+ 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
+ };
29
+ export default Viewer;
@@ -0,0 +1,2 @@
1
+ export declare function Lights(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=lights.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lights.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/lights.tsx"],"names":[],"mappings":"AAGA,wBAAgB,MAAM,4CAgDrB"}
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRef } from 'react';
3
+ export function Lights() {
4
+ const lightRef = useRef(null);
5
+ const shadowCamera = useRef(null);
6
+ const shadowCameraSize = 50; // The "area" around the camera to shadow
7
+ // useHelper(lightRef, DirectionalLightHelper, 1, 'red')
8
+ // useHelper(shadowCamera, CameraHelper)
9
+ return (_jsxs(_Fragment, { children: [_jsx("directionalLight", { ref: lightRef, position: [10, 10, 10], castShadow: true, intensity: 4, "shadow-bias": -0.002, "shadow-normalBias": 0.3, "shadow-mapSize": [1024, 1024], "shadow-radius": 3, "shadow-intensity": 0.4, children: _jsx("orthographicCamera", { ref: shadowCamera, attach: "shadow-camera", near: 1, far: 100, left: -shadowCameraSize, right: shadowCameraSize, top: shadowCameraSize, bottom: -shadowCameraSize }) }), _jsx("directionalLight", { position: [-10, 10, -10], intensity: 0.75 }), _jsx("directionalLight", { position: [-10, 10, 10], intensity: 1 }), _jsx("ambientLight", { intensity: 0.5, color: 'white' })] }));
10
+ }