@pascal-app/viewer 0.1.2 → 0.1.5

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 (87) hide show
  1. package/dist/components/renderers/building/building-renderer.d.ts +5 -0
  2. package/dist/components/renderers/building/building-renderer.d.ts.map +1 -0
  3. package/dist/components/renderers/building/building-renderer.js +11 -0
  4. package/dist/components/renderers/ceiling/ceiling-renderer.d.ts +5 -0
  5. package/dist/components/renderers/ceiling/ceiling-renderer.d.ts.map +1 -0
  6. package/dist/components/renderers/ceiling/ceiling-renderer.js +24 -0
  7. package/dist/components/renderers/guide/guide-renderer.d.ts +5 -0
  8. package/dist/components/renderers/guide/guide-renderer.d.ts.map +1 -0
  9. package/dist/components/renderers/guide/guide-renderer.js +36 -0
  10. package/dist/components/renderers/item/item-renderer.d.ts +5 -0
  11. package/dist/components/renderers/item/item-renderer.d.ts.map +1 -0
  12. package/dist/components/renderers/item/item-renderer.js +71 -0
  13. package/dist/components/renderers/level/level-renderer.d.ts +5 -0
  14. package/dist/components/renderers/level/level-renderer.d.ts.map +1 -0
  15. package/dist/components/renderers/level/level-renderer.js +11 -0
  16. package/dist/components/renderers/node-renderer.d.ts +5 -0
  17. package/dist/components/renderers/node-renderer.d.ts.map +1 -0
  18. package/dist/components/renderers/node-renderer.js +19 -0
  19. package/dist/components/renderers/roof/roof-renderer.d.ts +5 -0
  20. package/dist/components/renderers/roof/roof-renderer.d.ts.map +1 -0
  21. package/dist/components/renderers/roof/roof-renderer.js +10 -0
  22. package/dist/components/renderers/scan/scan-renderer.d.ts +5 -0
  23. package/dist/components/renderers/scan/scan-renderer.d.ts.map +1 -0
  24. package/dist/components/renderers/scan/scan-renderer.js +50 -0
  25. package/dist/components/renderers/scene-renderer.d.ts +2 -0
  26. package/dist/components/renderers/scene-renderer.d.ts.map +1 -0
  27. package/dist/components/renderers/scene-renderer.js +8 -0
  28. package/dist/components/renderers/slab/slab-renderer.d.ts +5 -0
  29. package/dist/components/renderers/slab/slab-renderer.d.ts.map +1 -0
  30. package/dist/components/renderers/slab/slab-renderer.js +10 -0
  31. package/dist/components/renderers/wall/wall-renderer.d.ts +5 -0
  32. package/dist/components/renderers/wall/wall-renderer.d.ts.map +1 -0
  33. package/dist/components/renderers/wall/wall-renderer.js +11 -0
  34. package/dist/components/renderers/zone/zone-renderer.d.ts +5 -0
  35. package/dist/components/renderers/zone/zone-renderer.d.ts.map +1 -0
  36. package/dist/components/renderers/zone/zone-renderer.js +154 -0
  37. package/dist/components/viewer/index.d.ts +13 -0
  38. package/dist/components/viewer/index.d.ts.map +1 -0
  39. package/dist/components/viewer/index.js +29 -0
  40. package/dist/components/viewer/lights.d.ts +2 -0
  41. package/dist/components/viewer/lights.d.ts.map +1 -0
  42. package/dist/components/viewer/lights.js +10 -0
  43. package/dist/components/viewer/post-processing.d.ts +17 -0
  44. package/dist/components/viewer/post-processing.d.ts.map +1 -0
  45. package/dist/components/viewer/post-processing.js +139 -0
  46. package/dist/components/viewer/selection-manager.d.ts +2 -0
  47. package/dist/components/viewer/selection-manager.d.ts.map +1 -0
  48. package/dist/components/viewer/selection-manager.js +279 -0
  49. package/dist/components/viewer/viewer-camera.d.ts +2 -0
  50. package/dist/components/viewer/viewer-camera.d.ts.map +1 -0
  51. package/dist/components/viewer/viewer-camera.js +7 -0
  52. package/dist/hooks/use-asset-url.d.ts +6 -0
  53. package/dist/hooks/use-asset-url.d.ts.map +1 -0
  54. package/dist/hooks/use-asset-url.js +21 -0
  55. package/dist/hooks/use-gltf-ktx2.d.ts +4 -0
  56. package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -0
  57. package/dist/hooks/use-gltf-ktx2.js +16 -0
  58. package/dist/hooks/use-grid-events.d.ts +12 -0
  59. package/dist/hooks/use-grid-events.d.ts.map +1 -0
  60. package/dist/hooks/use-grid-events.js +33 -0
  61. package/dist/hooks/use-node-events.d.ts +49 -0
  62. package/dist/hooks/use-node-events.d.ts.map +1 -0
  63. package/dist/hooks/use-node-events.js +38 -0
  64. package/dist/index.d.ts +5 -81
  65. package/dist/index.d.ts.map +1 -0
  66. package/dist/index.js +4 -46669
  67. package/dist/lib/asset-url.d.ts +15 -0
  68. package/dist/lib/asset-url.d.ts.map +1 -0
  69. package/dist/lib/asset-url.js +44 -0
  70. package/dist/store/use-viewer.d.ts +35 -0
  71. package/dist/store/use-viewer.d.ts.map +1 -0
  72. package/dist/store/use-viewer.js +46 -0
  73. package/dist/systems/guide/guide-system.d.ts +2 -0
  74. package/dist/systems/guide/guide-system.d.ts.map +1 -0
  75. package/dist/systems/guide/guide-system.js +16 -0
  76. package/dist/systems/level/level-system.d.ts +2 -0
  77. package/dist/systems/level/level-system.d.ts.map +1 -0
  78. package/dist/systems/level/level-system.js +23 -0
  79. package/dist/systems/scan/scan-system.d.ts +2 -0
  80. package/dist/systems/scan/scan-system.d.ts.map +1 -0
  81. package/dist/systems/scan/scan-system.js +16 -0
  82. package/dist/systems/wall/wall-cutout.d.ts +2 -0
  83. package/dist/systems/wall/wall-cutout.d.ts.map +1 -0
  84. package/dist/systems/wall/wall-cutout.js +103 -0
  85. package/package.json +49 -34
  86. package/dist/index.js.map +0 -1
  87. package/types.d.ts +0 -81
