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
18 changes: 18 additions & 0 deletions src/main/java/engine/processing/ProcessingImageLoader.java
Original file line number Diff line number Diff line change
@@ -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());
}
}
3 changes: 3 additions & 0 deletions src/main/java/engine/scene/Scene.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import engine.scene.camera.Camera;
import engine.scene.light.Light;
import workspace.GraphicsPImpl;
import workspace.ui.Graphics;

/**
Expand Down Expand Up @@ -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);
}
Expand Down
32 changes: 32 additions & 0 deletions src/main/java/engine/scene/camera/Camera.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package engine.scene.camera;

import engine.components.Transform;
import math.Matrix4f;
import math.Vector3f;

/**
Expand Down Expand Up @@ -141,4 +142,35 @@ public interface Camera {
* height).
*/
void setAspectRatio(float aspectRatio);

/**
* Computes the view matrix for this camera.
*
* <p>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.
*
* <p>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.
*
* <p>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();
}
26 changes: 26 additions & 0 deletions src/main/java/engine/scene/camera/PerspectiveCamera.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import engine.components.Transform;
import math.Mathf;
import math.Matrix4;
import math.Matrix4f;
import math.Vector3f;

/**
Expand Down Expand Up @@ -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
}
}
229 changes: 229 additions & 0 deletions src/main/java/math/Matrix4.java
Original file line number Diff line number Diff line change
@@ -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();
}
}
Loading