fix: holes in pencil tool

This commit is contained in:
trafficlunar 2025-02-14 14:18:47 +00:00
parent 7233452877
commit 214fed6e94
3 changed files with 60 additions and 18 deletions

View file

@ -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) => {

View file

@ -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<Position | null>(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[] = [];
const addBlock = (x: number, y: number) => {
if (isInSelection(x, y)) {
newBlocks.push({
name: selectedBlock,
x,
y,
});
}
};
// 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;
// Only add blocks within the selection
if (isInSelection(tileX, tileY)) {
radiusBlocks.push({
name: selectedBlock,
x: tileX,
y: tileY,
});
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 };
}

20
src/utils/interpolate.ts Normal file
View file

@ -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;
}