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

View File

@ -8,6 +8,9 @@ import {
Stage, Stage,
} from '@avocado/graphics'; } from '@avocado/graphics';
import { import {
floodwalk2D,
mod,
randomNumber,
Rectangle, Rectangle,
Vector, Vector,
} from '@avocado/math'; } from '@avocado/math';
@ -61,6 +64,7 @@ function TilesPage({
const [mute, setMute] = useState(Array(layerCount).fill(false)); const [mute, setMute] = useState(Array(layerCount).fill(false));
const [selection, setSelection] = useState([0, 0, 16, 16]); const [selection, setSelection] = useState([0, 0, 16, 16]);
const [tilesetMode, setTilesetMode] = useState(0); const [tilesetMode, setTilesetMode] = useState(0);
const [fillMode, setFillMode] = useState(0);
const [viewport, setViewport] = useState([0, 0]); const [viewport, setViewport] = useState([0, 0]);
const onRef = useCallback((stage) => { const onRef = useCallback((stage) => {
setTilesetEvents(stage?.events()); setTilesetEvents(stage?.events());
@ -107,7 +111,7 @@ function TilesPage({
return undefined; return undefined;
} }
const tileSize = [16, 16]; const tileSize = [16, 16];
const stamp = (translated) => { const stamp = (origin) => {
const position = Vector.div([selection[0], selection[1]], tileSize); const position = Vector.div([selection[0], selection[1]], tileSize);
const size = Vector.div([selection[2], selection[3]], tileSize); const size = Vector.div([selection[2], selection[3]], tileSize);
let [x, y] = position; let [x, y] = position;
@ -120,7 +124,7 @@ function TilesPage({
x -= size[0]; x -= size[0];
y += 1; y += 1;
} }
const where = Rectangle.compose(translated, size); const where = Rectangle.compose(origin, size);
room.tiles[currentLayer].stampAt(where, stamp); room.tiles[currentLayer].stampAt(where, stamp);
room.tiles[currentLayer].emit('update', where); room.tiles[currentLayer].emit('update', where);
const {data} = room.tiles[currentLayer].toJSON(); 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 = ( const onValue = (
{ {
position, position,
type, type,
}, },
) => { ) => {
const origin = Vector.floor(Vector.div(position, tileSize));
switch (type) { switch (type) {
case 'touchstart': case 'touchstart':
case 'mousedown': { case 'mousedown': {
const translated = Vector.floor(Vector.div(position, tileSize)); switch (tilesetMode) {
stamp(translated); case 0: {
stamp(origin);
break;
}
case 1: {
fill(origin);
break;
}
default:
}
setIsHoldingPaint(true); setIsHoldingPaint(true);
break; break;
} }
case 'touchmove': case 'touchmove':
case 'mousemove': { case 'mousemove': {
if (isHoldingPaint) { if (isHoldingPaint) {
const translated = Vector.floor(Vector.div(position, tileSize)); switch (tilesetMode) {
stamp(translated); case 0: {
stamp(origin);
break;
}
case 1: {
fill(origin);
break;
}
default:
}
} }
break; break;
} }
@ -291,9 +383,14 @@ function TilesPage({
<option value={1}>Fill</option> <option value={1}>Fill</option>
</select> </select>
{1 === tilesetMode && ( {1 === tilesetMode && (
<select> <select
<option>Tiling</option> defaultValue={fillMode}
<option>Random</option> onChange={(event) => {
setFillMode(parseInt(event.target.value, 10));
}}
>
<option value={0}>Tiling</option>
<option value={1}>Random</option>
</select> </select>
)} )}
</div> </div>