@spearwolf/twopoint5d
Version:
Create 2.5D realtime graphics and pixelart with WebGL and three.js
229 lines • 7.75 kB
JavaScript
var Quadrant;
(function (Quadrant) {
Quadrant["NorthEast"] = "northEast";
Quadrant["SouthEast"] = "southEast";
Quadrant["SouthWest"] = "southWest";
Quadrant["NorthWest"] = "northWest";
})(Quadrant || (Quadrant = {}));
const scoreAxis = (chunks, beforeKey, afterKey, origin) => {
const chunksCount = chunks.length;
let beforeCount = 0;
let intersectCount = 0;
let afterCount = 0;
for (let i = 0; i < chunksCount; i++) {
const c = chunks[i];
if (c[beforeKey] <= origin) {
beforeCount++;
}
else if (c[afterKey] >= origin) {
afterCount++;
}
else {
intersectCount++;
}
}
const noSubdivide = (beforeCount === 0 && intersectCount === 0) ||
(beforeCount === 0 && afterCount === 0) ||
(intersectCount === 0 && afterCount === 0);
if (noSubdivide)
return null;
const beforeDistance = Math.abs(0.5 - beforeCount / chunksCount);
const afterDistance = Math.abs(0.5 - afterCount / chunksCount);
const intersectDistance = (intersectCount / chunksCount) * ChunkQuadTreeNode.IntersectDistanceFactor;
const distance = beforeDistance +
intersectDistance +
afterDistance +
Math.abs(afterDistance - beforeDistance) * ChunkQuadTreeNode.BeforeAfterDeltaFactor;
return { distance, origin };
};
const findAxis = (chunks, beforeKey, afterKey) => {
chunks.sort((a, b) => a[beforeKey] - b[beforeKey]);
let best;
let lastOrigin = Number.NaN;
for (let i = 0; i < chunks.length; i++) {
const origin = chunks[i][beforeKey];
if (origin === lastOrigin)
continue;
lastOrigin = origin;
const axis = scoreAxis(chunks, beforeKey, afterKey, origin);
if (axis !== null && (best === undefined || axis.distance < best.distance)) {
best = axis;
}
}
return best;
};
export class ChunkQuadTreeNode {
static { this.IntersectDistanceFactor = Math.PI; }
static { this.BeforeAfterDeltaFactor = Math.PI; }
constructor(chunks) {
this.originX = null;
this.originY = null;
this.isLeaf = true;
this.nodes = {
northEast: null,
northWest: null,
southEast: null,
southWest: null,
};
if (chunks === undefined) {
this.chunks = [];
}
else if (Array.isArray(chunks)) {
this.chunks = chunks.slice();
}
else {
this.chunks = [chunks];
}
}
canSubdivide() {
return this.isLeaf && this.chunks.length > 1;
}
clear() {
this.chunks = [];
this.isLeaf = true;
this.originX = null;
this.originY = null;
this.nodes.northEast = null;
this.nodes.northWest = null;
this.nodes.southEast = null;
this.nodes.southWest = null;
}
subdivide(maxChunkNodes = 2) {
if (!this.canSubdivide() || this.chunks.length <= maxChunkNodes)
return;
const chunks = this.chunks.slice(0);
const xAxis = findAxis(chunks, 'right', 'left');
const yAxis = findAxis(chunks, 'bottom', 'top');
if (!xAxis || !yAxis)
return;
const originX = xAxis.origin;
const originY = yAxis.origin;
this.originX = originX;
this.originY = originY;
this.isLeaf = false;
const ne = [];
const nw = [];
const se = [];
const sw = [];
const straddlers = [];
for (let i = 0, n = chunks.length; i < n; i++) {
const chunk = chunks[i];
if (chunk.left >= originX) {
if (chunk.top >= originY)
se.push(chunk);
else if (chunk.bottom <= originY)
ne.push(chunk);
else
straddlers.push(chunk);
}
else if (chunk.right <= originX) {
if (chunk.top >= originY)
sw.push(chunk);
else if (chunk.bottom <= originY)
nw.push(chunk);
else
straddlers.push(chunk);
}
else {
straddlers.push(chunk);
}
}
this.chunks = straddlers;
this.nodes.northEast = ChunkQuadTreeNode.makeChild(ne, maxChunkNodes);
this.nodes.northWest = ChunkQuadTreeNode.makeChild(nw, maxChunkNodes);
this.nodes.southEast = ChunkQuadTreeNode.makeChild(se, maxChunkNodes);
this.nodes.southWest = ChunkQuadTreeNode.makeChild(sw, maxChunkNodes);
}
static makeChild(bucket, maxChunkNodes) {
if (bucket.length === 0)
return null;
const child = new ChunkQuadTreeNode();
child.chunks = bucket;
child.subdivide(maxChunkNodes);
return child;
}
appendChunk(chunk) {
if (this.isLeaf) {
this.chunks.push(chunk);
return;
}
const { originY, originX } = this;
if (chunk.left >= originX) {
if (chunk.top >= originY) {
this.appendToNode(Quadrant.SouthEast, chunk);
}
else if (chunk.bottom <= originY) {
this.appendToNode(Quadrant.NorthEast, chunk);
}
else {
this.chunks.push(chunk);
}
}
else if (chunk.right <= originX) {
if (chunk.top >= originY) {
this.appendToNode(Quadrant.SouthWest, chunk);
}
else if (chunk.bottom <= originY) {
this.appendToNode(Quadrant.NorthWest, chunk);
}
else {
this.chunks.push(chunk);
}
}
else {
this.chunks.push(chunk);
}
}
appendToNode(quadrant, chunk) {
const node = this.nodes[quadrant];
if (node) {
node.appendChunk(chunk);
}
else {
this.nodes[quadrant] = new ChunkQuadTreeNode(chunk);
}
}
findChunks(aabb, out = []) {
const local = this.chunks;
for (let i = 0, n = local.length; i < n; i++) {
const c = local[i];
if (c.isIntersecting(aabb))
out.push(c);
}
if (this.isNorthWest(aabb))
this.nodes.northWest.findChunks(aabb, out);
if (this.isNorthEast(aabb))
this.nodes.northEast.findChunks(aabb, out);
if (this.isSouthEast(aabb))
this.nodes.southEast.findChunks(aabb, out);
if (this.isSouthWest(aabb))
this.nodes.southWest.findChunks(aabb, out);
return out;
}
isNorthWest(aabb) {
return this.nodes.northWest && aabb.isNorthWest(this.originX, this.originY);
}
isNorthEast(aabb) {
return this.nodes.northEast && aabb.isNorthEast(this.originX, this.originY);
}
isSouthEast(aabb) {
return this.nodes.southEast && aabb.isSouthEast(this.originX, this.originY);
}
isSouthWest(aabb) {
return this.nodes.southWest && aabb.isSouthWest(this.originX, this.originY);
}
findChunksAt(x, y) {
const chunks = this.chunks.filter((chunk) => chunk.containsDataAt(x, y));
if (this.isLeaf)
return chunks;
const child = x < this.originX
? y < this.originY
? this.nodes.northWest
: this.nodes.southWest
: y < this.originY
? this.nodes.northEast
: this.nodes.southEast;
return child === null ? chunks : chunks.concat(child.findChunksAt(x, y));
}
}
//# sourceMappingURL=ChunkQuadTreeNode.js.map