fix(editor): harden editor interactions and WebGPU rendering

Fix editor bug sweep regressions, WebGPU CSG/material crashes, Shift snap bypass behavior, arrow handle drag projection, and the wall preview null guard covered by the Sentry follow-up PRs.
This commit is contained in:
Aymeric Rabot
2026-06-11 13:03:34 -04:00
committed by GitHub
parent 2d2dba5dba
commit aab48e053f
111 changed files with 2211 additions and 955 deletions
+3 -3
View File
@@ -63,7 +63,7 @@ export const columnFloorplanMoveTarget: FloorplanMoveTarget<ColumnNode> = ({ nod
return Math.round(value / step) * step
}
const gridSnapped = resolveCursor(planPoint, { snap }) as WallPlanPoint
// Figma-style alignment layered on the grid snap (Alt bypasses).
// Figma-style alignment layered on the grid snap (Alt bypasses alignment; Shift all snap).
const { point: snapped } = applyFloorplanAlignment(
gridSnapped,
movingFootprintAnchors(
@@ -73,13 +73,13 @@ export const columnFloorplanMoveTarget: FloorplanMoveTarget<ColumnNode> = ({ nod
rotationY,
),
candidates,
{ bypass: modifiers.altKey },
{ bypass: modifiers.altKey || modifiers.shiftKey },
)
const next: [number, number, number] = [snapped[0], originalPosition[1], snapped[1]]
lastPosition = next
const snapKey = `${snapped[0]},${snapped[1]}`
if (snapKey !== lastSnapKey) {
if (!modifiers.shiftKey && snapKey !== lastSnapKey) {
triggerSFX('sfx:grid-snap')
lastSnapKey = snapKey
}
+6 -6
View File
@@ -50,8 +50,8 @@ const snapToGridStep = (value: number) => {
return Math.round(value / step) * step
}
/** 90° steps, matching the GLB item / shelf placement rotation. */
const ROTATION_STEP = Math.PI / 2
/** 45° steps, matching the generic move tool's R/T rotation. */
const ROTATION_STEP = Math.PI / 4
/** Figma-style alignment-snap threshold (meters), matching the other tools. */
const ALIGNMENT_THRESHOLD_M = 0.08
@@ -124,15 +124,15 @@ function MoveColumnTool({ node }: { node: ColumnNode }) {
original: [node.position[0], node.position[2]],
anchor: dragAnchor,
mode: useAbsoluteCursorPlacement ? 'absolute' : 'relative',
snap: snapToGridStep,
snap: event.nativeEvent?.shiftKey === true ? (value) => value : snapToGridStep,
})
dragAnchor = resolved.anchor
let [x, z] = resolved.point
// Figma-style alignment snap on top of grid snap; Alt bypasses. The
// Figma-style alignment snap on top of grid snap; Alt bypasses alignment; Shift all snap. The
// guide connects to the candidate's nearest real anchor (resolver
// tie-break), so the dot always sits on an actual point.
const bypass = event.nativeEvent?.altKey === true
const bypass = event.nativeEvent?.altKey === true || event.nativeEvent?.shiftKey === true
if (!bypass && alignmentCandidates.length > 0) {
const result = resolveAlignment({
moving: movingFootprintAnchors(node, x, z, rotationY),
@@ -151,7 +151,7 @@ function MoveColumnTool({ node }: { node: ColumnNode }) {
applyPreview([x, 0, z])
}
// R / T rotate the dragged column about Y in 90° steps (matches the move
// R / T rotate the dragged column about Y in 45° steps (matches the move
// HUD's "Rotate" hints), committed on drop.
const onKeyDown = (e: KeyboardEvent) => {
if (e.metaKey || e.ctrlKey || e.altKey) return
+12 -3
View File
@@ -87,7 +87,8 @@ const ColumnTool = () => {
rawZ: event.localPosition[2],
gridStep: useEditor.getState().gridSnapStep,
candidates: alignmentCandidates,
bypassAlignment: event.nativeEvent?.altKey === true,
bypassAlignment: event.nativeEvent?.altKey === true || event.nativeEvent?.shiftKey === true,
bypassGrid: event.nativeEvent?.shiftKey === true,
})
useAlignmentGuides.getState().set(guides)
@@ -107,7 +108,10 @@ const ColumnTool = () => {
usePlacementPreview.getState().set({ ...previewNode, position })
const prev = previousSnapRef.current
if (!prev || prev[0] !== position[0] || prev[1] !== position[2]) {
if (
event.nativeEvent?.shiftKey !== true &&
(!prev || prev[0] !== position[0] || prev[1] !== position[2])
) {
triggerSFX('sfx:grid-snap')
previousSnapRef.current = [position[0], position[2]]
}
@@ -116,7 +120,12 @@ const ColumnTool = () => {
const commitAtCursor = (event: FloorPlacementClickTriggerEvent) => {
const position =
lastCursorRef.current ??
getLevelLocalSnappedPosition(activeLevelId, event, useEditor.getState().gridSnapStep)
getLevelLocalSnappedPosition(
activeLevelId,
event,
useEditor.getState().gridSnapStep,
event.nativeEvent?.shiftKey === true,
)
const column = createColumnFromPreset(DEFAULT_COLUMN_PRESET_ID, position)
useScene.getState().createNode(column, activeLevelId)