diff --git a/pom.xml b/pom.xml index 3ac0a2ce..468ca1ff 100644 --- a/pom.xml +++ b/pom.xml @@ -165,6 +165,11 @@ java-data-front 2.0.0 + + com.aparapi + aparapi + 3.0.0 + diff --git a/src/main/java/sh/ball/engine/Camera.java b/src/main/java/sh/ball/engine/Camera.java index 201eb2fe..4aa838b5 100644 --- a/src/main/java/sh/ball/engine/Camera.java +++ b/src/main/java/sh/ball/engine/Camera.java @@ -1,8 +1,5 @@ package sh.ball.engine; -import java.util.HashMap; -import java.util.Map; - import sh.ball.shapes.Line; import sh.ball.shapes.Shape; import sh.ball.shapes.Vector2; @@ -23,6 +20,8 @@ public class Camera { private static final int SAMPLE_RENDER_SAMPLES = 50; private static final int VERTEX_SAMPLES = 1000; + public final CameraDrawKernel kernel = new CameraDrawKernel(); + private double focalLength; private Vector3 pos; @@ -36,7 +35,7 @@ public class Camera { } public List draw(WorldObject worldObject) { - return getFrame(worldObject.getVertexPath()); + return kernel.draw(this, worldObject); } // Automatically finds the correct Z position to use to view the world object properly. @@ -94,24 +93,11 @@ public class Camera { double focal = focalLength; return new Vector2( - vertex.getX() * focal / (vertex.getZ() - pos.getZ()) + pos.getX(), - vertex.getY() * focal / (vertex.getZ() - pos.getZ()) + pos.getY() + vertex.x * focal / (vertex.z - pos.z) + pos.x, + vertex.y * focal / (vertex.z - pos.z) + pos.y ); } - public List getFrame(List vertexPath) { - List lines = new ArrayList<>(); - - for (int i = 0; i < vertexPath.size(); i += 2) { - lines.add(new Line( - project(vertexPath.get(i)), - project(vertexPath.get(i + 1)) - )); - } - - return lines; - } - public void setFocalLength(double focalLength) { this.focalLength = focalLength; } diff --git a/src/main/java/sh/ball/engine/CameraDrawKernel.java b/src/main/java/sh/ball/engine/CameraDrawKernel.java new file mode 100644 index 00000000..43995a06 --- /dev/null +++ b/src/main/java/sh/ball/engine/CameraDrawKernel.java @@ -0,0 +1,127 @@ +package sh.ball.engine; + +import com.aparapi.Kernel; +import com.aparapi.Range; +import com.aparapi.exception.QueryFailedException; +import sh.ball.shapes.Line; +import sh.ball.shapes.Shape; +import sh.ball.shapes.Vector2; + +import java.util.Arrays; +import java.util.List; + +public class CameraDrawKernel extends Kernel { + + private WorldObject prevObject = null; + private float[] vertices; + private float[] vertexResult; + private Shape[] lines; + private float rotationX; + private float rotationY; + private float rotationZ; + private float positionX; + private float positionY; + private float positionZ; + private float cameraPosX; + private float cameraPosY; + private float cameraPosZ; + private float focalLength; + + public CameraDrawKernel() {} + + public List draw(Camera camera, WorldObject object) { + if (prevObject != object) { + List vertices = object.getVertexPath(); + this.vertices = new float[vertices.size() * 3]; + this.vertexResult = new float[vertices.size() * 2]; + this.lines = new Line[vertices.size() / 2]; + for (int i = 0; i < vertices.size(); i++) { + Vector3 vertex = vertices.get(i); + this.vertices[3 * i] = (float) vertex.x; + this.vertices[3 * i + 1] = (float) vertex.y; + this.vertices[3 * i + 2] = (float) vertex.z; + } + } + prevObject = object; + Vector3 rotation = object.getRotation(); + Vector3 position = object.getPosition(); + this.rotationX = (float) rotation.x; + this.rotationY = (float) rotation.y; + this.rotationZ = (float) rotation.z; + this.positionX = (float) position.x; + this.positionY = (float) position.y; + this.positionZ = (float) position.z; + Vector3 cameraPos = camera.getPos(); + this.cameraPosX = (float) cameraPos.x; + this.cameraPosY = (float) cameraPos.y; + this.cameraPosZ = (float) cameraPos.z; + this.focalLength = (float) camera.getFocalLength(); + + int maxGroupSize = 256; + try { + maxGroupSize = getKernelMaxWorkGroupSize(getTargetDevice()); + } catch (QueryFailedException e) { + e.printStackTrace(); + } + + execute(Range.create(roundUp(vertices.length / 3, maxGroupSize), maxGroupSize)); + + for (int i = 0; i < vertices.length / 3; i += 2) { + lines[i / 2] = new Line( + new Vector2(vertexResult[2 * i], vertexResult[2 * i + 1]), + new Vector2(vertexResult[2 * i + 2], vertexResult[2 * i + 3]) + ); + } + + return Arrays.asList(lines); + } + + int roundUp(int round, int multiple) { + if (multiple == 0) { + return round; + } + + int remainder = round % multiple; + if (remainder == 0) { + return round; + } + + return round + multiple - remainder; + } + + @Override + public void run() { + int i = getGlobalId(); + // get vertex + float x = vertices[3 * i]; + float y = vertices[3 * i + 1]; + float z = vertices[3 * i + 2]; + + // rotate around x-axis + float cosValue = cos(rotationX); + float sinValue = sin(rotationX); + float y2 = cosValue * y - sinValue * z; + float z2 = sinValue * y + cosValue * z; + + // rotate around y-axis + cosValue = cos(rotationY); + sinValue = sin(rotationY); + float x2 = cosValue * x + sinValue * z2; + float z3 = -sinValue * x + cosValue * z2; + + // rotate around z-axis + cosValue = cos(rotationZ); + sinValue = sin(rotationZ); + float x3 = cosValue * x2 - sinValue * y2; + float y3 = sinValue * x2 + cosValue * y2; + + // add position + x = x3 + positionX; + y = y3 + positionY; + z = z3 + positionZ; + + // projection + vertexResult[2 * i] = x * focalLength / (z - cameraPosZ) + cameraPosX; + vertexResult[2 * i + 1] = y * focalLength / (z - cameraPosZ) + cameraPosY; + } +} diff --git a/src/main/java/sh/ball/engine/Vector3.java b/src/main/java/sh/ball/engine/Vector3.java index 5372f348..37313b4a 100644 --- a/src/main/java/sh/ball/engine/Vector3.java +++ b/src/main/java/sh/ball/engine/Vector3.java @@ -5,7 +5,7 @@ import java.util.Objects; public final class Vector3 { - private final double x, y, z; + public final double x, y, z; public Vector3(double x, double y, double z) { this.x = x; @@ -21,18 +21,6 @@ public final class Vector3 { this(0, 0, 0); } - public double getX() { - return x; - } - - public double getY() { - return y; - } - - public double getZ() { - return z; - } - public Vector3 add(Vector3 other) { return new Vector3( x + other.x, diff --git a/src/main/java/sh/ball/engine/WorldObject.java b/src/main/java/sh/ball/engine/WorldObject.java index 18edad5d..6215a899 100644 --- a/src/main/java/sh/ball/engine/WorldObject.java +++ b/src/main/java/sh/ball/engine/WorldObject.java @@ -44,13 +44,7 @@ public class WorldObject { } public List getVertexPath() { - List newVertices = new ArrayList<>(); - - for (Vector3 vertex : vertexPath) { - newVertices.add(vertex.rotate(rotation).add(position)); - } - - return newVertices; + return vertexPath; } public void getDrawPath(Set edges) { @@ -89,6 +83,10 @@ public class WorldObject { } } + public Vector3 getRotation() { + return rotation; + } + public void setRotation(Vector3 rotation) { this.rotation = rotation; } @@ -105,6 +103,10 @@ public class WorldObject { position = position.add(translation); } + public Vector3 getPosition() { + return position; + } + public void resetPosition() { position = new Vector3(); } diff --git a/src/main/java/sh/ball/gui/controller/ObjController.java b/src/main/java/sh/ball/gui/controller/ObjController.java index 8501b87a..7c097d14 100644 --- a/src/main/java/sh/ball/gui/controller/ObjController.java +++ b/src/main/java/sh/ball/gui/controller/ObjController.java @@ -124,11 +124,11 @@ public class ObjController implements Initializable, SubController { public Element save(Document document) { Element element = document.createElement("objectRotation"); Element x = document.createElement("x"); - x.appendChild(document.createTextNode(Double.toString(rotation.getX()))); + x.appendChild(document.createTextNode(Double.toString(rotation.x))); Element y = document.createElement("y"); - y.appendChild(document.createTextNode(Double.toString(rotation.getY()))); + y.appendChild(document.createTextNode(Double.toString(rotation.y))); Element z = document.createElement("z"); - z.appendChild(document.createTextNode(Double.toString(rotation.getZ()))); + z.appendChild(document.createTextNode(Double.toString(rotation.z))); element.appendChild(x); element.appendChild(y); element.appendChild(z); diff --git a/src/main/java/sh/ball/shapes/CubicBezierCurve.java b/src/main/java/sh/ball/shapes/CubicBezierCurve.java index 782d5c15..14058939 100644 --- a/src/main/java/sh/ball/shapes/CubicBezierCurve.java +++ b/src/main/java/sh/ball/shapes/CubicBezierCurve.java @@ -7,12 +7,13 @@ public class CubicBezierCurve extends Shape { private final Vector2 p2; private final Vector2 p3; + private double length = INVALID_LENGTH; + public CubicBezierCurve(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3) { this.p0 = p0; this.p1 = p1; this.p2 = p2; this.p3 = p3; - this.length = new Line(p0, p3).length; } @Override @@ -45,4 +46,12 @@ public class CubicBezierCurve extends Shape { return new CubicBezierCurve(p0.translate(vector), p1.translate(vector), p2.translate(vector), p3.translate(vector)); } + + @Override + public double getLength() { + if (length == INVALID_LENGTH) { + length = new Line(p0, p3).getLength(); + } + return length; + } } diff --git a/src/main/java/sh/ball/shapes/Ellipse.java b/src/main/java/sh/ball/shapes/Ellipse.java index 0d6ccc89..21c6b6c6 100644 --- a/src/main/java/sh/ball/shapes/Ellipse.java +++ b/src/main/java/sh/ball/shapes/Ellipse.java @@ -7,13 +7,13 @@ public final class Ellipse extends Shape { private final double rotation; private final Vector2 position; + private double length = INVALID_LENGTH; + public Ellipse(double a, double b, double rotation, Vector2 position) { this.a = a; this.b = b; this.rotation = rotation; this.position = position; - // Approximation of length. - this.length = 2 * Math.PI * Math.sqrt((a * a + b * b) / 2); } public Ellipse(double a, double b, Vector2 position) { @@ -57,4 +57,13 @@ public final class Ellipse extends Shape { public Ellipse translate(Vector2 vector) { return new Ellipse(a, b, rotation, position.translate(vector)); } + + @Override + public double getLength() { + if (length == INVALID_LENGTH) { + // Approximation of length. + length = 2 * Math.PI * Math.sqrt((a * a + b * b) / 2); + } + return length; + } } diff --git a/src/main/java/sh/ball/shapes/Line.java b/src/main/java/sh/ball/shapes/Line.java index 0a740a33..1961a349 100644 --- a/src/main/java/sh/ball/shapes/Line.java +++ b/src/main/java/sh/ball/shapes/Line.java @@ -8,23 +8,17 @@ public final class Line extends Shape { private final Vector2 a; private final Vector2 b; + private double length = INVALID_LENGTH; + public Line(Vector2 a, Vector2 b) { this.a = a; this.b = b; - this.length = calculateLength(); } public Line(double x1, double y1, double x2, double y2) { this(new Vector2(x1, y1), new Vector2(x2, y2)); } - private double calculateLength() { - double ac = Math.abs(getY2() - getY1()); - double cb = Math.abs(getX2() - getX1()); - - return Math.hypot(ac, cb); - } - @Override public Line rotate(double theta) { return new Line(a.rotate(theta), b.rotate(theta)); @@ -35,6 +29,17 @@ public final class Line extends Shape { return new Line(a.translate(vector), b.translate(vector)); } + @Override + public double getLength() { + if (length == INVALID_LENGTH) { + double ac = Math.abs(getY2() - getY1()); + double cb = Math.abs(getX2() - getX1()); + + length = Math.hypot(ac, cb); + } + return length; + } + @Override public Line scale(double factor) { return new Line(a.scale(factor), b.scale(factor)); diff --git a/src/main/java/sh/ball/shapes/Shape.java b/src/main/java/sh/ball/shapes/Shape.java index 7b93d773..cde9f1f1 100644 --- a/src/main/java/sh/ball/shapes/Shape.java +++ b/src/main/java/sh/ball/shapes/Shape.java @@ -8,7 +8,7 @@ import java.util.function.Function; public abstract class Shape { - protected double length; + protected static final double INVALID_LENGTH = -1; public abstract Vector2 nextVector(double drawingProgress); @@ -20,9 +20,7 @@ public abstract class Shape { public abstract Shape translate(Vector2 vector); - public double getLength() { - return length; - } + public abstract double getLength(); /* SHAPE HELPER FUNCTIONS */ diff --git a/src/main/java/sh/ball/shapes/Vector2.java b/src/main/java/sh/ball/shapes/Vector2.java index 1229853a..f1047d60 100644 --- a/src/main/java/sh/ball/shapes/Vector2.java +++ b/src/main/java/sh/ball/shapes/Vector2.java @@ -82,6 +82,11 @@ public final class Vector2 extends Shape { return new Vector2(getX() + vector.getX(), getY() + vector.getY()); } + @Override + public double getLength() { + return 0; + } + @Override public boolean equals(Object obj) { if (this == obj) {