Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions src/main/java/labs/introtoprogramming/lab5/object/Box.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
}
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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.List;

public class OptimizedObject extends SceneObject {
private KDTree tree;

public OptimizedObject(Transform transform, List<SceneObject> objects) {
super(transform);
tree = new KDTree(objects);
}

@Override
public boolean intersect(Ray ray) {
return tree.intersect(ray);
}

@Override
public Box getBoundary() {
return tree.boundary();
}
}
7 changes: 7 additions & 0 deletions src/main/java/labs/introtoprogramming/lab5/object/Sphere.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.subtract(size), pos.add(size));
}
}
21 changes: 21 additions & 0 deletions src/main/java/labs/introtoprogramming/lab5/object/Triangle.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,28 @@ public boolean intersect(Ray ray) {
}

double scale = v0v2.dotProduct(q) * invDet;
if (scale < 0) {
return false;
}
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);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package labs.introtoprogramming.lab5.scene;

import labs.introtoprogramming.lab5.geometry.Ray;
import labs.introtoprogramming.lab5.object.Box;

public class SceneObject {

Expand Down Expand Up @@ -37,4 +38,8 @@ public Mesh getMesh() {
public boolean intersect(Ray ray) {
return false;
}

public Box getBoundary() {
return null;
}
}
60 changes: 60 additions & 0 deletions src/main/java/labs/introtoprogramming/lab5/tree/KDNode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
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<SceneObject> objects;

public KDNode(List<SceneObject> objects, Box boundaryBox, KDNode[] children) {
this.objects = objects;
this.boundaryBox = boundaryBox;
this.children = children;
}

public SceneObject intersect(Ray ray) {
if (boundaryBox.intersect(ray)) {
double minDist = Double.MAX_VALUE;
SceneObject res = checkChildren(ray);
if (res != null) {
minDist = ray.getScale();
}
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 = null;
double minDist = Double.MAX_VALUE;
for (KDNode node : children) {
if (node == null) {
continue;
}
SceneObject obj = node.intersect(ray);
if (obj != null) {
double dist = ray.getScale();
if (dist < minDist) {
res = obj;
minDist = dist;
}
}
}
return res;
}
}
127 changes: 127 additions & 0 deletions src/main/java/labs/introtoprogramming/lab5/tree/KDTree.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
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 final double MIN_NUMBER_OF_OBJECTS = 3;
private KDNode root;
private SceneObject intersection;

public KDTree(List<SceneObject> objects) {
Box box = getBoundary(objects);
root = buildNode(objects, box);
}

private Box getBoundary(List<SceneObject> 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)
);
}
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);
}

private KDNode buildNode(List<SceneObject> 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<SceneObject> 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<SceneObject> getObjectInBox(List<SceneObject> objects, Box box) {
ArrayList<SceneObject> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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());
}
}
Original file line number Diff line number Diff line change
@@ -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<SceneObject> 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);
}
}
Loading