@pascal-app/viewer 0.1.13 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/error-boundary.d.ts +18 -0
- package/dist/components/error-boundary.d.ts.map +1 -0
- package/dist/components/error-boundary.js +11 -0
- package/dist/components/renderers/ceiling/ceiling-renderer.d.ts.map +1 -1
- package/dist/components/renderers/ceiling/ceiling-renderer.js +16 -9
- package/dist/components/renderers/door/door-renderer.d.ts +5 -0
- package/dist/components/renderers/door/door-renderer.d.ts.map +1 -0
- package/dist/components/renderers/door/door-renderer.js +11 -0
- package/dist/components/renderers/guide/guide-renderer.d.ts.map +1 -1
- package/dist/components/renderers/guide/guide-renderer.js +4 -2
- package/dist/components/renderers/item/item-renderer.d.ts.map +1 -1
- package/dist/components/renderers/item/item-renderer.js +98 -7
- package/dist/components/renderers/node-renderer.d.ts.map +1 -1
- package/dist/components/renderers/node-renderer.js +3 -1
- package/dist/components/renderers/roof/roof-materials.d.ts +4 -0
- package/dist/components/renderers/roof/roof-materials.d.ts.map +1 -0
- package/dist/components/renderers/roof/roof-materials.js +16 -0
- package/dist/components/renderers/roof/roof-renderer.d.ts.map +1 -1
- package/dist/components/renderers/roof/roof-renderer.js +5 -1
- package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts +5 -0
- package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts.map +1 -0
- package/dist/components/renderers/roof-segment/roof-segment-renderer.js +13 -0
- package/dist/components/renderers/scan/scan-renderer.d.ts.map +1 -1
- package/dist/components/renderers/scan/scan-renderer.js +3 -1
- package/dist/components/renderers/scene-renderer.d.ts.map +1 -1
- package/dist/components/renderers/scene-renderer.js +3 -3
- package/dist/components/renderers/site/site-renderer.d.ts.map +1 -1
- package/dist/components/renderers/site/site-renderer.js +4 -19
- package/dist/components/renderers/slab/slab-renderer.js +1 -1
- package/dist/components/renderers/wall/wall-renderer.d.ts.map +1 -1
- package/dist/components/renderers/wall/wall-renderer.js +7 -3
- package/dist/components/renderers/window/window-renderer.d.ts.map +1 -1
- package/dist/components/renderers/window/window-renderer.js +2 -1
- package/dist/components/renderers/zone/zone-renderer.d.ts.map +1 -1
- package/dist/components/renderers/zone/zone-renderer.js +33 -13
- package/dist/components/viewer/ground-occluder.d.ts +2 -0
- package/dist/components/viewer/ground-occluder.d.ts.map +1 -0
- package/dist/components/viewer/ground-occluder.js +75 -0
- package/dist/components/viewer/index.d.ts +1 -0
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +59 -6
- package/dist/components/viewer/lights.d.ts.map +1 -1
- package/dist/components/viewer/lights.js +69 -5
- package/dist/components/viewer/perf-monitor.d.ts +2 -0
- package/dist/components/viewer/perf-monitor.d.ts.map +1 -0
- package/dist/components/viewer/perf-monitor.js +42 -0
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +230 -107
- package/dist/components/viewer/selection-manager.d.ts.map +1 -1
- package/dist/components/viewer/selection-manager.js +47 -17
- package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
- package/dist/components/viewer/viewer-camera.js +2 -2
- package/dist/hooks/use-gltf-ktx2.d.ts +1 -1
- package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -1
- package/dist/hooks/use-gltf-ktx2.js +25 -7
- 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 +9 -1
- package/dist/hooks/use-node-events.d.ts.map +1 -1
- package/dist/hooks/use-node-events.js +5 -5
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/lib/asset-url.d.ts +1 -1
- package/dist/lib/asset-url.d.ts.map +1 -1
- package/dist/lib/asset-url.js +1 -1
- package/dist/lib/layers.d.ts +5 -0
- package/dist/lib/layers.d.ts.map +1 -0
- package/dist/lib/layers.js +4 -0
- package/dist/store/use-item-light-pool.d.ts +18 -0
- package/dist/store/use-item-light-pool.d.ts.map +1 -0
- package/dist/store/use-item-light-pool.js +30 -0
- package/dist/store/use-viewer.d.ts +56 -7
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +82 -17
- package/dist/systems/interactive/interactive-system.d.ts +2 -0
- package/dist/systems/interactive/interactive-system.d.ts.map +1 -0
- package/dist/systems/interactive/interactive-system.js +95 -0
- package/dist/systems/item-light/item-light-system.d.ts +2 -0
- package/dist/systems/item-light/item-light-system.d.ts.map +1 -0
- package/dist/systems/item-light/item-light-system.js +235 -0
- package/dist/systems/level/level-system.d.ts.map +1 -1
- package/dist/systems/level/level-system.js +19 -8
- package/dist/systems/level/level-utils.d.ts +17 -0
- package/dist/systems/level/level-utils.d.ts.map +1 -0
- package/dist/systems/level/level-utils.js +82 -0
- package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout.js +2 -4
- package/dist/systems/zone/zone-system.d.ts.map +1 -1
- package/dist/systems/zone/zone-system.js +29 -3
- package/package.json +7 -5
|
@@ -6,6 +6,7 @@ import { BufferGeometry, Color, DoubleSide, Float32BufferAttribute, Shape } from
|
|
|
6
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
|
+
import { ZONE_LAYER } from '../../../lib/layers';
|
|
9
10
|
const Y_OFFSET = 0.01;
|
|
10
11
|
const WALL_HEIGHT = 2.3;
|
|
11
12
|
/**
|
|
@@ -24,11 +25,11 @@ const createWallGradientMaterial = (zoneColor) => {
|
|
|
24
25
|
colorNode: baseColor,
|
|
25
26
|
opacityNode: finalOpacity,
|
|
26
27
|
side: DoubleSide,
|
|
27
|
-
depthWrite:
|
|
28
|
+
depthWrite: true,
|
|
28
29
|
depthTest: false,
|
|
29
30
|
userData: {
|
|
30
31
|
uOpacity: opacity,
|
|
31
|
-
}
|
|
32
|
+
},
|
|
32
33
|
});
|
|
33
34
|
};
|
|
34
35
|
/**
|
|
@@ -44,7 +45,7 @@ const createFloorMaterial = (zoneColor) => {
|
|
|
44
45
|
side: DoubleSide,
|
|
45
46
|
depthWrite: false,
|
|
46
47
|
depthTest: false,
|
|
47
|
-
userData: { uOpacity: opacity }
|
|
48
|
+
userData: { uOpacity: opacity },
|
|
48
49
|
});
|
|
49
50
|
};
|
|
50
51
|
/**
|
|
@@ -144,18 +145,37 @@ export const ZoneRenderer = ({ node }) => {
|
|
|
144
145
|
return createWallGradientMaterial(node.color);
|
|
145
146
|
}, [node?.color]);
|
|
146
147
|
const handlers = useNodeEvents(node, 'zone');
|
|
147
|
-
if (!node
|
|
148
|
+
if (!(node && floorShape && wallGeometry && floorMaterial && wallMaterial)) {
|
|
148
149
|
return null;
|
|
149
150
|
}
|
|
150
|
-
return (_jsxs("group", { ref: ref, ...handlers, children: [_jsx(Html, { name: "label", position: [centroid[0], 1, centroid[1]], style: {
|
|
151
|
-
pointerEvents: 'none'
|
|
152
|
-
}, zIndexRange: [10, 0], children: _jsx("div", { style: {
|
|
153
|
-
transform: 'translate3d(-50%, -50%, 0)',
|
|
154
|
-
width: 'max-content',
|
|
155
|
-
color: 'white',
|
|
156
|
-
textShadow: `-1px -1px 0 ${node.color}, 1px -1px 0 ${node.color}, -1px 1px 0 ${node.color}, 1px 1px 0 ${node.color}`,
|
|
151
|
+
return (_jsxs("group", { ref: ref, ...handlers, userData: { labelPosition: [centroid[0], 1, centroid[1]] }, children: [_jsx(Html, { name: "label", position: [centroid[0], 1, centroid[1]], style: { pointerEvents: 'none' }, zIndexRange: [10, 0], children: _jsxs("div", { id: `${node.id}-label`, style: {
|
|
157
152
|
display: 'flex',
|
|
158
|
-
|
|
153
|
+
flexDirection: 'column',
|
|
159
154
|
alignItems: 'center',
|
|
160
|
-
|
|
155
|
+
transform: 'translate3d(-50%, -50%, 0)',
|
|
156
|
+
opacity: 0,
|
|
157
|
+
transition: 'opacity 0.3s ease-in-out',
|
|
158
|
+
}, children: [_jsx("div", { style: {
|
|
159
|
+
width: 'max-content',
|
|
160
|
+
color: 'white',
|
|
161
|
+
textShadow: `-1px -1px 0 ${node.color}, 1px -1px 0 ${node.color}, -1px 1px 0 ${node.color}, 1px 1px 0 ${node.color}`,
|
|
162
|
+
textAlign: 'center',
|
|
163
|
+
}, children: _jsx("span", { children: node.name }) }), _jsxs("div", { className: "label-pin", style: {
|
|
164
|
+
display: 'flex',
|
|
165
|
+
flexDirection: 'column',
|
|
166
|
+
alignItems: 'center',
|
|
167
|
+
marginTop: '2px',
|
|
168
|
+
opacity: 0,
|
|
169
|
+
transition: 'opacity 0.5s ease-in-out',
|
|
170
|
+
}, children: [_jsx("div", { style: {
|
|
171
|
+
width: '2px',
|
|
172
|
+
height: '40px',
|
|
173
|
+
backgroundColor: node.color,
|
|
174
|
+
} }), _jsx("div", { style: {
|
|
175
|
+
width: '10px',
|
|
176
|
+
height: '10px',
|
|
177
|
+
borderRadius: '50%',
|
|
178
|
+
backgroundColor: node.color,
|
|
179
|
+
border: '1px solid white',
|
|
180
|
+
} })] })] }) }), _jsx("mesh", { layers: ZONE_LAYER, material: floorMaterial, name: "floor", position: [0, Y_OFFSET, 0], rotation: [-Math.PI / 2, 0, 0], children: _jsx("shapeGeometry", { args: [floorShape] }) }), _jsx("mesh", { geometry: wallGeometry, layers: ZONE_LAYER, material: wallMaterial, name: "walls" })] }));
|
|
161
181
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ground-occluder.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/ground-occluder.tsx"],"names":[],"mappings":"AAMA,eAAO,MAAM,cAAc,+CA+F1B,CAAA"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useScene } from '@pascal-app/core';
|
|
3
|
+
import polygonClipping from 'polygon-clipping';
|
|
4
|
+
import { useMemo } from 'react';
|
|
5
|
+
import * as THREE from 'three';
|
|
6
|
+
import useViewer from '../../store/use-viewer';
|
|
7
|
+
export const GroundOccluder = () => {
|
|
8
|
+
const theme = useViewer((state) => state.theme);
|
|
9
|
+
const bgColor = theme === 'dark' ? '#1f2433' : '#fafafa';
|
|
10
|
+
const nodes = useScene((state) => state.nodes);
|
|
11
|
+
const shape = useMemo(() => {
|
|
12
|
+
const s = new THREE.Shape();
|
|
13
|
+
const size = 1000;
|
|
14
|
+
// Create outer infinite plane
|
|
15
|
+
s.moveTo(-size, -size);
|
|
16
|
+
s.lineTo(size, -size);
|
|
17
|
+
s.lineTo(size, size);
|
|
18
|
+
s.lineTo(-size, size);
|
|
19
|
+
s.closePath();
|
|
20
|
+
const levelIndexById = new Map();
|
|
21
|
+
let lowestLevelIndex = Number.POSITIVE_INFINITY;
|
|
22
|
+
Object.values(nodes).forEach((node) => {
|
|
23
|
+
if (node.type !== 'level') {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
levelIndexById.set(node.id, node.level);
|
|
27
|
+
lowestLevelIndex = Math.min(lowestLevelIndex, node.level);
|
|
28
|
+
});
|
|
29
|
+
// Only the lowest level should punch through the ground plane.
|
|
30
|
+
// Upper-level slabs should still cast shadows, but they should not
|
|
31
|
+
// reveal their footprint on the level-zero ground material.
|
|
32
|
+
const polygons = [];
|
|
33
|
+
Object.values(nodes).forEach((node) => {
|
|
34
|
+
if (!(node.type === 'slab' && node.visible && node.polygon.length >= 3)) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (Number.isFinite(lowestLevelIndex)) {
|
|
38
|
+
const parentLevelIndex = node.parentId
|
|
39
|
+
? levelIndexById.get(node.parentId)
|
|
40
|
+
: undefined;
|
|
41
|
+
if (parentLevelIndex !== lowestLevelIndex) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
polygons.push(node.polygon);
|
|
46
|
+
});
|
|
47
|
+
if (polygons.length > 0) {
|
|
48
|
+
// Format for polygon-clipping: [[[x, y], [x, y], ...]]
|
|
49
|
+
const multiPolygons = polygons.map((pts) => {
|
|
50
|
+
const ring = pts.map((p) => [p[0], -p[1]]); // Negate Y (which was Z)
|
|
51
|
+
return [ring];
|
|
52
|
+
});
|
|
53
|
+
// Union all polygons together to prevent artifacts from overlapping
|
|
54
|
+
const unionedPolygons = polygonClipping.union(multiPolygons[0], ...multiPolygons.slice(1));
|
|
55
|
+
// Add each resulting unioned polygon as a hole
|
|
56
|
+
for (const geom of unionedPolygons) {
|
|
57
|
+
// First ring in each geometry is the exterior ring
|
|
58
|
+
if (geom.length > 0) {
|
|
59
|
+
const ring = geom[0];
|
|
60
|
+
const hole = new THREE.Path();
|
|
61
|
+
if (ring.length > 0) {
|
|
62
|
+
hole.moveTo(ring[0][0], ring[0][1]);
|
|
63
|
+
for (let i = 1; i < ring.length; i++) {
|
|
64
|
+
hole.lineTo(ring[i][0], ring[i][1]);
|
|
65
|
+
}
|
|
66
|
+
hole.closePath();
|
|
67
|
+
s.holes.push(hole);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return s;
|
|
73
|
+
}, [nodes]);
|
|
74
|
+
return (_jsxs("mesh", { "position-y": -0.05, "rotation-x": -Math.PI / 2, children: [_jsx("shapeGeometry", { args: [shape] }), _jsx("meshBasicMaterial", { color: bgColor, depthWrite: true, polygonOffset: true, polygonOffsetFactor: 1, polygonOffsetUnits: 1 })] }));
|
|
75
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/index.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/index.tsx"],"names":[],"mappings":"AAYA,OAAO,EAAkB,KAAK,kBAAkB,EAAsB,MAAM,oBAAoB,CAAA;AAEhG,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AA2CrC,OAAO,QAAQ,oBAAoB,CAAC;IAClC,UAAU,aAAc,SAAQ,kBAAkB,CAAC,OAAO,KAAK,CAAC;KAAG;CACpE;AA+BD,UAAU,WAAW;IACnB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,gBAAgB,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAA;IACvC,IAAI,CAAC,EAAE,OAAO,CAAA;CACf;AAED,QAAA,MAAM,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,WAAW,CA0DjC,CAAA;AASD,eAAe,MAAM,CAAA"}
|
|
@@ -1,30 +1,83 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { CeilingSystem, ItemSystem, RoofSystem, SlabSystem, WallSystem, WindowSystem } from '@pascal-app/core';
|
|
3
|
+
import { CeilingSystem, DoorSystem, ItemSystem, RoofSystem, SlabSystem, WallSystem, WindowSystem, } from '@pascal-app/core';
|
|
4
4
|
import { Bvh } from '@react-three/drei';
|
|
5
|
-
import { Canvas, extend } from '@react-three/fiber';
|
|
5
|
+
import { Canvas, extend, useFrame, useThree } from '@react-three/fiber';
|
|
6
|
+
import { useEffect, useMemo, useRef } from 'react';
|
|
6
7
|
import * as THREE from 'three/webgpu';
|
|
8
|
+
import useViewer from '../../store/use-viewer';
|
|
7
9
|
import { GuideSystem } from '../../systems/guide/guide-system';
|
|
10
|
+
import { ItemLightSystem } from '../../systems/item-light/item-light-system';
|
|
8
11
|
import { LevelSystem } from '../../systems/level/level-system';
|
|
9
12
|
import { ScanSystem } from '../../systems/scan/scan-system';
|
|
10
13
|
import { WallCutout } from '../../systems/wall/wall-cutout';
|
|
11
14
|
import { ZoneSystem } from '../../systems/zone/zone-system';
|
|
12
15
|
import { SceneRenderer } from '../renderers/scene-renderer';
|
|
16
|
+
import { GroundOccluder } from './ground-occluder';
|
|
13
17
|
import { Lights } from './lights';
|
|
18
|
+
import { PerfMonitor } from './perf-monitor';
|
|
14
19
|
import PostProcessing from './post-processing';
|
|
15
20
|
import { SelectionManager } from './selection-manager';
|
|
16
21
|
import { ViewerCamera } from './viewer-camera';
|
|
22
|
+
function AnimatedBackground({ isDark }) {
|
|
23
|
+
const targetColor = useMemo(() => new THREE.Color(), []);
|
|
24
|
+
const initialized = useRef(false);
|
|
25
|
+
useFrame(({ scene }, delta) => {
|
|
26
|
+
const dt = Math.min(delta, 0.1) * 4;
|
|
27
|
+
const targetHex = isDark ? '#1f2433' : '#ffffff';
|
|
28
|
+
if (!(scene.background && scene.background instanceof THREE.Color)) {
|
|
29
|
+
scene.background = new THREE.Color(targetHex);
|
|
30
|
+
initialized.current = true;
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (!initialized.current) {
|
|
34
|
+
scene.background.set(targetHex);
|
|
35
|
+
initialized.current = true;
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
targetColor.set(targetHex);
|
|
39
|
+
scene.background.lerp(targetColor, dt);
|
|
40
|
+
});
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
17
43
|
extend(THREE);
|
|
18
|
-
|
|
19
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Monitors the WebGPU device for loss events and logs them.
|
|
46
|
+
* WebGPU device loss can happen when:
|
|
47
|
+
* - Tab is backgrounded and OS reclaims GPU
|
|
48
|
+
* - Driver crash or GPU reset
|
|
49
|
+
* - Browser security policy kills the context
|
|
50
|
+
*/
|
|
51
|
+
function GPUDeviceWatcher() {
|
|
52
|
+
const gl = useThree((s) => s.gl);
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
const backend = gl.backend;
|
|
55
|
+
const device = backend?.device;
|
|
56
|
+
if (!device)
|
|
57
|
+
return;
|
|
58
|
+
device.lost.then((info) => {
|
|
59
|
+
console.error(`[viewer] WebGPU device lost: reason="${info.reason}", message="${info.message}". ` +
|
|
60
|
+
'The page must be reloaded to recover the GPU context.');
|
|
61
|
+
});
|
|
62
|
+
}, [gl]);
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const Viewer = ({ children, selectionManager = 'default', perf = false, }) => {
|
|
66
|
+
const theme = useViewer((state) => state.theme);
|
|
67
|
+
return (_jsxs(Canvas, { camera: { position: [50, 50, 50], fov: 50 }, className: `transition-colors duration-700 ${theme === 'dark' ? 'bg-[#1f2433]' : 'bg-[#fafafa]'}`, dpr: [1, 1.5], gl: (props) => {
|
|
20
68
|
const renderer = new THREE.WebGPURenderer(props);
|
|
21
|
-
await renderer.init();
|
|
22
69
|
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
|
23
70
|
renderer.toneMappingExposure = 0.9;
|
|
24
71
|
return renderer;
|
|
25
72
|
}, shadows: {
|
|
26
73
|
type: THREE.PCFShadowMap,
|
|
27
74
|
enabled: true,
|
|
28
|
-
},
|
|
75
|
+
}, children: [_jsx(GroundOccluder, {}), _jsx(ViewerCamera, {}), _jsx(Lights, {}), _jsx(Bvh, { children: _jsx(SceneRenderer, {}) }), _jsx(LevelSystem, {}), _jsx(GuideSystem, {}), _jsx(ScanSystem, {}), _jsx(WallCutout, {}), _jsx(CeilingSystem, {}), _jsx(DoorSystem, {}), _jsx(ItemSystem, {}), _jsx(RoofSystem, {}), _jsx(SlabSystem, {}), _jsx(WallSystem, {}), _jsx(WindowSystem, {}), _jsx(ZoneSystem, {}), _jsx(PostProcessing, {}), _jsx(GPUDeviceWatcher, {}), _jsx(ItemLightSystem, {}), selectionManager === 'default' && _jsx(SelectionManager, {}), perf && _jsx(PerfMonitor, {}), children] }));
|
|
76
|
+
};
|
|
77
|
+
const DebugRenderer = () => {
|
|
78
|
+
useFrame(({ gl, scene, camera }) => {
|
|
79
|
+
gl.render(scene, camera);
|
|
80
|
+
});
|
|
81
|
+
return null;
|
|
29
82
|
};
|
|
30
83
|
export default Viewer;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lights.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/lights.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"lights.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/lights.tsx"],"names":[],"mappings":"AAMA,wBAAgB,MAAM,4CAoIrB"}
|
|
@@ -1,10 +1,74 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { useFrame } from '@react-three/fiber';
|
|
3
|
+
import { useMemo, useRef } from 'react';
|
|
4
|
+
import * as THREE from 'three/webgpu';
|
|
5
|
+
import useViewer from '../../store/use-viewer';
|
|
3
6
|
export function Lights() {
|
|
4
|
-
const
|
|
7
|
+
const theme = useViewer((state) => state.theme);
|
|
8
|
+
const isDark = theme === 'dark';
|
|
9
|
+
const light1Ref = useRef(null);
|
|
5
10
|
const shadowCamera = useRef(null);
|
|
6
11
|
const shadowCameraSize = 50; // The "area" around the camera to shadow
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
12
|
+
const light2Ref = useRef(null);
|
|
13
|
+
const light3Ref = useRef(null);
|
|
14
|
+
const ambientRef = useRef(null);
|
|
15
|
+
const initialized = useRef(false);
|
|
16
|
+
const targets = useMemo(() => ({
|
|
17
|
+
l1Color: new THREE.Color(),
|
|
18
|
+
l2Color: new THREE.Color(),
|
|
19
|
+
l3Color: new THREE.Color(),
|
|
20
|
+
ambColor: new THREE.Color(),
|
|
21
|
+
}), []);
|
|
22
|
+
useFrame((_, delta) => {
|
|
23
|
+
// clamp delta to avoid huge jumps on tab switch
|
|
24
|
+
const dt = Math.min(delta, 0.1) * 4;
|
|
25
|
+
if (!initialized.current) {
|
|
26
|
+
if (light1Ref.current) {
|
|
27
|
+
light1Ref.current.intensity = isDark ? 0.8 : 4;
|
|
28
|
+
light1Ref.current.color.set(isDark ? '#e0e5ff' : '#ffffff');
|
|
29
|
+
if (light1Ref.current.shadow)
|
|
30
|
+
light1Ref.current.shadow.intensity = isDark ? 0.8 : 0.4;
|
|
31
|
+
}
|
|
32
|
+
if (light2Ref.current) {
|
|
33
|
+
light2Ref.current.intensity = isDark ? 0.2 : 0.75;
|
|
34
|
+
light2Ref.current.color.set(isDark ? '#8090ff' : '#ffffff');
|
|
35
|
+
}
|
|
36
|
+
if (light3Ref.current) {
|
|
37
|
+
light3Ref.current.intensity = isDark ? 0.3 : 1;
|
|
38
|
+
light3Ref.current.color.set(isDark ? '#a0b0ff' : '#ffffff');
|
|
39
|
+
}
|
|
40
|
+
if (ambientRef.current) {
|
|
41
|
+
ambientRef.current.intensity = isDark ? 0.15 : 0.5;
|
|
42
|
+
ambientRef.current.color.set(isDark ? '#a0b0ff' : '#ffffff');
|
|
43
|
+
}
|
|
44
|
+
initialized.current = true;
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (light1Ref.current) {
|
|
48
|
+
light1Ref.current.intensity = THREE.MathUtils.lerp(light1Ref.current.intensity, isDark ? 0.8 : 4, dt);
|
|
49
|
+
targets.l1Color.set(isDark ? '#e0e5ff' : '#ffffff');
|
|
50
|
+
light1Ref.current.color.lerp(targets.l1Color, dt);
|
|
51
|
+
if (light1Ref.current.shadow) {
|
|
52
|
+
if (light1Ref.current.shadow.intensity !== undefined) {
|
|
53
|
+
light1Ref.current.shadow.intensity = THREE.MathUtils.lerp(light1Ref.current.shadow.intensity, isDark ? 0.8 : 0.4, dt);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (light2Ref.current) {
|
|
58
|
+
light2Ref.current.intensity = THREE.MathUtils.lerp(light2Ref.current.intensity, isDark ? 0.2 : 0.75, dt);
|
|
59
|
+
targets.l2Color.set(isDark ? '#8090ff' : '#ffffff');
|
|
60
|
+
light2Ref.current.color.lerp(targets.l2Color, dt);
|
|
61
|
+
}
|
|
62
|
+
if (light3Ref.current) {
|
|
63
|
+
light3Ref.current.intensity = THREE.MathUtils.lerp(light3Ref.current.intensity, isDark ? 0.3 : 1, dt);
|
|
64
|
+
targets.l3Color.set(isDark ? '#a0b0ff' : '#ffffff');
|
|
65
|
+
light3Ref.current.color.lerp(targets.l3Color, dt);
|
|
66
|
+
}
|
|
67
|
+
if (ambientRef.current) {
|
|
68
|
+
ambientRef.current.intensity = THREE.MathUtils.lerp(ambientRef.current.intensity, isDark ? 0.15 : 0.5, dt);
|
|
69
|
+
targets.ambColor.set(isDark ? '#a0b0ff' : '#ffffff');
|
|
70
|
+
ambientRef.current.color.lerp(targets.ambColor, dt);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
return (_jsxs(_Fragment, { children: [_jsx("directionalLight", { castShadow: true, position: [10, 10, 10], ref: light1Ref, "shadow-bias": -0.002, "shadow-mapSize": [1024, 1024], "shadow-normalBias": 0.3, "shadow-radius": 3, children: _jsx("orthographicCamera", { attach: "shadow-camera", bottom: -shadowCameraSize, far: 100, left: -shadowCameraSize, near: 1, ref: shadowCamera, right: shadowCameraSize, top: shadowCameraSize }) }), _jsx("directionalLight", { position: [-10, 10, -10], ref: light2Ref }), _jsx("directionalLight", { position: [-10, 10, 10], ref: light3Ref }), _jsx("ambientLight", { ref: ambientRef })] }));
|
|
10
74
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"perf-monitor.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/perf-monitor.tsx"],"names":[],"mappings":"AAOA,eAAO,MAAM,WAAW,+CAoDvB,CAAA"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useScene } from '@pascal-app/core';
|
|
3
|
+
import { Html } from '@react-three/drei';
|
|
4
|
+
import { useFrame } from '@react-three/fiber';
|
|
5
|
+
import { useRef, useState } from 'react';
|
|
6
|
+
const SAMPLE_INTERVAL = 0.5; // seconds between display updates
|
|
7
|
+
export const PerfMonitor = () => {
|
|
8
|
+
const [stats, setStats] = useState({ fps: 0, frameMs: 0, drawCalls: 0, triangles: 0, dirty: 0 });
|
|
9
|
+
const frameCount = useRef(0);
|
|
10
|
+
const elapsed = useRef(0);
|
|
11
|
+
const lastMs = useRef(0);
|
|
12
|
+
useFrame(({ gl, clock }) => {
|
|
13
|
+
frameCount.current++;
|
|
14
|
+
const now = clock.elapsedTime;
|
|
15
|
+
const dt = now - elapsed.current;
|
|
16
|
+
if (dt >= SAMPLE_INTERVAL) {
|
|
17
|
+
const fps = Math.round(frameCount.current / dt);
|
|
18
|
+
const frameMs = lastMs.current;
|
|
19
|
+
const info = gl.info;
|
|
20
|
+
const drawCalls = info.render?.calls ?? 0;
|
|
21
|
+
const triangles = info.render?.triangles ?? 0;
|
|
22
|
+
const dirty = useScene.getState().dirtyNodes.size;
|
|
23
|
+
setStats({ fps, frameMs, drawCalls, triangles, dirty });
|
|
24
|
+
frameCount.current = 0;
|
|
25
|
+
elapsed.current = now;
|
|
26
|
+
}
|
|
27
|
+
lastMs.current = Math.round(clock.getDelta() * 1000 * 10) / 10;
|
|
28
|
+
});
|
|
29
|
+
return (_jsx(Html, { position: [0, 0, 0], style: { position: 'fixed', top: 8, left: 8, pointerEvents: 'none' }, zIndexRange: [100, 100], children: _jsx("div", { style: {
|
|
30
|
+
fontFamily: 'monospace',
|
|
31
|
+
fontSize: 11,
|
|
32
|
+
lineHeight: 1.5,
|
|
33
|
+
color: stats.fps < 30 ? '#f87171' : stats.fps < 55 ? '#fbbf24' : '#4ade80',
|
|
34
|
+
background: 'rgba(0,0,0,0.7)',
|
|
35
|
+
borderRadius: 6,
|
|
36
|
+
padding: '6px 10px',
|
|
37
|
+
whiteSpace: 'pre',
|
|
38
|
+
}, children: `FPS ${stats.fps}
|
|
39
|
+
DRAW ${stats.drawCalls}
|
|
40
|
+
TRI ${(stats.triangles / 1000).toFixed(1)}k
|
|
41
|
+
DIRTY ${stats.dirty}` }) }));
|
|
42
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"post-processing.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/post-processing.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"post-processing.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/post-processing.tsx"],"names":[],"mappings":"AA8BA,eAAO,MAAM,WAAW;;;;;;;;;;;;;CAavB,CAAA;AAQD,QAAA,MAAM,oBAAoB,YA4RzB,CAAA;AAED,eAAe,oBAAoB,CAAA"}
|