mirror of
https://github.com/trafficlunar/blockmatic.git
synced 2026-06-28 06:34:13 +00:00
feat: shape tool
This commit is contained in:
parent
8b89e17b8f
commit
a58375fa60
13 changed files with 660 additions and 17 deletions
|
|
@ -5,7 +5,8 @@ import { CanvasContext } from "@/context/Canvas";
|
|||
|
||||
export function useMoveTool(mouseMovement: Position) {
|
||||
const { setBlocks } = useContext(CanvasContext);
|
||||
const { selectionLayerBlocks, setSelectionCoords, setSelectionLayerBlocks, isInSelection } = useContext(SelectionContext);
|
||||
const { selectionLayerBlocks, confirmHistoryEntryNameRef, setSelectionCoords, setSelectionLayerBlocks, isInSelection } =
|
||||
useContext(SelectionContext);
|
||||
|
||||
const use = () => {
|
||||
// If there is no selection currently being moved...
|
||||
|
|
@ -29,6 +30,8 @@ export function useMoveTool(mouseMovement: Position) {
|
|||
// Increase each coordinate in the selection by the mouse movement
|
||||
setSelectionCoords((prev) => prev.map(([x, y]) => [x + mouseMovement.x, y + mouseMovement.y]));
|
||||
setSelectionLayerBlocks((prev) => prev.map((b) => ({ ...b, x: b.x + mouseMovement.x, y: b.y + mouseMovement.y })));
|
||||
|
||||
confirmHistoryEntryNameRef.current = "Move Selection";
|
||||
};
|
||||
|
||||
return { use };
|
||||
|
|
|
|||
154
src/hooks/tools/shape.ts
Normal file
154
src/hooks/tools/shape.ts
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
import { useContext } from "react";
|
||||
|
||||
import { SelectionContext } from "@/context/Selection";
|
||||
import { ToolContext } from "@/context/Tool";
|
||||
|
||||
export function useShapeTool(mouseCoords: Position, dragStartCoords: Position, holdingShift: boolean) {
|
||||
const { confirmHistoryEntryNameRef, setSelectionLayerBlocks } = useContext(SelectionContext);
|
||||
const { selectedBlock, radius, shape, filled } = useContext(ToolContext);
|
||||
|
||||
const isRadiusEven = radius == 1 || radius % 2 == 0;
|
||||
|
||||
const use = () => {
|
||||
switch (shape) {
|
||||
case "line": {
|
||||
// Bresenham line algorithm
|
||||
const result: Block[] = [];
|
||||
const dx = Math.abs(mouseCoords.x - dragStartCoords.x);
|
||||
const dy = Math.abs(mouseCoords.y - dragStartCoords.y);
|
||||
const sx = Math.sign(mouseCoords.x - dragStartCoords.x);
|
||||
const sy = Math.sign(mouseCoords.y - dragStartCoords.y);
|
||||
|
||||
let err = dx - dy;
|
||||
const currentStart = { ...dragStartCoords };
|
||||
|
||||
const offsetStart = isRadiusEven ? 0 : -Math.floor(radius / 2);
|
||||
const offsetEnd = isRadiusEven ? radius : Math.floor(radius / 2) + (isRadiusEven ? 0 : 1);
|
||||
|
||||
while (true) {
|
||||
// For loop increases the height/width of the line
|
||||
for (let offset = offsetStart; offset < offsetEnd; offset++) {
|
||||
result.push({ name: selectedBlock, x: currentStart.x, y: currentStart.y + offset });
|
||||
}
|
||||
|
||||
if (currentStart.x === mouseCoords.x && currentStart.y === mouseCoords.y) break;
|
||||
|
||||
const e2 = 2 * err;
|
||||
if (e2 > -dy) {
|
||||
err -= dy;
|
||||
currentStart.x += sx;
|
||||
}
|
||||
if (e2 < dx) {
|
||||
err += dx;
|
||||
currentStart.y += sy;
|
||||
}
|
||||
}
|
||||
|
||||
setSelectionLayerBlocks([...result]);
|
||||
confirmHistoryEntryNameRef.current = "Line";
|
||||
break;
|
||||
}
|
||||
case "rectangle": {
|
||||
const result: Block[] = [];
|
||||
|
||||
const startX = Math.min(dragStartCoords.x, mouseCoords.x);
|
||||
let endX = Math.max(dragStartCoords.x, mouseCoords.x);
|
||||
const startY = Math.min(dragStartCoords.y, mouseCoords.y);
|
||||
let endY = Math.max(dragStartCoords.y, mouseCoords.y);
|
||||
|
||||
const radiusOffset = isRadiusEven ? radius : radius - 1;
|
||||
const borderOffset = isRadiusEven ? 0 : 1;
|
||||
|
||||
// If holding shift, create a square
|
||||
if (holdingShift) {
|
||||
const width = Math.abs(endX - startX);
|
||||
const height = Math.abs(endY - startY);
|
||||
const size = Math.max(width, height);
|
||||
|
||||
endX = startX + (endX < startX ? -size : size);
|
||||
endY = startY + (endY < startY ? -size : size);
|
||||
}
|
||||
|
||||
for (let x = startX; x < endX + radiusOffset; x++) {
|
||||
for (let y = startY; y < endY + radiusOffset; y++) {
|
||||
// If not filled, only add border blocks
|
||||
if (
|
||||
filled ||
|
||||
(x > startX - radius && x < startX + radius) || // Left
|
||||
(x >= endX - borderOffset && x < endX + radius) || // Right
|
||||
(y > startY - radius && y < startY + radius) || // Top
|
||||
(y >= endY - borderOffset && y < endY + radius) // Bottom
|
||||
) {
|
||||
result.push({ name: selectedBlock, x, y });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setSelectionLayerBlocks(result);
|
||||
confirmHistoryEntryNameRef.current = "Rectangle";
|
||||
break;
|
||||
}
|
||||
case "circle": {
|
||||
const result: Block[] = [];
|
||||
|
||||
const dx = Math.abs(mouseCoords.x - dragStartCoords.x);
|
||||
const dy = Math.abs(mouseCoords.y - dragStartCoords.y);
|
||||
const calculatedRadius = Math.floor(Math.sqrt(dx * dx + dy * dy) / 2);
|
||||
|
||||
const originX = dragStartCoords.x + calculatedRadius;
|
||||
const originY = dragStartCoords.y + calculatedRadius;
|
||||
|
||||
// Bresenham circle algorithm
|
||||
let x = 0;
|
||||
let y = calculatedRadius;
|
||||
let d = 3 - 2 * calculatedRadius;
|
||||
|
||||
if (filled) {
|
||||
while (x <= y) {
|
||||
for (let fillY = originY - y; fillY <= originY + y; fillY++) {
|
||||
result.push({ name: selectedBlock, x: originX + x, y: fillY });
|
||||
result.push({ name: selectedBlock, x: originX - x, y: fillY });
|
||||
}
|
||||
for (let fillY = originY - x; fillY <= originY + x; fillY++) {
|
||||
result.push({ name: selectedBlock, x: originX + y, y: fillY });
|
||||
result.push({ name: selectedBlock, x: originX - y, y: fillY });
|
||||
}
|
||||
|
||||
if (d < 0) {
|
||||
d = d + 4 * x + 6;
|
||||
} else {
|
||||
d = d + 4 * (x - y) + 10;
|
||||
y--;
|
||||
}
|
||||
x++;
|
||||
}
|
||||
} else {
|
||||
while (x <= y) {
|
||||
result.push({ name: selectedBlock, x: originX + x, y: originY + y });
|
||||
result.push({ name: selectedBlock, x: originX - x, y: originY + y });
|
||||
result.push({ name: selectedBlock, x: originX + x, y: originY - y });
|
||||
result.push({ name: selectedBlock, x: originX - x, y: originY - y });
|
||||
result.push({ name: selectedBlock, x: originX + y, y: originY + x });
|
||||
result.push({ name: selectedBlock, x: originX - y, y: originY + x });
|
||||
result.push({ name: selectedBlock, x: originX + y, y: originY - x });
|
||||
result.push({ name: selectedBlock, x: originX - y, y: originY - x });
|
||||
|
||||
if (d < 0) {
|
||||
d = d + 4 * x + 6;
|
||||
} else {
|
||||
d = d + 4 * (x - y) + 10;
|
||||
y--;
|
||||
}
|
||||
x++;
|
||||
}
|
||||
}
|
||||
|
||||
setSelectionLayerBlocks(result);
|
||||
confirmHistoryEntryNameRef.current = "Circle";
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return { use };
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue