octree implementation
Some checks failed
studiorailgun/Renderer/pipeline/head There was a failure building this commit
Some checks failed
studiorailgun/Renderer/pipeline/head There was a failure building this commit
This commit is contained in:
parent
a125043400
commit
ef0c2ee7ca
@ -1,3 +1,3 @@
|
||||
#maven.buildNumber.plugin properties file
|
||||
#Tue Aug 20 15:21:51 EDT 2024
|
||||
buildNumber=286
|
||||
#Thu Aug 22 17:01:13 EDT 2024
|
||||
buildNumber=287
|
||||
|
||||
19
src/main/java/electrosphere/util/CodeUtils.java
Normal file
19
src/main/java/electrosphere/util/CodeUtils.java
Normal file
@ -0,0 +1,19 @@
|
||||
package electrosphere.util;
|
||||
|
||||
/**
|
||||
* Utilities for making devving faster
|
||||
*/
|
||||
public class CodeUtils {
|
||||
|
||||
/**
|
||||
* Used as placeholder when haven't implemented error handling yet
|
||||
* @param e The exception
|
||||
* @param explanation Any kind of explanation string. Ideally explains what the case is
|
||||
*/
|
||||
public static void todo(Exception e, String explanation){
|
||||
System.out.println("TODO: handle exception");
|
||||
System.out.println(explanation);
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
594
src/main/java/electrosphere/util/ds/Octree.java
Normal file
594
src/main/java/electrosphere/util/ds/Octree.java
Normal file
@ -0,0 +1,594 @@
|
||||
package electrosphere.util.ds;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.joml.Vector3d;
|
||||
|
||||
/**
|
||||
* An octree implementation
|
||||
*/
|
||||
public class Octree<T> {
|
||||
|
||||
/**
|
||||
* The number of children in a full, non-leaf node
|
||||
*/
|
||||
private static final int FULL_NODE_SIZE = 8;
|
||||
|
||||
|
||||
//The root node
|
||||
private OctreeNode<T> root = null;
|
||||
|
||||
/**
|
||||
* Table used for looking up the presence of nodes
|
||||
* Maps a position key to a corresponding Octree node
|
||||
*/
|
||||
Map<String,OctreeNode<T>> lookupTable = new HashMap<String,OctreeNode<T>>();
|
||||
|
||||
/**
|
||||
* Creates an octree
|
||||
* @param center The center of the root node of the octree
|
||||
*/
|
||||
public Octree(Vector3d boundLower, Vector3d boundUpper){
|
||||
root = new OctreeNode<T>(boundLower, boundUpper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a leaf to the octree
|
||||
* @param location The location of the leaf
|
||||
* @param data The data associated with the leaf
|
||||
* @throws IllegalArgumentException Thrown if a location is provided that already has an exact match
|
||||
*/
|
||||
public void addLeaf(Vector3d location, T data) throws IllegalArgumentException {
|
||||
//check if the location is already occupied
|
||||
if(this.containsLeaf(location)){
|
||||
throw new IllegalArgumentException("Tried adding leaf that is already occupied!");
|
||||
}
|
||||
OctreeNode<T> node = new OctreeNode<T>(data, location);
|
||||
OctreeNode<T> current = this.root;
|
||||
while(current.children.size() == FULL_NODE_SIZE){
|
||||
if(current.hasChildInQuadrant(location)){
|
||||
OctreeNode<T> child = current.getChildInQuadrant(location);
|
||||
if(child.isLeaf()){
|
||||
//get parent of the new fork
|
||||
OctreeNode<T> parent = child.parent;
|
||||
parent.removeChild(child);
|
||||
|
||||
//while the newly forked child isn't between the two children, keep forking
|
||||
OctreeNode<T> nonLeaf = parent.forkQuadrant(child.getLocation());
|
||||
while(nonLeaf.getQuadrant(child.getLocation()) == nonLeaf.getQuadrant(node.getLocation())){
|
||||
nonLeaf = nonLeaf.forkQuadrant(child.getLocation());
|
||||
}
|
||||
|
||||
|
||||
//add the child and the new node to the non-leaf node
|
||||
nonLeaf.addChild(child);
|
||||
nonLeaf.addChild(node);
|
||||
this.lookupTable.put(this.getLocationKey(location),node);
|
||||
break;
|
||||
} else {
|
||||
current = child;
|
||||
}
|
||||
} else {
|
||||
current.addChild(node);
|
||||
this.lookupTable.put(this.getLocationKey(location),node);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the octree contains a leaf at an exact location
|
||||
* @param location The location
|
||||
* @return true if a leaf exists for that exact location, false otherwise
|
||||
*/
|
||||
public boolean containsLeaf(Vector3d location){
|
||||
return lookupTable.containsKey(this.getLocationKey(location));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the leaf at an exact location
|
||||
* @param location The location
|
||||
* @return The leaf
|
||||
* @throws ArrayIndexOutOfBoundsException Thrown if a location is queries that does not have an associated leaf in the octree
|
||||
*/
|
||||
public OctreeNode<T> getLeaf(Vector3d location) throws ArrayIndexOutOfBoundsException {
|
||||
if(!this.containsLeaf(location)){
|
||||
throw new ArrayIndexOutOfBoundsException("Tried to get leaf at position that does not contain leaf!");
|
||||
}
|
||||
return lookupTable.get(this.getLocationKey(location));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a node from the octree
|
||||
* @param node The node
|
||||
*/
|
||||
public void removeNode(OctreeNode<T> node){
|
||||
OctreeNode<T> parent = node.parent;
|
||||
parent.removeChild(node);
|
||||
if(node.isLeaf()){
|
||||
this.lookupTable.remove(this.getLocationKey(node.getLocation()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the root node
|
||||
* @return The root node
|
||||
*/
|
||||
public OctreeNode<T> getRoot(){
|
||||
return this.root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of leaves under this tree
|
||||
* @return The number of leaves
|
||||
*/
|
||||
public int getNumLeaves(){
|
||||
return this.root.getNumLeaves();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the key from the location
|
||||
* @param location The location
|
||||
* @return The key
|
||||
*/
|
||||
private String getLocationKey(Vector3d location){
|
||||
return location.x + "_" + location.y + "_" + location.z;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A single node in the octree
|
||||
*/
|
||||
public static class OctreeNode<T> {
|
||||
|
||||
/*
|
||||
6 +----------+ 7
|
||||
/| / |
|
||||
/ | / |
|
||||
/ | / |
|
||||
2 +---------+ 3 + 5
|
||||
| /4 | /
|
||||
| / | /
|
||||
| / | /
|
||||
+---------+
|
||||
0 1
|
||||
|
||||
|
||||
|
||||
Y Z
|
||||
^ /
|
||||
| /
|
||||
|/
|
||||
+---> X
|
||||
|
||||
*/
|
||||
|
||||
//the location of the node
|
||||
private Vector3d midpoint;
|
||||
|
||||
//the bounds of the node
|
||||
private Vector3d lowerBound;
|
||||
private Vector3d upperBound;
|
||||
|
||||
//True if this is a leaf node, false otherwise
|
||||
private boolean isLeaf;
|
||||
|
||||
//the parent node
|
||||
private OctreeNode<T> parent;
|
||||
|
||||
//the children of this node
|
||||
private List<OctreeNode<T>> children = new LinkedList<OctreeNode<T>>();
|
||||
|
||||
//The data at the node
|
||||
private T data;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param data The data at this octree node's position
|
||||
* @param location The location of the node
|
||||
*/
|
||||
private OctreeNode(T data, Vector3d location){
|
||||
this.data = data;
|
||||
this.midpoint = location;
|
||||
this.isLeaf = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a non-leaf node
|
||||
* @param lowerBound The lower bound of the node
|
||||
* @param upperBound The upper bound of the node
|
||||
*/
|
||||
private OctreeNode(Vector3d lowerBound, Vector3d upperBound){
|
||||
this.data = null;
|
||||
this.midpoint = new Vector3d(
|
||||
((upperBound.x - lowerBound.x) / 2.0) + lowerBound.x,
|
||||
((upperBound.y - lowerBound.y) / 2.0) + lowerBound.y,
|
||||
((upperBound.z - lowerBound.z) / 2.0) + lowerBound.z
|
||||
);
|
||||
this.lowerBound = lowerBound;
|
||||
this.upperBound = upperBound;
|
||||
for(int i = 0; i < FULL_NODE_SIZE; i++){
|
||||
this.children.add(null);
|
||||
}
|
||||
this.isLeaf = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a non-leaf node
|
||||
* @param midpoint The midpoint of the node
|
||||
*/
|
||||
private OctreeNode(Vector3d midpoint){
|
||||
this.data = null;
|
||||
this.midpoint = midpoint;
|
||||
for(int i = 0; i < FULL_NODE_SIZE; i++){
|
||||
this.children.add(null);
|
||||
}
|
||||
this.isLeaf = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the data in the node
|
||||
* @return The data
|
||||
*/
|
||||
public T getData(){
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the location of the node
|
||||
* @return The location
|
||||
*/
|
||||
public Vector3d getLocation(){
|
||||
return midpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the parent node of this node
|
||||
* @return The parent node
|
||||
*/
|
||||
public OctreeNode<T> getParent(){
|
||||
return parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the children of this node
|
||||
* @return The children
|
||||
*/
|
||||
public List<OctreeNode<T>> getChildren(){
|
||||
return this.children.stream().filter((node)->{return node != null;}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this node has a child in a given quadrant
|
||||
* @param positionToCheck The position to check
|
||||
* @return true if there is a child in that quadrant, false otherwise
|
||||
*/
|
||||
public boolean hasChildInQuadrant(Vector3d positionToCheck){
|
||||
int positionQuadrant = this.getQuadrant(positionToCheck);
|
||||
OctreeNode<T> child = this.children.get(positionQuadrant);
|
||||
return child != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the child in a given quadrant
|
||||
* @param positionToQuery The position of the quadrant
|
||||
* @return The child if it exists, null otherwise
|
||||
*/
|
||||
public OctreeNode<T> getChildInQuadrant(Vector3d positionToQuery){
|
||||
int positionQuadrant = this.getQuadrant(positionToQuery);
|
||||
OctreeNode<T> child = this.children.get(positionQuadrant);
|
||||
return child;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this is a leaf node or not
|
||||
* @return true if it is a leaf, false otherwise
|
||||
*/
|
||||
public boolean isLeaf(){
|
||||
return this.isLeaf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of child nodes
|
||||
* @return The number of child nodes
|
||||
*/
|
||||
public int getNumChildren(){
|
||||
int acc = 0;
|
||||
for(OctreeNode<T> child : children){
|
||||
if(child != null){
|
||||
acc++;
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of leaves beneath this node
|
||||
* @return The number of leaves
|
||||
*/
|
||||
public int getNumLeaves(){
|
||||
int acc = 0;
|
||||
if(this.isLeaf()){
|
||||
return 1;
|
||||
} else {
|
||||
for(OctreeNode<T> child : this.children){
|
||||
if(child != null){
|
||||
if(child.isLeaf()){
|
||||
acc++;
|
||||
} else {
|
||||
acc = acc + child.getNumLeaves();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a child to this node
|
||||
* @param child The child
|
||||
*/
|
||||
private void addChild(OctreeNode<T> child){
|
||||
if(hasChildInQuadrant(child.midpoint)){
|
||||
throw new IllegalArgumentException("Trying to add child in occupied quadrant!");
|
||||
}
|
||||
int quadrant = this.getQuadrant(child.getLocation());
|
||||
this.children.set(quadrant,child);
|
||||
child.parent = this;
|
||||
this.setChildBounds(child);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a child from this node
|
||||
* @param child The child
|
||||
*/
|
||||
private void removeChild(OctreeNode<T> child){
|
||||
if(child == null){
|
||||
throw new IllegalArgumentException("Child cannot be null!");
|
||||
}
|
||||
int quadrant = this.getQuadrant(child.getLocation());
|
||||
this.children.set(quadrant,null);
|
||||
child.parent = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the quadrant of a position relative to this node
|
||||
* @param positionToCheck The position to check
|
||||
* @return The quadrant
|
||||
*/
|
||||
private int getQuadrant(Vector3d positionToCheck){
|
||||
if(positionToCheck.z < this.midpoint.z){
|
||||
if(positionToCheck.y < this.midpoint.y){
|
||||
if(positionToCheck.x < this.midpoint.x){
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
if(positionToCheck.x < this.midpoint.x){
|
||||
return 2;
|
||||
} else {
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(positionToCheck.y < this.midpoint.y){
|
||||
if(positionToCheck.x < this.midpoint.x){
|
||||
return 4;
|
||||
} else {
|
||||
return 5;
|
||||
}
|
||||
} else {
|
||||
if(positionToCheck.x < this.midpoint.x){
|
||||
return 6;
|
||||
} else {
|
||||
return 7;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the bounds of the child based on the quadrant it falls under
|
||||
* @param child The child
|
||||
*/
|
||||
private void setChildBounds(OctreeNode<T> child){
|
||||
if(child.midpoint.z < this.midpoint.z){
|
||||
if(child.midpoint.y < this.midpoint.y){
|
||||
if(child.midpoint.x < this.midpoint.x){
|
||||
child.lowerBound = new Vector3d(this.lowerBound);
|
||||
child.upperBound = new Vector3d(this.midpoint);
|
||||
} else {
|
||||
child.lowerBound = new Vector3d(
|
||||
this.midpoint.x,
|
||||
this.lowerBound.y,
|
||||
this.lowerBound.z
|
||||
);
|
||||
child.upperBound = new Vector3d(
|
||||
this.upperBound.x,
|
||||
this.midpoint.y,
|
||||
this.midpoint.z
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if(child.midpoint.x < this.midpoint.x){
|
||||
child.lowerBound = new Vector3d(
|
||||
this.lowerBound.x,
|
||||
this.midpoint.y,
|
||||
this.lowerBound.z
|
||||
);
|
||||
child.upperBound = new Vector3d(
|
||||
this.midpoint.x,
|
||||
this.upperBound.y,
|
||||
this.midpoint.z
|
||||
);
|
||||
} else {
|
||||
child.lowerBound = new Vector3d(
|
||||
this.midpoint.x,
|
||||
this.midpoint.y,
|
||||
this.lowerBound.z
|
||||
);
|
||||
child.upperBound = new Vector3d(
|
||||
this.upperBound.x,
|
||||
this.upperBound.y,
|
||||
this.midpoint.z
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(child.midpoint.y < this.midpoint.y){
|
||||
if(child.midpoint.x < this.midpoint.x){
|
||||
child.lowerBound = new Vector3d(
|
||||
this.lowerBound.x,
|
||||
this.lowerBound.y,
|
||||
this.midpoint.z
|
||||
);
|
||||
child.upperBound = new Vector3d(
|
||||
this.midpoint.x,
|
||||
this.midpoint.y,
|
||||
this.upperBound.z
|
||||
);
|
||||
} else {
|
||||
child.lowerBound = new Vector3d(
|
||||
this.midpoint.x,
|
||||
this.lowerBound.y,
|
||||
this.midpoint.z
|
||||
);
|
||||
child.upperBound = new Vector3d(
|
||||
this.upperBound.x,
|
||||
this.midpoint.y,
|
||||
this.upperBound.z
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if(child.midpoint.x < this.midpoint.x){
|
||||
child.lowerBound = new Vector3d(
|
||||
this.lowerBound.x,
|
||||
this.midpoint.y,
|
||||
this.midpoint.z
|
||||
);
|
||||
child.upperBound = new Vector3d(
|
||||
this.midpoint.x,
|
||||
this.upperBound.y,
|
||||
this.upperBound.z
|
||||
);
|
||||
} else {
|
||||
child.lowerBound = new Vector3d(this.midpoint);
|
||||
child.upperBound = new Vector3d(this.upperBound);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the midpoint of a given quadrant
|
||||
* @param quadrant The quadrant
|
||||
* @return The midpoint
|
||||
*/
|
||||
private Vector3d getQuadrantMidpoint(int quadrant){
|
||||
Vector3d lowerBound = null;
|
||||
Vector3d upperBound = null;
|
||||
if(quadrant == 0){
|
||||
lowerBound = new Vector3d(this.lowerBound);
|
||||
upperBound = new Vector3d(this.midpoint);
|
||||
} else if(quadrant == 1) {
|
||||
lowerBound = new Vector3d(
|
||||
this.midpoint.x,
|
||||
this.lowerBound.y,
|
||||
this.lowerBound.z
|
||||
);
|
||||
upperBound = new Vector3d(
|
||||
this.upperBound.x,
|
||||
this.midpoint.y,
|
||||
this.midpoint.z
|
||||
);
|
||||
} else if(quadrant == 2){
|
||||
lowerBound = new Vector3d(
|
||||
this.lowerBound.x,
|
||||
this.midpoint.y,
|
||||
this.lowerBound.z
|
||||
);
|
||||
upperBound = new Vector3d(
|
||||
this.midpoint.x,
|
||||
this.upperBound.y,
|
||||
this.midpoint.z
|
||||
);
|
||||
} else if(quadrant == 3) {
|
||||
lowerBound = new Vector3d(
|
||||
this.midpoint.x,
|
||||
this.midpoint.y,
|
||||
this.lowerBound.z
|
||||
);
|
||||
upperBound = new Vector3d(
|
||||
this.upperBound.x,
|
||||
this.upperBound.y,
|
||||
this.midpoint.z
|
||||
);
|
||||
} else if(quadrant == 4){
|
||||
lowerBound = new Vector3d(
|
||||
this.lowerBound.x,
|
||||
this.lowerBound.y,
|
||||
this.midpoint.z
|
||||
);
|
||||
upperBound = new Vector3d(
|
||||
this.midpoint.x,
|
||||
this.midpoint.y,
|
||||
this.upperBound.z
|
||||
);
|
||||
} else if(quadrant == 5) {
|
||||
lowerBound = new Vector3d(
|
||||
this.midpoint.x,
|
||||
this.lowerBound.y,
|
||||
this.midpoint.z
|
||||
);
|
||||
upperBound = new Vector3d(
|
||||
this.upperBound.x,
|
||||
this.midpoint.y,
|
||||
this.upperBound.z
|
||||
);
|
||||
} else if(quadrant == 6){
|
||||
lowerBound = new Vector3d(
|
||||
this.lowerBound.x,
|
||||
this.midpoint.y,
|
||||
this.midpoint.z
|
||||
);
|
||||
upperBound = new Vector3d(
|
||||
this.midpoint.x,
|
||||
this.upperBound.y,
|
||||
this.upperBound.z
|
||||
);
|
||||
} else if(quadrant == 7) {
|
||||
lowerBound = new Vector3d(this.midpoint);
|
||||
upperBound = new Vector3d(this.upperBound);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Trying to get midpoint of invalid quadrant!" + quadrant);
|
||||
}
|
||||
Vector3d midpoint = new Vector3d(
|
||||
((upperBound.x - lowerBound.x) / 2.0) + lowerBound.x,
|
||||
((upperBound.y - lowerBound.y) / 2.0) + lowerBound.y,
|
||||
((upperBound.z - lowerBound.z) / 2.0) + lowerBound.z
|
||||
);
|
||||
return midpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forks a quadrant
|
||||
* @param location The location within the quadrant
|
||||
* @return The midpoint of the new node
|
||||
*/
|
||||
private OctreeNode<T> forkQuadrant(Vector3d location){
|
||||
int quadrant = this.getQuadrant(location);
|
||||
Vector3d midpoint = this.getQuadrantMidpoint(quadrant);
|
||||
//create and add the non-leaf node
|
||||
OctreeNode<T> nonLeaf = new OctreeNode<T>(midpoint);
|
||||
this.addChild(nonLeaf);
|
||||
return nonLeaf;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
292
src/test/java/electrosphere/util/ds/OctreeTests.java
Normal file
292
src/test/java/electrosphere/util/ds/OctreeTests.java
Normal file
@ -0,0 +1,292 @@
|
||||
package electrosphere.util.ds;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import org.joml.Vector3d;
|
||||
|
||||
import annotations.FastTest;
|
||||
import electrosphere.util.ds.Octree.OctreeNode;
|
||||
|
||||
/**
|
||||
* Unit testing for the octree implementation
|
||||
*/
|
||||
public class OctreeTests {
|
||||
|
||||
/**
|
||||
* Creates an octree
|
||||
*/
|
||||
@FastTest
|
||||
public void testCreateOctree(){
|
||||
new Octree<Integer>(new Vector3d(0,0,0), new Vector3d(32,32,32));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single leaf
|
||||
*/
|
||||
@FastTest
|
||||
public void testAddNode(){
|
||||
Octree<Integer> octree = new Octree<Integer>(new Vector3d(0,0,0), new Vector3d(32,32,32));
|
||||
octree.addLeaf(new Vector3d(1,1,1), 1);
|
||||
//
|
||||
//validate the tree
|
||||
//
|
||||
List<OctreeNode<Integer>> openSet = new LinkedList<OctreeNode<Integer>>();
|
||||
openSet.add(octree.getRoot());
|
||||
while(openSet.size() > 0){
|
||||
OctreeNode<Integer> current = openSet.remove(0);
|
||||
if(current.isLeaf()){
|
||||
assertNotNull(current.getData());
|
||||
} else {
|
||||
assertNull(current.getData());
|
||||
for(OctreeNode<Integer> child : current.getChildren()){
|
||||
if(child != null && child.getParent() != current){
|
||||
fail("Child not attached to parent!");
|
||||
}
|
||||
if(child != null){
|
||||
openSet.add(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add two leaves in a line
|
||||
*/
|
||||
@FastTest
|
||||
public void testAddTwo(){
|
||||
Octree<Integer> octree = new Octree<Integer>(new Vector3d(0,0,0), new Vector3d(32,32,32));
|
||||
octree.addLeaf(new Vector3d(1,1,1), 1);
|
||||
octree.addLeaf(new Vector3d(2,1,1), 2);
|
||||
//
|
||||
//validate the tree
|
||||
//
|
||||
List<OctreeNode<Integer>> openSet = new LinkedList<OctreeNode<Integer>>();
|
||||
openSet.add(octree.getRoot());
|
||||
while(openSet.size() > 0){
|
||||
OctreeNode<Integer> current = openSet.remove(0);
|
||||
if(current.isLeaf()){
|
||||
assertNotNull(current.getData());
|
||||
} else {
|
||||
assertNull(current.getData());
|
||||
for(OctreeNode<Integer> child : current.getChildren()){
|
||||
if(child != null && child.getParent() != current){
|
||||
fail("Child not attached to parent!");
|
||||
}
|
||||
if(child != null){
|
||||
openSet.add(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
//Specific, expected values to check. Verifies that the octree construct itself correctly
|
||||
//
|
||||
OctreeNode<Integer> current = octree.getRoot().getChildren().get(0);
|
||||
assertEquals(8, current.getLocation().x);
|
||||
assertEquals(8, current.getLocation().y);
|
||||
assertEquals(8, current.getLocation().z);
|
||||
assertTrue(!current.isLeaf());
|
||||
assertEquals(1, current.getNumChildren());
|
||||
|
||||
current = current.getChildren().get(0);
|
||||
assertEquals(4, current.getLocation().x);
|
||||
assertEquals(4, current.getLocation().y);
|
||||
assertEquals(4, current.getLocation().z);
|
||||
assertTrue(!current.isLeaf());
|
||||
assertEquals(1, current.getNumChildren());
|
||||
|
||||
current = current.getChildren().get(0);
|
||||
assertEquals(2, current.getLocation().x);
|
||||
assertEquals(2, current.getLocation().y);
|
||||
assertEquals(2, current.getLocation().z);
|
||||
assertTrue(!current.isLeaf());
|
||||
assertEquals(2, current.getNumChildren());
|
||||
|
||||
OctreeNode<Integer> leaf1 = current.getChildren().get(0);
|
||||
OctreeNode<Integer> leaf2 = current.getChildren().get(1);
|
||||
assertTrue(leaf1.isLeaf());
|
||||
assertTrue(leaf2.isLeaf());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a whole bunch of nodes in a line
|
||||
*/
|
||||
@FastTest
|
||||
public void testAddLine(){
|
||||
Octree<Integer> octree = new Octree<Integer>(new Vector3d(0,0,0), new Vector3d(32,32,32));
|
||||
for(int i = 0; i < 31; i++){
|
||||
octree.addLeaf(new Vector3d(i,1,0), i);
|
||||
}
|
||||
//
|
||||
//validate the tree
|
||||
//
|
||||
List<OctreeNode<Integer>> openSet = new LinkedList<OctreeNode<Integer>>();
|
||||
openSet.add(octree.getRoot());
|
||||
while(openSet.size() > 0){
|
||||
OctreeNode<Integer> current = openSet.remove(0);
|
||||
if(current.isLeaf()){
|
||||
assertNotNull(current.getData());
|
||||
} else {
|
||||
assertNull(current.getData());
|
||||
for(OctreeNode<Integer> child : current.getChildren()){
|
||||
if(child != null && child.getParent() != current){
|
||||
fail("Child not attached to parent!");
|
||||
}
|
||||
if(child != null){
|
||||
openSet.add(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single leaf
|
||||
*/
|
||||
@FastTest
|
||||
public void testGetLeaf(){
|
||||
Octree<Integer> octree = new Octree<Integer>(new Vector3d(0,0,0), new Vector3d(32,32,32));
|
||||
octree.addLeaf(new Vector3d(1,1,1), 1);
|
||||
OctreeNode<Integer> leaf = octree.getLeaf(new Vector3d(1,1,1));
|
||||
assertNotNull(leaf);
|
||||
//
|
||||
//validate the tree
|
||||
//
|
||||
List<OctreeNode<Integer>> openSet = new LinkedList<OctreeNode<Integer>>();
|
||||
openSet.add(octree.getRoot());
|
||||
while(openSet.size() > 0){
|
||||
OctreeNode<Integer> current = openSet.remove(0);
|
||||
if(current.isLeaf()){
|
||||
assertNotNull(current.getData());
|
||||
} else {
|
||||
assertNull(current.getData());
|
||||
for(OctreeNode<Integer> child : current.getChildren()){
|
||||
if(child != null && child.getParent() != current){
|
||||
fail("Child not attached to parent!");
|
||||
}
|
||||
if(child != null){
|
||||
openSet.add(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a single leaf
|
||||
*/
|
||||
@FastTest
|
||||
public void testRemoveLeaf(){
|
||||
Octree<Integer> octree = new Octree<Integer>(new Vector3d(0,0,0), new Vector3d(32,32,32));
|
||||
octree.addLeaf(new Vector3d(1,1,1), 1);
|
||||
OctreeNode<Integer> leaf = octree.getLeaf(new Vector3d(1,1,1));
|
||||
assertNotNull(leaf);
|
||||
assertNotNull(leaf.getParent());
|
||||
octree.removeNode(leaf);
|
||||
//
|
||||
//validate the tree
|
||||
//
|
||||
List<OctreeNode<Integer>> openSet = new LinkedList<OctreeNode<Integer>>();
|
||||
openSet.add(octree.getRoot());
|
||||
while(openSet.size() > 0){
|
||||
OctreeNode<Integer> current = openSet.remove(0);
|
||||
if(current.isLeaf()){
|
||||
assertNotNull(current.getData());
|
||||
} else {
|
||||
assertNull(current.getData());
|
||||
for(OctreeNode<Integer> child : current.getChildren()){
|
||||
if(child != null && child.getParent() != current){
|
||||
fail("Child not attached to parent!");
|
||||
}
|
||||
if(child != null){
|
||||
openSet.add(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a leaf from a more complex tree
|
||||
*/
|
||||
@FastTest
|
||||
public void testRemoveLeaf2(){
|
||||
Octree<Integer> octree = new Octree<Integer>(new Vector3d(0,0,0), new Vector3d(32,32,32));
|
||||
octree.addLeaf(new Vector3d(1,1,1), 1);
|
||||
octree.addLeaf(new Vector3d(2,1,1), 2);
|
||||
octree.addLeaf(new Vector3d(3,1,1), 3);
|
||||
OctreeNode<Integer> leaf = octree.getLeaf(new Vector3d(2,1,1));
|
||||
assertNotNull(leaf);
|
||||
assertNotNull(leaf.getParent());
|
||||
|
||||
//removes 2 & 3, but not 1
|
||||
octree.removeNode(leaf.getParent());
|
||||
assertEquals(1, octree.getNumLeaves());
|
||||
|
||||
//remove 1
|
||||
leaf = octree.getLeaf(new Vector3d(1,1,1));
|
||||
assertNotNull(leaf);
|
||||
assertNotNull(leaf.getParent());
|
||||
octree.removeNode(leaf.getParent());
|
||||
assertEquals(0, octree.getNumLeaves());
|
||||
|
||||
//
|
||||
//validate the tree
|
||||
//
|
||||
List<OctreeNode<Integer>> openSet = new LinkedList<OctreeNode<Integer>>();
|
||||
openSet.add(octree.getRoot());
|
||||
while(openSet.size() > 0){
|
||||
OctreeNode<Integer> current = openSet.remove(0);
|
||||
if(current.isLeaf()){
|
||||
assertNotNull(current.getData());
|
||||
} else {
|
||||
assertNull(current.getData());
|
||||
for(OctreeNode<Integer> child : current.getChildren()){
|
||||
if(child != null && child.getParent() != current){
|
||||
fail("Child not attached to parent!");
|
||||
}
|
||||
if(child != null){
|
||||
openSet.add(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds lots of (random) points
|
||||
*/
|
||||
@FastTest
|
||||
public void testAddManyPoints(){
|
||||
Octree<Integer> octree = new Octree<Integer>(new Vector3d(0,0,0), new Vector3d(256,256,256));
|
||||
|
||||
|
||||
//
|
||||
//add points
|
||||
Random rand = new Random();
|
||||
for(int i = 0; i < 1000; i++){
|
||||
Vector3d loc = new Vector3d(
|
||||
rand.nextInt(0, 256),
|
||||
rand.nextInt(0, 256),
|
||||
rand.nextInt(0, 256)
|
||||
);
|
||||
octree.addLeaf(loc, i);
|
||||
}
|
||||
|
||||
//
|
||||
//verify at least one point was added
|
||||
assertEquals(true, octree.getNumLeaves() > 0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user