@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.
Files changed (92) hide show
  1. package/dist/components/error-boundary.d.ts +18 -0
  2. package/dist/components/error-boundary.d.ts.map +1 -0
  3. package/dist/components/error-boundary.js +11 -0
  4. package/dist/components/renderers/ceiling/ceiling-renderer.d.ts.map +1 -1
  5. package/dist/components/renderers/ceiling/ceiling-renderer.js +16 -9
  6. package/dist/components/renderers/door/door-renderer.d.ts +5 -0
  7. package/dist/components/renderers/door/door-renderer.d.ts.map +1 -0
  8. package/dist/components/renderers/door/door-renderer.js +11 -0
  9. package/dist/components/renderers/guide/guide-renderer.d.ts.map +1 -1
  10. package/dist/components/renderers/guide/guide-renderer.js +4 -2
  11. package/dist/components/renderers/item/item-renderer.d.ts.map +1 -1
  12. package/dist/components/renderers/item/item-renderer.js +98 -7
  13. package/dist/components/renderers/node-renderer.d.ts.map +1 -1
  14. package/dist/components/renderers/node-renderer.js +3 -1
  15. package/dist/components/renderers/roof/roof-materials.d.ts +4 -0
  16. package/dist/components/renderers/roof/roof-materials.d.ts.map +1 -0
  17. package/dist/components/renderers/roof/roof-materials.js +16 -0
  18. package/dist/components/renderers/roof/roof-renderer.d.ts.map +1 -1
  19. package/dist/components/renderers/roof/roof-renderer.js +5 -1
  20. package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts +5 -0
  21. package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts.map +1 -0
  22. package/dist/components/renderers/roof-segment/roof-segment-renderer.js +13 -0
  23. package/dist/components/renderers/scan/scan-renderer.d.ts.map +1 -1
  24. package/dist/components/renderers/scan/scan-renderer.js +3 -1
  25. package/dist/components/renderers/scene-renderer.d.ts.map +1 -1
  26. package/dist/components/renderers/scene-renderer.js +3 -3
  27. package/dist/components/renderers/site/site-renderer.d.ts.map +1 -1
  28. package/dist/components/renderers/site/site-renderer.js +4 -19
  29. package/dist/components/renderers/slab/slab-renderer.js +1 -1
  30. package/dist/components/renderers/wall/wall-renderer.d.ts.map +1 -1
  31. package/dist/components/renderers/wall/wall-renderer.js +7 -3
  32. package/dist/components/renderers/window/window-renderer.d.ts.map +1 -1
  33. package/dist/components/renderers/window/window-renderer.js +2 -1
  34. package/dist/components/renderers/zone/zone-renderer.d.ts.map +1 -1
  35. package/dist/components/renderers/zone/zone-renderer.js +33 -13
  36. package/dist/components/viewer/ground-occluder.d.ts +2 -0
  37. package/dist/components/viewer/ground-occluder.d.ts.map +1 -0
  38. package/dist/components/viewer/ground-occluder.js +75 -0
  39. package/dist/components/viewer/index.d.ts +1 -0
  40. package/dist/components/viewer/index.d.ts.map +1 -1
  41. package/dist/components/viewer/index.js +59 -6
  42. package/dist/components/viewer/lights.d.ts.map +1 -1
  43. package/dist/components/viewer/lights.js +69 -5
  44. package/dist/components/viewer/perf-monitor.d.ts +2 -0
  45. package/dist/components/viewer/perf-monitor.d.ts.map +1 -0
  46. package/dist/components/viewer/perf-monitor.js +42 -0
  47. package/dist/components/viewer/post-processing.d.ts.map +1 -1
  48. package/dist/components/viewer/post-processing.js +230 -107
  49. package/dist/components/viewer/selection-manager.d.ts.map +1 -1
  50. package/dist/components/viewer/selection-manager.js +47 -17
  51. package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
  52. package/dist/components/viewer/viewer-camera.js +2 -2
  53. package/dist/hooks/use-gltf-ktx2.d.ts +1 -1
  54. package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -1
  55. package/dist/hooks/use-gltf-ktx2.js +25 -7
  56. package/dist/hooks/use-grid-events.d.ts +12 -0
  57. package/dist/hooks/use-grid-events.d.ts.map +1 -0
  58. package/dist/hooks/use-grid-events.js +33 -0
  59. package/dist/hooks/use-node-events.d.ts +9 -1
  60. package/dist/hooks/use-node-events.d.ts.map +1 -1
  61. package/dist/hooks/use-node-events.js +5 -5
  62. package/dist/index.d.ts +4 -1
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +4 -1
  65. package/dist/lib/asset-url.d.ts +1 -1
  66. package/dist/lib/asset-url.d.ts.map +1 -1
  67. package/dist/lib/asset-url.js +1 -1
  68. package/dist/lib/layers.d.ts +5 -0
  69. package/dist/lib/layers.d.ts.map +1 -0
  70. package/dist/lib/layers.js +4 -0
  71. package/dist/store/use-item-light-pool.d.ts +18 -0
  72. package/dist/store/use-item-light-pool.d.ts.map +1 -0
  73. package/dist/store/use-item-light-pool.js +30 -0
  74. package/dist/store/use-viewer.d.ts +56 -7
  75. package/dist/store/use-viewer.d.ts.map +1 -1
  76. package/dist/store/use-viewer.js +82 -17
  77. package/dist/systems/interactive/interactive-system.d.ts +2 -0
  78. package/dist/systems/interactive/interactive-system.d.ts.map +1 -0
  79. package/dist/systems/interactive/interactive-system.js +95 -0
  80. package/dist/systems/item-light/item-light-system.d.ts +2 -0
  81. package/dist/systems/item-light/item-light-system.d.ts.map +1 -0
  82. package/dist/systems/item-light/item-light-system.js +235 -0
  83. package/dist/systems/level/level-system.d.ts.map +1 -1
  84. package/dist/systems/level/level-system.js +19 -8
  85. package/dist/systems/level/level-utils.d.ts +17 -0
  86. package/dist/systems/level/level-utils.d.ts.map +1 -0
  87. package/dist/systems/level/level-utils.js +82 -0
  88. package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
  89. package/dist/systems/wall/wall-cutout.js +2 -4
  90. package/dist/systems/zone/zone-system.d.ts.map +1 -1
  91. package/dist/systems/zone/zone-system.js +29 -3
  92. 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: false,
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 || !floorShape || !wallGeometry || !floorMaterial || !wallMaterial) {
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
- gap: '8px',
153
+ flexDirection: 'column',
159
154
  alignItems: 'center',
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" })] }));
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,2 @@
1
+ export declare const GroundOccluder: () => import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=ground-occluder.d.ts.map
@@ -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
+ };
@@ -7,6 +7,7 @@ declare module '@react-three/fiber' {
7
7
  interface ViewerProps {
8
8
  children?: React.ReactNode;
9
9
  selectionManager?: 'default' | 'custom';
10
+ perf?: boolean;
10
11
  }
11
12
  declare const Viewer: React.FC<ViewerProps>;
12
13
  export default Viewer;
@@ -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;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
+ {"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
- const Viewer = ({ children, selectionManager = 'default' }) => {
19
- return (_jsxs(Canvas, { dpr: [1, 1.5], className: 'bg-[#fafafa]', gl: async (props) => {
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
- }, 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] }));
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":"AAGA,wBAAgB,MAAM,4CAgDrB"}
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 { useRef } from 'react';
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 lightRef = useRef(null);
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
- // 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' })] }));
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,2 @@
1
+ export declare const PerfMonitor: () => import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=perf-monitor.d.ts.map
@@ -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":"AA0BA,eAAO,MAAM,WAAW;;;;;;;;;;;;;CAavB,CAAA;AAED,QAAA,MAAM,oBAAoB,YAgJzB,CAAA;AAED,eAAe,oBAAoB,CAAA"}
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"}