@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
125 lines (124 loc) • 3.98 kB
JavaScript
"use strict";
import { Box3, Vector3 } from "three";
const _position = new Vector3();
export class OctreeNode {
constructor(_bbox, _level = 0) {
this._bbox = _bbox;
this._level = _level;
this._leavesByOctant = {};
this._pointsByOctantId = {};
this._leaves = [];
this._boundingBoxesByOctant = {};
this._boundingBoxesByOctantPrepared = false;
this._center = this._bbox.max.clone().add(this._bbox.min).multiplyScalar(0.5);
}
level() {
return this._level;
}
traverse(callback) {
callback(this);
const octants = Object.values(this._leavesByOctant);
octants.forEach((node) => {
node.traverse(callback);
});
}
intersectsSphere(sphere) {
if (this._bbox) {
return this._bbox.intersectsSphere(sphere);
}
return false;
}
pointsInSphere(sphere, accumulatedPoints) {
if (this._leaves.length == 0) {
const foundPoints = Object.values(this._pointsByOctantId).flat();
const selectedPoints = foundPoints.filter((point) => sphere.containsPoint(point.position(_position)));
selectedPoints.forEach((point) => {
accumulatedPoints.push(point);
});
} else {
const leaves_intersecting_with_sphere = this._leaves.filter((leaf) => leaf.intersectsSphere(sphere));
leaves_intersecting_with_sphere.forEach((leaf) => leaf.pointsInSphere(sphere, accumulatedPoints));
}
}
boundingBox() {
return this._bbox;
}
setPoints(points) {
this._pointsByOctantId = {};
for (const point of points) {
this.addPoint(point);
}
const octantIds = Object.keys(this._pointsByOctantId);
if (octantIds.length > 1) {
for (const octantId of octantIds) {
this.createLeaf(octantId);
}
}
}
createLeaf(octantId) {
const box = this._leafBbox(octantId);
const leaf = new OctreeNode(box, this._level + 1);
this._leavesByOctant[octantId] = leaf;
this._leaves.push(leaf);
leaf.setPoints(this._pointsByOctantId[octantId]);
}
addPoint(point) {
const octantId = this._octantId(point.position(_position));
if (this._pointsByOctantId[octantId] == null) {
this._pointsByOctantId[octantId] = [];
}
this._pointsByOctantId[octantId].push(point);
}
_octantId(position) {
const x_pos = position.x > this._center.x ? 1 : 0;
const y_pos = position.y > this._center.y ? 1 : 0;
const z_pos = position.z > this._center.z ? 1 : 0;
return `${x_pos}${y_pos}${z_pos}`;
}
_leafBbox(octantId) {
if (!this._boundingBoxesByOctantPrepared) {
this._prepareLeavesBboxes();
this._boundingBoxesByOctantPrepared = true;
}
return this._boundingBoxesByOctant[octantId];
}
_bboxCenter(x_pos, y_pos, z_pos, target) {
target.copy(this._bbox.min);
if (x_pos == 1) {
target.x = this._bbox.max.x;
}
if (y_pos == 1) {
target.y = this._bbox.max.y;
}
if (z_pos == 1) {
target.z = this._bbox.max.z;
}
target.add(this._center).multiplyScalar(0.5);
}
_prepareLeavesBboxes() {
const bboxCenters = [
new Vector3(),
new Vector3(),
new Vector3(),
new Vector3(),
new Vector3(),
new Vector3(),
new Vector3(),
new Vector3()
];
this._bboxCenter(0, 0, 0, bboxCenters[0]);
this._bboxCenter(0, 0, 1, bboxCenters[1]);
this._bboxCenter(0, 1, 0, bboxCenters[2]);
this._bboxCenter(0, 1, 1, bboxCenters[3]);
this._bboxCenter(1, 0, 0, bboxCenters[4]);
this._bboxCenter(1, 0, 1, bboxCenters[5]);
this._bboxCenter(1, 1, 0, bboxCenters[6]);
this._bboxCenter(1, 1, 1, bboxCenters[7]);
const bboxSizeQuarter = this._bbox.max.clone().sub(this._bbox.min).multiplyScalar(0.25);
for (const bboxCenter of bboxCenters) {
const octantId = this._octantId(bboxCenter);
const bbox = new Box3(bboxCenter.clone().sub(bboxSizeQuarter), bboxCenter.clone().add(bboxSizeQuarter));
this._boundingBoxesByOctant[octantId] = bbox;
}
}
}