MBHaxe/src/octree/OctreeNode.hx
2022-07-29 21:55:01 +05:30

230 lines
6.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 bounds:Bounds;
/** 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;
}
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 = this.bounds.getSize().multiply(1 / 2);
newNode.bounds = this.bounds.clone();
newNode.bounds.setMin(new Point(this.bounds.xMin
+ newSize.x * ((i & 1) >> 0), this.bounds.yMin
+ newSize.y * ((i & 2) >> 1),
this.bounds.zMin
+ newSize.z * ((i & 4) >> 2)));
newNode.bounds.xSize = newSize.x;
newNode.bounds.ySize = newSize.y;
newNode.bounds.zSize = 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 function largerThan(object:IOctreeObject) {
return this.bounds.containsBounds(object.boundingBox);
// return this.size > (box.xMax - box.xMin) && this.size > (box.yMax - box.yMin) && this.size > (box.zMax - box.zMin);
}
public function containsCenter(object:IOctreeObject) {
return this.bounds.contains(object.boundingBox.getCenter());
}
public function containsPoint(point:Vector) {
return this.bounds.contains(point.toPoint());
}
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 (this.bounds.rayIntersection(ray, true) == -1)
return;
for (obj in this.objects) {
var iSecs = obj.rayCast(rayOrigin, rayDirection);
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(bounds:Bounds, intersections:Array<IOctreeElement>) {
if (this.bounds.collide(bounds)) {
for (obj in this.objects) {
if (obj.boundingBox.collide(bounds))
intersections.push(obj);
}
if (octants != null) {
for (octant in this.octants)
octant.boundingSearch(bounds, intersections);
}
}
}
public function getClosestPoint(point:Vector) {
var closest = new Vector();
if (this.bounds.xMin > point.x)
closest.x = this.bounds.xMin;
else if (this.bounds.xMax < point.x)
closest.x = this.bounds.xMax;
else
closest.x = point.x;
if (this.bounds.yMin > point.y)
closest.y = this.bounds.yMin;
else if (this.bounds.yMax < point.y)
closest.y = this.bounds.yMax;
else
closest.y = point.y;
if (this.bounds.zMin > point.z)
closest.z = this.bounds.zMin;
else if (this.bounds.zMax < point.z)
closest.z = this.bounds.zMax;
else
closest.z = point.z;
return closest;
}
public function getElementType() {
return 1;
}
public function setPriority(priority:Int) {
this.priority = priority;
}
}