feat: fill

This commit is contained in:
cha0s 2022-04-07 17:41:53 -05:00
parent 649f5ce859
commit ad0916727e
2 changed files with 108 additions and 12 deletions

View File

@ -1,10 +1,9 @@
export default function floodwalk2D(b, data, [x, y, w, h]) {
export default function floodwalk2D(eligible, data, [x, y, w, h]) {
const n = data.length;
let i = x + w * y;
if (i < 0 || i >= n) {
return [];
}
// const b = data[i];
const points = [];
const seen = [];
seen[-1] = true;
@ -13,7 +12,7 @@ export default function floodwalk2D(b, data, [x, y, w, h]) {
while (todo.length > 0) {
i = todo.pop();
const v = data[i];
if (!b.has(v)) {
if (!eligible.has(v)) {
// eslint-disable-next-line no-continue
continue;
}

View File

@ -8,6 +8,9 @@ import {
Stage,
} from '@avocado/graphics';
import {
floodwalk2D,
mod,
randomNumber,
Rectangle,
Vector,
} from '@avocado/math';
@ -61,6 +64,7 @@ function TilesPage({
const [mute, setMute] = useState(Array(layerCount).fill(false));
const [selection, setSelection] = useState([0, 0, 16, 16]);
const [tilesetMode, setTilesetMode] = useState(0);
const [fillMode, setFillMode] = useState(0);
const [viewport, setViewport] = useState([0, 0]);
const onRef = useCallback((stage) => {
setTilesetEvents(stage?.events());
@ -107,7 +111,7 @@ function TilesPage({
return undefined;
}
const tileSize = [16, 16];
const stamp = (translated) => {
const stamp = (origin) => {
const position = Vector.div([selection[0], selection[1]], tileSize);
const size = Vector.div([selection[2], selection[3]], tileSize);
let [x, y] = position;
@ -120,7 +124,7 @@ function TilesPage({
x -= size[0];
y += 1;
}
const where = Rectangle.compose(translated, size);
const where = Rectangle.compose(origin, size);
room.tiles[currentLayer].stampAt(where, stamp);
room.tiles[currentLayer].emit('update', where);
const {data} = room.tiles[currentLayer].toJSON();
@ -137,25 +141,113 @@ function TilesPage({
});
}
};
const fill = (origin) => {
const position = Vector.div([selection[0], selection[1]], tileSize);
const size = Vector.div([selection[2], selection[3]], tileSize);
const {width} = room.tiles[currentLayer];
const {$$data} = room.tiles[currentLayer];
const updates = floodwalk2D(
new Set([$$data[width * origin[1] + origin[0]]]),
room.tiles[currentLayer].$$data,
Rectangle.compose(origin, room.tiles[currentLayer].area),
);
const [min, max] = [[Infinity, Infinity], [-Infinity, -Infinity]];
for (let i = 0; i < updates.length; i++) {
const update = updates[i];
const [x, y] = [update % width, Math.floor(update / width)];
const offset = Vector.sub([x, y], origin);
if (x < min[0]) {
min[0] = x;
}
else if (x > max[0]) {
max[0] = x;
}
if (y < min[1]) {
min[1] = y;
}
else if (y > max[1]) {
max[1] = y;
}
let tileLocation;
switch (fillMode) {
case 0: {
tileLocation = Vector.add(
position,
[
mod(offset[0], size[0]),
mod(offset[1], size[1]),
],
);
break;
}
case 1: {
tileLocation = Vector.add(
position,
[
Math.floor(randomNumber(0, size[0])),
Math.floor(randomNumber(0, size[1])),
],
);
break;
}
default:
}
$$data[update] = (tileLocation[1] * (viewport[0] / tileSize[0])) + tileLocation[0];
}
const where = Rectangle.compose(min, Vector.add([1, 1], Vector.sub(max, min)));
room.tiles[currentLayer].emit('update', where);
const {data} = room.tiles[currentLayer].toJSON();
if (!data) {
patch({
op: 'remove',
path: `/tiles/${currentLayer}/data`,
});
}
else {
patch({
path: `/tiles/${currentLayer}/data`,
value: data,
});
}
};
const onValue = (
{
position,
type,
},
) => {
const origin = Vector.floor(Vector.div(position, tileSize));
switch (type) {
case 'touchstart':
case 'mousedown': {
const translated = Vector.floor(Vector.div(position, tileSize));
stamp(translated);
switch (tilesetMode) {
case 0: {
stamp(origin);
break;
}
case 1: {
fill(origin);
break;
}
default:
}
setIsHoldingPaint(true);
break;
}
case 'touchmove':
case 'mousemove': {
if (isHoldingPaint) {
const translated = Vector.floor(Vector.div(position, tileSize));
stamp(translated);
switch (tilesetMode) {
case 0: {
stamp(origin);
break;
}
case 1: {
fill(origin);
break;
}
default:
}
}
break;
}
@ -291,9 +383,14 @@ function TilesPage({
<option value={1}>Fill</option>
</select>
{1 === tilesetMode && (
<select>
<option>Tiling</option>
<option>Random</option>
<select
defaultValue={fillMode}
onChange={(event) => {
setFillMode(parseInt(event.target.value, 10));
}}
>
<option value={0}>Tiling</option>
<option value={1}>Random</option>
</select>
)}
</div>