From 5d61e60f95696d290e02f5d6160bc1b0aff07545 Mon Sep 17 00:00:00 2001 From: wass08 Date: Wed, 18 Feb 2026 14:00:28 +0900 Subject: [PATCH] free mode for slabs and ceiling --- .../components/tools/ceiling/ceiling-tool.tsx | 29 ++++++++++++------- .../components/tools/slab/slab-tool.tsx | 29 ++++++++++++------- .../components/ui/helpers/ceiling-helper.tsx | 4 +++ .../components/ui/helpers/slab-helper.tsx | 4 +++ 4 files changed, 44 insertions(+), 22 deletions(-) diff --git a/apps/editor/components/tools/ceiling/ceiling-tool.tsx b/apps/editor/components/tools/ceiling/ceiling-tool.tsx index bab38edc..3a56a74b 100644 --- a/apps/editor/components/tools/ceiling/ceiling-tool.tsx +++ b/apps/editor/components/tools/ceiling/ceiling-tool.tsx @@ -73,8 +73,10 @@ export const CeilingTool: React.FC = () => { const [points, setPoints] = useState>([]) const [cursorPosition, setCursorPosition] = useState<[number, number]>([0, 0]) + const [snappedCursorPosition, setSnappedCursorPosition] = useState<[number, number]>([0, 0]) const [levelY, setLevelY] = useState(0) const previousSnappedPointRef = useRef<[number, number] | null>(null) + const shiftPressed = useRef(false) // Update cursor position and lines on grid move useEffect(() => { @@ -93,9 +95,10 @@ export const CeilingTool: React.FC = () => { const ceilingY = event.position[1] + CEILING_HEIGHT const gridY = event.position[1] + GRID_OFFSET - // Calculate snapped display position + // Calculate snapped display position (bypass snap when Shift is held) const lastPoint = points[points.length - 1] - const displayPoint = lastPoint ? calculateSnapPoint(lastPoint, gridPosition) : gridPosition + const displayPoint = (shiftPressed.current || !lastPoint) ? gridPosition : calculateSnapPoint(lastPoint, gridPosition) + setSnappedCursorPosition(displayPoint) // Play snap sound when the snapped position actually changes (only when drawing) if (points.length > 0 && previousSnappedPointRef.current && @@ -111,9 +114,8 @@ export const CeilingTool: React.FC = () => { const onGridClick = (_event: GridEvent) => { if (!currentLevelId) return - // Calculate snapped click point - const lastPoint = points[points.length - 1] - const clickPoint = lastPoint ? calculateSnapPoint(lastPoint, cursorPosition) : cursorPosition + // Use the last displayed snapped position (respects Shift state from onGridMove) + const clickPoint = previousSnappedPointRef.current ?? cursorPosition // Check if clicking on the first point to close the shape const firstPoint = points[0] @@ -148,12 +150,19 @@ export const CeilingTool: React.FC = () => { setPoints([]) } + const onKeyDown = (e: KeyboardEvent) => { if (e.key === 'Shift') shiftPressed.current = true } + const onKeyUp = (e: KeyboardEvent) => { if (e.key === 'Shift') shiftPressed.current = false } + document.addEventListener('keydown', onKeyDown) + document.addEventListener('keyup', onKeyUp) + emitter.on('grid:move', onGridMove) emitter.on('grid:click', onGridClick) emitter.on('grid:double-click', onGridDoubleClick) emitter.on('tool:cancel', onCancel) return () => { + document.removeEventListener('keydown', onKeyDown) + document.removeEventListener('keyup', onKeyUp) emitter.off('grid:move', onGridMove) emitter.off('grid:click', onGridClick) emitter.off('grid:double-click', onGridDoubleClick) @@ -172,8 +181,7 @@ export const CeilingTool: React.FC = () => { } const ceilingY = levelY + CEILING_HEIGHT - const lastPoint = points[points.length - 1] - const snappedCursor = lastPoint ? calculateSnapPoint(lastPoint, cursorPosition) : cursorPosition + const snappedCursor = snappedCursorPosition // Build main line points const linePoints: Vector3[] = points.map(([x, z]) => new Vector3(x, ceilingY, z)) @@ -201,14 +209,13 @@ export const CeilingTool: React.FC = () => { } else { closingLineRef.current.visible = false } - }, [points, cursorPosition, levelY]) + }, [points, snappedCursorPosition, levelY]) // Create preview shape when we have 3+ points const previewShape = useMemo(() => { if (points.length < 3) return null - const lastPoint = points[points.length - 1] - const snappedCursor = lastPoint ? calculateSnapPoint(lastPoint, cursorPosition) : cursorPosition + const snappedCursor = snappedCursorPosition const allPoints = [...points, snappedCursor] @@ -230,7 +237,7 @@ export const CeilingTool: React.FC = () => { shape.closePath() return shape - }, [points, cursorPosition]) + }, [points, snappedCursorPosition]) return ( diff --git a/apps/editor/components/tools/slab/slab-tool.tsx b/apps/editor/components/tools/slab/slab-tool.tsx index 59739d2e..facdc8de 100644 --- a/apps/editor/components/tools/slab/slab-tool.tsx +++ b/apps/editor/components/tools/slab/slab-tool.tsx @@ -71,8 +71,10 @@ export const SlabTool: React.FC = () => { const [points, setPoints] = useState>([]) const [cursorPosition, setCursorPosition] = useState<[number, number]>([0, 0]) + const [snappedCursorPosition, setSnappedCursorPosition] = useState<[number, number]>([0, 0]) const [levelY, setLevelY] = useState(0) const previousSnappedPointRef = useRef<[number, number] | null>(null) + const shiftPressed = useRef(false) // Update cursor position and lines on grid move useEffect(() => { @@ -88,9 +90,10 @@ export const SlabTool: React.FC = () => { setCursorPosition(gridPosition) setLevelY(event.position[1]) - // Calculate snapped display position + // Calculate snapped display position (bypass snap when Shift is held) const lastPoint = points[points.length - 1] - const displayPoint = lastPoint ? calculateSnapPoint(lastPoint, gridPosition) : gridPosition + const displayPoint = (shiftPressed.current || !lastPoint) ? gridPosition : calculateSnapPoint(lastPoint, gridPosition) + setSnappedCursorPosition(displayPoint) // Play snap sound when the snapped position actually changes (only when drawing) if (points.length > 0 && previousSnappedPointRef.current && @@ -105,9 +108,8 @@ export const SlabTool: React.FC = () => { const onGridClick = (_event: GridEvent) => { if (!currentLevelId) return - // Calculate snapped click point - const lastPoint = points[points.length - 1] - const clickPoint = lastPoint ? calculateSnapPoint(lastPoint, cursorPosition) : cursorPosition + // Use the last displayed snapped position (respects Shift state from onGridMove) + const clickPoint = previousSnappedPointRef.current ?? cursorPosition // Check if clicking on the first point to close the shape const firstPoint = points[0] @@ -142,12 +144,19 @@ export const SlabTool: React.FC = () => { setPoints([]) } + const onKeyDown = (e: KeyboardEvent) => { if (e.key === 'Shift') shiftPressed.current = true } + const onKeyUp = (e: KeyboardEvent) => { if (e.key === 'Shift') shiftPressed.current = false } + document.addEventListener('keydown', onKeyDown) + document.addEventListener('keyup', onKeyUp) + emitter.on('grid:move', onGridMove) emitter.on('grid:click', onGridClick) emitter.on('grid:double-click', onGridDoubleClick) emitter.on('tool:cancel', onCancel) return () => { + document.removeEventListener('keydown', onKeyDown) + document.removeEventListener('keyup', onKeyUp) emitter.off('grid:move', onGridMove) emitter.off('grid:click', onGridClick) emitter.off('grid:double-click', onGridDoubleClick) @@ -166,8 +175,7 @@ export const SlabTool: React.FC = () => { } const y = levelY + Y_OFFSET - const lastPoint = points[points.length - 1] - const snappedCursor = lastPoint ? calculateSnapPoint(lastPoint, cursorPosition) : cursorPosition + const snappedCursor = snappedCursorPosition // Build main line points const linePoints: Vector3[] = points.map(([x, z]) => new Vector3(x, y, z)) @@ -195,14 +203,13 @@ export const SlabTool: React.FC = () => { } else { closingLineRef.current.visible = false } - }, [points, cursorPosition, levelY]) + }, [points, snappedCursorPosition, levelY]) // Create preview shape when we have 3+ points const previewShape = useMemo(() => { if (points.length < 3) return null - const lastPoint = points[points.length - 1] - const snappedCursor = lastPoint ? calculateSnapPoint(lastPoint, cursorPosition) : cursorPosition + const snappedCursor = snappedCursorPosition const allPoints = [...points, snappedCursor] @@ -224,7 +231,7 @@ export const SlabTool: React.FC = () => { shape.closePath() return shape - }, [points, cursorPosition]) + }, [points, snappedCursorPosition]) return ( diff --git a/apps/editor/components/ui/helpers/ceiling-helper.tsx b/apps/editor/components/ui/helpers/ceiling-helper.tsx index 8a93ae05..e961dd03 100644 --- a/apps/editor/components/ui/helpers/ceiling-helper.tsx +++ b/apps/editor/components/ui/helpers/ceiling-helper.tsx @@ -1,6 +1,10 @@ export function CeilingHelper() { return (
+
+ Shift + Allow non-45° angles +
Esc Cancel diff --git a/apps/editor/components/ui/helpers/slab-helper.tsx b/apps/editor/components/ui/helpers/slab-helper.tsx index 135616ca..f501b732 100644 --- a/apps/editor/components/ui/helpers/slab-helper.tsx +++ b/apps/editor/components/ui/helpers/slab-helper.tsx @@ -1,6 +1,10 @@ export function SlabHelper() { return (
+
+ Shift + Allow non-45° angles +
Esc Cancel