feat(editor): live floor-stacking, unified handle system, slab-hole editing + interaction polish (#375)
- Live slab-stacking Y previews for all floor-placed kinds (item/shelf/spawn/column/stair) during placement + both move pathways, via a shared core resolver; canonical positions unchanged. - Unified 3D handle system (one drag pipeline + one visual primitive) with forgiving invisible hit-areas on every handle, kept on EDITOR_LAYER so they don't poison the MRT scene pass. - Hover + click-to-edit slab holes in 3D (manual hole -> hole editor; stair/elevator hole -> select owner); generic cross-arrow polygon-move grip; normalized handle interaction colors. - NaN-safe node mutations + non-finite shadow-light bounds guard. - Built on #373 (level-scoped alignment / registry slab tool); #373 owns X/Z alignment, this owns Y floor-stacking.
This commit is contained in:
@@ -1,6 +1,12 @@
|
||||
'use client'
|
||||
|
||||
import { type SpawnNode, useLiveTransforms, useRegistry } from '@pascal-app/core'
|
||||
import {
|
||||
type AnyNodeId,
|
||||
type SpawnNode,
|
||||
useLiveNodeOverrides,
|
||||
useLiveTransforms,
|
||||
useRegistry,
|
||||
} from '@pascal-app/core'
|
||||
import { createDefaultMaterial, useNodeEvents, useViewer } from '@pascal-app/viewer'
|
||||
import { useMemo, useRef } from 'react'
|
||||
import { Color, type Group, Shape } from 'three'
|
||||
@@ -20,6 +26,11 @@ const SPAWN_COLOR = new Color('#22c55e')
|
||||
const SpawnRenderer = ({ node }: { node: SpawnNode }) => {
|
||||
const ref = useRef<Group>(null!)
|
||||
const handlers = useNodeEvents(node, 'spawn')
|
||||
const liveOverride = useLiveNodeOverrides((state) => state.get(node.id as AnyNodeId))
|
||||
const effectiveNode = useMemo(
|
||||
() => (liveOverride ? ({ ...node, ...liveOverride } as SpawnNode) : node),
|
||||
[node, liveOverride],
|
||||
)
|
||||
const liveTransform = useLiveTransforms((state) => state.get(node.id))
|
||||
const walkthroughMode = useViewer((state) => state.walkthroughMode)
|
||||
const shading = useViewer((state) => state.shading)
|
||||
@@ -52,10 +63,10 @@ const SpawnRenderer = ({ node }: { node: SpawnNode }) => {
|
||||
|
||||
return (
|
||||
<group
|
||||
position={liveTransform?.position ?? node.position}
|
||||
position={liveTransform?.position ?? effectiveNode.position}
|
||||
ref={ref}
|
||||
rotation={[0, liveTransform?.rotation ?? node.rotation, 0]}
|
||||
visible={!walkthroughMode}
|
||||
rotation={[0, liveTransform?.rotation ?? effectiveNode.rotation, 0]}
|
||||
visible={!walkthroughMode && effectiveNode.visible !== false}
|
||||
>
|
||||
<mesh position={[0, 0.09, 0]} rotation={[-Math.PI / 2, 0, 0]} {...handlers}>
|
||||
<ringGeometry args={[0.34, 0.48, 48]} />
|
||||
|
||||
Reference in New Issue
Block a user