UNPKG

@spearwolf/twopoint5d

Version:

Create 2.5D realtime graphics and pixelart with WebGL and three.js

229 lines 7.75 kB
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