@pascal-app/viewer 0.1.11 → 0.1.13
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/ceiling/ceiling-renderer.d.ts.map +1 -1
- package/dist/components/renderers/ceiling/ceiling-renderer.js +20 -6
- package/dist/components/renderers/item/item-renderer.d.ts.map +1 -1
- package/dist/components/renderers/item/item-renderer.js +17 -3
- package/dist/components/renderers/node-renderer.d.ts.map +1 -1
- package/dist/components/renderers/node-renderer.js +2 -1
- package/dist/components/renderers/site/site-renderer.d.ts.map +1 -1
- package/dist/components/renderers/site/site-renderer.js +16 -2
- package/dist/components/renderers/window/window-renderer.d.ts +5 -0
- package/dist/components/renderers/window/window-renderer.d.ts.map +1 -0
- package/dist/components/renderers/window/window-renderer.js +10 -0
- package/dist/components/renderers/zone/zone-renderer.d.ts.map +1 -1
- package/dist/components/renderers/zone/zone-renderer.js +13 -6
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +4 -3
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +8 -8
- package/dist/components/viewer/selection-manager.d.ts.map +1 -1
- package/dist/components/viewer/selection-manager.js +21 -7
- package/dist/hooks/use-node-events.d.ts +5 -1
- package/dist/hooks/use-node-events.d.ts.map +1 -1
- package/dist/hooks/use-node-events.js +32 -5
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/store/use-viewer.d.ts +2 -0
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +2 -0
- package/dist/systems/zone/zone-system.d.ts +2 -0
- package/dist/systems/zone/zone-system.d.ts.map +1 -0
- package/dist/systems/zone/zone-system.js +57 -0
- package/package.json +5 -3
- package/dist/hooks/use-grid-events.d.ts +0 -12
- package/dist/hooks/use-grid-events.d.ts.map +0 -1
- package/dist/hooks/use-grid-events.js +0 -33
|
@@ -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;
|
|
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,
|
|
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
|
|
12
|
-
color:
|
|
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
|
-
//
|
|
18
|
-
ceilingMaterial.opacityNode = mix(float(1.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;
|
|
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;AAoCvF,eAAO,MAAM,YAAY,GAAI,UAAU;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,4CAexD,CAAA"}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
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';
|
|
10
|
+
import { NodeRenderer } from '../node-renderer';
|
|
9
11
|
// Shared materials to avoid creating new instances for every mesh
|
|
10
12
|
const defaultMaterial = new MeshStandardNodeMaterial({
|
|
11
13
|
color: 0xffffff,
|
|
@@ -31,7 +33,19 @@ const getMaterialForOriginal = (original) => {
|
|
|
31
33
|
export const ItemRenderer = ({ node }) => {
|
|
32
34
|
const ref = useRef(null);
|
|
33
35
|
useRegistry(node.id, node.type, ref);
|
|
34
|
-
return (
|
|
36
|
+
return (_jsxs("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 }) }), node.children?.map((childId) => (_jsx(NodeRenderer, { nodeId: childId }, childId)))] }));
|
|
37
|
+
};
|
|
38
|
+
const previewMaterial = new MeshStandardNodeMaterial({
|
|
39
|
+
color: '#cccccc',
|
|
40
|
+
roughness: 1,
|
|
41
|
+
metalness: 0,
|
|
42
|
+
depthTest: false,
|
|
43
|
+
});
|
|
44
|
+
const previewOpacity = smoothstep(0.42, 0.55, positionLocal.y.add(time.mul(-0.2)).mul(10).fract());
|
|
45
|
+
previewMaterial.opacityNode = previewOpacity;
|
|
46
|
+
previewMaterial.transparent = true;
|
|
47
|
+
const PreviewModel = ({ node }) => {
|
|
48
|
+
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
49
|
};
|
|
36
50
|
const ModelRenderer = ({ node }) => {
|
|
37
51
|
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;
|
|
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;AAczD,eAAO,MAAM,YAAY,GAAI,YAAY;IAAE,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,mDAqBjE,CAAA"}
|
|
@@ -11,10 +11,11 @@ import { ScanRenderer } from './scan/scan-renderer';
|
|
|
11
11
|
import { SiteRenderer } from './site/site-renderer';
|
|
12
12
|
import { SlabRenderer } from './slab/slab-renderer';
|
|
13
13
|
import { WallRenderer } from './wall/wall-renderer';
|
|
14
|
+
import { WindowRenderer } from './window/window-renderer';
|
|
14
15
|
import { ZoneRenderer } from './zone/zone-renderer';
|
|
15
16
|
export const NodeRenderer = ({ nodeId }) => {
|
|
16
17
|
const node = useScene((state) => state.nodes[nodeId]);
|
|
17
18
|
if (!node)
|
|
18
19
|
return null;
|
|
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 })] }));
|
|
20
|
+
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 === 'window' && _jsx(WindowRenderer, { 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 })] }));
|
|
20
21
|
};
|
|
@@ -1 +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;
|
|
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,mDAwFxD,CAAA"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useRegistry } from '@pascal-app/core';
|
|
3
|
+
import { Html } from '@react-three/drei';
|
|
3
4
|
import { useMemo, useRef } from 'react';
|
|
4
|
-
import { BufferGeometry,
|
|
5
|
+
import { BufferGeometry, Float32BufferAttribute, Shape } from 'three';
|
|
5
6
|
import { useNodeEvents } from '../../../hooks/use-node-events';
|
|
6
7
|
import { NodeRenderer } from '../node-renderer';
|
|
7
8
|
const Y_OFFSET = 0.01;
|
|
@@ -49,9 +50,22 @@ export const SiteRenderer = ({ node }) => {
|
|
|
49
50
|
return null;
|
|
50
51
|
return createBoundaryLineGeometry(node.polygon.points);
|
|
51
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]);
|
|
52
66
|
const handlers = useNodeEvents(node, 'site');
|
|
53
67
|
if (!node || !floorShape || !lineGeometry) {
|
|
54
68
|
return null;
|
|
55
69
|
}
|
|
56
|
-
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("
|
|
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], receiveShadow: true, children: [_jsx("shapeGeometry", { args: [floorShape] }), _jsx("shadowMaterial", { transparent: true, opacity: 0.75 })] }), _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}`)))] }));
|
|
57
71
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"window-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/window/window-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAK/D,eAAO,MAAM,cAAc,GAAI,UAAU;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,4CAqB5D,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 WindowRenderer = ({ node }) => {
|
|
6
|
+
const ref = useRef(null);
|
|
7
|
+
useRegistry(node.id, 'window', ref);
|
|
8
|
+
const handlers = useNodeEvents(node, 'window');
|
|
9
|
+
return (_jsxs("mesh", { ref: ref, castShadow: true, receiveShadow: true, visible: node.visible, position: node.position, rotation: node.rotation, ...handlers, children: [_jsx("boxGeometry", { args: [0, 0, 0] }), _jsx("meshStandardMaterial", { color: "#d1d5db" })] }));
|
|
10
|
+
};
|
|
@@ -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;
|
|
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
|
|
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:
|
|
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.
|
|
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;
|
|
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,CA+CjC,CAAA;AAED,eAAe,MAAM,CAAA"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { CeilingSystem, ItemSystem, RoofSystem, SlabSystem, WallSystem } from '@pascal-app/core';
|
|
3
|
+
import { CeilingSystem, ItemSystem, RoofSystem, SlabSystem, WallSystem, WindowSystem } from '@pascal-app/core';
|
|
4
4
|
import { Bvh } from '@react-three/drei';
|
|
5
5
|
import { Canvas, extend } from '@react-three/fiber';
|
|
6
6
|
import * as THREE from 'three/webgpu';
|
|
@@ -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(WindowSystem, {}), _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,
|
|
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"}
|
|
@@ -12,8 +12,8 @@ export const SSGI_PARAMS = {
|
|
|
12
12
|
enabled: true,
|
|
13
13
|
sliceCount: 2,
|
|
14
14
|
stepCount: 8,
|
|
15
|
-
radius:
|
|
16
|
-
expFactor:
|
|
15
|
+
radius: 1,
|
|
16
|
+
expFactor: 1.5,
|
|
17
17
|
thickness: 0.5,
|
|
18
18
|
backfaceLighting: 0.5,
|
|
19
19
|
aoIntensity: 1.5,
|
|
@@ -70,8 +70,6 @@ const PostProcessingPasses = () => {
|
|
|
70
70
|
const ao = giPass.a;
|
|
71
71
|
// Composite: scene * AO + diffuse * GI
|
|
72
72
|
const compositePass = vec4(add(scenePassColor.rgb.mul(ao), scenePassDiffuse.rgb.mul(gi)), scenePassColor.a);
|
|
73
|
-
// TRAA (Temporal Reprojection Anti-Aliasing)
|
|
74
|
-
const traaPass = traa(compositePass, scenePassDepth, scenePassVelocity, camera);
|
|
75
73
|
function generateSelectedOutlinePass() {
|
|
76
74
|
const edgeStrength = uniform(3);
|
|
77
75
|
const edgeGlow = uniform(0);
|
|
@@ -116,10 +114,12 @@ const PostProcessingPasses = () => {
|
|
|
116
114
|
const postProcessing = new PostProcessing(renderer);
|
|
117
115
|
const selectedOutlinePass = generateSelectedOutlinePass();
|
|
118
116
|
const hoverOutlinePass = generateHoverOutlinePass();
|
|
119
|
-
// Combine
|
|
120
|
-
const
|
|
121
|
-
? selectedOutlinePass.add(hoverOutlinePass).
|
|
122
|
-
: selectedOutlinePass.add(hoverOutlinePass).
|
|
117
|
+
// Combine composite with outlines BEFORE applying TRAA
|
|
118
|
+
const compositeWithOutlines = SSGI_PARAMS.enabled
|
|
119
|
+
? vec4(add(compositePass.rgb, selectedOutlinePass.add(hoverOutlinePass)), compositePass.a)
|
|
120
|
+
: vec4(add(scenePassColor.rgb, selectedOutlinePass.add(hoverOutlinePass)), scenePassColor.a);
|
|
121
|
+
// TRAA (Temporal Reprojection Anti-Aliasing) - applied AFTER combining everything
|
|
122
|
+
const finalOutput = traa(compositeWithOutlines, scenePassDepth, scenePassVelocity, camera);
|
|
123
123
|
postProcessing.outputNode = finalOutput;
|
|
124
124
|
postProcessingRef.current = postProcessing;
|
|
125
125
|
return () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"selection-manager.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/selection-manager.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"selection-manager.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/selection-manager.tsx"],"names":[],"mappings":"AAkOA,eAAO,MAAM,gBAAgB,+CAoE5B,CAAA"}
|
|
@@ -144,14 +144,16 @@ const getStrategy = () => {
|
|
|
144
144
|
isValid: (node) => node.type === 'zone' && node.parentId === levelId,
|
|
145
145
|
};
|
|
146
146
|
}
|
|
147
|
-
// Zone selected -> can select/hover contents (walls, items, slabs, ceilings, roofs)
|
|
147
|
+
// Zone selected -> can select/hover contents (walls, items, slabs, ceilings, roofs, windows)
|
|
148
148
|
return {
|
|
149
|
-
types: ['wall', 'item', 'slab', 'ceiling', 'roof'],
|
|
149
|
+
types: ['wall', 'item', 'slab', 'ceiling', 'roof', 'window'],
|
|
150
150
|
handleClick: (node) => {
|
|
151
151
|
const { selectedIds } = useViewer.getState().selection;
|
|
152
152
|
// Toggle selection - if already selected, deselect; otherwise select
|
|
153
153
|
if (selectedIds.includes(node.id)) {
|
|
154
|
-
useViewer
|
|
154
|
+
useViewer
|
|
155
|
+
.getState()
|
|
156
|
+
.setSelection({ selectedIds: selectedIds.filter((id) => id !== node.id) });
|
|
155
157
|
}
|
|
156
158
|
else {
|
|
157
159
|
useViewer.getState().setSelection({ selectedIds: [node.id] });
|
|
@@ -168,7 +170,7 @@ const getStrategy = () => {
|
|
|
168
170
|
}
|
|
169
171
|
},
|
|
170
172
|
isValid: (node) => {
|
|
171
|
-
const validTypes = ['wall', 'item', 'slab', 'ceiling', 'roof'];
|
|
173
|
+
const validTypes = ['wall', 'item', 'slab', 'ceiling', 'roof', 'window'];
|
|
172
174
|
if (!validTypes.includes(node.type))
|
|
173
175
|
return false;
|
|
174
176
|
return isNodeInZone(node, levelId, zoneId);
|
|
@@ -210,7 +212,17 @@ export const SelectionManager = () => {
|
|
|
210
212
|
useViewer.setState({ hoveredId: null });
|
|
211
213
|
};
|
|
212
214
|
// Subscribe to all node types
|
|
213
|
-
const allTypes = [
|
|
215
|
+
const allTypes = [
|
|
216
|
+
'building',
|
|
217
|
+
'level',
|
|
218
|
+
'zone',
|
|
219
|
+
'wall',
|
|
220
|
+
'item',
|
|
221
|
+
'slab',
|
|
222
|
+
'ceiling',
|
|
223
|
+
'roof',
|
|
224
|
+
'window',
|
|
225
|
+
];
|
|
214
226
|
for (const type of allTypes) {
|
|
215
227
|
emitter.on(`${type}:enter`, onEnter);
|
|
216
228
|
emitter.on(`${type}:leave`, onLeave);
|
|
@@ -223,14 +235,16 @@ export const SelectionManager = () => {
|
|
|
223
235
|
emitter.off(`${type}:click`, onClick);
|
|
224
236
|
}
|
|
225
237
|
};
|
|
226
|
-
}, [
|
|
238
|
+
}, []);
|
|
227
239
|
return (_jsxs(_Fragment, { children: [_jsx(PointerMissedHandler, { clickHandledRef: clickHandledRef }), _jsx(OutlinerSync, {})] }));
|
|
228
240
|
};
|
|
229
|
-
const PointerMissedHandler = ({ clickHandledRef }) => {
|
|
241
|
+
const PointerMissedHandler = ({ clickHandledRef, }) => {
|
|
230
242
|
const gl = useThree((s) => s.gl);
|
|
231
243
|
useEffect(() => {
|
|
232
244
|
const handleClick = (event) => {
|
|
233
245
|
// Only handle left clicks
|
|
246
|
+
if (useViewer.getState().cameraDragging)
|
|
247
|
+
return;
|
|
234
248
|
if (event.button !== 0)
|
|
235
249
|
return;
|
|
236
250
|
// Use requestAnimationFrame to check after R3F event handlers
|
|
@@ -1,4 +1,4 @@
|
|
|
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';
|
|
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 WindowEvent, type WindowNode, type ZoneEvent, type ZoneNode } from '@pascal-app/core';
|
|
2
2
|
import type { ThreeEvent } from '@react-three/fiber';
|
|
3
3
|
type NodeConfig = {
|
|
4
4
|
site: {
|
|
@@ -37,6 +37,10 @@ type NodeConfig = {
|
|
|
37
37
|
node: RoofNode;
|
|
38
38
|
event: RoofEvent;
|
|
39
39
|
};
|
|
40
|
+
window: {
|
|
41
|
+
node: WindowNode;
|
|
42
|
+
event: WindowEvent;
|
|
43
|
+
};
|
|
40
44
|
};
|
|
41
45
|
type NodeType = keyof NodeConfig;
|
|
42
46
|
export declare function useNodeEvents<T extends NodeType>(node: NodeConfig[T]['node'], type: T): {
|
|
@@ -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,EACb,KAAK,SAAS,EACd,KAAK,QAAQ,EACd,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;
|
|
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,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,QAAQ,EACd,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAGpD,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;IAC1C,MAAM,EAAE;QAAE,IAAI,EAAE,UAAU,CAAC;QAAC,KAAK,EAAE,WAAW,CAAA;KAAE,CAAA;CACjD,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;qBAK1B,UAAU,CAAC,YAAY,CAAC;iBAK5B,UAAU,CAAC,YAAY,CAAC;wBAKjB,UAAU,CAAC,YAAY,CAAC;wBAIxB,UAAU,CAAC,YAAY,CAAC;uBAIzB,UAAU,CAAC,YAAY,CAAC;uBAIxB,UAAU,CAAC,YAAY,CAAC;uBAIxB,UAAU,CAAC,YAAY,CAAC;EAK9C"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { emitter, } from '@pascal-app/core';
|
|
2
|
+
import useViewer from '../store/use-viewer';
|
|
2
3
|
export function useNodeEvents(node, type) {
|
|
3
4
|
const emit = (suffix, e) => {
|
|
4
5
|
const eventKey = `${type}:${suffix}`;
|
|
@@ -15,24 +16,50 @@ export function useNodeEvents(node, type) {
|
|
|
15
16
|
};
|
|
16
17
|
return {
|
|
17
18
|
onPointerDown: (e) => {
|
|
19
|
+
if (useViewer.getState().cameraDragging)
|
|
20
|
+
return;
|
|
18
21
|
if (e.button !== 0)
|
|
19
22
|
return;
|
|
20
23
|
emit('pointerdown', e);
|
|
21
24
|
},
|
|
22
25
|
onPointerUp: (e) => {
|
|
26
|
+
if (useViewer.getState().cameraDragging)
|
|
27
|
+
return;
|
|
23
28
|
if (e.button !== 0)
|
|
24
29
|
return;
|
|
25
30
|
emit('pointerup', e);
|
|
26
31
|
},
|
|
27
32
|
onClick: (e) => {
|
|
33
|
+
if (useViewer.getState().cameraDragging)
|
|
34
|
+
return;
|
|
28
35
|
if (e.button !== 0)
|
|
29
36
|
return;
|
|
30
37
|
emit('click', e);
|
|
31
38
|
},
|
|
32
|
-
onPointerEnter: (e) =>
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
39
|
+
onPointerEnter: (e) => {
|
|
40
|
+
if (useViewer.getState().cameraDragging)
|
|
41
|
+
return;
|
|
42
|
+
emit('enter', e);
|
|
43
|
+
},
|
|
44
|
+
onPointerLeave: (e) => {
|
|
45
|
+
if (useViewer.getState().cameraDragging)
|
|
46
|
+
return;
|
|
47
|
+
emit('leave', e);
|
|
48
|
+
},
|
|
49
|
+
onPointerMove: (e) => {
|
|
50
|
+
if (useViewer.getState().cameraDragging)
|
|
51
|
+
return;
|
|
52
|
+
emit('move', e);
|
|
53
|
+
},
|
|
54
|
+
onDoubleClick: (e) => {
|
|
55
|
+
if (useViewer.getState().cameraDragging)
|
|
56
|
+
return;
|
|
57
|
+
emit('double-click', e);
|
|
58
|
+
},
|
|
59
|
+
onContextMenu: (e) => {
|
|
60
|
+
if (useViewer.getState().cameraDragging)
|
|
61
|
+
return;
|
|
62
|
+
emit('context-menu', e);
|
|
63
|
+
},
|
|
37
64
|
};
|
|
38
65
|
}
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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
|
@@ -29,6 +29,8 @@ type ViewerState = {
|
|
|
29
29
|
outliner: Outliner;
|
|
30
30
|
exportScene: (() => Promise<void>) | null;
|
|
31
31
|
setExportScene: (fn: (() => Promise<void>) | null) => void;
|
|
32
|
+
cameraDragging: boolean;
|
|
33
|
+
setCameraDragging: (dragging: boolean) => void;
|
|
32
34
|
};
|
|
33
35
|
declare const useViewer: import("zustand").UseBoundStore<import("zustand").StoreApi<ViewerState>>;
|
|
34
36
|
export default useViewer;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-viewer.d.ts","sourceRoot":"","sources":["../../src/store/use-viewer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,OAAO,EACP,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,QAAQ,EACT,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAItC,KAAK,aAAa,GAAG;IACnB,UAAU,EAAE,YAAY,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACtC,OAAO,EAAE,SAAS,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAChC,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC9B,WAAW,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;CAC/B,CAAC;AAEF,KAAK,QAAQ,GAAG;IACd,eAAe,EAAE,QAAQ,EAAE,CAAC;IAC5B,cAAc,EAAE,QAAQ,EAAE,CAAC;CAC5B,CAAC;AAEF,KAAK,WAAW,GAAG;IACjB,SAAS,EAAE,aAAa,CAAA;IACxB,SAAS,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAChD,YAAY,EAAE,CAAC,EAAE,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,KAAK,IAAI,CAAA;IAEjE,UAAU,EAAE,aAAa,GAAG,cAAc,CAAA;IAC1C,aAAa,EAAE,CAAC,IAAI,EAAE,aAAa,GAAG,cAAc,KAAK,IAAI,CAAA;IAE7D,SAAS,EAAE,SAAS,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,CAAA;IACrD,YAAY,EAAE,CAAC,IAAI,EAAE,SAAS,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,KAAK,IAAI,CAAA;IAExE,QAAQ,EAAE,IAAI,GAAG,SAAS,GAAG,MAAM,CAAA;IACnC,WAAW,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,SAAS,GAAG,MAAM,KAAK,IAAI,CAAA;IAEtD,SAAS,EAAE,OAAO,CAAA;IAClB,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IAErC,UAAU,EAAE,OAAO,CAAA;IACnB,aAAa,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IAGtC,YAAY,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,KAAK,IAAI,CAAA;IACvD,cAAc,EAAE,MAAM,IAAI,CAAA;IAE1B,QAAQ,EAAE,QAAQ,CAAA;IAGlB,WAAW,EAAE,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAA;IACzC,cAAc,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,KAAK,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"use-viewer.d.ts","sourceRoot":"","sources":["../../src/store/use-viewer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,OAAO,EACP,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,QAAQ,EACT,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAItC,KAAK,aAAa,GAAG;IACnB,UAAU,EAAE,YAAY,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACtC,OAAO,EAAE,SAAS,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAChC,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC9B,WAAW,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;CAC/B,CAAC;AAEF,KAAK,QAAQ,GAAG;IACd,eAAe,EAAE,QAAQ,EAAE,CAAC;IAC5B,cAAc,EAAE,QAAQ,EAAE,CAAC;CAC5B,CAAC;AAEF,KAAK,WAAW,GAAG;IACjB,SAAS,EAAE,aAAa,CAAA;IACxB,SAAS,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAChD,YAAY,EAAE,CAAC,EAAE,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,KAAK,IAAI,CAAA;IAEjE,UAAU,EAAE,aAAa,GAAG,cAAc,CAAA;IAC1C,aAAa,EAAE,CAAC,IAAI,EAAE,aAAa,GAAG,cAAc,KAAK,IAAI,CAAA;IAE7D,SAAS,EAAE,SAAS,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,CAAA;IACrD,YAAY,EAAE,CAAC,IAAI,EAAE,SAAS,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,KAAK,IAAI,CAAA;IAExE,QAAQ,EAAE,IAAI,GAAG,SAAS,GAAG,MAAM,CAAA;IACnC,WAAW,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,SAAS,GAAG,MAAM,KAAK,IAAI,CAAA;IAEtD,SAAS,EAAE,OAAO,CAAA;IAClB,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IAErC,UAAU,EAAE,OAAO,CAAA;IACnB,aAAa,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IAGtC,YAAY,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,KAAK,IAAI,CAAA;IACvD,cAAc,EAAE,MAAM,IAAI,CAAA;IAE1B,QAAQ,EAAE,QAAQ,CAAA;IAGlB,WAAW,EAAE,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAA;IACzC,cAAc,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,KAAK,IAAI,CAAA;IAE1D,cAAc,EAAE,OAAO,CAAA;IACvB,iBAAiB,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAA;CAC/C,CAAA;AAED,QAAA,MAAM,SAAS,0EAwDZ,CAAC;AAEJ,eAAe,SAAS,CAAC"}
|
package/dist/store/use-viewer.js
CHANGED
|
@@ -42,5 +42,7 @@ const useViewer = create()((set, get) => ({
|
|
|
42
42
|
outliner: { selectedObjects: [], hoveredObjects: [] },
|
|
43
43
|
exportScene: null,
|
|
44
44
|
setExportScene: (fn) => set({ exportScene: fn }),
|
|
45
|
+
cameraDragging: false,
|
|
46
|
+
setCameraDragging: (dragging) => set({ cameraDragging: dragging }),
|
|
45
47
|
}));
|
|
46
48
|
export default useViewer;
|
|
@@ -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.
|
|
3
|
+
"version": "0.1.13",
|
|
4
4
|
"description": "3D viewer component for Pascal building editor",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -49,8 +49,10 @@
|
|
|
49
49
|
],
|
|
50
50
|
"repository": {
|
|
51
51
|
"type": "git",
|
|
52
|
-
"url": "https://github.com/
|
|
52
|
+
"url": "https://github.com/pascalorg/editor.git",
|
|
53
53
|
"directory": "packages/viewer"
|
|
54
54
|
},
|
|
55
|
-
"license": "MIT"
|
|
55
|
+
"license": "MIT",
|
|
56
|
+
"homepage": "https://github.com/pascalorg/editor/tree/main/packages/viewer#readme",
|
|
57
|
+
"bugs": "https://github.com/pascalorg/editor/issues"
|
|
56
58
|
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { ThreeEvent } from "@react-three/fiber";
|
|
2
|
-
export declare function useGridEvents(): {
|
|
3
|
-
onPointerDown: (e: ThreeEvent<PointerEvent>) => void;
|
|
4
|
-
onPointerUp: (e: ThreeEvent<PointerEvent>) => void;
|
|
5
|
-
onClick: (e: ThreeEvent<PointerEvent>) => void;
|
|
6
|
-
onPointerEnter: (e: ThreeEvent<PointerEvent>) => void;
|
|
7
|
-
onPointerLeave: (e: ThreeEvent<PointerEvent>) => void;
|
|
8
|
-
onPointerMove: (e: ThreeEvent<PointerEvent>) => void;
|
|
9
|
-
onDoubleClick: (e: ThreeEvent<PointerEvent>) => void;
|
|
10
|
-
onContextMenu: (e: ThreeEvent<PointerEvent>) => void;
|
|
11
|
-
};
|
|
12
|
-
//# sourceMappingURL=use-grid-events.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"use-grid-events.d.ts","sourceRoot":"","sources":["../../src/hooks/use-grid-events.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,wBAAgB,aAAa;uBAYN,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,33 +0,0 @@
|
|
|
1
|
-
import { emitter } from "@pascal-app/core";
|
|
2
|
-
export function useGridEvents() {
|
|
3
|
-
const emit = (suffix, e) => {
|
|
4
|
-
const eventKey = `grid:${suffix}`;
|
|
5
|
-
const payload = {
|
|
6
|
-
position: [e.point.x, e.point.y, e.point.z],
|
|
7
|
-
nativeEvent: e,
|
|
8
|
-
};
|
|
9
|
-
emitter.emit(eventKey, payload);
|
|
10
|
-
};
|
|
11
|
-
return {
|
|
12
|
-
onPointerDown: (e) => {
|
|
13
|
-
if (e.button !== 0)
|
|
14
|
-
return;
|
|
15
|
-
emit("pointerdown", e);
|
|
16
|
-
},
|
|
17
|
-
onPointerUp: (e) => {
|
|
18
|
-
if (e.button !== 0)
|
|
19
|
-
return;
|
|
20
|
-
emit("pointerup", e);
|
|
21
|
-
},
|
|
22
|
-
onClick: (e) => {
|
|
23
|
-
if (e.button !== 0)
|
|
24
|
-
return;
|
|
25
|
-
emit("click", e);
|
|
26
|
-
},
|
|
27
|
-
onPointerEnter: (e) => emit("enter", e),
|
|
28
|
-
onPointerLeave: (e) => emit("leave", e),
|
|
29
|
-
onPointerMove: (e) => emit("move", e),
|
|
30
|
-
onDoubleClick: (e) => emit("double-click", e),
|
|
31
|
-
onContextMenu: (e) => emit("context-menu", e),
|
|
32
|
-
};
|
|
33
|
-
}
|