2024-07-11 01:16:10 -05:00
|
|
|
import {CHUNK_SIZE} from '@/constants.js';
|
2024-06-26 21:08:09 -05:00
|
|
|
import Component from '@/ecs/component.js';
|
2024-07-07 17:31:27 -05:00
|
|
|
import {floodwalk2D, ortho, removeCollinear} from '@/util/math.js';
|
2024-06-14 12:05:02 -05:00
|
|
|
|
2024-06-26 21:08:09 -05:00
|
|
|
import vector2d from './helpers/vector-2d';
|
2024-06-25 04:50:13 -05:00
|
|
|
|
2024-07-07 17:29:36 -05:00
|
|
|
class LayerProxy {
|
2024-07-11 01:16:10 -05:00
|
|
|
$$chunks = [];
|
|
|
|
$$sourceJson = {};
|
2024-07-07 17:29:36 -05:00
|
|
|
constructor(instance, Component, index) {
|
|
|
|
this.instance = instance;
|
|
|
|
this.Component = Component;
|
|
|
|
this.index = index;
|
2024-07-11 01:16:10 -05:00
|
|
|
this.$$chunks = Array(
|
|
|
|
Math.ceil(this.area.x / CHUNK_SIZE) * Math.ceil(this.area.y / CHUNK_SIZE)
|
|
|
|
).fill(0).map(() => ({}));
|
2024-07-07 17:29:36 -05:00
|
|
|
}
|
|
|
|
get area() {
|
|
|
|
return this.layer.area;
|
|
|
|
}
|
2024-07-10 17:27:20 -05:00
|
|
|
clone() {
|
2024-07-11 01:16:10 -05:00
|
|
|
const {$$chunks, $$sourceJson} = this.instance.$$layersProxies[this.index];
|
2024-07-10 17:27:20 -05:00
|
|
|
const proxy = new LayerProxy(this.instance, this.Component, this.index);
|
2024-07-11 01:16:10 -05:00
|
|
|
proxy.$$chunks = [...$$chunks];
|
2024-07-10 17:27:20 -05:00
|
|
|
proxy.$$sourceJson = $$sourceJson;
|
|
|
|
return proxy;
|
2024-07-11 01:16:10 -05:00
|
|
|
}
|
|
|
|
compute(index) {
|
|
|
|
const cx = Math.floor((index % this.area.x) / CHUNK_SIZE);
|
|
|
|
const cy = Math.floor(Math.floor(index / this.area.x) / CHUNK_SIZE);
|
|
|
|
const ax = Math.ceil(this.area.x / CHUNK_SIZE);
|
|
|
|
return cy * ax + cx;
|
2024-07-10 17:27:20 -05:00
|
|
|
}
|
2024-07-07 17:29:36 -05:00
|
|
|
get data() {
|
|
|
|
return this.layer.data;
|
|
|
|
}
|
2024-07-07 17:31:27 -05:00
|
|
|
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;
|
|
|
|
}
|
2024-07-07 17:29:36 -05:00
|
|
|
get layer() {
|
|
|
|
return this.instance.layers[this.index];
|
|
|
|
}
|
2024-07-07 23:30:48 -05:00
|
|
|
async load() {
|
2024-07-20 02:32:50 -05:00
|
|
|
this.$$sourceJson = this.layer.source
|
|
|
|
? await this.Component.ecs.readJson(this.layer.source)
|
|
|
|
: {};
|
2024-07-07 23:30:48 -05:00
|
|
|
}
|
2024-07-07 17:29:36 -05:00
|
|
|
get source() {
|
|
|
|
return this.layer.source;
|
|
|
|
}
|
2024-07-07 23:30:48 -05:00
|
|
|
get sourceJson() {
|
|
|
|
return this.$$sourceJson;
|
|
|
|
}
|
2024-07-07 17:29:36 -05:00
|
|
|
stamp(at, data) {
|
|
|
|
const changes = {};
|
|
|
|
for (const row in data) {
|
|
|
|
const columns = data[row];
|
|
|
|
for (const column in columns) {
|
|
|
|
const tile = columns[column];
|
|
|
|
const x = at.x + parseInt(column);
|
|
|
|
const y = at.y + parseInt(row);
|
|
|
|
if (x < 0 || y < 0 || x >= this.layer.area.x || y >= this.layer.area.y) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const calculated = y * this.layer.area.x + x;
|
|
|
|
this.layer.data[calculated] = tile;
|
|
|
|
changes[calculated] = tile;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.Component.markChange(this.instance.entity, 'layerChange', {[this.index]: changes});
|
|
|
|
}
|
|
|
|
tile({x, y}) {
|
|
|
|
if (x < 0 || y < 0 || x >= this.layer.area.x || y >= this.layer.area.y) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
return this.layer.data[y * this.layer.area.x + x];
|
|
|
|
}
|
|
|
|
get tileSize() {
|
|
|
|
return this.layer.tileSize;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-26 21:08:09 -05:00
|
|
|
export default class TileLayers extends Component {
|
2024-07-11 01:16:10 -05:00
|
|
|
async createMany(entities) {
|
|
|
|
for (const [, {layerChange, layers}] of entities) {
|
|
|
|
if (layers) {
|
|
|
|
for (const layer of layers) {
|
|
|
|
const area = layer.area.x * layer.area.y;
|
|
|
|
if (layer.data.length < area) {
|
|
|
|
for (let i = 0; i < area; ++i) {
|
|
|
|
if (!layer.data[i]) {
|
|
|
|
layer.data[i] = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (layerChange) {
|
|
|
|
for (const layerIndex in layerChange) {
|
|
|
|
for (const calculated in layerChange[layerIndex]) {
|
|
|
|
const tile = layerChange[layerIndex][calculated];
|
|
|
|
layers[layerIndex].data[calculated] = tile;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return super.createMany(entities);
|
|
|
|
}
|
2024-07-07 23:30:48 -05:00
|
|
|
async insertMany(entities) {
|
2024-07-11 01:16:10 -05:00
|
|
|
for (const [, {layers}] of entities) {
|
|
|
|
if (layers) {
|
|
|
|
for (const layer of layers) {
|
|
|
|
const area = layer.area.x * layer.area.y;
|
|
|
|
if (layer.data.length < area) {
|
|
|
|
for (let i = 0; i < area; ++i) {
|
|
|
|
if (!layer.data[i]) {
|
|
|
|
layer.data[i] = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
await super.insertMany(entities);
|
2024-06-26 21:08:09 -05:00
|
|
|
for (const [id, {layerChange}] of entities) {
|
|
|
|
if (layerChange) {
|
|
|
|
const component = this.get(id);
|
|
|
|
const {layers} = component;
|
|
|
|
for (const layerIndex in layerChange) {
|
2024-07-11 01:16:10 -05:00
|
|
|
const proxy = component.$$layersProxies[layerIndex].clone();
|
|
|
|
const chunksChanged = new Set();
|
|
|
|
for (const tileIndex in layerChange[layerIndex]) {
|
|
|
|
chunksChanged.add(proxy.compute(tileIndex));
|
|
|
|
const tile = layerChange[layerIndex][tileIndex];
|
|
|
|
layers[layerIndex].data[tileIndex] = tile;
|
|
|
|
}
|
|
|
|
for (const chunkChanged of chunksChanged) {
|
|
|
|
proxy.$$chunks[chunkChanged] = {};
|
2024-06-25 04:50:13 -05:00
|
|
|
}
|
2024-07-11 01:16:10 -05:00
|
|
|
component.$$layersProxies[layerIndex] = proxy;
|
2024-06-25 04:50:13 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-06-26 21:08:09 -05:00
|
|
|
}
|
2024-07-07 23:30:48 -05:00
|
|
|
instanceFromSchema() {
|
|
|
|
return class TileLayersInstance extends super.instanceFromSchema() {
|
|
|
|
$$layersProxies = {};
|
|
|
|
layer(index) {
|
|
|
|
return this.$$layersProxies[index];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
async load(instance) {
|
2024-07-07 17:29:36 -05:00
|
|
|
for (const index in instance.layers) {
|
|
|
|
instance.$$layersProxies[index] = new LayerProxy(instance, this, index);
|
2024-07-07 23:30:48 -05:00
|
|
|
await instance.$$layersProxies[index].load();
|
2024-07-07 17:29:36 -05:00
|
|
|
}
|
|
|
|
}
|
2024-06-26 21:08:09 -05:00
|
|
|
mergeDiff(original, update) {
|
|
|
|
if (!update.layerChange) {
|
|
|
|
return super.mergeDiff(original, update);
|
|
|
|
}
|
|
|
|
const layerChange = {
|
|
|
|
...original.layerChange,
|
|
|
|
};
|
|
|
|
for (const index in update.layerChange) {
|
|
|
|
layerChange[index] = {
|
|
|
|
...layerChange[index],
|
|
|
|
...update.layerChange[index],
|
2024-06-25 04:50:13 -05:00
|
|
|
};
|
|
|
|
}
|
2024-06-26 21:08:09 -05:00
|
|
|
return {layerChange};
|
|
|
|
}
|
|
|
|
static properties = {
|
|
|
|
layers: {
|
|
|
|
type: 'array',
|
|
|
|
subtype: {
|
|
|
|
type: 'object',
|
|
|
|
properties: {
|
|
|
|
area: vector2d('float32'),
|
|
|
|
data: {
|
|
|
|
type: 'array',
|
|
|
|
subtype: {
|
|
|
|
type: 'uint16',
|
2024-06-25 04:50:13 -05:00
|
|
|
},
|
2024-06-12 13:19:16 -05:00
|
|
|
},
|
2024-06-26 21:08:09 -05:00
|
|
|
source: {type: 'string'},
|
|
|
|
tileSize: vector2d('float32'),
|
2024-06-12 13:19:16 -05:00
|
|
|
},
|
|
|
|
},
|
2024-06-26 21:08:09 -05:00
|
|
|
},
|
|
|
|
};
|
2024-06-25 04:50:13 -05:00
|
|
|
}
|