From 214fed6e945b186be426e579a20b6ddae75349e5 Mon Sep 17 00:00:00 2001 From: trafficlunar Date: Fri, 14 Feb 2025 14:18:47 +0000 Subject: [PATCH] fix: holes in pencil tool --- src/components/canvas/Canvas.tsx | 4 ++- src/hooks/tools/pencil.ts | 54 ++++++++++++++++++++++---------- src/utils/interpolate.ts | 20 ++++++++++++ 3 files changed, 60 insertions(+), 18 deletions(-) create mode 100644 src/utils/interpolate.ts diff --git a/src/components/canvas/Canvas.tsx b/src/components/canvas/Canvas.tsx index d591307..9c02425 100644 --- a/src/components/canvas/Canvas.tsx +++ b/src/components/canvas/Canvas.tsx @@ -188,6 +188,8 @@ function Canvas() { setDragging(false); updateCssCursor(); + pencilTool.stop(); + // History entries for pencil and eraser if (tool === "pencil" || tool === "eraser") { // startBlocksRef will mutate if we pass it directly @@ -210,7 +212,7 @@ function Canvas() { () => setSelectionCoords([...prevSelection]) ); } - }, [updateCssCursor, blocks, tool, addHistory, setBlocks, selectionCoords, setSelectionCoords]); + }, [updateCssCursor, pencilTool, blocks, tool, addHistory, setBlocks, selectionCoords, setSelectionCoords]); const onWheel = useCallback( (e: React.WheelEvent) => { diff --git a/src/hooks/tools/pencil.ts b/src/hooks/tools/pencil.ts index cadb128..f5d8163 100644 --- a/src/hooks/tools/pencil.ts +++ b/src/hooks/tools/pencil.ts @@ -1,10 +1,11 @@ -import { useContext } from "react"; +import { useContext, useRef } from "react"; import { CanvasContext } from "@/context/Canvas"; import { SelectionContext } from "@/context/Selection"; import { ToolContext } from "@/context/Tool"; -import { useRadiusPosition } from "../useRadiusPosition"; +import { useRadiusPosition } from "@/hooks/useRadiusPosition"; +import { interpolate } from "@/utils/interpolate"; export function usePencilTool(mouseCoords: Position) { const { blocks, setBlocks } = useContext(CanvasContext); @@ -12,33 +13,52 @@ export function usePencilTool(mouseCoords: Position) { const { selectedBlock, radius } = useContext(ToolContext); const radiusPosition = useRadiusPosition(mouseCoords); + const lastPosition = useRef(null); + + const stop = () => { + lastPosition.current = null; + }; const use = () => { + // Don't allow the user to add air blocks if (selectedBlock == "air") return; - const radiusBlocks: Block[] = []; + const newBlocks: Block[] = []; - for (let x = 0; x < radius; x++) { - for (let y = 0; y < radius; y++) { - const tileX = radiusPosition.x + x; - const tileY = radiusPosition.y + y; + const addBlock = (x: number, y: number) => { + if (isInSelection(x, y)) { + newBlocks.push({ + name: selectedBlock, + x, + y, + }); + } + }; - // Only add blocks within the selection - if (isInSelection(tileX, tileY)) { - radiusBlocks.push({ - name: selectedBlock, - x: tileX, - y: tileY, - }); + // Interpolate to remove holes + if (lastPosition.current) { + const interpolatedPositions = interpolate(radius, lastPosition.current, radiusPosition); + if (!interpolatedPositions) return; + + interpolatedPositions.forEach(({ x, y }) => addBlock(x, y)); + } else { + for (let x = 0; x < radius; x++) { + for (let y = 0; y < radius; y++) { + const tileX = radiusPosition.x + x; + const tileY = radiusPosition.y + y; + + addBlock(tileX, tileY); } } } + // Remove duplicates const mergedBlocks = blocks.filter((block) => { - return !radiusBlocks.some((newBlock) => block.x === newBlock.x && block.y === newBlock.y); + return !newBlocks.some((b) => block.x === b.x && block.y === b.y); }); - setBlocks([...mergedBlocks, ...radiusBlocks]); + setBlocks([...mergedBlocks, ...newBlocks]); + lastPosition.current = radiusPosition; }; - return { use }; + return { stop, use }; } diff --git a/src/utils/interpolate.ts b/src/utils/interpolate.ts new file mode 100644 index 0000000..9fe9ea4 --- /dev/null +++ b/src/utils/interpolate.ts @@ -0,0 +1,20 @@ +export function interpolate(radius: number, position0: Position, position1: Position): Position[] { + const dx = position1.x - position0.x; + const dy = position1.y - position0.y; + const steps = Math.max(Math.abs(dx), Math.abs(dy)); + + const positions: Position[] = []; + + for (let i = 0; i <= steps; i++) { + const x = Math.round(position0.x + (dx * i) / steps); + const y = Math.round(position0.y + (dy * i) / steps); + + for (let rx = 0; rx < radius; rx++) { + for (let ry = 0; ry < radius; ry++) { + positions.push({ x: x + rx, y: y + ry }); + } + } + } + + return positions; +}