@pascal-app/viewer 0.1.13 → 0.2.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/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 +92 -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 +1 -1
- 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 +55 -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-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 +2 -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 +52 -7
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +79 -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 +6 -5
|
@@ -0,0 +1,55 @@
|
|
|
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
|
+
// Collect all polygons for slabs and zones
|
|
21
|
+
const polygons = [];
|
|
22
|
+
Object.values(nodes).forEach((node) => {
|
|
23
|
+
if (node.type === 'slab' && node.polygon && node.polygon.length >= 3) {
|
|
24
|
+
polygons.push(node.polygon);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
if (polygons.length > 0) {
|
|
28
|
+
// Format for polygon-clipping: [[[x, y], [x, y], ...]]
|
|
29
|
+
const multiPolygons = polygons.map((pts) => {
|
|
30
|
+
const ring = pts.map((p) => [p[0], -p[1]]); // Negate Y (which was Z)
|
|
31
|
+
return [ring];
|
|
32
|
+
});
|
|
33
|
+
// Union all polygons together to prevent artifacts from overlapping
|
|
34
|
+
const unionedPolygons = polygonClipping.union(multiPolygons[0], ...multiPolygons.slice(1));
|
|
35
|
+
// Add each resulting unioned polygon as a hole
|
|
36
|
+
for (const geom of unionedPolygons) {
|
|
37
|
+
// First ring in each geometry is the exterior ring
|
|
38
|
+
if (geom.length > 0) {
|
|
39
|
+
const ring = geom[0];
|
|
40
|
+
const hole = new THREE.Path();
|
|
41
|
+
if (ring.length > 0) {
|
|
42
|
+
hole.moveTo(ring[0][0], ring[0][1]);
|
|
43
|
+
for (let i = 1; i < ring.length; i++) {
|
|
44
|
+
hole.lineTo(ring[i][0], ring[i][1]);
|
|
45
|
+
}
|
|
46
|
+
hole.closePath();
|
|
47
|
+
s.holes.push(hole);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return s;
|
|
53
|
+
}, [nodes]);
|
|
54
|
+
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 })] }));
|
|
55
|
+
};
|
|
@@ -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"}
|