feat: curved wall and fixes (#236)
* feat: add polygon movement support to editor and implement dedicated move tools for walls, slabs, and ceilings * feat: add uv2 attribute to ceiling and slab geometries for lightmap support * Fix curved wall miters and align roof position sliders * Fix wall measurement label and extension anchors * Fix WebGPU renderer initialization order * fix(editor): scope wall movement to the selected level --------- Co-authored-by: Pascal <open@pascal.app>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { type SlabNode, useRegistry } from '@pascal-app/core'
|
||||
import { useMemo, useRef } from 'react'
|
||||
import { useEffect, useMemo, useRef } from 'react'
|
||||
import * as THREE from 'three'
|
||||
import type { Mesh } from 'three'
|
||||
import { useNodeEvents } from '../../../hooks/use-node-events'
|
||||
import {
|
||||
@@ -17,10 +18,20 @@ export const SlabRenderer = ({ node }: { node: SlabNode }) => {
|
||||
|
||||
const material = useMemo(() => {
|
||||
const presetMaterial = createMaterialFromPresetRef(node.materialPreset)
|
||||
if (presetMaterial) return presetMaterial
|
||||
const mat = node.material
|
||||
if (!mat) return DEFAULT_SLAB_MATERIAL
|
||||
return createMaterial(mat)
|
||||
const sourceMaterial = presetMaterial ?? (node.material ? createMaterial(node.material) : DEFAULT_SLAB_MATERIAL)
|
||||
const slabMaterial = sourceMaterial.clone()
|
||||
|
||||
// Slabs participate in the WebGPU MRT scene pass. Keeping them opaque avoids
|
||||
// pipeline variants that can fail when geometry is regenerated while a
|
||||
// transparent/custom material is attached.
|
||||
slabMaterial.transparent = false
|
||||
slabMaterial.opacity = 1
|
||||
slabMaterial.alphaMap = null
|
||||
slabMaterial.side = THREE.DoubleSide
|
||||
slabMaterial.depthWrite = true
|
||||
slabMaterial.needsUpdate = true
|
||||
|
||||
return slabMaterial
|
||||
}, [
|
||||
node.material,
|
||||
node.material?.preset,
|
||||
@@ -29,6 +40,12 @@ export const SlabRenderer = ({ node }: { node: SlabNode }) => {
|
||||
node.materialPreset,
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
material.dispose()
|
||||
}
|
||||
}, [material])
|
||||
|
||||
return (
|
||||
<mesh
|
||||
castShadow
|
||||
|
||||
@@ -106,11 +106,11 @@ const Viewer: React.FC<ViewerProps> = ({
|
||||
camera={{ position: [50, 50, 50], fov: 50 }}
|
||||
className={`transition-colors duration-700 ${theme === 'dark' ? 'bg-[#1f2433]' : 'bg-[#fafafa]'}`}
|
||||
dpr={[1, 1.5]}
|
||||
gl={(props) => {
|
||||
gl={async (props) => {
|
||||
const renderer = new THREE.WebGPURenderer(props as any)
|
||||
renderer.toneMapping = THREE.ACESFilmicToneMapping
|
||||
renderer.toneMappingExposure = 0.9
|
||||
// renderer.init() // Only use when using <DebugRenderer />
|
||||
await renderer.init()
|
||||
return renderer
|
||||
}}
|
||||
resize={{
|
||||
|
||||
@@ -53,7 +53,6 @@ const PostProcessingPasses = () => {
|
||||
const hasPipelineErrorRef = useRef(false)
|
||||
const retryCountRef = useRef(0)
|
||||
const rebuildTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
const [isInitialized, setIsInitialized] = useState(false)
|
||||
|
||||
// Background color uniform — updated every frame via lerp, read by the TSL pipeline.
|
||||
// Initialised from the current theme so there's no flash on first render.
|
||||
@@ -85,35 +84,6 @@ const PostProcessingPasses = () => {
|
||||
setPipelineVersion((v) => v + 1)
|
||||
}, [])
|
||||
|
||||
// Renderer initialization
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true
|
||||
|
||||
const initRenderer = async () => {
|
||||
try {
|
||||
if (renderer && (renderer as any).init) {
|
||||
await (renderer as any).init()
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
setIsInitialized(true)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[viewer] Failed to initialize renderer for post-processing.', error)
|
||||
if (mounted) {
|
||||
setIsInitialized(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
initRenderer()
|
||||
|
||||
return () => {
|
||||
mounted = false
|
||||
}
|
||||
}, [renderer])
|
||||
|
||||
// Reset retry state when project changes
|
||||
useEffect(() => {
|
||||
// Intentionally touch projectId so the effect reruns on project switches.
|
||||
@@ -141,7 +111,7 @@ const PostProcessingPasses = () => {
|
||||
void projectId
|
||||
void pipelineVersion
|
||||
|
||||
if (!(renderer && scene && camera && isInitialized)) {
|
||||
if (!(renderer && scene && camera)) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -298,7 +268,6 @@ const PostProcessingPasses = () => {
|
||||
scene,
|
||||
camera,
|
||||
hoverHighlightMode,
|
||||
isInitialized,
|
||||
zoneLayers,
|
||||
projectId,
|
||||
pipelineVersion,
|
||||
@@ -310,10 +279,6 @@ const PostProcessingPasses = () => {
|
||||
bgCurrent.current.lerp(bgTarget.current, Math.min(delta, 0.1) * 4)
|
||||
bgUniform.current.value.copy(bgCurrent.current)
|
||||
|
||||
if (!isInitialized) {
|
||||
return
|
||||
}
|
||||
|
||||
if (hasPipelineErrorRef.current || !renderPipelineRef.current) {
|
||||
try {
|
||||
if ((renderer as any).setClearAlpha) {
|
||||
|
||||
Reference in New Issue
Block a user