MBHaxe/src/octree/OctreeNode.hx
2024-05-09 23:56:16 +05:30

249 lines
7.3 KiB
Haxe

package octree;
import h3d.col.Ray;
import h3d.col.Bounds;
import h3d.col.Point;
import h3d.Vector;
class OctreeNode implements IOctreeElement {
public var octree:Octree;
public var parent:OctreeNode = null;
public var priority:Int;
public var position:Int;
/** The min corner of the bounding box. */
public var xMin:Float;
public var yMin:Float;
public var zMin:Float;
public var xMax:Float;
public var yMax:Float;
public var zMax:Float;
/** The size of the bounding box on all three axes. This forces the bounding box to be a cube. */
public var octants:Array<OctreeNode> = null;
/** A list of objects contained in this node. Note that the node doesn't need to be a leaf node for this set to be non-empty; since this is an octree of bounding boxes, some volumes cannot fit into an octant and therefore need to be stored in the node itself. */
public var objects = new Array<IOctreeObject>();
/** The total number of objects in the subtree with this node as its root. */
public var count = 0;
public var depth:Int;
public function new(octree:Octree, depth:Int) {
this.octree = octree;
this.depth = depth;
this.xMin = 0;
this.yMin = 0;
this.zMin = 0;
this.xMax = 1;
this.yMax = 1;
this.zMax = 1;
}
public function insert(object:IOctreeObject) {
this.count++;
if (this.octants != null) {
if (this.octants[0].largerThan(object)) {
for (i in 0...8) {
var octant = this.octants[i];
if (octant.containsCenter(object)) {
octant.insert(object);
return;
}
}
}
this.objects.push(object);
this.octree.objectToNode.set(object, this);
} else {
this.objects.push(object);
this.octree.objectToNode.set(object, this);
this.split(); // Try splitting this node
}
}
public function split() {
if (this.objects.length <= 8 || this.depth == 8)
return;
this.createOctants();
// Put the objects into the correct octants. Note that all objects that couldn't fit into any octant will remain in the set.
for (object in this.objects) {
if (this.octants[0].largerThan(object)) {
for (i in 0...8) {
var octant = this.octants[i];
if (octant.containsCenter(object)) {
octant.insert(object);
this.objects.remove(object);
}
}
}
}
// Try recursively splitting each octant
for (i in 0...8) {
this.octants[i].split();
}
}
public function createOctants() {
this.octants = [];
for (i in 0...8) {
var newNode = new OctreeNode(this.octree, this.depth + 1);
newNode.parent = this;
var newSize = new Vector(xMax - xMin, yMax - yMin, zMax - zMin);
newNode.xMin = this.xMin + newSize.x * ((i & 1) >> 0);
newNode.yMin = this.yMin + newSize.y * ((i & 2) >> 1);
newNode.zMin = this.zMin + newSize.z * ((i & 4) >> 2);
newNode.xMax = newNode.xMin + newSize.x;
newNode.yMax = newNode.yMin + newSize.y;
newNode.zMax = newNode.zMin + newSize.z;
this.octants.push(newNode);
}
}
// Note: The requirement for this method to be called is that `object` is contained directly in this node.
public function remove(object:IOctreeObject) {
this.objects.remove(object);
this.count--;
this.merge();
// Clean up all ancestors
var node = this.parent;
while (node != null) {
node.count--; // Reduce the count for all ancestor nodes up until the root
node.merge();
node = node.parent;
}
}
public function update(object:IOctreeObject) {
this.objects.remove(object);
var node = this;
while (node != null) {
node.count--;
node.merge();
if (node.largerThan(object) && node.containsCenter(object)) {
node.insert(object);
return true;
}
node = node.parent;
}
return false;
}
public function merge() {
if (this.count > 8 || (this.octants == null))
return;
// Add all objects in the octants back to this node
for (i in 0...8) {
var octant = this.octants[i];
for (object in octant.objects) {
this.objects.push(object);
this.octree.objectToNode.set(object, this);
}
}
this.octants = null; // ...then devare the octants
}
public inline function largerThan(object:IOctreeObject) {
return xMin <= object.boundingBox.xMin && yMin <= object.boundingBox.yMin && zMin <= object.boundingBox.zMin && xMax >= object.boundingBox.xMax
&& yMax >= object.boundingBox.yMax && zMax >= object.boundingBox.zMax;
// return this.size > (box.xMax - box.xMin) && this.size > (box.yMax - box.yMin) && this.size > (box.zMax - box.zMin);
}
public inline function containsCenter(object:IOctreeObject) {
return this.containsPoint2(object.boundingBox.getCenter());
}
public inline function containsPoint(p:Vector) {
return p.x >= xMin && p.x < xMax && p.y >= yMin && p.y < yMax && p.z >= zMin && p.z < zMax;
}
public inline function containsPoint2(p:h3d.col.Point) {
return p.x >= xMin && p.x < xMax && p.y >= yMin && p.y < yMax && p.z >= zMin && p.z < zMax;
}
inline function rayIntersection(r:Ray, bestMatch:Bool):Float {
var minTx = (xMin - r.px) / r.lx;
var minTy = (yMin - r.py) / r.ly;
var minTz = (zMin - r.pz) / r.lz;
var maxTx = (xMax - r.px) / r.lx;
var maxTy = (yMax - r.py) / r.ly;
var maxTz = (zMax - r.pz) / r.lz;
var realMinTx = Math.min(minTx, maxTx);
var realMinTy = Math.min(minTy, maxTy);
var realMinTz = Math.min(minTz, maxTz);
var realMaxTx = Math.max(minTx, maxTx);
var realMaxTy = Math.max(minTy, maxTy);
var realMaxTz = Math.max(minTz, maxTz);
var minmax = Math.min(Math.min(realMaxTx, realMaxTy), realMaxTz);
var maxmin = Math.max(Math.max(realMinTx, realMinTy), realMinTz);
if (minmax < maxmin)
return -1;
return maxmin;
}
public function raycast(rayOrigin:Vector, rayDirection:Vector, intersections:Array<OctreeIntersection>) {
var ray = Ray.fromValues(rayOrigin.x, rayOrigin.y, rayOrigin.z, rayDirection.x, rayDirection.y, rayDirection.z);
// Construct the loose bounding box of this node (2x in size, with the regular bounding box in the center)
if (rayIntersection(ray, true) == -1)
return;
for (obj in this.objects) {
var iSecs = [];
obj.rayCast(rayOrigin, rayDirection, iSecs);
for (intersection in iSecs) {
var intersectionData = new OctreeIntersection();
intersectionData.distance = rayOrigin.distance(intersection.point);
intersectionData.object = intersection.object;
intersectionData.point = intersection.point;
intersectionData.normal = intersection.normal;
intersections.push(intersectionData);
}
}
if (this.octants != null) {
for (i in 0...8) {
var octant = this.octants[i];
octant.raycast(rayOrigin, rayDirection, intersections);
}
}
}
public function boundingSearch(b:Bounds, intersections:Array<IOctreeElement>) {
if (!(xMin > b.xMax || yMin > b.yMax || zMin > b.zMax || xMax < b.xMin || yMax < b.yMin || zMax < b.zMin)) {
for (obj in this.objects) {
if (obj.boundingBox.collide(b))
intersections.push(obj);
}
if (octants != null) {
for (octant in this.octants)
octant.boundingSearch(b, intersections);
}
}
}
public inline function getClosestPoint(point:Vector) {
var closest = new Vector(Math.min(Math.max(this.xMin, point.x), this.xMax), Math.min(Math.max(this.yMin, point.y), this.yMax),
Math.min(Math.max(this.zMin, point.z), this.zMax));
return closest;
}
public function getElementType() {
return 1;
}
public function setPriority(priority:Int) {
this.priority = priority;
}
}