Move render systems out of core

This commit is contained in:
sudhir
2026-05-01 15:07:47 +05:30
parent eee7af9e25
commit 23eec44714
18 changed files with 126 additions and 109 deletions
+2 -2
View File
@@ -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": {
-2
View File
@@ -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"
+2 -10
View File
@@ -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'
-24
View File
@@ -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,
})
+2
View File
@@ -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'
+16
View File
@@ -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,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')
@@ -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 })
@@ -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]
@@ -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
@@ -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
@@ -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')
@@ -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'
@@ -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)
}
@@ -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 })