free mode for slabs and ceiling
This commit is contained in:
@@ -73,8 +73,10 @@ export const CeilingTool: React.FC = () => {
|
|||||||
|
|
||||||
const [points, setPoints] = useState<Array<[number, number]>>([])
|
const [points, setPoints] = useState<Array<[number, number]>>([])
|
||||||
const [cursorPosition, setCursorPosition] = useState<[number, number]>([0, 0])
|
const [cursorPosition, setCursorPosition] = useState<[number, number]>([0, 0])
|
||||||
|
const [snappedCursorPosition, setSnappedCursorPosition] = useState<[number, number]>([0, 0])
|
||||||
const [levelY, setLevelY] = useState(0)
|
const [levelY, setLevelY] = useState(0)
|
||||||
const previousSnappedPointRef = useRef<[number, number] | null>(null)
|
const previousSnappedPointRef = useRef<[number, number] | null>(null)
|
||||||
|
const shiftPressed = useRef(false)
|
||||||
|
|
||||||
// Update cursor position and lines on grid move
|
// Update cursor position and lines on grid move
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -93,9 +95,10 @@ export const CeilingTool: React.FC = () => {
|
|||||||
const ceilingY = event.position[1] + CEILING_HEIGHT
|
const ceilingY = event.position[1] + CEILING_HEIGHT
|
||||||
const gridY = event.position[1] + GRID_OFFSET
|
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 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)
|
// Play snap sound when the snapped position actually changes (only when drawing)
|
||||||
if (points.length > 0 && previousSnappedPointRef.current &&
|
if (points.length > 0 && previousSnappedPointRef.current &&
|
||||||
@@ -111,9 +114,8 @@ export const CeilingTool: React.FC = () => {
|
|||||||
const onGridClick = (_event: GridEvent) => {
|
const onGridClick = (_event: GridEvent) => {
|
||||||
if (!currentLevelId) return
|
if (!currentLevelId) return
|
||||||
|
|
||||||
// Calculate snapped click point
|
// Use the last displayed snapped position (respects Shift state from onGridMove)
|
||||||
const lastPoint = points[points.length - 1]
|
const clickPoint = previousSnappedPointRef.current ?? cursorPosition
|
||||||
const clickPoint = lastPoint ? calculateSnapPoint(lastPoint, cursorPosition) : cursorPosition
|
|
||||||
|
|
||||||
// Check if clicking on the first point to close the shape
|
// Check if clicking on the first point to close the shape
|
||||||
const firstPoint = points[0]
|
const firstPoint = points[0]
|
||||||
@@ -148,12 +150,19 @@ export const CeilingTool: React.FC = () => {
|
|||||||
setPoints([])
|
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:move', onGridMove)
|
||||||
emitter.on('grid:click', onGridClick)
|
emitter.on('grid:click', onGridClick)
|
||||||
emitter.on('grid:double-click', onGridDoubleClick)
|
emitter.on('grid:double-click', onGridDoubleClick)
|
||||||
emitter.on('tool:cancel', onCancel)
|
emitter.on('tool:cancel', onCancel)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
document.removeEventListener('keydown', onKeyDown)
|
||||||
|
document.removeEventListener('keyup', onKeyUp)
|
||||||
emitter.off('grid:move', onGridMove)
|
emitter.off('grid:move', onGridMove)
|
||||||
emitter.off('grid:click', onGridClick)
|
emitter.off('grid:click', onGridClick)
|
||||||
emitter.off('grid:double-click', onGridDoubleClick)
|
emitter.off('grid:double-click', onGridDoubleClick)
|
||||||
@@ -172,8 +181,7 @@ export const CeilingTool: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ceilingY = levelY + CEILING_HEIGHT
|
const ceilingY = levelY + CEILING_HEIGHT
|
||||||
const lastPoint = points[points.length - 1]
|
const snappedCursor = snappedCursorPosition
|
||||||
const snappedCursor = lastPoint ? calculateSnapPoint(lastPoint, cursorPosition) : cursorPosition
|
|
||||||
|
|
||||||
// Build main line points
|
// Build main line points
|
||||||
const linePoints: Vector3[] = points.map(([x, z]) => new Vector3(x, ceilingY, z))
|
const linePoints: Vector3[] = points.map(([x, z]) => new Vector3(x, ceilingY, z))
|
||||||
@@ -201,14 +209,13 @@ export const CeilingTool: React.FC = () => {
|
|||||||
} else {
|
} else {
|
||||||
closingLineRef.current.visible = false
|
closingLineRef.current.visible = false
|
||||||
}
|
}
|
||||||
}, [points, cursorPosition, levelY])
|
}, [points, snappedCursorPosition, levelY])
|
||||||
|
|
||||||
// Create preview shape when we have 3+ points
|
// Create preview shape when we have 3+ points
|
||||||
const previewShape = useMemo(() => {
|
const previewShape = useMemo(() => {
|
||||||
if (points.length < 3) return null
|
if (points.length < 3) return null
|
||||||
|
|
||||||
const lastPoint = points[points.length - 1]
|
const snappedCursor = snappedCursorPosition
|
||||||
const snappedCursor = lastPoint ? calculateSnapPoint(lastPoint, cursorPosition) : cursorPosition
|
|
||||||
|
|
||||||
const allPoints = [...points, snappedCursor]
|
const allPoints = [...points, snappedCursor]
|
||||||
|
|
||||||
@@ -230,7 +237,7 @@ export const CeilingTool: React.FC = () => {
|
|||||||
shape.closePath()
|
shape.closePath()
|
||||||
|
|
||||||
return shape
|
return shape
|
||||||
}, [points, cursorPosition])
|
}, [points, snappedCursorPosition])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group>
|
<group>
|
||||||
|
|||||||
@@ -71,8 +71,10 @@ export const SlabTool: React.FC = () => {
|
|||||||
|
|
||||||
const [points, setPoints] = useState<Array<[number, number]>>([])
|
const [points, setPoints] = useState<Array<[number, number]>>([])
|
||||||
const [cursorPosition, setCursorPosition] = useState<[number, number]>([0, 0])
|
const [cursorPosition, setCursorPosition] = useState<[number, number]>([0, 0])
|
||||||
|
const [snappedCursorPosition, setSnappedCursorPosition] = useState<[number, number]>([0, 0])
|
||||||
const [levelY, setLevelY] = useState(0)
|
const [levelY, setLevelY] = useState(0)
|
||||||
const previousSnappedPointRef = useRef<[number, number] | null>(null)
|
const previousSnappedPointRef = useRef<[number, number] | null>(null)
|
||||||
|
const shiftPressed = useRef(false)
|
||||||
|
|
||||||
// Update cursor position and lines on grid move
|
// Update cursor position and lines on grid move
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -88,9 +90,10 @@ export const SlabTool: React.FC = () => {
|
|||||||
setCursorPosition(gridPosition)
|
setCursorPosition(gridPosition)
|
||||||
setLevelY(event.position[1])
|
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 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)
|
// Play snap sound when the snapped position actually changes (only when drawing)
|
||||||
if (points.length > 0 && previousSnappedPointRef.current &&
|
if (points.length > 0 && previousSnappedPointRef.current &&
|
||||||
@@ -105,9 +108,8 @@ export const SlabTool: React.FC = () => {
|
|||||||
const onGridClick = (_event: GridEvent) => {
|
const onGridClick = (_event: GridEvent) => {
|
||||||
if (!currentLevelId) return
|
if (!currentLevelId) return
|
||||||
|
|
||||||
// Calculate snapped click point
|
// Use the last displayed snapped position (respects Shift state from onGridMove)
|
||||||
const lastPoint = points[points.length - 1]
|
const clickPoint = previousSnappedPointRef.current ?? cursorPosition
|
||||||
const clickPoint = lastPoint ? calculateSnapPoint(lastPoint, cursorPosition) : cursorPosition
|
|
||||||
|
|
||||||
// Check if clicking on the first point to close the shape
|
// Check if clicking on the first point to close the shape
|
||||||
const firstPoint = points[0]
|
const firstPoint = points[0]
|
||||||
@@ -142,12 +144,19 @@ export const SlabTool: React.FC = () => {
|
|||||||
setPoints([])
|
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:move', onGridMove)
|
||||||
emitter.on('grid:click', onGridClick)
|
emitter.on('grid:click', onGridClick)
|
||||||
emitter.on('grid:double-click', onGridDoubleClick)
|
emitter.on('grid:double-click', onGridDoubleClick)
|
||||||
emitter.on('tool:cancel', onCancel)
|
emitter.on('tool:cancel', onCancel)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
document.removeEventListener('keydown', onKeyDown)
|
||||||
|
document.removeEventListener('keyup', onKeyUp)
|
||||||
emitter.off('grid:move', onGridMove)
|
emitter.off('grid:move', onGridMove)
|
||||||
emitter.off('grid:click', onGridClick)
|
emitter.off('grid:click', onGridClick)
|
||||||
emitter.off('grid:double-click', onGridDoubleClick)
|
emitter.off('grid:double-click', onGridDoubleClick)
|
||||||
@@ -166,8 +175,7 @@ export const SlabTool: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const y = levelY + Y_OFFSET
|
const y = levelY + Y_OFFSET
|
||||||
const lastPoint = points[points.length - 1]
|
const snappedCursor = snappedCursorPosition
|
||||||
const snappedCursor = lastPoint ? calculateSnapPoint(lastPoint, cursorPosition) : cursorPosition
|
|
||||||
|
|
||||||
// Build main line points
|
// Build main line points
|
||||||
const linePoints: Vector3[] = points.map(([x, z]) => new Vector3(x, y, z))
|
const linePoints: Vector3[] = points.map(([x, z]) => new Vector3(x, y, z))
|
||||||
@@ -195,14 +203,13 @@ export const SlabTool: React.FC = () => {
|
|||||||
} else {
|
} else {
|
||||||
closingLineRef.current.visible = false
|
closingLineRef.current.visible = false
|
||||||
}
|
}
|
||||||
}, [points, cursorPosition, levelY])
|
}, [points, snappedCursorPosition, levelY])
|
||||||
|
|
||||||
// Create preview shape when we have 3+ points
|
// Create preview shape when we have 3+ points
|
||||||
const previewShape = useMemo(() => {
|
const previewShape = useMemo(() => {
|
||||||
if (points.length < 3) return null
|
if (points.length < 3) return null
|
||||||
|
|
||||||
const lastPoint = points[points.length - 1]
|
const snappedCursor = snappedCursorPosition
|
||||||
const snappedCursor = lastPoint ? calculateSnapPoint(lastPoint, cursorPosition) : cursorPosition
|
|
||||||
|
|
||||||
const allPoints = [...points, snappedCursor]
|
const allPoints = [...points, snappedCursor]
|
||||||
|
|
||||||
@@ -224,7 +231,7 @@ export const SlabTool: React.FC = () => {
|
|||||||
shape.closePath()
|
shape.closePath()
|
||||||
|
|
||||||
return shape
|
return shape
|
||||||
}, [points, cursorPosition])
|
}, [points, snappedCursorPosition])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group>
|
<group>
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
export function CeilingHelper() {
|
export function CeilingHelper() {
|
||||||
return (
|
return (
|
||||||
<div className="pointer-events-none fixed right-4 top-1/2 -translate-y-1/2 z-40 flex flex-col gap-2 rounded-lg border border-border bg-background/95 px-4 py-3 shadow-lg backdrop-blur-md">
|
<div className="pointer-events-none fixed right-4 top-1/2 -translate-y-1/2 z-40 flex flex-col gap-2 rounded-lg border border-border bg-background/95 px-4 py-3 shadow-lg backdrop-blur-md">
|
||||||
|
<div className="flex items-center gap-2 text-sm">
|
||||||
|
<kbd className="rounded bg-muted px-2 py-1 text-xs font-medium">Shift</kbd>
|
||||||
|
<span className="text-muted-foreground">Allow non-45° angles</span>
|
||||||
|
</div>
|
||||||
<div className="flex items-center gap-2 text-sm">
|
<div className="flex items-center gap-2 text-sm">
|
||||||
<kbd className="rounded bg-muted px-2 py-1 text-xs font-medium">Esc</kbd>
|
<kbd className="rounded bg-muted px-2 py-1 text-xs font-medium">Esc</kbd>
|
||||||
<span className="text-muted-foreground">Cancel</span>
|
<span className="text-muted-foreground">Cancel</span>
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
export function SlabHelper() {
|
export function SlabHelper() {
|
||||||
return (
|
return (
|
||||||
<div className="pointer-events-none fixed right-4 top-1/2 -translate-y-1/2 z-40 flex flex-col gap-2 rounded-lg border border-border bg-background/95 px-4 py-3 shadow-lg backdrop-blur-md">
|
<div className="pointer-events-none fixed right-4 top-1/2 -translate-y-1/2 z-40 flex flex-col gap-2 rounded-lg border border-border bg-background/95 px-4 py-3 shadow-lg backdrop-blur-md">
|
||||||
|
<div className="flex items-center gap-2 text-sm">
|
||||||
|
<kbd className="rounded bg-muted px-2 py-1 text-xs font-medium">Shift</kbd>
|
||||||
|
<span className="text-muted-foreground">Allow non-45° angles</span>
|
||||||
|
</div>
|
||||||
<div className="flex items-center gap-2 text-sm">
|
<div className="flex items-center gap-2 text-sm">
|
||||||
<kbd className="rounded bg-muted px-2 py-1 text-xs font-medium">Esc</kbd>
|
<kbd className="rounded bg-muted px-2 py-1 text-xs font-medium">Esc</kbd>
|
||||||
<span className="text-muted-foreground">Cancel</span>
|
<span className="text-muted-foreground">Cancel</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user