@@ -0,0 +1,15 @@
1
+ export declare const ASSETS_CDN_URL = "https://pascal-cdn.wawasensei.dev";
2
+ /**
3
+ * Resolves an asset URL to the appropriate format:
4
+ * - If URL starts with http:// or https://, return as-is (external URL)
5
+ * - If URL starts with asset://, resolve from IndexedDB storage
6
+ * - If URL starts with /, prepend CDN URL (absolute path)
7
+ * - Otherwise, prepend CDN URL (relative path)
8
+ */
9
+ export declare function resolveAssetUrl(url: string | undefined | null): Promise<string | null>;
10
+ /**
11
+ * Synchronous version for URLs that don't need IndexedDB resolution
12
+ * Only use this if you're sure the URL is not an asset:// URL
13
+ */
14
+ export declare function resolveCdnUrl(url: string | undefined | null): string | null;
15
+ //# sourceMappingURL=asset-url.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asset-url.d.ts","sourceRoot":"","sources":["../../src/lib/asset-url.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,cAAc,sCAAsC,CAAA;AAEjE;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAgB5F;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAiB3E"}
@@ -0,0 +1,44 @@
1
+ import { loadAssetUrl } from '@pascal-app/core';
2
+ export const ASSETS_CDN_URL = 'https://pascal-cdn.wawasensei.dev';
3
+ /**
4
+ * Resolves an asset URL to the appropriate format:
5
+ * - If URL starts with http:// or https://, return as-is (external URL)
6
+ * - If URL starts with asset://, resolve from IndexedDB storage
7
+ * - If URL starts with /, prepend CDN URL (absolute path)
8
+ * - Otherwise, prepend CDN URL (relative path)
9
+ */
10
+ export async function resolveAssetUrl(url) {
11
+ if (!url)
12
+ return null;
13
+ // External URL - use as-is
14
+ if (url.startsWith('http://') || url.startsWith('https://')) {
15
+ return url;
16
+ }
17
+ // IndexedDB asset - resolve from storage
18
+ if (url.startsWith('asset://')) {
19
+ return loadAssetUrl(url);
20
+ }
21
+ // Absolute or relative path - prepend CDN URL
22
+ const normalizedPath = url.startsWith('/') ? url : `/${url}`;
23
+ return `${ASSETS_CDN_URL}${normalizedPath}`;
24
+ }
25
+ /**
26
+ * Synchronous version for URLs that don't need IndexedDB resolution
27
+ * Only use this if you're sure the URL is not an asset:// URL
28
+ */
29
+ export function resolveCdnUrl(url) {
30
+ if (!url)
31
+ return null;
32
+ // External URL - use as-is
33
+ if (url.startsWith('http://') || url.startsWith('https://')) {
34
+ return url;
35
+ }
36
+ // Don't use this for asset:// URLs - use resolveAssetUrl instead
37
+ if (url.startsWith('asset://')) {
38
+ console.warn('Use resolveAssetUrl() for asset:// URLs, not resolveCdnUrl()');
39
+ return null;
40
+ }
41
+ // Absolute or relative path - prepend CDN URL
42
+ const normalizedPath = url.startsWith('/') ? url : `/${url}`;
43
+ return `${ASSETS_CDN_URL}${normalizedPath}`;
44
+ }
@@ -0,0 +1,35 @@
1
+ import type { AnyNode, BaseNode, BuildingNode, LevelNode, ZoneNode } from "@pascal-app/core";
2
+ import type { Object3D } from "three";
3
+ type SelectionPath = {
4
+ buildingId: BuildingNode["id"] | null;
5
+ levelId: LevelNode["id"] | null;
6
+ zoneId: ZoneNode["id"] | null;
7
+ selectedIds: BaseNode["id"][];
8
+ };
9
+ type Outliner = {
10
+ selectedObjects: Object3D[];
11
+ hoveredObjects: Object3D[];
12
+ };
13
+ type ViewerState = {
14
+ selection: SelectionPath;
15
+ hoveredId: AnyNode['id'] | ZoneNode['id'] | null;
16
+ setHoveredId: (id: AnyNode['id'] | ZoneNode['id'] | null) => void;
17
+ cameraMode: 'perspective' | 'orthographic';
18
+ setCameraMode: (mode: 'perspective' | 'orthographic') => void;
19
+ levelMode: 'stacked' | 'exploded' | 'solo' | 'manual';
20
+ setLevelMode: (mode: 'stacked' | 'exploded' | 'solo' | 'manual') => void;
21
+ wallMode: 'up' | 'cutaway' | 'down';
22
+ setWallMode: (mode: 'up' | 'cutaway' | 'down') => void;
23
+ showScans: boolean;
24
+ setShowScans: (show: boolean) => void;
25
+ showGuides: boolean;
26
+ setShowGuides: (show: boolean) => void;
27
+ setSelection: (updates: Partial<SelectionPath>) => void;
28
+ resetSelection: () => void;
29
+ outliner: Outliner;
30
+ exportScene: (() => Promise<void>) | null;
31
+ setExportScene: (fn: (() => Promise<void>) | null) => void;
32
+ };
33
+ declare const useViewer: import("zustand").UseBoundStore<import("zustand").StoreApi<ViewerState>>;
34
+ export default useViewer;
35
+ //# sourceMappingURL=use-viewer.d.ts.map
@@ -0,0 +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;CAC3D,CAAA;AAED,QAAA,MAAM,SAAS,0EAqDZ,CAAC;AAEJ,eAAe,SAAS,CAAC"}
@@ -0,0 +1,46 @@
1
+ "use client";
2
+ import { create } from "zustand";
3
+ const useViewer = create()((set, get) => ({
4
+ selection: { buildingId: null, levelId: null, zoneId: null, selectedIds: [] },
5
+ hoveredId: null,
6
+ setHoveredId: (id) => set({ hoveredId: id }),
7
+ cameraMode: "perspective",
8
+ setCameraMode: (mode) => set({ cameraMode: mode }),
9
+ levelMode: "stacked",
10
+ setLevelMode: (mode) => set({ levelMode: mode }),
11
+ wallMode: 'cutaway',
12
+ setWallMode: (mode) => set({ wallMode: mode }),
13
+ showScans: true,
14
+ setShowScans: (show) => set({ showScans: show }),
15
+ showGuides: true,
16
+ setShowGuides: (show) => set({ showGuides: show }),
17
+ setSelection: (updates) => set((state) => {
18
+ const newSelection = { ...state.selection, ...updates };
19
+ // Hierarchy Guard: If we change a high-level parent, reset the children
20
+ if (updates.buildingId !== undefined) {
21
+ newSelection.levelId = null;
22
+ newSelection.zoneId = null;
23
+ newSelection.selectedIds = [];
24
+ }
25
+ else if (updates.levelId !== undefined) {
26
+ newSelection.zoneId = null;
27
+ newSelection.selectedIds = [];
28
+ }
29
+ else if (updates.zoneId !== undefined) {
30
+ newSelection.selectedIds = [];
31
+ }
32
+ return { selection: newSelection };
33
+ }),
34
+ resetSelection: () => set({
35
+ selection: {
36
+ buildingId: null,
37
+ levelId: null,
38
+ zoneId: null,
39
+ selectedIds: [],
40
+ },
41
+ }),
42
+ outliner: { selectedObjects: [], hoveredObjects: [] },
43
+ exportScene: null,
44
+ setExportScene: (fn) => set({ exportScene: fn }),
45
+ }));
46
+ export default useViewer;
@@ -0,0 +1,2 @@
1
+ export declare const GuideSystem: () => null;
2
+ //# sourceMappingURL=guide-system.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guide-system.d.ts","sourceRoot":"","sources":["../../../src/systems/guide/guide-system.tsx"],"names":[],"mappings":"AAIA,eAAO,MAAM,WAAW,YAavB,CAAA"}
@@ -0,0 +1,16 @@
1
+ import { sceneRegistry } from '@pascal-app/core';
2
+ import { useEffect } from 'react';
3
+ import useViewer from '../../store/use-viewer';
4
+ export const GuideSystem = () => {
5
+ const showGuides = useViewer((state) => state.showGuides);
6
+ useEffect(() => {
7
+ const guides = sceneRegistry.byType.guide || new Set();
8
+ guides.forEach((guideId) => {
9
+ const node = sceneRegistry.nodes.get(guideId);
10
+ if (node) {
11
+ node.visible = showGuides;
12
+ }
13
+ });
14
+ }, [showGuides]);
15
+ return null;
16
+ };
@@ -0,0 +1,2 @@
1
+ export declare const LevelSystem: () => null;
2
+ //# sourceMappingURL=level-system.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"level-system.d.ts","sourceRoot":"","sources":["../../../src/systems/level/level-system.tsx"],"names":[],"mappings":"AAQA,eAAO,MAAM,WAAW,YAkBvB,CAAA"}
@@ -0,0 +1,23 @@
1
+ import { sceneRegistry, useScene } from '@pascal-app/core';
2
+ import { useFrame } from '@react-three/fiber';
3
+ import { lerp } from 'three/src/math/MathUtils.js';
4
+ import useViewer from '../../store/use-viewer';
5
+ const LEVEL_HEIGHT = 2.5;
6
+ const EXPLODED_GAP = 5;
7
+ export const LevelSystem = () => {
8
+ useFrame((_, delta) => {
9
+ const levelMode = useViewer.getState().levelMode;
10
+ const selectedLevel = useViewer.getState().selection.levelId;
11
+ sceneRegistry.byType.level.forEach((levelId) => {
12
+ const obj = sceneRegistry.nodes.get(levelId);
13
+ if (obj) {
14
+ const level = useScene.getState().nodes[levelId];
15
+ const targetY = (level.level || 0) *
16
+ (LEVEL_HEIGHT + (levelMode === 'exploded' ? EXPLODED_GAP : 0));
17
+ obj.position.y = lerp(obj.position.y, targetY, delta * 3);
18
+ obj.visible = levelMode !== 'solo' || level?.id === selectedLevel || !selectedLevel;
19
+ }
20
+ });
21
+ });
22
+ return null;
23
+ };
@@ -0,0 +1,2 @@
1
+ export declare const ScanSystem: () => null;
2
+ //# sourceMappingURL=scan-system.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan-system.d.ts","sourceRoot":"","sources":["../../../src/systems/scan/scan-system.tsx"],"names":[],"mappings":"AAIA,eAAO,MAAM,UAAU,YAatB,CAAA"}
@@ -0,0 +1,16 @@
1
+ import { sceneRegistry } from '@pascal-app/core';
2
+ import { useEffect } from 'react';
3
+ import useViewer from '../../store/use-viewer';
4
+ export const ScanSystem = () => {
5
+ const showScans = useViewer((state) => state.showScans);
6
+ useEffect(() => {
7
+ const scans = sceneRegistry.byType.scan || new Set();
8
+ scans.forEach((scanId) => {
9
+ const node = sceneRegistry.nodes.get(scanId);
10
+ if (node) {
11
+ node.visible = showScans;
12
+ }
13
+ });
14
+ }, [showScans]);
15
+ return null;
16
+ };
@@ -0,0 +1,2 @@
1
+ export declare const WallCutout: () => null;
2
+ //# sourceMappingURL=wall-cutout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wall-cutout.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-cutout.tsx"],"names":[],"mappings":"AAgDA,eAAO,MAAM,UAAU,YAkEtB,CAAA"}
@@ -0,0 +1,103 @@
1
+ import { sceneRegistry, useScene } from '@pascal-app/core';
2
+ import { useFrame } from '@react-three/fiber';
3
+ import { useRef } from 'react';
4
+ import { Fn, float, fract, length, mix, positionLocal, smoothstep, step, vec2 } from 'three/tsl';
5
+ import { MeshStandardNodeMaterial, Vector3 } from 'three/webgpu';
6
+ import useViewer from '../../store/use-viewer';
7
+ const tmpVec = new Vector3();
8
+ const u = new Vector3();
9
+ const v = new Vector3();
10
+ // Dot pattern shader
11
+ const dotPattern = Fn(() => {
12
+ // Create a repeating grid pattern based on world position
13
+ const scale = float(0.1); // Dot grid spacing (10cm)
14
+ const dotSize = float(0.3); // Size of dots relative to grid
15
+ // Use XY coordinates for pattern on wall face
16
+ const uv = vec2(positionLocal.x, positionLocal.y).div(scale);
17
+ const gridUV = fract(uv);
18
+ // Distance from center of grid cell (creates circular dots)
19
+ const dist = length(gridUV.sub(0.5));
20
+ // Create dots: 1 where we want dots, 0 elsewhere
21
+ const dots = step(dist, dotSize.mul(0.5));
22
+ // Vertical fade: fade out as Y increases (from bottom to top)
23
+ const fadeHeight = float(2.5); // Fade over 2.5 meters
24
+ const yFade = float(1).sub(smoothstep(float(0), fadeHeight, positionLocal.y));
25
+ return dots.mul(yFade);
26
+ });
27
+ const invsibleWallMaterial = new MeshStandardNodeMaterial({
28
+ transparent: true,
29
+ opacityNode: mix(float(0.0), float(0.24), dotPattern()),
30
+ color: 'white',
31
+ depthWrite: false,
32
+ emissive: 'white',
33
+ });
34
+ const wallMaterial = new MeshStandardNodeMaterial({
35
+ color: 'white',
36
+ roughness: 1,
37
+ metalness: 0,
38
+ });
39
+ export const WallCutout = () => {
40
+ const lastCameraPosition = useRef(new Vector3());
41
+ const lastCameraTarget = useRef(new Vector3());
42
+ const lastUpdateTime = useRef(0);
43
+ const lastWallMode = useRef(useViewer.getState().wallMode);
44
+ const lastNumberOfWalls = useRef(0);
45
+ useFrame(({ camera, clock }) => {
46
+ const wallMode = useViewer.getState().wallMode;
47
+ const currentTime = clock.elapsedTime;
48
+ const currentCameraPosition = camera.position;
49
+ camera.getWorldDirection(tmpVec);
50
+ tmpVec.add(currentCameraPosition);
51
+ // Throttle: only update if camera moved significantly AND enough time passed
52
+ const distanceMoved = currentCameraPosition.distanceTo(lastCameraPosition.current);
53
+ const directionChanged = tmpVec.distanceTo(lastCameraTarget.current);
54
+ const timeSinceUpdate = currentTime - lastUpdateTime.current;
55
+ // Update if moved > 0.5m OR direction changed > 0.3 AND at least 100ms passed
56
+ if (((distanceMoved > 0.5 || directionChanged > 0.3) && timeSinceUpdate > 0.1) ||
57
+ lastWallMode.current !== wallMode ||
58
+ sceneRegistry.byType.wall.size !== lastNumberOfWalls.current) {
59
+ // Camera has moved, update cutout logic here
60
+ // Update last known positions and time
61
+ lastCameraPosition.current.copy(currentCameraPosition);
62
+ lastCameraTarget.current.copy(tmpVec);
63
+ lastUpdateTime.current = currentTime;
64
+ camera.getWorldDirection(u);
65
+ const walls = sceneRegistry.byType.wall;
66
+ walls.forEach((wallId) => {
67
+ const wallMesh = sceneRegistry.nodes.get(wallId);
68
+ if (!wallMesh)
69
+ return;
70
+ const wallNode = useScene.getState().nodes[wallId];
71
+ if (!wallNode || wallNode.type !== 'wall')
72
+ return;
73
+ let hideWall = wallNode.frontSide === 'interior' && wallNode.backSide === 'interior';
74
+ if (wallMode === 'up') {
75
+ hideWall = false;
76
+ }
77
+ else if (wallMode === 'down') {
78
+ hideWall = true;
79
+ }
80
+ else {
81
+ wallMesh.getWorldDirection(v);
82
+ if (v.dot(u) < 0) {
83
+ // Front side
84
+ if (wallNode.frontSide === 'exterior' && wallNode.backSide !== 'exterior') {
85
+ hideWall = true;
86
+ }
87
+ }
88
+ else {
89
+ // Back side
90
+ if (wallNode.backSide === 'exterior' && wallNode.frontSide !== 'exterior') {
91
+ hideWall = true;
92
+ }
93
+ }
94
+ }
95
+ ;
96
+ wallMesh.material = hideWall ? invsibleWallMaterial : wallMaterial;
97
+ });
98
+ lastWallMode.current = wallMode;
99
+ lastNumberOfWalls.current = sceneRegistry.byType.wall.size;
100
+ }
101
+ });
102
+ return null;
103
+ };
package/package.json CHANGED
@@ -1,51 +1,66 @@
1
1
  {
2
2
  "name": "@pascal-app/viewer",
3
- "version": "0.1.2",
4
- "description": "React Three Fiber component for viewing Pascal 3D scenes",
5
- "private": false,
3
+ "version": "0.1.5",
4
+ "description": "3D viewer component for Pascal building editor",
6
5
  "type": "module",
7
- "main": "./dist/index.js",
8
- "module": "./dist/index.js",
9
- "types": "./dist/index.d.ts",
6
+ "main": "./src/index.ts",
7
+ "types": "./src/index.ts",
10
8
  "exports": {
11
9
  ".": {
12
- "import": "./dist/index.js",
13
- "types": "./dist/index.d.ts"
10
+ "types": "./src/index.ts",
11
+ "import": "./src/index.ts",
12
+ "default": "./src/index.ts"
14
13
  }
15
14
  },
16
15
  "files": [
17
16
  "dist",
18
- "types.d.ts"
17
+ "README.md"
19
18
  ],
20
- "sideEffects": false,
21
19
  "scripts": {
22
- "build": "tsup",
23
- "dev": "tsup --watch",
24
- "typecheck": "tsc --noEmit"
20
+ "build": "tsc --declaration --emitDeclarationOnly && tsc",
21
+ "prepublishOnly": "npm run build"
25
22
  },
26
- "dependencies": {
27
- "@react-spring/three": "^10.0.0",
28
- "@react-three/csg": "^4.0.0",
29
- "@react-three/drei": "^10.0.0",
30
- "@react-three/uikit": "^1.0.0",
31
- "clsx": "^2.1.1",
32
- "immer": "^10.2.0",
33
- "mitt": "^3.0.1",
34
- "nanoid": "^5.1.6",
35
- "tailwind-merge": "^3.4.0",
36
- "three-custom-shader-material": "^6.1.0",
37
- "zod": "^4.2.1",
38
- "zustand": "^5.0.9"
23
+ "publishConfig": {
24
+ "main": "./dist/index.js",
25
+ "types": "./dist/index.d.ts",
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.d.ts",
29
+ "import": "./dist/index.js",
30
+ "default": "./dist/index.js"
31
+ }
32
+ }
39
33
  },
