mirror of
https://github.com/trafficlunar/blockmatic.git
synced 2026-06-28 14:44:12 +00:00
refactor: move components into folders
This commit is contained in:
parent
1dbb126b90
commit
ceb87c2088
8 changed files with 15 additions and 8 deletions
81
src/components/canvas/Blocks.tsx
Normal file
81
src/components/canvas/Blocks.tsx
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import { useEffect } from "react";
|
||||
import { Sprite } from "@pixi/react";
|
||||
|
||||
import blocksData from "@/data/blocks/programmer-art/average_colors.json";
|
||||
import { Texture } from "pixi.js";
|
||||
|
||||
interface Props {
|
||||
blocks: Block[];
|
||||
setBlocks: React.Dispatch<React.SetStateAction<Block[]>>;
|
||||
textures: Record<string, Texture>;
|
||||
image: HTMLImageElement | undefined;
|
||||
imageDimensions: Dimension;
|
||||
}
|
||||
|
||||
function Blocks({ blocks, setBlocks, textures, image, imageDimensions }: Props) {
|
||||
const findClosestBlock = (r: number, g: number, b: number, a: number) => {
|
||||
let closestBlock = "";
|
||||
let closestDistance = Infinity;
|
||||
|
||||
Object.entries(blocksData).forEach(([block, rgba]) => {
|
||||
const distance = Math.sqrt(Math.pow(r - rgba[0], 2) + Math.pow(g - rgba[1], 2) + Math.pow(b - rgba[2], 2) + Math.pow(a - rgba[3], 3));
|
||||
if (distance < closestDistance) {
|
||||
closestDistance = distance;
|
||||
closestBlock = block;
|
||||
}
|
||||
});
|
||||
|
||||
return closestBlock;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (image) {
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
if (ctx) {
|
||||
canvas.width = imageDimensions.width;
|
||||
canvas.height = imageDimensions.height;
|
||||
ctx.drawImage(image, 0, 0, imageDimensions.width, imageDimensions.height);
|
||||
|
||||
const imageData = ctx.getImageData(0, 0, imageDimensions.width, imageDimensions.height);
|
||||
const newBlocks: Block[] = [];
|
||||
|
||||
for (let i = 0; i < imageData.data.length; i += 4) {
|
||||
const block = findClosestBlock(imageData.data[i], imageData.data[i + 1], imageData.data[i + 2], imageData.data[i + 3]);
|
||||
|
||||
const x = Math.floor((i / 4) % imageData.width);
|
||||
const y = Math.floor(i / 4 / imageData.width);
|
||||
|
||||
newBlocks.push({
|
||||
name: block,
|
||||
x,
|
||||
y,
|
||||
});
|
||||
}
|
||||
|
||||
setBlocks(newBlocks);
|
||||
}
|
||||
}
|
||||
}, [image, imageDimensions, setBlocks]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log(blocks);
|
||||
}, [blocks]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{blocks.map((block, index) => {
|
||||
const texture = textures[block.name];
|
||||
if (!texture) {
|
||||
console.warn(`Texture not found for block: ${block.name}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return <Sprite key={index} texture={texture} x={block.x * 16} y={block.y * 16} />;
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Blocks;
|
||||
19
src/components/canvas/CanvasBorder.tsx
Normal file
19
src/components/canvas/CanvasBorder.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { Graphics } from "@pixi/react";
|
||||
|
||||
interface Props {
|
||||
canvasSize: CanvasSize;
|
||||
}
|
||||
|
||||
function CanvasBorder({ canvasSize }: Props) {
|
||||
return (
|
||||
<Graphics
|
||||
draw={(g) => {
|
||||
g.clear();
|
||||
g.lineStyle(2, 0xffffff, 0.25, 1);
|
||||
g.drawRect(canvasSize.minX * 16, canvasSize.minY * 16, (canvasSize.maxX - canvasSize.minX) * 16, (canvasSize.maxY - canvasSize.minY) * 16);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default CanvasBorder;
|
||||
21
src/components/canvas/Cursor.tsx
Normal file
21
src/components/canvas/Cursor.tsx
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { Graphics } from "@pixi/react";
|
||||
|
||||
interface Props {
|
||||
mouseCoords: Position
|
||||
}
|
||||
|
||||
function Cursor({ mouseCoords }: Props) {
|
||||
return (
|
||||
<Graphics
|
||||
x={mouseCoords.x * 16}
|
||||
y={mouseCoords.y * 16}
|
||||
draw={(g) => {
|
||||
g.clear();
|
||||
g.lineStyle(1, 0xffffff, 1);
|
||||
g.drawRect(0, 0, 16, 16);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default Cursor;
|
||||
32
src/components/canvas/Grid.tsx
Normal file
32
src/components/canvas/Grid.tsx
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { Graphics } from "@pixi/react";
|
||||
|
||||
interface Props {
|
||||
stageSize: Dimension;
|
||||
coords: Position;
|
||||
scale: number;
|
||||
}
|
||||
|
||||
function Grid({ stageSize, coords, scale }: Props) {
|
||||
return (
|
||||
<Graphics
|
||||
draw={(g) => {
|
||||
g.clear();
|
||||
g.lineStyle(1, 0xffffff, 0.1);
|
||||
|
||||
const tileSize = 16 * scale;
|
||||
|
||||
for (let x = coords.x % tileSize; x < stageSize.width; x += tileSize) {
|
||||
g.moveTo(x, 0);
|
||||
g.lineTo(x, stageSize.height);
|
||||
}
|
||||
|
||||
for (let y = coords.y % tileSize; y < stageSize.height; y += tileSize) {
|
||||
g.moveTo(0, y);
|
||||
g.lineTo(stageSize.width, y);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default Grid;
|
||||
53
src/components/canvas/information/Canvas.tsx
Normal file
53
src/components/canvas/information/Canvas.tsx
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import React from "react";
|
||||
import { Slider } from "@/components/ui/slider";
|
||||
|
||||
interface Props {
|
||||
scale: number;
|
||||
setScale: React.Dispatch<React.SetStateAction<number>>;
|
||||
setCoords: React.Dispatch<React.SetStateAction<Position>>;
|
||||
canvasSize: CanvasSize;
|
||||
stageSize: Dimension;
|
||||
}
|
||||
|
||||
function CanvasInformation({ scale, setScale, setCoords, canvasSize, stageSize }: Props) {
|
||||
const onValueChange = (value: number[]) => {
|
||||
const newScale = value[0];
|
||||
setScale(newScale);
|
||||
|
||||
// Center canvas
|
||||
const blockCenterX = ((canvasSize.minX + canvasSize.maxX) * 16) / 2;
|
||||
const blockCenterY = ((canvasSize.minY + canvasSize.maxY) * 16) / 2;
|
||||
|
||||
setCoords({
|
||||
x: stageSize.width / 2 - blockCenterX * newScale,
|
||||
y: stageSize.height / 2 - blockCenterY * newScale,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="absolute right-4 bottom-4 flex items-end gap-1">
|
||||
<div className="flex flex-col items-end gap-1">
|
||||
<div className="info-child">{Math.floor(scale * 100)}%</div>
|
||||
<div className="info-child">
|
||||
<span>W: {canvasSize.maxX - canvasSize.minX} </span>
|
||||
<span>H: {canvasSize.maxY - canvasSize.minY}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="info-child">
|
||||
<Slider
|
||||
defaultValue={[1]}
|
||||
min={0.1}
|
||||
max={32}
|
||||
step={0.1}
|
||||
value={[scale]}
|
||||
onValueChange={onValueChange}
|
||||
orientation="vertical"
|
||||
className="!h-32"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CanvasInformation;
|
||||
32
src/components/canvas/information/Cursor.tsx
Normal file
32
src/components/canvas/information/Cursor.tsx
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { useEffect, useState } from "react";
|
||||
|
||||
interface Props {
|
||||
mouseCoords: Position;
|
||||
blocks: Block[];
|
||||
}
|
||||
|
||||
function CursorInformation({ mouseCoords, blocks }: Props) {
|
||||
const [position, setPosition] = useState<Position>({ x: 0, y: 0 });
|
||||
const [block, setBlock] = useState<Block>();
|
||||
|
||||
useEffect(() => {
|
||||
setPosition({
|
||||
x: mouseCoords.x,
|
||||
y: mouseCoords.y,
|
||||
});
|
||||
|
||||
setBlock(blocks.find((b) => b.x === mouseCoords.x && b.y === mouseCoords.y));
|
||||
}, [mouseCoords]);
|
||||
|
||||
return (
|
||||
<div className="absolute left-4 bottom-4 flex flex-col gap-1">
|
||||
<div className="info-child">{block?.name ?? "air"}</div>
|
||||
<div className="info-child">
|
||||
<span>X: {position.x} </span>
|
||||
<span>Y: {position.y}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CursorInformation;
|
||||
Loading…
Add table
Add a link
Reference in a new issue