From f691c40a51585e5e50d4937f2032bb7a8dadf30e Mon Sep 17 00:00:00 2001 From: Brook333 Date: Tue, 11 Jun 2019 13:56:01 +0300 Subject: [PATCH 1/3] feat(tree): add KD-tree --- .../introtoprogramming/lab5/object/Box.java | 15 ++- .../lab5/object/OptimizedObject.java | 27 ++++ .../lab5/object/Sphere.java | 7 + .../lab5/object/Triangle.java | 18 +++ .../lab5/scene/SceneObject.java | 5 + .../introtoprogramming/lab5/tree/KDNode.java | 55 ++++++++ .../introtoprogramming/lab5/tree/KDTree.java | 121 ++++++++++++++++++ .../lab5/tree/KDNodeTests.java | 70 ++++++++++ .../lab5/tree/KDTreeTests.java | 51 ++++++++ 9 files changed, 367 insertions(+), 2 deletions(-) create mode 100644 src/main/java/labs/introtoprogramming/lab5/object/OptimizedObject.java create mode 100644 src/main/java/labs/introtoprogramming/lab5/tree/KDNode.java create mode 100644 src/main/java/labs/introtoprogramming/lab5/tree/KDTree.java create mode 100644 src/test/java/labs/introtoprogramming/lab5/tree/KDNodeTests.java create mode 100644 src/test/java/labs/introtoprogramming/lab5/tree/KDTreeTests.java diff --git a/src/main/java/labs/introtoprogramming/lab5/object/Box.java b/src/main/java/labs/introtoprogramming/lab5/object/Box.java index 88370e5..e95b3de 100644 --- a/src/main/java/labs/introtoprogramming/lab5/object/Box.java +++ b/src/main/java/labs/introtoprogramming/lab5/object/Box.java @@ -9,8 +9,8 @@ public class Box extends SceneObject { private static final double DELTA = 1e-6; private static final double DEFAULT_SIZE = 1; - private Vector3 lowerBounds; - private Vector3 upperBounds; + public Vector3 lowerBounds; + public Vector3 upperBounds; public Box(Transform transform, double size) { super(transform); @@ -20,6 +20,12 @@ public Box(Transform transform, double size) { lowerBounds = pos.add(bound.multiply(-1)); } + public Box(Transform transform, Vector3 lowerBounds, Vector3 upperBounds) { + super(transform); + this.lowerBounds = lowerBounds; + this.upperBounds = upperBounds; + } + public Box(Transform transform) { this(transform, DEFAULT_SIZE); } @@ -88,6 +94,11 @@ public boolean intersect(Ray ray) { return true; } + @Override + public Box getBoundary() { + return new Box(transform, lowerBounds, upperBounds); + } + private double axisDirection(double val) { return Math.abs(val) < DELTA ? val >= 0 ? 1 / DELTA : -1 / DELTA : 1 / val; } diff --git a/src/main/java/labs/introtoprogramming/lab5/object/OptimizedObject.java b/src/main/java/labs/introtoprogramming/lab5/object/OptimizedObject.java new file mode 100644 index 0000000..a8a50e7 --- /dev/null +++ b/src/main/java/labs/introtoprogramming/lab5/object/OptimizedObject.java @@ -0,0 +1,27 @@ +package labs.introtoprogramming.lab5.object; + +import labs.introtoprogramming.lab5.geometry.Ray; +import labs.introtoprogramming.lab5.scene.SceneObject; +import labs.introtoprogramming.lab5.scene.Transform; +import labs.introtoprogramming.lab5.tree.KDTree; + +import java.util.ArrayList; + +public class OptimizedObject extends SceneObject { + private KDTree tree; + + public OptimizedObject(Transform transform, ArrayList objects) { + super(transform); + tree = new KDTree(objects); + } + + @Override + public boolean intersect(Ray ray) { + return tree.intersect(ray); + } + + @Override + public Box getBoundary() { + return tree.boundary(); + } +} diff --git a/src/main/java/labs/introtoprogramming/lab5/object/Sphere.java b/src/main/java/labs/introtoprogramming/lab5/object/Sphere.java index 529bf07..4decaa0 100644 --- a/src/main/java/labs/introtoprogramming/lab5/object/Sphere.java +++ b/src/main/java/labs/introtoprogramming/lab5/object/Sphere.java @@ -48,4 +48,11 @@ public boolean intersect(Ray ray) { ray.setScale(intersection); return true; } + + @Override + public Box getBoundary() { + Vector3 pos = transform.position(); + Vector3 size = Vector3.ONE.multiply(radius); + return new Box(new Transform(), pos.add(size), pos.add(size.multiply(-1))); + } } diff --git a/src/main/java/labs/introtoprogramming/lab5/object/Triangle.java b/src/main/java/labs/introtoprogramming/lab5/object/Triangle.java index dd5c6ae..879b894 100644 --- a/src/main/java/labs/introtoprogramming/lab5/object/Triangle.java +++ b/src/main/java/labs/introtoprogramming/lab5/object/Triangle.java @@ -59,4 +59,22 @@ public boolean intersect(Ray ray) { ray.setScale(scale); return true; } + + @Override + public Box getBoundary() { + Vector3 v0 = transform.applyPoint(this.v0); + Vector3 v1 = transform.applyPoint(this.v1); + Vector3 v2 = transform.applyPoint(this.v2); + Vector3 lower = new Vector3( + Math.min(Math.min(v0.x, v1.x), v2.x), + Math.min(Math.min(v0.y, v1.y), v2.y), + Math.min(Math.min(v0.z, v1.z), v2.z) + ); + Vector3 upper = new Vector3( + Math.max(Math.max(v0.x, v1.x), v2.x), + Math.max(Math.max(v0.y, v1.y), v2.y), + Math.max(Math.max(v0.z, v1.z), v2.z) + ); + return new Box(new Transform(), lower, upper); + } } diff --git a/src/main/java/labs/introtoprogramming/lab5/scene/SceneObject.java b/src/main/java/labs/introtoprogramming/lab5/scene/SceneObject.java index 1af25e1..d2b3b78 100644 --- a/src/main/java/labs/introtoprogramming/lab5/scene/SceneObject.java +++ b/src/main/java/labs/introtoprogramming/lab5/scene/SceneObject.java @@ -1,6 +1,7 @@ package labs.introtoprogramming.lab5.scene; import labs.introtoprogramming.lab5.geometry.Ray; +import labs.introtoprogramming.lab5.object.Box; public class SceneObject { @@ -37,4 +38,8 @@ public Mesh getMesh() { public boolean intersect(Ray ray) { return false; } + + public Box getBoundary() { + return null; + } } diff --git a/src/main/java/labs/introtoprogramming/lab5/tree/KDNode.java b/src/main/java/labs/introtoprogramming/lab5/tree/KDNode.java new file mode 100644 index 0000000..64a209c --- /dev/null +++ b/src/main/java/labs/introtoprogramming/lab5/tree/KDNode.java @@ -0,0 +1,55 @@ +package labs.introtoprogramming.lab5.tree; + +import labs.introtoprogramming.lab5.geometry.Ray; +import labs.introtoprogramming.lab5.object.Box; +import labs.introtoprogramming.lab5.scene.SceneObject; + +import java.util.List; + +public class KDNode { + Box boundaryBox; + private KDNode[] children; + + private List objects; + + public KDNode(List objects, Box boundaryBox, KDNode[] children) { + this.objects = objects; + this.boundaryBox = boundaryBox; + this.children = children; + } + + public SceneObject intersect(Ray ray) { + if (boundaryBox.intersect(ray)) { + SceneObject res = checkChildren(ray); + if (res != null) { + return res; + } + double minDist = Double.MAX_VALUE; + for (SceneObject obj : objects) { + if (obj.intersect(ray)) { + double dist = ray.getScale(); + if (dist < minDist) { + minDist = dist; + res = obj; + } + } + } + return res; + } + return null; + } + + private SceneObject checkChildren(Ray ray) { + SceneObject res; + for (KDNode node : children) { + if (node == null) { + continue; + } + res = node.intersect(ray); + if (res != null) { + return res; + } + } + return null; + } +} diff --git a/src/main/java/labs/introtoprogramming/lab5/tree/KDTree.java b/src/main/java/labs/introtoprogramming/lab5/tree/KDTree.java new file mode 100644 index 0000000..c6f09c1 --- /dev/null +++ b/src/main/java/labs/introtoprogramming/lab5/tree/KDTree.java @@ -0,0 +1,121 @@ +package labs.introtoprogramming.lab5.tree; + +import labs.introtoprogramming.lab5.geometry.Ray; +import labs.introtoprogramming.lab5.geometry.Vector3; +import labs.introtoprogramming.lab5.object.Box; +import labs.introtoprogramming.lab5.scene.SceneObject; +import labs.introtoprogramming.lab5.scene.Transform; + +import java.util.ArrayList; +import java.util.List; + +public class KDTree { + private static double MIN_NUMBER_OF_OBJECTS = 3; + private KDNode root; + private SceneObject intersection; + + public KDTree(List objects) { + Box box = getBoundary(objects); + root = buildNode(objects, box); + } + + private Box getBoundary(List objects) { + if (objects.size() == 0) { + return new Box(new Transform(), Vector3.ZERO, Vector3.ZERO); + } + Vector3 lowerBound = new Vector3(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE); + Vector3 upperBound = new Vector3(Double.MIN_VALUE, Double.MIN_VALUE, Double.MIN_VALUE); + for (SceneObject obj : objects) { + Box boundary = obj.getBoundary(); + if (boundary == null) { + continue; + } + lowerBound = new Vector3( + Math.min(lowerBound.x, boundary.lowerBounds.x), + Math.min(lowerBound.y, boundary.lowerBounds.y), + Math.min(lowerBound.z, boundary.lowerBounds.z) + ); + upperBound = new Vector3( + Math.max(upperBound.x, boundary.upperBounds.x), + Math.max(upperBound.y, boundary.upperBounds.y), + Math.max(upperBound.z, boundary.upperBounds.z) + ); + } + return new Box(new Transform(), lowerBound, upperBound); + } + + private KDNode buildNode(List objects, Box box) { + if (objects.size() <= MIN_NUMBER_OF_OBJECTS) { + return new KDNode(objects, box, new KDNode[0]); + } + KDNode[] children = getChildren(objects, box); + return new KDNode(objects, box, children); + } + + private KDNode[] getChildren(List objects, Box box) { + Vector3 mid = new Vector3( + (box.upperBounds.x + box.lowerBounds.x) / 2, + (box.upperBounds.y + box.lowerBounds.y) / 2, + (box.upperBounds.z + box.lowerBounds.z) / 2 + ); + Vector3 u = box.upperBounds; + Vector3 l = box.lowerBounds; + KDNode[] children = new KDNode[8]; + Box b = new Box(new Transform(), mid, u); + children[0] = buildNode(getObjectInBox(objects, b), b); + b = new Box(new Transform(), new Vector3(l.x, mid.y, mid.z), new Vector3(mid.x, u.y, u.z)); + children[1] = buildNode(getObjectInBox(objects, b), b); + b = new Box(new Transform(), new Vector3(mid.x, l.y, mid.z), new Vector3(u.x, mid.y, u.z)); + children[2] = buildNode(getObjectInBox(objects, b), b); + b = new Box(new Transform(), new Vector3(l.x, l.y, mid.z), new Vector3(mid.x, mid.y, u.z)); + children[3] = buildNode(getObjectInBox(objects, b), b); + b = new Box(new Transform(), new Vector3(mid.x, mid.y, l.z), new Vector3(u.x, u.y, mid.z)); + children[4] = buildNode(getObjectInBox(objects, b), b); + b = new Box(new Transform(), new Vector3(l.x, mid.y, l.z), new Vector3(mid.x, u.y, mid.z)); + children[5] = buildNode(getObjectInBox(objects, b), b); + b = new Box(new Transform(), new Vector3(mid.x, l.y, l.z), new Vector3(u.x, mid.y, mid.z)); + children[6] = buildNode(getObjectInBox(objects, b), b); + b = new Box(new Transform(), l, mid); + children[7] = buildNode(getObjectInBox(objects, b), b); + return children; + } + + private List getObjectInBox(List objects, Box box) { + ArrayList res = new ArrayList<>(); + for (SceneObject obj : objects) { + Box boundary = obj.getBoundary(); + if (boundary == null) { + continue; + } + if (boxInBox(boundary, box)) { + res.add(obj); + } + } + for (SceneObject obj : res) { + objects.remove(obj); + } + return res; + } + + private boolean boxInBox(Box in, Box out) { + return in.upperBounds.x <= out.upperBounds.x + && in.upperBounds.y <= out.upperBounds.y + && in.upperBounds.z <= out.upperBounds.z + && in.lowerBounds.x >= out.lowerBounds.x + && in.lowerBounds.y >= out.lowerBounds.y + && in.lowerBounds.z >= out.lowerBounds.z; + } + + public boolean intersect(Ray ray) { + intersection = root.intersect(ray); + return intersection != null; + } + + public SceneObject getIntersection() { + return intersection; + } + + public Box boundary() { + return root.boundaryBox; + } +} diff --git a/src/test/java/labs/introtoprogramming/lab5/tree/KDNodeTests.java b/src/test/java/labs/introtoprogramming/lab5/tree/KDNodeTests.java new file mode 100644 index 0000000..8a37d68 --- /dev/null +++ b/src/test/java/labs/introtoprogramming/lab5/tree/KDNodeTests.java @@ -0,0 +1,70 @@ +package labs.introtoprogramming.lab5.tree; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import labs.introtoprogramming.lab5.geometry.Ray; +import labs.introtoprogramming.lab5.geometry.Vector3; +import labs.introtoprogramming.lab5.object.Box; +import labs.introtoprogramming.lab5.object.Sphere; +import labs.introtoprogramming.lab5.object.Triangle; +import labs.introtoprogramming.lab5.scene.SceneObject; +import labs.introtoprogramming.lab5.scene.Transform; + +import org.junit.Test; + +public class KDNodeTests { + private static List DUMMY_OBJECTS = Arrays.asList( + new Box(new Transform(), Vector3.ONE, Vector3.ONE.multiply(-1)), + new Triangle(new Transform(), Vector3.RIGHT, Vector3.RIGHT.multiply(-1), Vector3.UP), + new Sphere(new Transform(), 1) + ); + + @Test + public void testInstantiation() { + KDNode node = new KDNode(Collections.emptyList(), + new Box(new Transform(), Vector3.ZERO, Vector3.ZERO), + new KDNode[0]); + assertEquals(node.boundaryBox.upperBounds, Vector3.ZERO); + assertEquals(node.boundaryBox.lowerBounds, Vector3.ZERO); + } + + @Test + public void testIntersectionNoObjects() { + KDNode node = new KDNode(Collections.emptyList(), + new Box(new Transform(), Vector3.ZERO, Vector3.ZERO), + new KDNode[0]); + assertNull(node.intersect(new Ray(Vector3.ZERO, Vector3.ZERO))); + } + + @Test + public void testIntersectionMissBox() { + KDNode node = new KDNode(DUMMY_OBJECTS, + new Box(new Transform(), Vector3.ONE.multiply(-1), Vector3.ZERO), + new KDNode[0]); + assertNull(node.intersect(new Ray(Vector3.FORWARD, Vector3.UP))); + } + + @Test + public void testIntersectionChildIntersection() { + KDNode child = new KDNode(DUMMY_OBJECTS, + new Box(new Transform(), Vector3.ONE.multiply(-5), Vector3.ONE.multiply(5)), + new KDNode[0]); + KDNode node = new KDNode(Collections.emptyList(), + new Box(new Transform(), Vector3.ONE.multiply(-5), Vector3.ONE.multiply(5)), + new KDNode[] { child }); + assertEquals(DUMMY_OBJECTS.get(2), node.intersect(new Ray(Vector3.FORWARD, Vector3.UP))); + } + + @Test + public void testIntersectionOwnObjectIntersection() { + KDNode node = new KDNode(DUMMY_OBJECTS, + new Box(new Transform(), Vector3.ONE.multiply(-5), Vector3.ONE.multiply(5)), + new KDNode[0]); + assertEquals(DUMMY_OBJECTS.get(2), node.intersect(new Ray(Vector3.FORWARD, Vector3.UP))); + } +} diff --git a/src/test/java/labs/introtoprogramming/lab5/tree/KDTreeTests.java b/src/test/java/labs/introtoprogramming/lab5/tree/KDTreeTests.java new file mode 100644 index 0000000..8ec4b51 --- /dev/null +++ b/src/test/java/labs/introtoprogramming/lab5/tree/KDTreeTests.java @@ -0,0 +1,51 @@ +package labs.introtoprogramming.lab5.tree; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import labs.introtoprogramming.lab5.geometry.Ray; +import labs.introtoprogramming.lab5.geometry.Vector3; +import labs.introtoprogramming.lab5.object.Box; +import labs.introtoprogramming.lab5.object.Sphere; +import labs.introtoprogramming.lab5.object.Triangle; +import labs.introtoprogramming.lab5.scene.SceneObject; +import labs.introtoprogramming.lab5.scene.Transform; +import org.junit.Test; + + +public class KDTreeTests { + + private static List DUMMY_OBJECTS = Arrays.asList( + new Box(new Transform(), Vector3.ONE.multiply(-3), Vector3.ONE.multiply(3)), + new Triangle(new Transform(), Vector3.RIGHT, Vector3.RIGHT.multiply(-1), Vector3.UP), + new Sphere(new Transform(), 1) + ); + + @Test + public void testInstantiation() { + KDTree tree = new KDTree(DUMMY_OBJECTS); + Box boundary = tree.boundary(); + assertEquals(boundary.lowerBounds, DUMMY_OBJECTS.get(0).getBoundary().lowerBounds); + assertEquals(boundary.upperBounds, DUMMY_OBJECTS.get(0).getBoundary().upperBounds); + } + + @Test + public void testEmptyList() { + KDTree tree = new KDTree(Collections.emptyList()); + assertFalse(tree.intersect(new Ray(Vector3.ZERO, Vector3.ZERO))); + assertNull(tree.getIntersection()); + } + + @Test + public void test() { + KDTree tree = new KDTree(DUMMY_OBJECTS); + assertTrue(tree.intersect(new Ray(Vector3.ZERO, Vector3.FORWARD))); + assertEquals(DUMMY_OBJECTS.get(1), tree.getIntersection()); + } +} From 457c93fcd2afa0fa00d66a99d706e8c30a3f1ffa Mon Sep 17 00:00:00 2001 From: Brook333 Date: Tue, 11 Jun 2019 14:28:34 +0300 Subject: [PATCH 2/3] fix(tree): fix node check of objects inresection --- .../lab5/object/Triangle.java | 3 +++ .../introtoprogramming/lab5/tree/KDNode.java | 19 ++++++++++++------- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/main/java/labs/introtoprogramming/lab5/object/Triangle.java b/src/main/java/labs/introtoprogramming/lab5/object/Triangle.java index 879b894..042a7e2 100644 --- a/src/main/java/labs/introtoprogramming/lab5/object/Triangle.java +++ b/src/main/java/labs/introtoprogramming/lab5/object/Triangle.java @@ -56,6 +56,9 @@ public boolean intersect(Ray ray) { } double scale = v0v2.dotProduct(q) * invDet; + if (scale < 0) { + return false; + } ray.setScale(scale); return true; } diff --git a/src/main/java/labs/introtoprogramming/lab5/tree/KDNode.java b/src/main/java/labs/introtoprogramming/lab5/tree/KDNode.java index 64a209c..9629b41 100644 --- a/src/main/java/labs/introtoprogramming/lab5/tree/KDNode.java +++ b/src/main/java/labs/introtoprogramming/lab5/tree/KDNode.java @@ -20,11 +20,11 @@ public KDNode(List objects, Box boundaryBox, KDNode[] children) { public SceneObject intersect(Ray ray) { if (boundaryBox.intersect(ray)) { + double minDist = Double.MAX_VALUE; SceneObject res = checkChildren(ray); if (res != null) { - return res; + minDist = ray.getScale(); } - double minDist = Double.MAX_VALUE; for (SceneObject obj : objects) { if (obj.intersect(ray)) { double dist = ray.getScale(); @@ -40,16 +40,21 @@ public SceneObject intersect(Ray ray) { } private SceneObject checkChildren(Ray ray) { - SceneObject res; + SceneObject res = null; + double minDist = Double.MAX_VALUE; for (KDNode node : children) { if (node == null) { continue; } - res = node.intersect(ray); - if (res != null) { - return res; + SceneObject obj = node.intersect(ray); + if (obj != null) { + double dist = ray.getScale(); + if (dist < minDist) { + res = obj; + minDist = dist; + } } } - return null; + return res; } } From 9bbfe3b4140db7e558dbdca125367511e1d11564 Mon Sep 17 00:00:00 2001 From: Brook333 Date: Tue, 11 Jun 2019 15:35:32 +0300 Subject: [PATCH 3/3] refactor(tree): add tests for boundary --- .../lab5/object/OptimizedObject.java | 4 +- .../lab5/object/Sphere.java | 2 +- .../introtoprogramming/lab5/tree/KDTree.java | 8 ++- .../lab5/object/BoxTests.java | 9 +++ .../lab5/object/DiskTests.java | 7 ++ .../lab5/object/OptimizedObjectTests.java | 40 +++++++++++ .../lab5/object/PlaneTests.java | 7 ++ .../lab5/object/SphereTests.java | 8 +++ .../lab5/object/TriangleTests.java | 17 +++++ .../lab5/tree/KDNodeTests.java | 49 ++++++++++++- .../lab5/tree/KDTreeTests.java | 72 ++++++++++++++++++- 11 files changed, 216 insertions(+), 7 deletions(-) create mode 100644 src/test/java/labs/introtoprogramming/lab5/object/OptimizedObjectTests.java diff --git a/src/main/java/labs/introtoprogramming/lab5/object/OptimizedObject.java b/src/main/java/labs/introtoprogramming/lab5/object/OptimizedObject.java index a8a50e7..1415279 100644 --- a/src/main/java/labs/introtoprogramming/lab5/object/OptimizedObject.java +++ b/src/main/java/labs/introtoprogramming/lab5/object/OptimizedObject.java @@ -5,12 +5,12 @@ import labs.introtoprogramming.lab5.scene.Transform; import labs.introtoprogramming.lab5.tree.KDTree; -import java.util.ArrayList; +import java.util.List; public class OptimizedObject extends SceneObject { private KDTree tree; - public OptimizedObject(Transform transform, ArrayList objects) { + public OptimizedObject(Transform transform, List objects) { super(transform); tree = new KDTree(objects); } diff --git a/src/main/java/labs/introtoprogramming/lab5/object/Sphere.java b/src/main/java/labs/introtoprogramming/lab5/object/Sphere.java index 4decaa0..5576e72 100644 --- a/src/main/java/labs/introtoprogramming/lab5/object/Sphere.java +++ b/src/main/java/labs/introtoprogramming/lab5/object/Sphere.java @@ -53,6 +53,6 @@ public boolean intersect(Ray ray) { public Box getBoundary() { Vector3 pos = transform.position(); Vector3 size = Vector3.ONE.multiply(radius); - return new Box(new Transform(), pos.add(size), pos.add(size.multiply(-1))); + return new Box(new Transform(), pos.subtract(size), pos.add(size)); } } diff --git a/src/main/java/labs/introtoprogramming/lab5/tree/KDTree.java b/src/main/java/labs/introtoprogramming/lab5/tree/KDTree.java index c6f09c1..252a7d9 100644 --- a/src/main/java/labs/introtoprogramming/lab5/tree/KDTree.java +++ b/src/main/java/labs/introtoprogramming/lab5/tree/KDTree.java @@ -10,7 +10,7 @@ import java.util.List; public class KDTree { - private static double MIN_NUMBER_OF_OBJECTS = 3; + private static final double MIN_NUMBER_OF_OBJECTS = 3; private KDNode root; private SceneObject intersection; @@ -41,6 +41,12 @@ private Box getBoundary(List objects) { Math.max(upperBound.z, boundary.upperBounds.z) ); } + if (lowerBound.equals(Vector3.ONE.multiply(Double.MAX_VALUE))) { + lowerBound = Vector3.ONE.multiply(Double.MIN_VALUE); + } + if (upperBound.equals(Vector3.ONE.multiply(Double.MIN_VALUE))) { + upperBound = Vector3.ONE.multiply(Double.MAX_VALUE); + } return new Box(new Transform(), lowerBound, upperBound); } diff --git a/src/test/java/labs/introtoprogramming/lab5/object/BoxTests.java b/src/test/java/labs/introtoprogramming/lab5/object/BoxTests.java index ac1334d..5151540 100644 --- a/src/test/java/labs/introtoprogramming/lab5/object/BoxTests.java +++ b/src/test/java/labs/introtoprogramming/lab5/object/BoxTests.java @@ -2,6 +2,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import labs.introtoprogramming.lab5.geometry.Ray; @@ -81,4 +82,12 @@ public void testIntersectionMinusZeroDirection() { Box box = new Box(new Transform(Vector3.RIGHT.multiply(2))); assertFalse(box.intersect(ray)); } + + @Test + public void testBoundary() { + Box box = new Box(new Transform(Vector3.RIGHT.multiply(2))); + Box boundary = box.getBoundary(); + assertEquals(box.upperBounds, boundary.upperBounds); + assertEquals(box.lowerBounds, boundary.lowerBounds); + } } diff --git a/src/test/java/labs/introtoprogramming/lab5/object/DiskTests.java b/src/test/java/labs/introtoprogramming/lab5/object/DiskTests.java index 33ad99a..e3b432c 100644 --- a/src/test/java/labs/introtoprogramming/lab5/object/DiskTests.java +++ b/src/test/java/labs/introtoprogramming/lab5/object/DiskTests.java @@ -2,6 +2,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import labs.introtoprogramming.lab5.geometry.Ray; @@ -43,4 +44,10 @@ public void testGreaterThanRadius() { assertFalse(disk.intersect(ray)); assertEquals(1, ray.getScale(), DELTA); } + + @Test + public void testBoundary() { + Disk disk = new Disk(new Transform()); + assertNull(disk.getBoundary()); + } } diff --git a/src/test/java/labs/introtoprogramming/lab5/object/OptimizedObjectTests.java b/src/test/java/labs/introtoprogramming/lab5/object/OptimizedObjectTests.java new file mode 100644 index 0000000..e803b78 --- /dev/null +++ b/src/test/java/labs/introtoprogramming/lab5/object/OptimizedObjectTests.java @@ -0,0 +1,40 @@ +package labs.introtoprogramming.lab5.object; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.List; + +import labs.introtoprogramming.lab5.geometry.Ray; +import labs.introtoprogramming.lab5.geometry.Vector3; +import labs.introtoprogramming.lab5.scene.SceneObject; +import labs.introtoprogramming.lab5.scene.Transform; +import org.junit.Test; + +public class OptimizedObjectTests { + private static final double DELTA = 1e-10; + + private static List DUMMY_OBJECTS = Arrays.asList( + new Box(new Transform(), Vector3.ONE.multiply(-5), Vector3.ONE.multiply(5)), + new Triangle(new Transform(), Vector3.RIGHT, Vector3.RIGHT.multiply(-1), Vector3.UP), + new Sphere(new Transform(), 1) + ); + + @Test + public void testInstantiation() { + OptimizedObject obj = new OptimizedObject(new Transform(), DUMMY_OBJECTS); + Box boundary = obj.getBoundary(); + Box expected = DUMMY_OBJECTS.get(0).getBoundary(); + assertEquals(expected.lowerBounds, boundary.lowerBounds); + assertEquals(expected.upperBounds, boundary.upperBounds); + } + + @Test + public void testIntersection() { + OptimizedObject obj = new OptimizedObject(new Transform(), DUMMY_OBJECTS); + Ray ray = new Ray(Vector3.FORWARD.multiply(2), Vector3.FORWARD.multiply(-1)); + assertTrue(obj.intersect(ray)); + assertEquals(1, ray.getScale(), DELTA); + } +} diff --git a/src/test/java/labs/introtoprogramming/lab5/object/PlaneTests.java b/src/test/java/labs/introtoprogramming/lab5/object/PlaneTests.java index 3ababdd..8af0ece 100644 --- a/src/test/java/labs/introtoprogramming/lab5/object/PlaneTests.java +++ b/src/test/java/labs/introtoprogramming/lab5/object/PlaneTests.java @@ -2,6 +2,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import labs.introtoprogramming.lab5.geometry.Ray; @@ -43,4 +44,10 @@ public void testBelowRayOrigin() { assertFalse(plane.intersect(ray)); assertEquals(1, ray.getScale(), DELTA); } + + @Test + public void testBoundary() { + Plane plane = new Plane(new Transform()); + assertNull(plane.getBoundary()); + } } diff --git a/src/test/java/labs/introtoprogramming/lab5/object/SphereTests.java b/src/test/java/labs/introtoprogramming/lab5/object/SphereTests.java index 11e0696..38adda2 100644 --- a/src/test/java/labs/introtoprogramming/lab5/object/SphereTests.java +++ b/src/test/java/labs/introtoprogramming/lab5/object/SphereTests.java @@ -43,4 +43,12 @@ public void testIntersectionOppositeDirection() { assertFalse(sphere.intersect(ray)); assertEquals(1, ray.getScale(), DELTA); } + + @Test + public void testBoundary() { + Sphere sphere = new Sphere(new Transform(), 5); + Box boundary = sphere.getBoundary(); + assertEquals(Vector3.ONE.multiply(5), boundary.upperBounds); + assertEquals(Vector3.ONE.multiply(-5), boundary.lowerBounds); + } } diff --git a/src/test/java/labs/introtoprogramming/lab5/object/TriangleTests.java b/src/test/java/labs/introtoprogramming/lab5/object/TriangleTests.java index 5d960d2..7e60794 100644 --- a/src/test/java/labs/introtoprogramming/lab5/object/TriangleTests.java +++ b/src/test/java/labs/introtoprogramming/lab5/object/TriangleTests.java @@ -65,4 +65,21 @@ public void testNoIntersectionOutOfBounds() { assertFalse(triangle.intersect(ray)); assertEquals(1, ray.getScale(), DELTA); } + + @Test + public void testNoIntersectionOppositeDirection() { + Ray ray = new Ray(Vector3.ZERO, Vector3.FORWARD); + Triangle triangle = new Triangle(new Transform(Vector3.FORWARD.multiply(-9)), + Vector3.RIGHT, Vector3.UP, Vector3.RIGHT.multiply(-1)); + assertFalse(triangle.intersect(ray)); + assertEquals(1, ray.getScale(), DELTA); + } + + @Test + public void testBoundary() { + Triangle triangle = new Triangle(new Transform(), Vector3.RIGHT, Vector3.FORWARD, Vector3.UP); + Box boundary = triangle.getBoundary(); + assertEquals(Vector3.ONE, boundary.upperBounds); + assertEquals(Vector3.ZERO, boundary.lowerBounds); + } } diff --git a/src/test/java/labs/introtoprogramming/lab5/tree/KDNodeTests.java b/src/test/java/labs/introtoprogramming/lab5/tree/KDNodeTests.java index 8a37d68..6b4816d 100644 --- a/src/test/java/labs/introtoprogramming/lab5/tree/KDNodeTests.java +++ b/src/test/java/labs/introtoprogramming/lab5/tree/KDNodeTests.java @@ -19,7 +19,7 @@ public class KDNodeTests { private static List DUMMY_OBJECTS = Arrays.asList( - new Box(new Transform(), Vector3.ONE, Vector3.ONE.multiply(-1)), + new Box(new Transform(), Vector3.ONE.multiply(-5), Vector3.ONE.multiply(5)), new Triangle(new Transform(), Vector3.RIGHT, Vector3.RIGHT.multiply(-1), Vector3.UP), new Sphere(new Transform(), 1) ); @@ -67,4 +67,51 @@ public void testIntersectionOwnObjectIntersection() { new KDNode[0]); assertEquals(DUMMY_OBJECTS.get(2), node.intersect(new Ray(Vector3.FORWARD, Vector3.UP))); } + + @Test + public void testIntersectionEmptyChild() { + KDNode child = new KDNode(Collections.emptyList(), + new Box(new Transform(), Vector3.ONE.multiply(-5), Vector3.ONE.multiply(5)), + new KDNode[0]); + KDNode node = new KDNode(Collections.emptyList(), + new Box(new Transform(), Vector3.ONE.multiply(-5), Vector3.ONE.multiply(5)), + new KDNode[] { child }); + assertNull(node.intersect(new Ray(Vector3.FORWARD, Vector3.UP))); + } + + @Test + public void testIntersectionNullChild() { + KDNode node = new KDNode(Collections.emptyList(), + new Box(new Transform(), Vector3.ONE.multiply(-5), Vector3.ONE.multiply(5)), + new KDNode[2]); + assertNull(node.intersect(new Ray(Vector3.FORWARD, Vector3.UP))); + } + + @Test + public void testIntersectionNoChildIntersection() { + KDNode child = new KDNode(DUMMY_OBJECTS, + new Box(new Transform(), Vector3.ONE.multiply(-5), Vector3.ONE.multiply(5)), + new KDNode[0]); + KDNode node = new KDNode(Collections.emptyList(), + new Box(new Transform(), Vector3.ONE.multiply(-5), Vector3.ONE.multiply(5)), + new KDNode[] { child }); + assertNull(node.intersect(new Ray(Vector3.FORWARD.multiply(100), Vector3.FORWARD))); + } + + @Test + public void testIntersectionMultyChildIntersection() { + KDNode first = new KDNode(Collections.singletonList(DUMMY_OBJECTS.get(0)), + new Box(new Transform(), Vector3.ONE.multiply(-5), Vector3.ONE.multiply(5)), + new KDNode[0]); + KDNode second = new KDNode(Collections.singletonList(DUMMY_OBJECTS.get(1)), + new Box(new Transform(), Vector3.ONE.multiply(-5), Vector3.ONE.multiply(5)), + new KDNode[0]); + KDNode third = new KDNode(Collections.singletonList(DUMMY_OBJECTS.get(2)), + new Box(new Transform(), Vector3.ONE.multiply(-5), Vector3.ONE.multiply(5)), + new KDNode[0]); + KDNode node = new KDNode(Collections.emptyList(), + new Box(new Transform(), Vector3.ONE.multiply(-5), Vector3.ONE.multiply(5)), + new KDNode[] { first, second, third }); + assertEquals(DUMMY_OBJECTS.get(2), node.intersect(new Ray(Vector3.FORWARD, Vector3.UP))); + } } diff --git a/src/test/java/labs/introtoprogramming/lab5/tree/KDTreeTests.java b/src/test/java/labs/introtoprogramming/lab5/tree/KDTreeTests.java index 8ec4b51..61d0ae4 100644 --- a/src/test/java/labs/introtoprogramming/lab5/tree/KDTreeTests.java +++ b/src/test/java/labs/introtoprogramming/lab5/tree/KDTreeTests.java @@ -5,6 +5,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertFalse; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -12,6 +13,8 @@ import labs.introtoprogramming.lab5.geometry.Ray; import labs.introtoprogramming.lab5.geometry.Vector3; import labs.introtoprogramming.lab5.object.Box; +import labs.introtoprogramming.lab5.object.Disk; +import labs.introtoprogramming.lab5.object.Plane; import labs.introtoprogramming.lab5.object.Sphere; import labs.introtoprogramming.lab5.object.Triangle; import labs.introtoprogramming.lab5.scene.SceneObject; @@ -27,6 +30,54 @@ public class KDTreeTests { new Sphere(new Transform(), 1) ); + private static List DUMMY_TRIANGLES = Arrays.asList( + new Triangle(new Transform(Vector3.FORWARD.multiply(-3)), Vector3.RIGHT, Vector3.UP, Vector3.RIGHT.multiply(-1)), + new Triangle(new Transform(Vector3.FORWARD.multiply(-16)), Vector3.RIGHT, Vector3.UP, Vector3.RIGHT.multiply(-1)), + new Triangle(new Transform(Vector3.FORWARD.multiply(-9)), Vector3.RIGHT, Vector3.UP, Vector3.RIGHT.multiply(-1)), + new Triangle(new Transform(Vector3.ONE.multiply(-10)), Vector3.RIGHT, Vector3.UP, Vector3.RIGHT.multiply(-1)), + new Triangle(new Transform(Vector3.ONE.multiply(-8)), Vector3.RIGHT, Vector3.UP, Vector3.RIGHT.multiply(-1)), + new Triangle(new Transform(Vector3.FORWARD.multiply(-10).add(Vector3.UP)), Vector3.RIGHT, Vector3.UP, Vector3.RIGHT.multiply(-1)), + new Triangle(new Transform(Vector3.FORWARD.multiply(-10).add(Vector3.UP.multiply(3))), + Vector3.RIGHT, Vector3.UP, Vector3.RIGHT.multiply(-1)), + new Triangle(new Transform(Vector3.FORWARD.multiply(-10).add(Vector3.UP.multiply(-3))), + Vector3.RIGHT, Vector3.UP, Vector3.RIGHT.multiply(-1)), + new Triangle(new Transform(Vector3.FORWARD.multiply(-10).add(Vector3.UP.multiply(-1))), + Vector3.RIGHT, Vector3.UP, Vector3.RIGHT.multiply(-1)), + new Triangle(new Transform(Vector3.FORWARD.multiply(-12)), Vector3.RIGHT, Vector3.UP, Vector3.RIGHT.multiply(-1)), + new Triangle(new Transform(Vector3.FORWARD.multiply(-9)), Vector3.RIGHT, Vector3.UP, Vector3.RIGHT.multiply(-1)), + new Triangle(new Transform(Vector3.FORWARD.multiply(-15)), Vector3.RIGHT, Vector3.UP, Vector3.RIGHT.multiply(-1)), + new Triangle(new Transform(Vector3.FORWARD.multiply(-18)), Vector3.RIGHT, Vector3.UP, Vector3.RIGHT.multiply(-1)), + new Triangle(new Transform(Vector3.ONE.multiply(-9)), Vector3.RIGHT, Vector3.UP, Vector3.RIGHT.multiply(-1)), + new Triangle(new Transform(Vector3.ONE.multiply(-11)), Vector3.RIGHT, Vector3.UP, Vector3.RIGHT.multiply(-1)), + new Triangle(new Transform(Vector3.FORWARD.multiply(-12).add(Vector3.UP.multiply(3))), + Vector3.RIGHT, Vector3.UP, Vector3.RIGHT.multiply(-1)), + new Triangle(new Transform(Vector3.FORWARD.multiply(-12).add(Vector3.UP.multiply(-3))), + Vector3.RIGHT, Vector3.UP, Vector3.RIGHT.multiply(-1)), + new Triangle(new Transform(Vector3.FORWARD.multiply(-12).add(Vector3.UP.multiply(-1))), + Vector3.RIGHT, Vector3.UP, Vector3.RIGHT.multiply(-1)), + new Triangle(new Transform(Vector3.FORWARD.multiply(-12).add(Vector3.UP.multiply(-1))), + Vector3.RIGHT, Vector3.UP, Vector3.RIGHT.multiply(-1)), + new Triangle(new Transform(Vector3.FORWARD.multiply(-12).add(Vector3.UP.multiply(-1))), + Vector3.RIGHT, Vector3.UP, Vector3.RIGHT.multiply(-1)), + new Triangle(new Transform(Vector3.FORWARD.multiply(-12).add(Vector3.UP.multiply(-1))), + Vector3.RIGHT, Vector3.UP, Vector3.RIGHT.multiply(-1)), + new Triangle(new Transform(Vector3.FORWARD.multiply(-12).add(Vector3.UP.multiply(-1))), + Vector3.RIGHT, Vector3.UP, Vector3.RIGHT.multiply(-1)), + new Triangle(new Transform(Vector3.FORWARD.multiply(-12).add(Vector3.UP.multiply(-1))), + Vector3.RIGHT, Vector3.UP, Vector3.RIGHT.multiply(-1)), + new Triangle(new Transform(Vector3.FORWARD.multiply(-12).add(Vector3.UP.multiply(-1))), + Vector3.RIGHT, Vector3.UP, Vector3.RIGHT.multiply(-1)), + new Triangle(new Transform(Vector3.FORWARD.multiply(-12).add(Vector3.UP)), Vector3.RIGHT, + Vector3.UP, Vector3.RIGHT.multiply(-1)) + ); + + private static List DUMMY_OBJECTS_WITHOUT_BOUNDARY = Arrays.asList( + new Plane(new Transform()), + new Disk(new Transform(Vector3.ONE.multiply(-50))), + new Plane(new Transform(Vector3.ONE.multiply(100))), + new Disk(new Transform(Vector3.ONE.multiply(50))) + ); + @Test public void testInstantiation() { KDTree tree = new KDTree(DUMMY_OBJECTS); @@ -43,9 +94,26 @@ public void testEmptyList() { } @Test - public void test() { - KDTree tree = new KDTree(DUMMY_OBJECTS); + public void testIntersection() { + KDTree tree = new KDTree(new ArrayList<>(DUMMY_OBJECTS)); assertTrue(tree.intersect(new Ray(Vector3.ZERO, Vector3.FORWARD))); assertEquals(DUMMY_OBJECTS.get(1), tree.getIntersection()); } + + @Test + public void testBuild() { + KDTree tree = new KDTree(new ArrayList<>(DUMMY_TRIANGLES)); + assertTrue(tree.intersect(new Ray(Vector3.FORWARD, Vector3.FORWARD.multiply(-1)))); + assertEquals(DUMMY_TRIANGLES.get(0), tree.getIntersection()); + } + + @Test + public void testBuildObjectWithoutBoundary() { + KDTree tree = new KDTree(new ArrayList<>(DUMMY_OBJECTS_WITHOUT_BOUNDARY)); + Box boundary = tree.boundary(); + assertEquals(Vector3.ONE.multiply(Double.MAX_VALUE), boundary.upperBounds); + assertEquals(Vector3.ONE.multiply(Double.MIN_VALUE), boundary.lowerBounds); + assertTrue(tree.intersect(new Ray(Vector3.UP.multiply(-2), Vector3.UP.multiply(1)))); + assertEquals(DUMMY_OBJECTS_WITHOUT_BOUNDARY.get(0), tree.getIntersection()); + } }