40
34
  "peerDependencies": {
41
- "react": "^19.0.0",
42
- "react-dom": "^19.0.0",
43
- "three": ">=0.156",
44
- "@react-three/fiber": ">=9.0.0"
35
+ "@pascal-app/core": "^0.1.4",
36
+ "@react-three/drei": "^10",
37
+ "@react-three/fiber": "^9",
38
+ "react": "^18 || ^19",
39
+ "three": "^0.182"
40
+ },
41
+ "dependencies": {
42
+ "zustand": "^5"
45
43
  },
46
44
  "devDependencies": {
47
- "@pascal/core": "0.1.0",
48
- "@types/react": "^19.0.0",
49
- "tsup": "^8.5.1"
50
- }
45
+ "@repo/typescript-config": "*",
46
+ "@types/react": "^19.2.2",
47
+ "typescript": "5.9.2",
48
+ "@types/three": "^0.182.0"
49
+ },
50
+ "keywords": [
51
+ "3d",
52
+ "building",
53
+ "editor",
54
+ "viewer",
55
+ "architecture",
56
+ "webgpu",
57
+ "three.js",
58
+ "react-three-fiber"
59
+ ],
60
+ "repository": {
61
+ "type": "git",
62
+ "url": "https://github.com/your-username/pascal-editor.git",
63
+ "directory": "packages/viewer"
64
+ },
65
+ "license": "MIT"
51
66
  }