From cd875f802527f182d5bb9c17a8c18ab998923dc8 Mon Sep 17 00:00:00 2001 From: cha0s Date: Sun, 7 Jul 2024 17:31:27 -0500 Subject: [PATCH] feat: tile hulls --- app/ecs-components/index.js | 2 +- app/ecs-components/tile-layers.js | 62 ++++++++++++++++++++++++++ app/ecs-components/tile-layers.test.js | 45 +++++++++++++++++++ 3 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 app/ecs-components/tile-layers.test.js diff --git a/app/ecs-components/index.js b/app/ecs-components/index.js index 91de02e..d077722 100644 --- a/app/ecs-components/index.js +++ b/app/ecs-components/index.js @@ -1,7 +1,7 @@ import gather from '@/util/gather.js'; const Gathered = gather( - import.meta.glob('./*.js', {eager: true, import: 'default'}), + import.meta.glob(['./*.js', '!./*.test.js'], {eager: true, import: 'default'}), ); const Components = {}; diff --git a/app/ecs-components/tile-layers.js b/app/ecs-components/tile-layers.js index 4cde1bf..9235d92 100644 --- a/app/ecs-components/tile-layers.js +++ b/app/ecs-components/tile-layers.js @@ -1,4 +1,5 @@ import Component from '@/ecs/component.js'; +import {floodwalk2D, ortho, removeCollinear} from '@/util/math.js'; import vector2d from './helpers/vector-2d'; @@ -14,6 +15,67 @@ class LayerProxy { get data() { return this.layer.data; } + get hulls() { + const {data, area, tileSize} = this; + const hulls = []; + const seen = {}; + const n = data.length; + const {x: w, y: h} = area; + let x = 0; + let y = 0; + for (let i = 0; i < n; ++i) { + if (data[i]) { + if (!seen[i]) { + const indices = floodwalk2D(new Set([data[i]]), data, {x, y, w, h}); + if (indices.size > 0) { + const pointHash = Object.create(null); + const points = []; + const seePoint = ({x, y}) => { + if (pointHash[y]?.[x]) { + return false; + } + if (!pointHash[y]) { + pointHash[y] = Object.create({}); + } + return pointHash[y][x] = true; + }; + for (const index of indices) { + seen[index] = true; + const op = { + x: tileSize.x * (index % area.x), + y: tileSize.y * (Math.floor(index / area.x)), + }; + let p; + const tsq = {x: tileSize.x / 4, y: tileSize.y / 4}; + p = {x: op.x + tsq.x, y: op.y + tsq.y}; + if (seePoint(p)) { + points.push(p); + } + p = {x: op.x + tileSize.x - tsq.x, y: op.y + tsq.y}; + if (seePoint(p)) { + points.push(p); + } + p = {x: op.x + tileSize.x - tsq.x, y: op.y + tileSize.y - tsq.y}; + if (seePoint(p)) { + points.push(p); + } + p = {x: op.x + tsq.x, y: op.y + tileSize.y - tsq.y}; + if (seePoint(p)) { + points.push(p); + } + } + hulls.push(removeCollinear(ortho(points, {x: tileSize.x / 2, y: tileSize.y / 2}))); + } + } + } + x += 1; + if (x === w) { + x -= w; + y += 1; + } + } + return hulls; + } get layer() { return this.instance.layers[this.index]; } diff --git a/app/ecs-components/tile-layers.test.js b/app/ecs-components/tile-layers.test.js new file mode 100644 index 0000000..064542e --- /dev/null +++ b/app/ecs-components/tile-layers.test.js @@ -0,0 +1,45 @@ +import {expect, test} from 'vitest'; + +import Ecs from '@/ecs/ecs.js'; + +import TileLayers from './tile-layers.js'; + +test('creates hulls', async () => { + const Component = new TileLayers(new Ecs()); + const data = Array(64).fill(0); + data[9] = 1; + data[10] = 1; + data[17] = 1; + data[18] = 1; + const layers = await Component.create(1, { + layers: [ + { + area: {x: 8, y: 8}, + data, + source: '', + tileSize: {x: 16, y: 16}, + } + ], + }); + expect(layers.layer(0).hulls) + .to.deep.equal([ + [ + {x: 20, y: 20}, + {x: 44, y: 20}, + {x: 44, y: 44}, + {x: 20, y: 44}, + ] + ]); + data[11] = 1; + expect(layers.layer(0).hulls) + .to.deep.equal([ + [ + {x: 20, y: 20}, + {x: 60, y: 20}, + {x: 60, y: 28}, + {x: 44, y: 28}, + {x: 44, y: 44}, + {x: 20, y: 44}, + ] + ]); +});