Move render systems out of core
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
@@ -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": {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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<MaterialProperties['side'], THREE.Side> = {
|
||||
front: THREE.FrontSide,
|
||||
|
||||
+1
-3
@@ -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')
|
||||
+7
-4
@@ -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 })
|
||||
+8
-4
@@ -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]
|
||||
+10
-5
@@ -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
|
||||
+18
-12
@@ -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
|
||||
+7
-4
@@ -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')
|
||||
+11
-6
@@ -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<AnyNodeId>()
|
||||
const MAX_STAIRS_PER_FRAME = 2
|
||||
@@ -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'
|
||||
|
||||
|
||||
+24
-14
@@ -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)
|
||||
}
|
||||
+7
-4
@@ -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 })
|
||||
Reference in New Issue
Block a user