Fix shelf cursor tracking + bigger snap step; tint registry spawn red
Three small follow-ups after the first user-visible spike test. shelf cursor stuck at origin: The placeholder cursor is a translucent slab inside a <group ref>. The tool was calling setCursor(state) on every grid:move, which triggered a React re-render. R3F re-applies props on each render, and since the <group> had no `position` prop, the implicit default [0,0,0] clobbered the imperative `position.set` from the previous tick. Result: cursor stuck at level origin instead of following the mouse. Fix: drop the unused useState entirely. Pure imperative position updates via the ref. No re-renders, no clobbering. (The legacy spawn tool gets away with the same pattern because CursorSphere buffers its position prop differently — but for the spike, the simpler model is fine.) shelf snap step: Was 0.1 (10cm) — much finer than the editor's default 0.5 grid. Bumped to 0.5 (matches the toolbar grid setting and the legacy half-meter snap pattern used by spawn/column). registry-driven spawn renderer paints red: Temporary verification marker. With NEXT_PUBLIC_USE_REGISTRY_FOR_ SPAWN=1, spawns rendered via the new path now appear in #ef4444 red. Legacy renderer stays in #22c55e green. Easy visual check for "which dispatch path is this spawn on?" Reverted in the PR that signs off spawn parity (alongside legacy file deletion). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
88546d26f0
commit
ac71c1a83b
@@ -3,42 +3,43 @@
|
|||||||
import {
|
import {
|
||||||
emitter,
|
emitter,
|
||||||
type GridEvent,
|
type GridEvent,
|
||||||
ShelfNode,
|
|
||||||
sceneRegistry,
|
sceneRegistry,
|
||||||
|
ShelfNode,
|
||||||
snapPointToGrid,
|
snapPointToGrid,
|
||||||
useScene,
|
useScene,
|
||||||
} from '@pascal-app/core'
|
} from '@pascal-app/core'
|
||||||
import { useViewer } from '@pascal-app/viewer'
|
import { useViewer } from '@pascal-app/viewer'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef } from 'react'
|
||||||
import { type Group, Vector3 } from 'three'
|
import { type Group, Vector3 } from 'three'
|
||||||
|
|
||||||
const worldVector = new Vector3()
|
const worldVector = new Vector3()
|
||||||
|
const GRID_STEP = 0.5 // match the editor's default placement grid
|
||||||
|
|
||||||
function getLevelLocalPosition(levelId: string, event: GridEvent): [number, number, number] {
|
function getLevelLocalPosition(levelId: string, event: GridEvent): [number, number, number] {
|
||||||
const levelObject = sceneRegistry.nodes.get(levelId)
|
const levelObject = sceneRegistry.nodes.get(levelId)
|
||||||
if (!levelObject) {
|
if (!levelObject) {
|
||||||
const [sx, sz] = snapPointToGrid([event.localPosition[0], event.localPosition[2]], 0.1)
|
const [sx, sz] = snapPointToGrid([event.localPosition[0], event.localPosition[2]], GRID_STEP)
|
||||||
return [sx, event.localPosition[1], sz]
|
return [sx, event.localPosition[1], sz]
|
||||||
}
|
}
|
||||||
worldVector.set(event.position[0], event.position[1], event.position[2])
|
worldVector.set(event.position[0], event.position[1], event.position[2])
|
||||||
levelObject.updateWorldMatrix(true, false)
|
levelObject.updateWorldMatrix(true, false)
|
||||||
levelObject.worldToLocal(worldVector)
|
levelObject.worldToLocal(worldVector)
|
||||||
const [sx, sz] = snapPointToGrid([worldVector.x, worldVector.z], 0.1)
|
const [sx, sz] = snapPointToGrid([worldVector.x, worldVector.z], GRID_STEP)
|
||||||
return [sx, worldVector.y, sz]
|
return [sx, worldVector.y, sz]
|
||||||
}
|
}
|
||||||
|
|
||||||
const ShelfTool = () => {
|
const ShelfTool = () => {
|
||||||
const activeLevelId = useViewer((state) => state.selection.levelId)
|
const activeLevelId = useViewer((state) => state.selection.levelId)
|
||||||
const [, setCursor] = useState<[number, number, number] | null>(null)
|
|
||||||
const cursorRef = useRef<Group>(null)
|
const cursorRef = useRef<Group>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!activeLevelId) return
|
if (!activeLevelId) return
|
||||||
|
|
||||||
const onGridMove = (event: GridEvent) => {
|
const onGridMove = (event: GridEvent) => {
|
||||||
const [sx, sz] = snapPointToGrid([event.localPosition[0], event.localPosition[2]], 0.1)
|
// Imperative position update — no React state, so the component
|
||||||
const next: [number, number, number] = [sx, event.localPosition[1], sz]
|
// doesn't re-render. R3F-applied props would otherwise clobber the
|
||||||
setCursor(next)
|
// imperative `position.set` on the next render.
|
||||||
|
const next = getLevelLocalPosition(activeLevelId, event)
|
||||||
cursorRef.current?.position.set(next[0], next[1], next[2])
|
cursorRef.current?.position.set(next[0], next[1], next[2])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,6 +65,9 @@ const ShelfTool = () => {
|
|||||||
|
|
||||||
if (!activeLevelId) return null
|
if (!activeLevelId) return null
|
||||||
|
|
||||||
|
// Cursor preview — a translucent shelf-shaped slab. No `position` prop on
|
||||||
|
// the group; we move it imperatively via the ref so React re-renders don't
|
||||||
|
// reset it to the origin.
|
||||||
return (
|
return (
|
||||||
<group ref={cursorRef}>
|
<group ref={cursorRef}>
|
||||||
<mesh position={[0, 0.9, 0]}>
|
<mesh position={[0, 0.9, 0]}>
|
||||||
|
|||||||
@@ -5,7 +5,13 @@ import { useNodeEvents, useViewer } from '@pascal-app/viewer'
|
|||||||
import { useMemo, useRef } from 'react'
|
import { useMemo, useRef } from 'react'
|
||||||
import { Color, type Group, Shape } from 'three'
|
import { Color, type Group, Shape } from 'three'
|
||||||
|
|
||||||
const SPAWN_COLOR = new Color('#22c55e')
|
// TEMPORARY (Phase 2 verification): the registry-driven renderer paints
|
||||||
|
// spawns RED so you can visually tell which dispatch path is live. The
|
||||||
|
// legacy renderer in @pascal-app/viewer is still green. Revert this to
|
||||||
|
// '#22c55e' once the registry path is signed off for parity. Tracked by
|
||||||
|
// the NEXT_PUBLIC_USE_REGISTRY_FOR_SPAWN flag — if a spawn renders red
|
||||||
|
// you're on the new path; green = legacy.
|
||||||
|
const SPAWN_COLOR = new Color('#ef4444')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registry-driven spawn renderer. Behaviorally identical to the legacy
|
* Registry-driven spawn renderer. Behaviorally identical to the legacy
|
||||||
|
|||||||
Reference in New Issue
Block a user