fix: improve ceiling handle feedback and WebGPU placeholders
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
'use client'
|
||||
|
||||
import { type CeilingNode, resolveLevelId, useLiveNodeOverrides, useScene } from '@pascal-app/core'
|
||||
import { PolygonEditor } from '@pascal-app/editor'
|
||||
import { PolygonEditor, triggerSFX } from '@pascal-app/editor'
|
||||
import { useViewer } from '@pascal-app/viewer'
|
||||
import { useCallback, useEffect } from 'react'
|
||||
import { useCallback, useEffect, useRef } from 'react'
|
||||
|
||||
/**
|
||||
* Phase 5 Stage D — ceiling boundary editor (registry-driven).
|
||||
@@ -23,6 +23,8 @@ export const CeilingBoundaryEditor: React.FC<{ ceilingId: CeilingNode['id'] }> =
|
||||
const updateNode = useScene((s) => s.updateNode)
|
||||
const markDirty = useScene((s) => s.markDirty)
|
||||
const setSelection = useViewer((s) => s.setSelection)
|
||||
const setHoveredId = useViewer((s) => s.setHoveredId)
|
||||
const ownsCeilingHoverRef = useRef(false)
|
||||
|
||||
const ceiling = ceilingNode?.type === 'ceiling' ? (ceilingNode as CeilingNode) : null
|
||||
|
||||
@@ -48,10 +50,43 @@ export const CeilingBoundaryEditor: React.FC<{ ceilingId: CeilingNode['id'] }> =
|
||||
[ceilingId, markDirty],
|
||||
)
|
||||
|
||||
const setCeilingHandleHover = useCallback(
|
||||
(active: boolean) => {
|
||||
if (active) {
|
||||
ownsCeilingHoverRef.current = true
|
||||
setHoveredId(ceilingId)
|
||||
return
|
||||
}
|
||||
if (ownsCeilingHoverRef.current && useViewer.getState().hoveredId === ceilingId) {
|
||||
setHoveredId(null)
|
||||
}
|
||||
ownsCeilingHoverRef.current = false
|
||||
},
|
||||
[ceilingId, setHoveredId],
|
||||
)
|
||||
|
||||
const handleHandleHoverChange = useCallback(
|
||||
(index: number | null) => {
|
||||
setCeilingHandleHover(index !== null)
|
||||
},
|
||||
[setCeilingHandleHover],
|
||||
)
|
||||
|
||||
const handleDragStateChange = useCallback(
|
||||
(isDragging: boolean) => {
|
||||
setCeilingHandleHover(isDragging)
|
||||
},
|
||||
[setCeilingHandleHover],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
useLiveNodeOverrides.getState().clear(ceilingId)
|
||||
useScene.getState().markDirty(ceilingId)
|
||||
if (ownsCeilingHoverRef.current && useViewer.getState().hoveredId === ceilingId) {
|
||||
useViewer.getState().setHoveredId(null)
|
||||
}
|
||||
ownsCeilingHoverRef.current = false
|
||||
}
|
||||
}, [ceilingId])
|
||||
|
||||
@@ -61,10 +96,17 @@ export const CeilingBoundaryEditor: React.FC<{ ceilingId: CeilingNode['id'] }> =
|
||||
<PolygonEditor
|
||||
allowEdgeMove
|
||||
color="#d4d4d4"
|
||||
highlightConnectedHandles
|
||||
levelId={resolveLevelId(ceiling, useScene.getState().nodes)}
|
||||
minVertices={3}
|
||||
onDragStateChange={handleDragStateChange}
|
||||
onDragCommit={() => triggerSFX('sfx:item-place')}
|
||||
onDragStart={() => triggerSFX('sfx:item-pick')}
|
||||
onEdgeHoverChange={handleHandleHoverChange}
|
||||
onMidpointHoverChange={handleHandleHoverChange}
|
||||
onPolygonChange={handlePolygonChange}
|
||||
onPolygonPreview={handlePolygonPreview}
|
||||
onVertexHoverChange={handleHandleHoverChange}
|
||||
polygon={ceiling.polygon}
|
||||
surfaceHeight={ceiling.height ?? 2.5}
|
||||
/>
|
||||
|
||||
@@ -13,13 +13,17 @@ import { BufferGeometry, Float32BufferAttribute } from 'three'
|
||||
* (count 0) makes three.js create no GPU buffer for it, so vertex buffer slot 0
|
||||
* is never bound and WebGPU rejects the draw with "Vertex buffer slot 0 … was
|
||||
* not set", which poisons the whole command encoder (cascading into "Invalid
|
||||
* CommandBuffer" on every queue submit). Three real vertices give it a bound
|
||||
* buffer; the `groupCount` count-0 groups keep nothing drawn while matching the
|
||||
* mesh's material-array length so raycasts / BVH never index past the materials.
|
||||
* CommandBuffer" on every queue submit). The zero normals and UVs keep lit
|
||||
* node-material pipelines from compiling additional required-but-unbound
|
||||
* vertex buffers. Three real vertices give it bound buffers; the `groupCount`
|
||||
* count-0 groups keep nothing drawn while matching the mesh's material-array
|
||||
* length so raycasts / BVH never index past the materials.
|
||||
*/
|
||||
export function createPlaceholderGeometry(groupCount = 0): BufferGeometry {
|
||||
const geometry = new BufferGeometry()
|
||||
geometry.setAttribute('position', new Float32BufferAttribute(new Float32Array(9), 3))
|
||||
geometry.setAttribute('normal', new Float32BufferAttribute(new Float32Array(9), 3))
|
||||
geometry.setAttribute('uv', new Float32BufferAttribute(new Float32Array(6), 2))
|
||||
for (let group = 0; group < groupCount; group++) {
|
||||
geometry.addGroup(0, 0, group)
|
||||
}
|
||||
|
||||
@@ -15,8 +15,15 @@ import {
|
||||
useNodeEvents,
|
||||
useViewer,
|
||||
} from '@pascal-app/viewer'
|
||||
import { useMemo, useRef } from 'react'
|
||||
import { BufferGeometry, Float32BufferAttribute, type Group, Path, Shape } from 'three'
|
||||
import { useEffect, useMemo, useRef } from 'react'
|
||||
import {
|
||||
BufferGeometry,
|
||||
Float32BufferAttribute,
|
||||
type Group,
|
||||
Path,
|
||||
Shape,
|
||||
ShapeGeometry,
|
||||
} from 'three'
|
||||
import { MeshLambertNodeMaterial } from 'three/webgpu'
|
||||
|
||||
const Y_OFFSET = 0.01
|
||||
@@ -134,6 +141,13 @@ export const SiteRenderer = ({ node }: { node: SiteNode }) => {
|
||||
if (!polygonPoints || polygonPoints.length < 2) return null
|
||||
return createBoundaryLineGeometry(polygonPoints)
|
||||
}, [polygonPoints])
|
||||
useEffect(() => () => lineGeometry?.dispose(), [lineGeometry])
|
||||
|
||||
const groundGeometry = useMemo(() => {
|
||||
if (!groundShape) return null
|
||||
return new ShapeGeometry(groundShape)
|
||||
}, [groundShape])
|
||||
useEffect(() => () => groundGeometry?.dispose(), [groundGeometry])
|
||||
|
||||
const handlers = useNodeEvents(node, 'site')
|
||||
|
||||
@@ -149,15 +163,14 @@ export const SiteRenderer = ({ node }: { node: SiteNode }) => {
|
||||
))}
|
||||
|
||||
{/* Ground fill: site polygon with slab holes, occludes below-grade geometry */}
|
||||
{groundShape && (
|
||||
{groundGeometry && (
|
||||
<mesh
|
||||
geometry={groundGeometry}
|
||||
material={groundMaterial}
|
||||
position={[0, -0.05, 0]}
|
||||
receiveShadow
|
||||
rotation={[-Math.PI / 2, 0, 0]}
|
||||
>
|
||||
<shapeGeometry args={[groundShape]} />
|
||||
</mesh>
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Simple boundary line */}
|
||||
|
||||
Reference in New Issue
Block a user