@pascal-app/viewer 0.1.1 → 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.
- package/dist/components/renderers/building/building-renderer.d.ts +5 -0
- package/dist/components/renderers/building/building-renderer.d.ts.map +1 -0
- package/dist/components/renderers/building/building-renderer.js +11 -0
- package/dist/components/renderers/ceiling/ceiling-renderer.d.ts +5 -0
- package/dist/components/renderers/ceiling/ceiling-renderer.d.ts.map +1 -0
- package/dist/components/renderers/ceiling/ceiling-renderer.js +24 -0
- package/dist/components/renderers/guide/guide-renderer.d.ts +5 -0
- package/dist/components/renderers/guide/guide-renderer.d.ts.map +1 -0
- package/dist/components/renderers/guide/guide-renderer.js +36 -0
- package/dist/components/renderers/item/item-renderer.d.ts +5 -0
- package/dist/components/renderers/item/item-renderer.d.ts.map +1 -0
- package/dist/components/renderers/item/item-renderer.js +70 -0
- package/dist/components/renderers/level/level-renderer.d.ts +5 -0
- package/dist/components/renderers/level/level-renderer.d.ts.map +1 -0
- package/dist/components/renderers/level/level-renderer.js +11 -0
- package/dist/components/renderers/node-renderer.d.ts +5 -0
- package/dist/components/renderers/node-renderer.d.ts.map +1 -0
- package/dist/components/renderers/node-renderer.js +19 -0
- package/dist/components/renderers/roof/roof-renderer.d.ts +5 -0
- package/dist/components/renderers/roof/roof-renderer.d.ts.map +1 -0
- package/dist/components/renderers/roof/roof-renderer.js +10 -0
- package/dist/components/renderers/scan/scan-renderer.d.ts +5 -0
- package/dist/components/renderers/scan/scan-renderer.d.ts.map +1 -0
- package/dist/components/renderers/scan/scan-renderer.js +50 -0
- package/dist/components/renderers/scene-renderer.d.ts +2 -0
- package/dist/components/renderers/scene-renderer.d.ts.map +1 -0
- package/dist/components/renderers/scene-renderer.js +8 -0
- package/dist/components/renderers/slab/slab-renderer.d.ts +5 -0
- package/dist/components/renderers/slab/slab-renderer.d.ts.map +1 -0
- package/dist/components/renderers/slab/slab-renderer.js +10 -0
- package/dist/components/renderers/wall/wall-renderer.d.ts +5 -0
- package/dist/components/renderers/wall/wall-renderer.d.ts.map +1 -0
- package/dist/components/renderers/wall/wall-renderer.js +11 -0
- package/dist/components/renderers/zone/zone-renderer.d.ts +5 -0
- package/dist/components/renderers/zone/zone-renderer.d.ts.map +1 -0
- package/dist/components/renderers/zone/zone-renderer.js +154 -0
- package/dist/components/viewer/index.d.ts +13 -0
- package/dist/components/viewer/index.d.ts.map +1 -0
- package/dist/components/viewer/index.js +29 -0
- package/dist/components/viewer/lights.d.ts +2 -0
- package/dist/components/viewer/lights.d.ts.map +1 -0
- package/dist/components/viewer/lights.js +10 -0
- package/dist/components/viewer/post-processing.d.ts +17 -0
- package/dist/components/viewer/post-processing.d.ts.map +1 -0
- package/dist/components/viewer/post-processing.js +139 -0
- package/dist/components/viewer/selection-manager.d.ts +2 -0
- package/dist/components/viewer/selection-manager.d.ts.map +1 -0
- package/dist/components/viewer/selection-manager.js +279 -0
- package/dist/components/viewer/viewer-camera.d.ts +2 -0
- package/dist/components/viewer/viewer-camera.d.ts.map +1 -0
- package/dist/components/viewer/viewer-camera.js +7 -0
- package/dist/hooks/use-asset-url.d.ts +6 -0
- package/dist/hooks/use-asset-url.d.ts.map +1 -0
- package/dist/hooks/use-asset-url.js +21 -0
- package/dist/hooks/use-gltf-ktx2.d.ts +4 -0
- package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -0
- package/dist/hooks/use-gltf-ktx2.js +16 -0
- package/dist/hooks/use-grid-events.d.ts +12 -0
- package/dist/hooks/use-grid-events.d.ts.map +1 -0
- package/dist/hooks/use-grid-events.js +33 -0
- package/dist/hooks/use-node-events.d.ts +49 -0
- package/dist/hooks/use-node-events.d.ts.map +1 -0
- package/dist/hooks/use-node-events.js +38 -0
- package/dist/index.d.ts +4 -81
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -19168
- package/dist/store/use-viewer.d.ts +35 -0
- package/dist/store/use-viewer.d.ts.map +1 -0
- package/dist/store/use-viewer.js +46 -0
- package/dist/systems/guide/guide-system.d.ts +2 -0
- package/dist/systems/guide/guide-system.d.ts.map +1 -0
- package/dist/systems/guide/guide-system.js +16 -0
- package/dist/systems/level/level-system.d.ts +2 -0
- package/dist/systems/level/level-system.d.ts.map +1 -0
- package/dist/systems/level/level-system.js +23 -0
- package/dist/systems/scan/scan-system.d.ts +2 -0
- package/dist/systems/scan/scan-system.d.ts.map +1 -0
- package/dist/systems/scan/scan-system.js +16 -0
- package/dist/systems/wall/wall-cutout.d.ts +2 -0
- package/dist/systems/wall/wall-cutout.d.ts.map +1 -0
- package/dist/systems/wall/wall-cutout.js +103 -0
- package/package.json +36 -32
- package/dist/index.js.map +0 -1
- package/types.d.ts +0 -81
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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
|
+
}
|