diff --git a/src/main/java/engine/processing/ProcessingImageLoader.java b/src/main/java/engine/processing/ProcessingImageLoader.java new file mode 100644 index 00000000..a79dc130 --- /dev/null +++ b/src/main/java/engine/processing/ProcessingImageLoader.java @@ -0,0 +1,18 @@ +package engine.processing; + +import engine.resources.ImageLoader; +import processing.core.PApplet; + +public class ProcessingImageLoader implements ImageLoader { + PApplet parent; + + public ProcessingImageLoader(PApplet parent) { + this.parent = parent; + } + + @Override + public Object loadImage(String path) { + return parent.loadImage( + ProcessingImageLoader.class.getClassLoader().getResource("images/" + path).getPath()); + } +} diff --git a/src/main/java/engine/scene/Scene.java b/src/main/java/engine/scene/Scene.java index 598e7d7e..d8ccdd64 100644 --- a/src/main/java/engine/scene/Scene.java +++ b/src/main/java/engine/scene/Scene.java @@ -8,6 +8,7 @@ import engine.scene.camera.Camera; import engine.scene.light.Light; +import workspace.GraphicsPImpl; import workspace.ui.Graphics; /** @@ -123,6 +124,8 @@ public void render(Graphics g) { renderLights(g); synchronized (rootNodes) { + GraphicsPImpl.faceCount = 0; + GraphicsPImpl.vertexCount = 0; for (SceneNode node : rootNodes) { node.render(g); } diff --git a/src/main/java/engine/scene/camera/Camera.java b/src/main/java/engine/scene/camera/Camera.java index 66c2e21a..f7f353fc 100644 --- a/src/main/java/engine/scene/camera/Camera.java +++ b/src/main/java/engine/scene/camera/Camera.java @@ -1,6 +1,7 @@ package engine.scene.camera; import engine.components.Transform; +import math.Matrix4f; import math.Vector3f; /** @@ -141,4 +142,35 @@ public interface Camera { * height). */ void setAspectRatio(float aspectRatio); + + /** + * Computes the view matrix for this camera. + * + *

The view matrix represents the transformation that converts world-space coordinates into + * camera-space coordinates, based on the camera's position and orientation. + * + * @return The view matrix as a {@link Matrix4f}. + */ + Matrix4f getViewMatrix(); + + /** + * Computes the projection matrix for this camera. + * + *

The projection matrix defines how the 3D scene is projected onto a 2D screen, taking into + * account the field of view, aspect ratio, and near/far clipping planes. The specific + * implementation depends on whether the camera uses perspective or orthographic projection. + * + * @return The projection matrix as a {@link Matrix4f}. + */ + Matrix4f getProjectionMatrix(); + + /** + * Computes the combined view-projection matrix for this camera. + * + *

The view-projection matrix combines the view and projection transformations into a single + * matrix, allowing objects to be transformed directly from world space to clip space. + * + * @return The combined view-projection matrix as a {@link Matrix4f}. + */ + Matrix4f getViewProjectionMatrix(); } diff --git a/src/main/java/engine/scene/camera/PerspectiveCamera.java b/src/main/java/engine/scene/camera/PerspectiveCamera.java index 677eaa55..be9c2841 100644 --- a/src/main/java/engine/scene/camera/PerspectiveCamera.java +++ b/src/main/java/engine/scene/camera/PerspectiveCamera.java @@ -2,6 +2,8 @@ import engine.components.Transform; import math.Mathf; +import math.Matrix4; +import math.Matrix4f; import math.Vector3f; /** @@ -147,4 +149,28 @@ public float getAspectRatio() { public void setAspectRatio(float aspectRatio) { this.aspectRatio = aspectRatio; } + + @Override + public Matrix4f getViewMatrix() { + Matrix4f view = new Matrix4f(); + view.setViewMatrix(transform.getPosition(), transform.getRotation()); + // FIXME + return view; + } + + @Override + public Matrix4f getProjectionMatrix() { + Matrix4f projection = new Matrix4f(); + Matrix4 m = Matrix4.perspective(fov, aspectRatio, nearPlane, farPlane); + ; + projection = new Matrix4f(m.getValues()); + return projection; + } + + @Override + public Matrix4f getViewProjectionMatrix() { + Matrix4f view = getViewMatrix(); + Matrix4f projection = getProjectionMatrix(); + return projection.multiply(view); // Assuming Matrix4f.multiply() is column-major + } } diff --git a/src/main/java/math/Matrix4.java b/src/main/java/math/Matrix4.java new file mode 100644 index 00000000..ebc0f6fa --- /dev/null +++ b/src/main/java/math/Matrix4.java @@ -0,0 +1,229 @@ +package math; + +import java.util.Arrays; + +// Potential Optimizations loop unrolling +public class Matrix4 { + + // 0 1 2 3 + // 4 5 6 7 + // 8 9 10 11 + // 12 13 14 15 + + // 00 01 02 03 + // 10 11 12 13 + // 20 21 22 23 + // 30 31 32 33 + + private static final float[] IDENTITY_VALUES = + new float[] { + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + }; + + public static final Matrix4 IDENTITY = new Matrix4().setToIdentity(); + + public static final Matrix4 ZERO = new Matrix4(); + + private float[] values; + + public Matrix4() { + this.values = new float[16]; + } + + public Matrix4(float... values) { + setValues(values); + } + + public Matrix4(Matrix4 other) { + this.values = Arrays.copyOf(other.values, 16); + } + + public Matrix4 add(Matrix4 other) { + Matrix4 resultMatrix = new Matrix4(); + for (int i = 0; i < 16; i++) { + resultMatrix.values[i] = this.values[i] + other.values[i]; + } + return resultMatrix; + } + + public Matrix4 addLocal(Matrix4 other) { + for (int i = 0; i < 16; i++) { + this.values[i] += other.values[i]; + } + return this; + } + + public Matrix4 multiply(Matrix4 other) { + Matrix4 result = new Matrix4(); + for (int row = 0; row < 4; row++) { + for (int col = 0; col < 4; col++) { + float sum = 0.0f; + for (int k = 0; k < 4; k++) { + sum += this.values[row * 4 + k] * other.values[k * 4 + col]; + } + result.values[row * 4 + col] = sum; + } + } + return result; + } + + public Matrix4 multiplyLocal(Matrix4 other) { + setValues(multiply(other).values); + return this; + } + + public Vector3f multiply(Vector3f point) { + float x = values[0] * point.x + values[1] * point.y + values[2] * point.z + values[3]; + float y = values[4] * point.x + values[5] * point.y + values[6] * point.z + values[7]; + float z = values[8] * point.x + values[9] * point.y + values[10] * point.z + values[11]; + float w = values[12] * point.x + values[13] * point.y + values[14] * point.z + values[15]; + + // Homogeneous division if using projective transformations + if (w != 0) { + x /= w; + y /= w; + z /= w; + } + return new Vector3f(x, y, z); + } + + public Matrix4 transpose() { + return new Matrix4( + values[0], + values[4], + values[8], + values[12], + values[1], + values[5], + values[9], + values[13], + values[2], + values[6], + values[10], + values[14], + values[3], + values[7], + values[11], + values[15]); + } + + public Matrix4 transposeLocal() { + setValues( + values[0], + values[4], + values[8], + values[12], + values[1], + values[5], + values[9], + values[13], + values[2], + values[6], + values[10], + values[14], + values[3], + values[7], + values[11], + values[15]); + return this; + } + + public Matrix4 setToIdentity() { + Arrays.fill(values, 0); + values[0] = values[5] = values[10] = values[15] = 1; + return this; + } + + public float[] getValues() { + return Arrays.copyOf(values, values.length); + } + + public void setValues(float... values) { + if (values.length != 16) { + throw new IllegalArgumentException("Matrix4 must have 16 elements."); + } + this.values = values; + } + + public boolean isIdentity() { + return Arrays.equals(values, IDENTITY_VALUES); + } + + public static Matrix4 perspective(float fov, float aspect, float zNear, float zFar) { + validateNearFarPlanes(zNear, zFar); + + float tanHalfFOV = (float) Math.tan(fov / 2); + Matrix4 matrix = new Matrix4(); + + matrix.values[0] = 1 / (tanHalfFOV * aspect); + matrix.values[5] = -1 / tanHalfFOV; // Note the negative sign to flip the Y-axis. + matrix.values[10] = -(zFar + zNear) / (zFar - zNear); + matrix.values[11] = -(2 * zFar * zNear) / (zFar - zNear); + matrix.values[14] = -1; + matrix.values[15] = 0; + + return matrix; + } + + public static Matrix4 lookAt(Vector3f eye, Vector3f center, Vector3f up) { + Vector3f forward = eye.subtract(center).normalize(); + Vector3f right = up.cross(forward).normalize(); + Vector3f cameraUp = forward.cross(right); + + Matrix4 result = new Matrix4(); + result.values[0] = right.x; + result.values[1] = right.y; + result.values[2] = right.z; + result.values[3] = -right.dot(eye); + + result.values[4] = cameraUp.x; + result.values[5] = cameraUp.y; + result.values[6] = cameraUp.z; + result.values[7] = -cameraUp.dot(eye); + + result.values[8] = -forward.x; + result.values[9] = -forward.y; + result.values[10] = -forward.z; + result.values[11] = -forward.dot(eye); + + result.values[12] = 0; + result.values[13] = 0; + result.values[14] = 0; + result.values[15] = 1; + + return result; + } + + private static void validateNearFarPlanes(float zNear, float zFar) { + if (zNear <= 0 || zFar <= 0) { + throw new IllegalArgumentException("Near and far planes must be positive values."); + } + + if (zNear > zFar) { + throw new IllegalArgumentException( + String.format("Near plane (%.2f) cannot be greater than far plane (%.2f).", zNear, zFar)); + } + + if (zNear == zFar) { + throw new IllegalArgumentException( + String.format("Near plane (%.2f) cannot be equal to far plane (%.2f).", zNear, zFar)); + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 4; i++) { + sb.append("["); + for (int j = 0; j < 4; j++) { + sb.append(values[i * 4 + j]); + if (j < 3) sb.append(", "); + } + sb.append("]\n"); + } + return sb.toString(); + } +}