refactor: generation

This commit is contained in:
cha0s 2021-03-28 10:01:13 -05:00
parent f68e5e9616
commit ed264d8f0b
9 changed files with 191 additions and 119 deletions

View File

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

View File

@ -0,0 +1,39 @@
export default class Generator {
constructor({
calculate,
children = [],
covers,
size: [w, h],
}) {
this.calculate = calculate;
this.children = children;
this.covers = covers;
this.size = [w, h];
this.matrix = new Array(w * h).fill(0);
}
compute(i, x, y) {
if (!this.covers([x, y])) {
return;
}
this.matrix[i] = this.calculate([x, y]);
if (!this.children) {
return;
}
for (let j = 0; j < this.children.length; j++) {
this.children[j].compute(i, x, y);
}
}
generate = () => {
const [w, h] = this.size;
let i = 0;
for (let y = 0; y < h; ++y) {
for (let x = 0; x < w; ++x) {
this.compute(i++, x, y);
}
}
};
}

View File

@ -3,6 +3,7 @@ import * as Rectangle from './rectangle';
import types from './types';
import * as Vector from './vector';
export {default as Generator} from './generator';
export * from './math';
export {default as floodwalk2D} from './floodwalk';
export {default as QuadTree} from './quadtree';

View File

@ -21,35 +21,31 @@ export default (Layer) => class PhysicsLayer extends Layer {
if (!tileset) {
return;
}
const indices = Object.keys(this.#impassable).map((i) => parseInt(i, 10));
for (let i = 0; i < indices.length; i++) {
const hulls = this.tiles.indexHulls(indices[i]);
if (0 === hulls.length) {
// eslint-disable-next-line no-continue
continue;
}
for (let j = 0; j < hulls.length; ++j) {
const scaled = [];
for (let k = 0; k < hulls[j].length; ++k) {
scaled.push(Vector.mul(hulls[j][k], tileset.tileSize));
}
const [vertices, position] = Vertice.localize(scaled);
const shape = new PolygonShape({position, vertices});
const body = this.#world.createBody(shape);
body.static = true;
this.#tileBodies.push(body);
this.#world.addBody(body);
const hulls = this.tiles.indexHulls(this.#impassable);
if (0 === hulls.length) {
return;
}
for (let j = 0; j < hulls.length; ++j) {
const scaled = [];
for (let k = 0; k < hulls[j].length; ++k) {
scaled.push(Vector.mul(hulls[j][k], tileset.tileSize));
}
const [vertices, position] = Vertice.localize(scaled);
const shape = new PolygonShape({position, vertices});
const body = this.#world.createBody(shape);
body.static = true;
this.#tileBodies.push(body);
this.#world.addBody(body);
}
}
async load(json = {}) {
await super.load(json);
const {impassable = []} = json;
this.#impassable = impassable.reduce((r, index) => ({...r, [index]: true}), {});
this.#impassable = new Set(impassable);
}
onTileDataChanged() {
onTilesUpdate() {
this.removeTileBodies();
this.addTileBodies();
}
@ -63,6 +59,14 @@ export default (Layer) => class PhysicsLayer extends Layer {
}
}
setTiles(tiles) {
if (this.tiles) {
this.tiles.off('update', this.onTilesUpdate);
}
super.setTiles(tiles);
this.tiles.on('update', this.onTilesUpdate, this);
}
set world(world) {
this.removeTileBodies();
this.#world = world;

View File

@ -11,6 +11,7 @@ export default class LayerView extends Container {
super();
this.entityListView = new EntityListView(layer.entityList);
this.tilesView = new TilesView(layer.tiles, layer.tileset, renderer);
this.lastExtent = [0, 0, 0, 0];
this.layer = layer;
this.addChild(this.tilesView);
this.addChild(this.entityListView);
@ -27,17 +28,18 @@ export default class LayerView extends Container {
}
renderChunksForExtent(extent) {
this.lastExtent = extent;
this.tilesView.renderChunksForExtent(extent);
}
}
// if (module.hot) {
// module.hot.accept('./tiles-renderer', () => {
// const it = views.values();
// for (let value = it.next(); value.done !== true; value = it.next()) {
// const {value: view} = value;
// view.render();
// }
// });
// }
if (module.hot) {
module.hot.accept('./tiles-view', () => {
const it = views.values();
for (let value = it.next(); value.done !== true; value = it.next()) {
const {value: view} = value;
view.renderChunksForExtent(view.lastExtent);
}
});
}

View File

@ -126,12 +126,13 @@ export default (latus) => {
this.startSynchronizing(tiles);
}
stampAt(position, tiles, w, h) {
this.tiles.stampAt(position, tiles, w, h);
stampAt([x, y, w, h], tiles) {
this.tiles.stampAt([x, y, w, h], tiles);
}
tick(elapsed) {
this.entityList.tick(elapsed);
this.tiles.tick();
}
tileAt(position) {

View File

@ -19,6 +19,8 @@ export default (latus) => {
);
return class Tiles extends decorate(Class) {
#hasUpdates = false;
#packets = [];
#updates = [];
@ -44,7 +46,7 @@ export default (latus) => {
this.#updates = [];
}
indexHulls(index) {
indexHulls(indices) {
const hulls = [];
const seen = [];
const n = this.data.length;
@ -52,9 +54,9 @@ export default (latus) => {
let x = 0;
let y = 0;
for (let i = 0; i < n; ++i) {
if (index === this.data[i]) {
if (indices.has(this.data[i])) {
if (!seen[i]) {
const points = floodwalk2D(this.data, [x, y], w, h);
const points = floodwalk2D(indices, this.data, [x, y, w, h]);
if (points.length > 0) {
const body = [];
for (let j = 0; j < points.length; ++j) {
@ -64,28 +66,28 @@ export default (latus) => {
const xx = k % w;
const yy = Math.floor(k / w);
const u = k - w;
if ((u < 0 || yy < 1) || this.data[u] !== index) {
if ((u < 0 || yy < 1) || !indices.has(this.data[u])) {
body.push([xx, yy]);
body.push([xx + 1, yy]);
}
const r = k + 1;
if ((r < 0 || xx + 1 >= w) || this.data[r] !== index) {
if ((r < 0 || xx + 1 >= w) || !indices.has(this.data[r])) {
body.push([xx + 1, yy]);
body.push([xx + 1, yy + 1]);
}
const d = k + w;
if ((d < 0 || yy + 1 >= h) || this.data[d] !== index) {
if ((d < 0 || yy + 1 >= h) || !indices.has(this.data[d])) {
body.push([xx + 1, yy + 1]);
body.push([xx, yy + 1]);
}
const l = k - 1;
if ((l < 0 || xx < 1) || this.data[l] !== index) {
if ((l < 0 || xx < 1) || !indices.has(this.data[l])) {
body.push([xx, yy + 1]);
body.push([xx, yy]);
}
}
}
const hull = Vertice.removeCollinear(Vertice.smooth(body));
const hull = Vertice.removeCollinear(Vertice.ortho(Vertice.unique(body)));
if (hull.length > 0) {
hulls.push(hull);
}
@ -135,6 +137,7 @@ export default (latus) => {
this.data[index] = tile;
if ('client' !== process.env.SIDE) {
this.#updates.push([[x, y], [1, 1], [tile]]);
this.#hasUpdates = true;
}
}
@ -195,9 +198,17 @@ export default (latus) => {
}
if (isDirty && 'client' !== process.env.SIDE) {
this.#updates.push([[x, y], [w, h], tiles]);
this.#hasUpdates = true;
}
}
tick() {
if (this.#hasUpdates) {
this.emit('update');
}
this.#hasUpdates = false;
}
tileAt([x, y]) {
const [w, h] = this.size;
return x < 0 || x >= w || y < 0 || y >= h ? undefined : this.data[y * w + x];

View File

@ -1,10 +1,14 @@
import {
Canvas,
Container,
// Image,
Image,
Sprite,
} from '@avocado/graphics';
import {Vector} from '@avocado/math';
import {
noise,
Vector,
Vertice,
} from '@avocado/math';
export default class TilesView extends Container {
@ -12,11 +16,20 @@ export default class TilesView extends Container {
constructor(tiles, tileset, renderer) {
super();
this.wrapper = new Container();
this.addChild(this.wrapper);
this.tiles = tiles;
this.tiles.on('update', this.onTilesUpdate, this);
this.tileset = tileset;
this.renderer = renderer;
this.rendered = [];
// this.hulls = this.tiles.indexHulls(0);
// const mask = this.renderMask(Vector.mul(this.tiles.size, this.tileset.tileSize));
// if (mask) {
// this.addChild(mask);
// this.wrapper.mask = mask;
// mask.anchor = [0, 0];
// }
}
chunksForExtent([x, y, w, h]) {
@ -104,9 +117,12 @@ export default class TilesView extends Container {
if (cx >= fw || cy >= fh) {
return;
}
const sw = cx + cw > fw ? (cx + cw) - fw : cw;
const sh = cy + ch > fh ? (cy + ch) - fh : ch;
const slice = this.tiles.slice([cx, cy, sw, sh]);
const slice = this.tiles.slice([
cx,
cy,
cx + cw > fw ? (cx + cw) - fw : cw,
cy + ch > fh ? (cy + ch) - fh : ch,
]);
if (!slice) {
return;
}
@ -147,7 +163,7 @@ export default class TilesView extends Container {
sprite.anchor = [0, 0];
sprite.position = [cux * cw * tw, cuy * ch * th];
this.rendered[this.constructor.hashChunk([cux, cuy])] = sprite;
this.addChild(sprite);
this.wrapper.addChild(sprite);
container.destroy();
// if (mask) {
// mask.destroy();
@ -166,73 +182,72 @@ export default class TilesView extends Container {
}
}
// renderMask(rectangle) {
// const hulls = this.tiles.indexHulls(0);
// if (!hulls.length > 0) {
// return undefined;
// }
// const size = Rectangle.size(rectangle);
// const {tileSize} = this.tileset;
// const canvasSize = Vector.mul(size, tileSize);
// const canvas = window.document.createElement('canvas');
// [canvas.width, canvas.height] = canvasSize;
// const ctx = canvas.getContext('2d');
// ctx.fillStyle = 'rgba(255, 255, 255, 1)';
// ctx.fillRect(0, 0, canvas.width, canvas.height);
// ctx.fillStyle = 'rgba(0, 0, 0, 1)';
// for (let i = 0; i < hulls.length; ++i) {
// const scaled = [];
// for (let j = 0; j < hulls[i].length; j++) {
// scaled.push(Vector.scale(hulls[i][j], 16));
// }
// scaled.push(Vector.scale(hulls[i][0], 16));
// ctx.beginPath();
// ctx.moveTo(scaled[0][0], scaled[0][1]);
// for (let j = 0; j < scaled.length - 1; j++) {
// const p0 = scaled[j + 0];
// const p1 = scaled[j + 1];
// const points = Vertice.bresenham(p0, p1);
// const isReversed = (
// (p0[0] > p1[0] && points[0][0] < points[points.length - 1][0])
// || (p0[1] > p1[1] && points[0][1] < points[points.length - 1][1])
// );
// const u = Vector.scale(
// Vector.fromRadians((Math.PI * 0.5) + Vector.toRadians(Vector.sub(p1, p0))),
// 2,
// );
// for (
// let k = (isReversed ? points.length - 1 : 0);
// (isReversed ? k >= 0 : k < points.length);
// k += (isReversed ? -1 : 1)
// ) {
// const [x, y] = points[k];
// const shift = Vector.add(
// u,
// Vector.scale(
// Vector.mul(
// [
// noise(Vector.scale([x, y], 10)),
// noise(Vector.scale([y, x], 10)),
// ],
// [
// noise(Vector.scale([x, y], 20)),
// noise(Vector.scale([y, x], 20)),
// ],
// ),
// 5,
// ),
// );
// const [vx, vy] = Vector.add(
// points[k],
// shift,
// );
// ctx.lineTo(vx, vy);
// }
// }
// ctx.closePath();
// ctx.fill();
// }
// return new Sprite(Image.from(canvas));
// }
renderMask(size) {
const {hulls} = this;
if (!hulls.length > 0) {
return undefined;
}
const {tileSize} = this.tileset;
const canvasSize = Vector.mul(size, tileSize);
const canvas = window.document.createElement('canvas');
[canvas.width, canvas.height] = canvasSize;
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'rgba(255, 255, 255, 1)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'rgba(0, 0, 0, 1)';
for (let i = 0; i < hulls.length; ++i) {
const scaled = [];
for (let j = 0; j < hulls[i].length; j++) {
scaled.push(Vector.scale(hulls[i][j], 16));
}
scaled.push(Vector.scale(hulls[i][0], 16));
ctx.beginPath();
ctx.moveTo(scaled[0][0], scaled[0][1]);
for (let j = 0; j < scaled.length - 1; j++) {
const p0 = scaled[j + 0];
const p1 = scaled[j + 1];
const points = Vertice.bresenham(p0, p1);
const isReversed = (
(p0[0] > p1[0] && points[0][0] < points[points.length - 1][0])
|| (p0[1] > p1[1] && points[0][1] < points[points.length - 1][1])
);
const u = Vector.scale(
Vector.fromRadians((Math.PI * 0.5) + Vector.toRadians(Vector.sub(p1, p0))),
2,
);
for (
let k = (isReversed ? points.length - 1 : 0);
(isReversed ? k >= 0 : k < points.length);
k += (isReversed ? -1 : 1)
) {
const [x, y] = points[k];
const shift = Vector.add(
u,
Vector.scale(
Vector.mul(
[
noise(Vector.scale([x, y], 10)),
noise(Vector.scale([y, x], 10)),
],
[
noise(Vector.scale([x, y], 20)),
noise(Vector.scale([y, x], 20)),
],
),
5,
),
);
const [vx, vy] = Vector.add(
points[k],
shift,
);
ctx.lineTo(vx, vy);
}
}
ctx.closePath();
ctx.fill();
}
return new Sprite(Image.from(canvas));
}
}

View File

@ -79,7 +79,7 @@ describe('Tiles', () => {
size: [8, 4],
},
);
expect(tiles.indexHulls(1)).to.deep.equal(hulls);
expect(tiles.indexHulls(new Set([1]))).to.deep.equal(hulls);
});
it("can stamp at", async () => {
const {Tiles} = latus.get('%resources');
@ -138,6 +138,6 @@ describe('Tiles', () => {
size: [8, 4],
},
);
expect(tiles.indexHulls(1)).to.deep.equal(hulls);
expect(tiles.indexHulls(new Set([1]))).to.deep.equal(hulls);
});
});