diff --git a/bun.lock b/bun.lock index 0b9d6554..c0e5fbbe 100644 --- a/bun.lock +++ b/bun.lock @@ -56,8 +56,6 @@ "idb-keyval": "^6.2.2", "mitt": "^3.0.1", "nanoid": "^5.1.6", - "three-bvh-csg": "^0.0.18", - "three-mesh-bvh": "^0.9.8", "zod": "^4.3.5", "zundo": "^2.3.0", "zustand": "^5", @@ -196,6 +194,8 @@ "version": "0.6.0", "dependencies": { "polygon-clipping": "^0.15.7", + "three-bvh-csg": "^0.0.18", + "three-mesh-bvh": "^0.9.8", "zustand": "^5", }, "devDependencies": { diff --git a/packages/core/package.json b/packages/core/package.json index 13128823..8cffdb40 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -67,8 +67,6 @@ "idb-keyval": "^6.2.2", "mitt": "^3.0.1", "nanoid": "^5.1.6", - "three-bvh-csg": "^0.0.18", - "three-mesh-bvh": "^0.9.8", "zod": "^4.3.5", "zundo": "^2.3.0", "zustand": "^5" diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index e81a36bb..59e84f2b 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -52,7 +52,6 @@ export { type MaterialCategory, toLibraryMaterialRef, } from './material-library' -export { baseMaterial, glassMaterial } from './materials' export * from './schema' export { getSceneHistoryPauseDepth, @@ -67,13 +66,7 @@ export { } from './store/use-interactive' export { default as useLiveTransforms, type LiveTransform } from './store/use-live-transforms' export { clearSceneHistory, default as useScene } from './store/use-scene' -export { CeilingSystem } from './systems/ceiling/ceiling-system' -export { DoorSystem } from './systems/door/door-system' -export { FenceSystem } from './systems/fence/fence-system' -export { ItemSystem } from './systems/item/item-system' -export { RoofSystem } from './systems/roof/roof-system' -export { SlabSystem } from './systems/slab/slab-system' -export { StairSystem } from './systems/stair/stair-system' +export { syncAutoStairOpenings } from './systems/stair/stair-opening-sync' export { getClampedWallCurveOffset, getMaxWallCurveOffset, @@ -95,14 +88,13 @@ export { } from './systems/wall/wall-footprint' export { calculateLevelMiters, + getAdjacentWallIds, getWallMiterBoundaryPoints, type Point2D, pointToKey, type WallMiterBoundaryPoints, type WallMiterData, } from './systems/wall/wall-mitering' -export { WallSystem } from './systems/wall/wall-system' -export { WindowSystem } from './systems/window/window-system' export type { SceneGraph } from './utils/clone-scene-graph' export { cloneLevelSubtree, cloneSceneGraph, forkSceneGraph } from './utils/clone-scene-graph' export { isObject } from './utils/types' diff --git a/packages/core/src/materials.ts b/packages/core/src/materials.ts deleted file mode 100644 index 089d2677..00000000 --- a/packages/core/src/materials.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { DoubleSide, MeshStandardNodeMaterial } from 'three/webgpu' - -/** - * Shared base material for structural elements: walls, frames, slabs, roof. - */ -export const baseMaterial = new MeshStandardNodeMaterial({ - color: '#f2f0ed', - roughness: 0.5, - metalness: 0, -}) - -/** - * Shared glass material for windows, glazed door panels, and glass items. - */ -export const glassMaterial = new MeshStandardNodeMaterial({ - name: 'glass', - color: 'lightblue', - roughness: 0.05, - metalness: 0.1, - transparent: true, - opacity: 0.35, - side: DoubleSide, - depthWrite: false, -}) diff --git a/packages/viewer/package.json b/packages/viewer/package.json index 1c214282..77c4ed75 100644 --- a/packages/viewer/package.json +++ b/packages/viewer/package.json @@ -30,6 +30,8 @@ }, "dependencies": { "polygon-clipping": "^0.15.7", + "three-bvh-csg": "^0.0.18", + "three-mesh-bvh": "^0.9.8", "zustand": "^5" }, "devDependencies": { diff --git a/packages/viewer/src/components/renderers/item/item-renderer.tsx b/packages/viewer/src/components/renderers/item/item-renderer.tsx index 5bcfbd2d..86b44d23 100644 --- a/packages/viewer/src/components/renderers/item/item-renderer.tsx +++ b/packages/viewer/src/components/renderers/item/item-renderer.tsx @@ -1,8 +1,6 @@ import { type AnimationEffect, type AnyNodeId, - baseMaterial, - glassMaterial, type Interactive, type ItemNode, type LightEffect, @@ -21,6 +19,7 @@ import { positionLocal, smoothstep, time } from 'three/tsl' import { MeshStandardNodeMaterial } from 'three/webgpu' import { useNodeEvents } from '../../../hooks/use-node-events' import { resolveCdnUrl } from '../../../lib/asset-url' +import { baseMaterial, glassMaterial } from '../../../lib/materials' import { useItemLightPool } from '../../../store/use-item-light-pool' import { requestItemMeshMetadataSync, diff --git a/packages/viewer/src/components/viewer/index.tsx b/packages/viewer/src/components/viewer/index.tsx index 1a77e688..13f478b5 100644 --- a/packages/viewer/src/components/viewer/index.tsx +++ b/packages/viewer/src/components/viewer/index.tsx @@ -1,27 +1,25 @@ 'use client' -import { - CeilingSystem, - DoorSystem, - FenceSystem, - ItemSystem, - RoofSystem, - SlabSystem, - StairSystem, - WallSystem, - WindowSystem, -} from '@pascal-app/core' import { Bvh } from '@react-three/drei' import { Canvas, extend, type ThreeToJSXElements, useFrame, useThree } from '@react-three/fiber' import { useEffect, useMemo, useRef } from 'react' import * as THREE from 'three/webgpu' import useViewer from '../../store/use-viewer' +import { CeilingSystem } from '../../systems/ceiling/ceiling-system' +import { DoorSystem } from '../../systems/door/door-system' +import { FenceSystem } from '../../systems/fence/fence-system' import { GuideSystem } from '../../systems/guide/guide-system' +import { ItemSystem } from '../../systems/item/item-system' import { ItemLightSystem } from '../../systems/item-light/item-light-system' import { ItemMeshMetadataSystem } from '../../systems/item-mesh-metadata/item-mesh-metadata-system' import { LevelSystem } from '../../systems/level/level-system' +import { RoofSystem } from '../../systems/roof/roof-system' import { ScanSystem } from '../../systems/scan/scan-system' +import { SlabSystem } from '../../systems/slab/slab-system' +import { StairSystem } from '../../systems/stair/stair-system' import { WallCutout } from '../../systems/wall/wall-cutout' +import { WallSystem } from '../../systems/wall/wall-system' +import { WindowSystem } from '../../systems/window/window-system' import { ZoneSystem } from '../../systems/zone/zone-system' import { ErrorBoundary } from '../error-boundary' import { SceneRenderer } from '../renderers/scene-renderer' diff --git a/packages/viewer/src/lib/materials.ts b/packages/viewer/src/lib/materials.ts index 0aec65dd..b390e436 100644 --- a/packages/viewer/src/lib/materials.ts +++ b/packages/viewer/src/lib/materials.ts @@ -7,6 +7,22 @@ import { resolveMaterial, } from '@pascal-app/core' import * as THREE from 'three' +import { MeshStandardNodeMaterial } from 'three/webgpu' + +export const baseMaterial = new MeshStandardNodeMaterial({ + color: '#f2f0ed', + roughness: 0.5, + metalness: 0.0, +}) + +export const glassMaterial = new MeshStandardNodeMaterial({ + color: '#e0f2fe', + roughness: 0.05, + metalness: 0.0, + transparent: true, + opacity: 0.35, + side: THREE.DoubleSide, +}) const sideMap: Record = { front: THREE.FrontSide, diff --git a/packages/core/src/systems/ceiling/ceiling-system.tsx b/packages/viewer/src/systems/ceiling/ceiling-system.tsx similarity index 94% rename from packages/core/src/systems/ceiling/ceiling-system.tsx rename to packages/viewer/src/systems/ceiling/ceiling-system.tsx index 4f47fab1..5e383d2b 100644 --- a/packages/core/src/systems/ceiling/ceiling-system.tsx +++ b/packages/viewer/src/systems/ceiling/ceiling-system.tsx @@ -1,8 +1,6 @@ import { useFrame } from '@react-three/fiber' +import { type AnyNodeId, type CeilingNode, sceneRegistry, useScene } from '@pascal-app/core' import * as THREE from 'three' -import { sceneRegistry } from '../../hooks/scene-registry/scene-registry' -import type { AnyNodeId, CeilingNode } from '../../schema' -import useScene from '../../store/use-scene' function ensureUv2Attribute(geometry: THREE.BufferGeometry) { const uv = geometry.getAttribute('uv') diff --git a/packages/core/src/systems/door/door-system.tsx b/packages/viewer/src/systems/door/door-system.tsx similarity index 97% rename from packages/core/src/systems/door/door-system.tsx rename to packages/viewer/src/systems/door/door-system.tsx index 93f4abd4..a782ed95 100644 --- a/packages/core/src/systems/door/door-system.tsx +++ b/packages/viewer/src/systems/door/door-system.tsx @@ -1,9 +1,12 @@ import { useFrame } from '@react-three/fiber' +import { + type AnyNodeId, + type DoorNode, + sceneRegistry, + useScene, +} from '@pascal-app/core' import * as THREE from 'three' -import { sceneRegistry } from '../../hooks/scene-registry/scene-registry' -import { baseMaterial, glassMaterial } from '../../materials' -import type { AnyNodeId, DoorNode } from '../../schema' -import useScene from '../../store/use-scene' +import { baseMaterial, glassMaterial } from '../../lib/materials' // Invisible material for root mesh — used as selection hitbox only const hitboxMaterial = new THREE.MeshBasicMaterial({ visible: false }) diff --git a/packages/core/src/systems/fence/fence-system.tsx b/packages/viewer/src/systems/fence/fence-system.tsx similarity index 96% rename from packages/core/src/systems/fence/fence-system.tsx rename to packages/viewer/src/systems/fence/fence-system.tsx index 4fcc1a86..c0a2605c 100644 --- a/packages/core/src/systems/fence/fence-system.tsx +++ b/packages/viewer/src/systems/fence/fence-system.tsx @@ -1,10 +1,14 @@ import { useFrame } from '@react-three/fiber' +import { + type AnyNodeId, + type FenceNode, + getWallCurveFrameAt, + getWallCurveLength, + sceneRegistry, + useScene, +} from '@pascal-app/core' import * as THREE from 'three' import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js' -import { sceneRegistry } from '../../hooks/scene-registry/scene-registry' -import type { AnyNodeId, FenceNode } from '../../schema' -import useScene from '../../store/use-scene' -import { getWallCurveFrameAt, getWallCurveLength } from '../wall/wall-curve' type FencePart = { position: [number, number, number] diff --git a/packages/core/src/systems/item/item-system.tsx b/packages/viewer/src/systems/item/item-system.tsx similarity index 84% rename from packages/core/src/systems/item/item-system.tsx rename to packages/viewer/src/systems/item/item-system.tsx index dbfbf7e3..75173160 100644 --- a/packages/core/src/systems/item/item-system.tsx +++ b/packages/viewer/src/systems/item/item-system.tsx @@ -1,10 +1,15 @@ import { useFrame } from '@react-three/fiber' +import { + type AnyNodeId, + getScaledDimensions, + type ItemNode, + resolveLevelId, + sceneRegistry, + spatialGridManager, + useScene, + type WallNode, +} from '@pascal-app/core' import type * as THREE from 'three' -import { sceneRegistry } from '../../hooks/scene-registry/scene-registry' -import { spatialGridManager } from '../../hooks/spatial-grid/spatial-grid-manager' -import { resolveLevelId } from '../../hooks/spatial-grid/spatial-grid-sync' -import { type AnyNodeId, getScaledDimensions, type ItemNode, type WallNode } from '../../schema' -import useScene from '../../store/use-scene' // ============================================================================ // ITEM SYSTEM diff --git a/packages/core/src/systems/roof/roof-system.tsx b/packages/viewer/src/systems/roof/roof-system.tsx similarity index 97% rename from packages/core/src/systems/roof/roof-system.tsx rename to packages/viewer/src/systems/roof/roof-system.tsx index 5a1f06d1..a7479fb0 100644 --- a/packages/core/src/systems/roof/roof-system.tsx +++ b/packages/viewer/src/systems/roof/roof-system.tsx @@ -1,21 +1,30 @@ import { useFrame } from '@react-three/fiber' +import { + type AnyNode, + type AnyNodeId, + type RoofNode, + type RoofSegmentNode, + type RoofType, + sceneRegistry, + useScene, +} from '@pascal-app/core' import * as THREE from 'three' import { mergeVertices } from 'three/examples/jsm/utils/BufferGeometryUtils.js' import { ADDITION, Brush, Evaluator, SUBTRACTION } from 'three-bvh-csg' import { computeBoundsTree } from 'three-mesh-bvh' -import { sceneRegistry } from '../../hooks/scene-registry/scene-registry' -import type { AnyNode, AnyNodeId, RoofNode, RoofSegmentNode } from '../../schema' -import type { RoofType } from '../../schema/nodes/roof-segment' -import useScene from '../../store/use-scene' const csgEvaluator = new Evaluator() csgEvaluator.useGroups = true ;(csgEvaluator as any).consolidateGroups = false // shared dummyMats across brushes causes consolidation to misalign groupIndices vs groupOrder indices → crash csgEvaluator.attributes = ['position', 'normal', 'uv'] +function computeGeometryBoundsTree(geometry: THREE.BufferGeometry) { + ;(geometry as any).computeBoundsTree = computeBoundsTree + ;(geometry as any).computeBoundsTree({ maxLeafSize: 10 }) +} + function prepareBrushForCSG(brush: Brush) { - brush.geometry.computeBoundsTree = computeBoundsTree - brush.geometry.computeBoundsTree({ maxLeafSize: 10 }) + computeGeometryBoundsTree(brush.geometry) brush.updateMatrixWorld() } @@ -78,8 +87,7 @@ export const RoofSystem = () => { mesh.geometry.dispose() const placeholder = new THREE.BufferGeometry() placeholder.setAttribute('position', new THREE.Float32BufferAttribute([], 3)) - placeholder.computeBoundsTree = computeBoundsTree - placeholder.computeBoundsTree({ maxLeafSize: 10 }) + computeGeometryBoundsTree(placeholder) mesh.geometry = placeholder } mesh.position.set(node.position[0], node.position[1], node.position[2]) @@ -134,8 +142,7 @@ function updateRoofSegmentGeometry(node: RoofSegmentNode, mesh: THREE.Mesh) { mesh.geometry.dispose() mesh.geometry = newGeo - newGeo.computeBoundsTree = computeBoundsTree - newGeo.computeBoundsTree({ maxLeafSize: 10 }) + computeGeometryBoundsTree(newGeo) mesh.position.set(node.position[0], node.position[1], node.position[2]) mesh.rotation.y = node.rotation @@ -524,8 +531,7 @@ export function getRoofSegmentBrushes( // when a group exists but covers no triangles (can happen after mergeVertices) geo.groups = geo.groups.filter((g) => g.count > 0) if (geo.groups.length === 0) return null - geo.computeBoundsTree = computeBoundsTree - geo.computeBoundsTree({ maxLeafSize: 10 }) + computeGeometryBoundsTree(geo) const brush = new Brush(geo, dummyMats) brush.updateMatrixWorld() return brush diff --git a/packages/core/src/systems/slab/slab-system.tsx b/packages/viewer/src/systems/slab/slab-system.tsx similarity index 96% rename from packages/core/src/systems/slab/slab-system.tsx rename to packages/viewer/src/systems/slab/slab-system.tsx index 16920d3a..d9f02df9 100644 --- a/packages/core/src/systems/slab/slab-system.tsx +++ b/packages/viewer/src/systems/slab/slab-system.tsx @@ -1,9 +1,12 @@ import { useFrame } from '@react-three/fiber' +import { + type AnyNodeId, + getRenderableSlabPolygon, + sceneRegistry, + type SlabNode, + useScene, +} from '@pascal-app/core' import * as THREE from 'three' -import { sceneRegistry } from '../../hooks/scene-registry/scene-registry' -import { getRenderableSlabPolygon } from '../../lib/slab-polygon' -import type { AnyNodeId, SlabNode } from '../../schema' -import useScene from '../../store/use-scene' function ensureUv2Attribute(geometry: THREE.BufferGeometry) { const uv = geometry.getAttribute('uv') diff --git a/packages/core/src/systems/stair/stair-system.tsx b/packages/viewer/src/systems/stair/stair-system.tsx similarity index 98% rename from packages/core/src/systems/stair/stair-system.tsx rename to packages/viewer/src/systems/stair/stair-system.tsx index 77e40029..6f1e96d5 100644 --- a/packages/core/src/systems/stair/stair-system.tsx +++ b/packages/viewer/src/systems/stair/stair-system.tsx @@ -1,13 +1,18 @@ import { useFrame } from '@react-three/fiber' +import { + type AnyNode, + type AnyNodeId, + resolveLevelId, + sceneRegistry, + spatialGridManager, + type StairNode, + type StairSegmentNode, + syncAutoStairOpenings, + useScene, +} from '@pascal-app/core' import { useEffect, useRef } from 'react' import * as THREE from 'three' import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js' -import { sceneRegistry } from '../../hooks/scene-registry/scene-registry' -import { spatialGridManager } from '../../hooks/spatial-grid/spatial-grid-manager' -import { resolveLevelId } from '../../hooks/spatial-grid/spatial-grid-sync' -import type { AnyNode, AnyNodeId, StairNode, StairSegmentNode } from '../../schema' -import useScene from '../../store/use-scene' -import { syncAutoStairOpenings } from './stair-opening-sync' const pendingStairUpdates = new Set() const MAX_STAIRS_PER_FRAME = 2 diff --git a/packages/viewer/src/systems/wall/wall-materials.ts b/packages/viewer/src/systems/wall/wall-materials.ts index 5edccfea..fb1b4e99 100644 --- a/packages/viewer/src/systems/wall/wall-materials.ts +++ b/packages/viewer/src/systems/wall/wall-materials.ts @@ -1,5 +1,4 @@ import { - baseMaterial, getEffectiveWallSurfaceMaterial, getMaterialPresetByRef, getWallSurfaceMaterialSignature, @@ -10,7 +9,7 @@ import { import { Color, type Material } from 'three' import { Fn, float, fract, length, mix, positionLocal, smoothstep, step, vec2 } from 'three/tsl' import { MeshStandardNodeMaterial } from 'three/webgpu' -import { createMaterial, createMaterialFromPresetRef } from '../../lib/materials' +import { baseMaterial, createMaterial, createMaterialFromPresetRef } from '../../lib/materials' const DEFAULT_WALL_COLOR = '#f2f0ed' diff --git a/packages/core/src/systems/wall/wall-system.tsx b/packages/viewer/src/systems/wall/wall-system.tsx similarity index 96% rename from packages/core/src/systems/wall/wall-system.tsx rename to packages/viewer/src/systems/wall/wall-system.tsx index dc2bf8a2..98c98467 100644 --- a/packages/core/src/systems/wall/wall-system.tsx +++ b/packages/viewer/src/systems/wall/wall-system.tsx @@ -2,21 +2,29 @@ import { useFrame } from '@react-three/fiber' import * as THREE from 'three' import { Brush, Evaluator, SUBTRACTION } from 'three-bvh-csg' import { computeBoundsTree } from 'three-mesh-bvh' -import { sceneRegistry } from '../../hooks/scene-registry/scene-registry' -import { spatialGridManager } from '../../hooks/spatial-grid/spatial-grid-manager' -import { resolveLevelId } from '../../hooks/spatial-grid/spatial-grid-sync' -import type { AnyNode, AnyNodeId, DoorNode, WallNode, WindowNode } from '../../schema' -import useScene from '../../store/use-scene' -import { getWallCurveFrameAt, getWallSurfacePolygon, isCurvedWall } from './wall-curve' -import { DEFAULT_WALL_HEIGHT, getWallPlanFootprint, getWallThickness } from './wall-footprint' import { calculateLevelMiters, + type AnyNode, + type AnyNodeId, + type DoorNode, getAdjacentWallIds, + DEFAULT_WALL_HEIGHT, + getWallCurveFrameAt, getWallMiterBoundaryPoints, + getWallPlanFootprint, + getWallSurfacePolygon, + getWallThickness, + isCurvedWall, type Point2D, pointToKey, + resolveLevelId, + sceneRegistry, + spatialGridManager, + useScene, + type WallNode, type WallMiterData, -} from './wall-mitering' + type WindowNode, +} from '@pascal-app/core' // Reusable CSG evaluator for better performance const csgEvaluator = new Evaluator() @@ -24,6 +32,11 @@ const CURVED_WALL_3D_ENDPOINT_INSET = 0.0015 const WALL_FACE_NORMAL_Y_EPSILON = 0.6 const WALL_FACE_EDGE_DISTANCE_EPSILON = 0.003 +function computeGeometryBoundsTree(geometry: THREE.BufferGeometry) { + ;(geometry as any).computeBoundsTree = computeBoundsTree + ;(geometry as any).computeBoundsTree({ maxLeafSize: 10 }) +} + type WallBoundaryEdgeTag = 'front' | 'back' | 'base' type TaggedWallBoundaryEdge = { @@ -495,8 +508,7 @@ export function generateExtrudedWall( // Create wall brush from geometry // Pre-compute BVH with new API to avoid deprecation warning - geometry.computeBoundsTree = computeBoundsTree - geometry.computeBoundsTree({ maxLeafSize: 10 }) + computeGeometryBoundsTree(geometry) const wallBrush = new Brush(geometry) wallBrush.updateMatrixWorld() @@ -599,8 +611,7 @@ function collectCutoutBrushes( ) // Pre-compute BVH with new API to avoid deprecation warning - boxGeo.computeBoundsTree = computeBoundsTree - boxGeo.computeBoundsTree({ maxLeafSize: 10 }) + computeGeometryBoundsTree(boxGeo) const brush = new Brush(boxGeo) brushes.push(brush) @@ -638,8 +649,7 @@ function createShapedOpeningCutoutBrush(opening: ShapedOpeningNode, wallThicknes }) geometry.translate(0, 0, -depth / 2) - geometry.computeBoundsTree = computeBoundsTree - geometry.computeBoundsTree({ maxLeafSize: 10 }) + computeGeometryBoundsTree(geometry) return new Brush(geometry) } diff --git a/packages/core/src/systems/window/window-system.tsx b/packages/viewer/src/systems/window/window-system.tsx similarity index 96% rename from packages/core/src/systems/window/window-system.tsx rename to packages/viewer/src/systems/window/window-system.tsx index 65d68455..01a77f43 100644 --- a/packages/core/src/systems/window/window-system.tsx +++ b/packages/viewer/src/systems/window/window-system.tsx @@ -1,9 +1,12 @@ import { useFrame } from '@react-three/fiber' +import { + type AnyNodeId, + sceneRegistry, + useScene, + type WindowNode, +} from '@pascal-app/core' import * as THREE from 'three' -import { sceneRegistry } from '../../hooks/scene-registry/scene-registry' -import { baseMaterial, glassMaterial } from '../../materials' -import type { AnyNodeId, WindowNode } from '../../schema' -import useScene from '../../store/use-scene' +import { baseMaterial, glassMaterial } from '../../lib/materials' // Invisible material for root mesh — used as selection hitbox only const hitboxMaterial = new THREE.MeshBasicMaterial({ visible: false })