Add roof material targets and UV mapping

This commit is contained in:
sudhir
2026-04-20 12:57:06 +05:30
parent 05002a7865
commit bbc351d3a4
11 changed files with 428 additions and 47 deletions
@@ -1,9 +1,9 @@
import { type AnyNodeId, type RoofNode, type RoofSegmentNode, useRegistry, useScene } from '@pascal-app/core'
import { useMemo, useRef } from 'react'
import type * as THREE from 'three'
import { useEffect, useMemo, useRef } from 'react'
import * as THREE from 'three'
import { useNodeEvents } from '../../../hooks/use-node-events'
import { createMaterial, createMaterialFromPresetRef } from '../../../lib/materials'
import useViewer from '../../../store/use-viewer'
import { getRoofMaterialArray } from '../../../systems/roof/roof-materials'
import { roofDebugMaterials, roofMaterials } from '../roof/roof-materials'
export const RoofSegmentRenderer = ({ node }: { node: RoofSegmentNode }) => {
@@ -16,16 +16,22 @@ export const RoofSegmentRenderer = ({ node }: { node: RoofSegmentNode }) => {
const debugColors = useViewer((s) => s.debugColors)
const parentNode =
node.parentId ? (nodes[node.parentId as AnyNodeId] as RoofNode | undefined) : undefined
const placeholderGeometry = useMemo(() => {
const geometry = new THREE.BufferGeometry()
geometry.setAttribute('position', new THREE.Float32BufferAttribute([], 3))
geometry.addGroup(0, 0, 0)
geometry.addGroup(0, 0, 1)
geometry.addGroup(0, 0, 2)
geometry.addGroup(0, 0, 3)
return geometry
}, [])
const customMaterial = useMemo(() => {
const effectiveMaterialPreset = node.materialPreset ?? parentNode?.materialPreset
const effectiveMaterial = node.material ?? parentNode?.material
if (node.material !== undefined || typeof node.materialPreset === 'string') {
return null
}
const presetMaterial = createMaterialFromPresetRef(effectiveMaterialPreset)
if (presetMaterial) return presetMaterial
const mat = effectiveMaterial
if (!mat) return null
return createMaterial(mat)
return parentNode ? getRoofMaterialArray(parentNode) : null
}, [
node.materialPreset,
node.material,
@@ -37,21 +43,31 @@ export const RoofSegmentRenderer = ({ node }: { node: RoofSegmentNode }) => {
parentNode?.material?.preset,
parentNode?.material?.properties,
parentNode?.material?.texture,
parentNode?.topMaterial,
parentNode?.topMaterialPreset,
parentNode?.edgeMaterial,
parentNode?.edgeMaterialPreset,
parentNode?.wallMaterial,
parentNode?.wallMaterialPreset,
])
const material = debugColors ? roofDebugMaterials : customMaterial || roofMaterials
useEffect(() => {
return () => {
placeholderGeometry.dispose()
}
}, [placeholderGeometry])
return (
<mesh
geometry={placeholderGeometry}
material={material}
position={node.position}
ref={ref}
rotation-y={node.rotation}
visible={node.visible}
{...handlers}
>
{/* RoofSystem will replace this geometry in the next frame */}
<boxGeometry args={[0, 0, 0]} />
</mesh>
/>
)
}
@@ -1,9 +1,9 @@
import { type RoofNode, useRegistry } from '@pascal-app/core'
import { useMemo, useRef } from 'react'
import type * as THREE from 'three'
import { useEffect, useMemo, useRef } from 'react'
import * as THREE from 'three'
import { useNodeEvents } from '../../../hooks/use-node-events'
import { createMaterial, createMaterialFromPresetRef } from '../../../lib/materials'
import useViewer from '../../../store/use-viewer'
import { getRoofMaterialArray } from '../../../systems/roof/roof-materials'
import { NodeRenderer } from '../node-renderer'
import { roofDebugMaterials, roofMaterials } from './roof-materials'
@@ -14,17 +14,41 @@ export const RoofRenderer = ({ node }: { node: RoofNode }) => {
const handlers = useNodeEvents(node, 'roof')
const debugColors = useViewer((s) => s.debugColors)
const placeholderGeometry = useMemo(() => {
const geometry = new THREE.BufferGeometry()
geometry.setAttribute('position', new THREE.Float32BufferAttribute([], 3))
geometry.addGroup(0, 0, 0)
geometry.addGroup(0, 0, 1)
geometry.addGroup(0, 0, 2)
geometry.addGroup(0, 0, 3)
return geometry
}, [])
const customMaterial = useMemo(() => {
const presetMaterial = createMaterialFromPresetRef(node.materialPreset)
if (presetMaterial) return presetMaterial
const mat = node.material
if (!mat) return null
return createMaterial(mat)
}, [node.materialPreset, node.material, node.material?.preset, node.material?.properties, node.material?.texture])
const customMaterial = useMemo(
() => getRoofMaterialArray(node),
[
node.materialPreset,
node.material,
node.material?.preset,
node.material?.properties,
node.material?.texture,
node.topMaterial,
node.topMaterialPreset,
node.edgeMaterial,
node.edgeMaterialPreset,
node.wallMaterial,
node.wallMaterialPreset,
],
)
const material = debugColors ? roofDebugMaterials : customMaterial || roofMaterials
useEffect(() => {
return () => {
placeholderGeometry.dispose()
}
}, [placeholderGeometry])
return (
<group
position={node.position}
@@ -33,9 +57,13 @@ export const RoofRenderer = ({ node }: { node: RoofNode }) => {
visible={node.visible}
{...handlers}
>
<mesh castShadow material={material} name="merged-roof" receiveShadow>
<boxGeometry args={[0, 0, 0]} />
</mesh>
<mesh
castShadow
geometry={placeholderGeometry}
material={material}
name="merged-roof"
receiveShadow
/>
<group name="segments-wrapper" visible={false}>
{(node.children ?? []).map((childId) => (
<NodeRenderer key={childId} nodeId={childId} />
@@ -0,0 +1,46 @@
import {
getEffectiveRoofSurfaceMaterial,
type RoofNode,
type RoofSegmentNode,
} from '@pascal-app/core'
import * as THREE from 'three'
import { createMaterial, createMaterialFromPresetRef } from '../../lib/materials'
export type RoofMaterialArray = [THREE.Material, THREE.Material, THREE.Material, THREE.Material]
function createResolvedMaterial(
material: RoofNode['material'] | RoofSegmentNode['material'] | undefined,
materialPreset: string | undefined,
): THREE.Material | null {
if (materialPreset) {
return createMaterialFromPresetRef(materialPreset)
}
if (material) {
return createMaterial(material)
}
return null
}
export function getRoofMaterialArray(node: RoofNode): RoofMaterialArray | null {
const top = getEffectiveRoofSurfaceMaterial(node, 'top')
const edge = getEffectiveRoofSurfaceMaterial(node, 'edge')
const wall = getEffectiveRoofSurfaceMaterial(node, 'wall')
const topMaterial = createResolvedMaterial(top.material, top.materialPreset)
const edgeMaterial = createResolvedMaterial(edge.material, edge.materialPreset)
const wallMaterial = createResolvedMaterial(wall.material, wall.materialPreset)
if (!(topMaterial || edgeMaterial || wallMaterial)) {
return null
}
return [
edgeMaterial ?? wallMaterial ?? topMaterial ?? new THREE.MeshStandardMaterial(),
wallMaterial ?? edgeMaterial ?? topMaterial ?? new THREE.MeshStandardMaterial(),
wallMaterial ?? edgeMaterial ?? topMaterial ?? new THREE.MeshStandardMaterial(),
topMaterial ?? wallMaterial ?? edgeMaterial ?? new THREE.MeshStandardMaterial(),
]
}