diff --git a/src/main/java/engine/components/FlyByCameraControl.java b/src/main/java/engine/components/FlyByCameraControl.java index d96751bd..e4ce9aa5 100644 --- a/src/main/java/engine/components/FlyByCameraControl.java +++ b/src/main/java/engine/components/FlyByCameraControl.java @@ -78,13 +78,12 @@ public void update(float tpf) { float mouseY = input.getMouseDeltaY() * mouseSensitivity * tpf; handleRotation(mouseX, mouseY); - updateTarget(); Vector3f velocity = calculateVelocity(); if (velocity.length() > 0) { applyMovement(velocity, tpf); } - + updateTarget(); input.center(); } @@ -147,7 +146,6 @@ private void applyMovement(Vector3f velocity, float tpf) { Vector3f position = camera.getTransform().getPosition(); position.addLocal(velocity.mult(moveSpeed * tpf)); camera.getTransform().setPosition(position); - updateTarget(); } /** @@ -180,4 +178,46 @@ public void onAttach() { public void onDetach() { // Not used yet } + + /** + * Returns the current movement speed of the camera. + * + * @return The movement speed in units per second. + */ + public float getMoveSpeed() { + return moveSpeed; + } + + /** + * Sets the movement speed of the camera. + * + * @param moveSpeed The new movement speed in units per second. + */ + public void setMoveSpeed(float moveSpeed) { + this.moveSpeed = moveSpeed; + } + + /** + * Returns the current mouse sensitivity used for camera rotation. + * + *
The mouse sensitivity determines how much the camera rotates based on mouse movement. Higher + * sensitivity values result in larger rotations for smaller mouse movements. + * + * @return The current mouse sensitivity. + */ + public float getMouseSensitivity() { + return mouseSensitivity; + } + + /** + * Sets the mouse sensitivity used for camera rotation. + * + *
The mouse sensitivity determines how much the camera rotates based on mouse movement. Higher
+ * sensitivity values result in larger rotations for smaller mouse movements.
+ *
+ * @param mouseSensitivity The new mouse sensitivity value.
+ */
+ public void setMouseSensitivity(float mouseSensitivity) {
+ this.mouseSensitivity = mouseSensitivity;
+ }
}
diff --git a/src/main/java/engine/processing/ProcessingApplication.java b/src/main/java/engine/processing/ProcessingApplication.java
index 8ba85e30..2b755112 100644
--- a/src/main/java/engine/processing/ProcessingApplication.java
+++ b/src/main/java/engine/processing/ProcessingApplication.java
@@ -6,6 +6,7 @@
import engine.input.KeyInput;
import engine.input.MouseInput;
import engine.resources.ResourceManager;
+import engine.resources.TextureManager;
import processing.core.PApplet;
import workspace.GraphicsPImpl;
import workspace.ui.Graphics;
@@ -31,6 +32,7 @@ public void settings() {
public void setup() {
Graphics g = new GraphicsPImpl(this);
ResourceManager.getInstance().setImageLoader(new ProcessingImageLoader(this));
+ TextureManager.getInstance().setTextureLoader(new ProcessingTextureLoader(this));
container.setGraphics(g);
getSurface().setTitle(settings.getTitle());
setupInput();
diff --git a/src/main/java/engine/processing/ProcessingTexture.java b/src/main/java/engine/processing/ProcessingTexture.java
new file mode 100644
index 00000000..b350bc2a
--- /dev/null
+++ b/src/main/java/engine/processing/ProcessingTexture.java
@@ -0,0 +1,43 @@
+package engine.processing;
+
+import engine.resources.Texture;
+import processing.core.PImage;
+
+public class ProcessingTexture implements Texture {
+
+ private final PImage image;
+
+ public ProcessingTexture(PImage image) {
+ this.image = image;
+ }
+
+ @Override
+ public int getWidth() {
+ return image.width;
+ }
+
+ @Override
+ public int getHeight() {
+ return image.height;
+ }
+
+ @Override
+ public void bind(int unit) {
+ // Processing doesn't use texture units in the same way, just bind globally
+ image.loadPixels();
+ }
+
+ @Override
+ public void unbind() {
+ // No specific unbind operation for Processing
+ }
+
+ @Override
+ public void delete() {
+ // Processing handles memory management automatically
+ }
+
+ public PImage getImage() {
+ return image;
+ }
+}
diff --git a/src/main/java/engine/processing/ProcessingTextureLoader.java b/src/main/java/engine/processing/ProcessingTextureLoader.java
new file mode 100644
index 00000000..b1daa208
--- /dev/null
+++ b/src/main/java/engine/processing/ProcessingTextureLoader.java
@@ -0,0 +1,27 @@
+package engine.processing;
+
+import engine.resources.Texture;
+import engine.resources.TextureLoader;
+import processing.core.PApplet;
+import processing.core.PImage;
+
+public class ProcessingTextureLoader implements TextureLoader {
+
+ private final PApplet parent;
+
+ public ProcessingTextureLoader(PApplet parent) {
+ this.parent = parent;
+ }
+
+ @Override
+ public Texture loadTexture(String filePath) {
+ PImage image =
+ parent.loadImage(
+ ProcessingTextureLoader.class
+ .getClassLoader()
+ .getResource("images/" + filePath)
+ .getPath());
+ ProcessingTexture texture = new ProcessingTexture(image);
+ return texture;
+ }
+}
diff --git a/src/main/java/engine/render/Material.java b/src/main/java/engine/render/Material.java
index 3793d9d1..40debe56 100644
--- a/src/main/java/engine/render/Material.java
+++ b/src/main/java/engine/render/Material.java
@@ -1,5 +1,6 @@
package engine.render;
+import engine.resources.Texture;
import math.Color;
import workspace.ui.Graphics;
@@ -67,6 +68,10 @@ public class Material {
/** Shininess factor for specular highlights. */
private final float shininess;
+ private Texture normalTexture;
+
+ private Texture diffuseTexture;
+
/**
* Constructor to set the base color of the material.
*
@@ -83,11 +88,13 @@ private Material(Builder builder) {
this.diffuse = builder.diffuse;
this.specular = builder.specular;
this.shininess = builder.shininess;
+ this.normalTexture = builder.normalTexture;
+ this.diffuseTexture = builder.diffuseTexture;
}
/**
- * Builder class to facilitate the creation of custom materials with specific lighting and shader
- * properties.
+ * Builder class to facilitate the creation of custom materials with specific lighting, shader and
+ * texture properties.
*/
public static class Builder {
@@ -103,6 +110,10 @@ public static class Builder {
private float shininess = 10.0f;
+ private Texture diffuseTexture = null;
+
+ private Texture normalTexture = null;
+
/**
* Sets the base color of the material.
*
@@ -163,6 +174,28 @@ public Builder setUseLighting(boolean useLighting) {
return this;
}
+ /**
+ * Sets the normal texture of the material.
+ *
+ * @param normalTexture The normal texture, can be null
+ * @return The builder instance for chaining
+ */
+ public Builder setNormalTexture(Texture normalTexture) {
+ this.normalTexture = normalTexture;
+ return this;
+ }
+
+ /**
+ * Sets the diffuse texture of the material.
+ *
+ * @param diffuseTexture The diffuse texture, can be null.
+ * @return The builder instance for chaining
+ */
+ public Builder setDiffuseTexture(Texture diffuseTexture) {
+ this.diffuseTexture = diffuseTexture;
+ return this;
+ }
+
/**
* Builds and returns the Material instance with the set properties.
*
@@ -180,6 +213,13 @@ public Material build() {
*/
public void apply(Graphics g) {
g.setMaterial(this);
+
+ if (diffuseTexture != null) {
+ g.bindTexture(diffuseTexture, 0); // Bind to texture unit 0
+ }
+ if (normalTexture != null) {
+ g.bindTexture(normalTexture, 1); // Bind to texture unit 1
+ }
}
/**
@@ -240,4 +280,12 @@ public float[] getSpecular() {
public float getShininess() {
return shininess;
}
+
+ public Texture getDiffuseTexture() {
+ return diffuseTexture;
+ }
+
+ public Texture getNormalTexture() {
+ return normalTexture;
+ }
}
diff --git a/src/main/java/engine/resources/Texture.java b/src/main/java/engine/resources/Texture.java
new file mode 100644
index 00000000..53857bc4
--- /dev/null
+++ b/src/main/java/engine/resources/Texture.java
@@ -0,0 +1,14 @@
+package engine.resources;
+
+public interface Texture {
+
+ int getWidth();
+
+ int getHeight();
+
+ void bind(int unit); // Bind to a specific texture unit
+
+ void unbind();
+
+ void delete();
+}
diff --git a/src/main/java/engine/resources/TextureLoader.java b/src/main/java/engine/resources/TextureLoader.java
new file mode 100644
index 00000000..6ea10108
--- /dev/null
+++ b/src/main/java/engine/resources/TextureLoader.java
@@ -0,0 +1,6 @@
+package engine.resources;
+
+public interface TextureLoader {
+
+ Texture loadTexture(String filePath);
+}
diff --git a/src/main/java/engine/resources/TextureManager.java b/src/main/java/engine/resources/TextureManager.java
new file mode 100644
index 00000000..bc70210e
--- /dev/null
+++ b/src/main/java/engine/resources/TextureManager.java
@@ -0,0 +1,45 @@
+package engine.resources;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class TextureManager {
+
+ private static TextureManager instance;
+
+ private TextureLoader imageLoader;
+
+ private final Map This method performs a matrix-vector multiplication, treating this vector as a column vector
+ * and applying the transformation defined by the given 4x4 matrix. The resulting vector
+ * represents the transformed coordinates.
+ *
+ * The operation is mathematically equivalent to:
+ *
+ * This method sets the list of UV coordinates that will be used for the mesh. The provided
+ * list of UV coordinates will replace any existing UVs. The list must be a valid {@link
+ * ArrayList} of {@link Vector2f} objects. If the provided list is {@code null}, an {@link
+ * IllegalArgumentException} will be thrown.
+ *
+ * @param uvs The list of UV coordinates to be set. It must not be {@code null} and should contain
+ * {@link Vector2f} objects representing the UV mapping for the mesh.
+ * @throws IllegalArgumentException if the provided {@code uvs} list is {@code null}.
+ */
+ public void setUvs(ArrayList This method returns the UV coordinates associated with the given index. If the index is out
+ * of bounds (either negative or beyond the size of the list), a default UV coordinate (0, 0) is
+ * returned to avoid potential errors or exceptions. The method does not throw an exception when
+ * an invalid index is provided, ensuring that the calling code can proceed without disruption.
+ *
+ * @param index The index of the UV coordinate to retrieve.
+ * @return The UV coordinates as a {@link Vector2f}. If the index is out of bounds, returns {@code
+ * new Vector2f(0, 0)}. The return value will never be {@code null}.
+ */
+ public Vector2f getUvAt(int index) {
+ if (index < 0 || index >= uvs.size()) {
+ return new Vector2f(0, 0);
+ }
+ return uvs.get(index);
+ }
}
diff --git a/src/main/java/mesh/creator/assets/ArchCreator.java b/src/main/java/mesh/creator/assets/ArchCreator.java
index a865597e..047603e7 100644
--- a/src/main/java/mesh/creator/assets/ArchCreator.java
+++ b/src/main/java/mesh/creator/assets/ArchCreator.java
@@ -5,194 +5,191 @@
import mesh.Mesh3D;
import mesh.creator.IMeshCreator;
import mesh.modifier.SolidifyModifier;
+import mesh.modifier.TranslateModifier;
public class ArchCreator implements IMeshCreator {
- private int segments;
+ private int segments;
- private float radius;
+ private float radius;
- private float extendTop;
+ private float extendTop;
- private float extendBottom;
+ private float extendBottom;
- private float extendLeft;
+ private float extendLeft;
- private float extendRight;
+ private float extendRight;
- private float depth;
+ private float depth;
- private Mesh3D mesh;
-
- public ArchCreator() {
- segments = 15;
- radius = 1;
- extendTop = 0.5f;
- extendBottom = 2;
- extendLeft = 1;
- extendRight = 1;
- depth = 1;
- }
-
- @Override
- public Mesh3D create() {
- initializeMesh();
- createVertices();
- createFaces();
- createArc();
- solidify();
- snapToGround();
- return mesh;
- }
-
- private void createVertices() {
- createLeftVertices();
- createRightVertices();
- }
-
- private void createFaces() {
- createLeftFace();
- createRightFace();
- }
-
- private void createArc() {
- float extendStep = calculateWidth() / segments;
- float offsetLeft = -radius - extendLeft;
-
- for (int i = 0; i <= segments; i++) {
- float x = offsetLeft + (i * extendStep);
- Vector3f v1 = new Vector3f(x, -radius - extendTop, 0);
- Vector3f v0 = createPointOnCircleAt(i);
-
- if (i > 0 && i < segments)
- v1.setX(v0.getX());
-
- addFaceAt(i);
- mesh.add(v0);
- mesh.add(v1);
- }
- }
-
- private Vector3f createPointOnCircleAt(int i) {
- float angle = Mathf.PI + (i * (Mathf.PI / segments));
- return pointOnCircle(angle);
- }
-
- private Vector3f pointOnCircle(float angrad) {
- float x = radius * Mathf.cos(angrad);
- float y = radius * Mathf.sin(angrad);
- return new Vector3f(x, y, 0);
- }
-
- private float calculateWidth() {
- return radius + radius + extendLeft + extendRight;
- }
-
- private void snapToGround() {
- mesh.translateY(-extendBottom);
- mesh.translateZ(depth / 2f);
- }
-
- private void initializeMesh() {
- mesh = new Mesh3D();
- }
-
- private void solidify() {
- new SolidifyModifier(depth).modify(mesh);
- }
-
- private void createLeftFace() {
- mesh.addFace(0, 1, 5, 4);
- }
-
- private void createRightFace() {
- int a = 2 * (segments + 1) + 4;
- addFace(3, 2, a - 2, a - 1);
- }
-
- private void addFace(int... indices) {
- mesh.addFace(indices);
- }
-
- private void createLeftVertices() {
- addVertex(-radius, extendBottom, 0);
- addVertex(-radius - extendLeft, extendBottom, 0);
- }
-
- private void createRightVertices() {
- addVertex(radius, extendBottom, 0);
- addVertex(radius + extendRight, extendBottom, 0);
- }
-
- private void addFaceAt(int i) {
- if (i >= segments)
- return;
- int index = (i * 2) + 4;
- int index0 = index;
- int index1 = index + 1;
- int index2 = index + 3;
- int index3 = index + 2;
- mesh.addFace(index0, index1, index2, index3);
- }
-
- private void addVertex(float x, float y, float z) {
- mesh.addVertex(x, y, z);
- }
-
- public int getSegments() {
- return segments;
- }
-
- public void setSegments(int segments) {
- this.segments = segments;
- }
-
- public float getRadius() {
- return radius;
- }
-
- public void setRadius(float radius) {
- this.radius = radius;
- }
-
- public float getExtendTop() {
- return extendTop;
- }
-
- public void setExtendTop(float extendTop) {
- this.extendTop = extendTop;
- }
-
- public float getExtendBottom() {
- return extendBottom;
- }
-
- public void setExtendBottom(float extendBottom) {
- this.extendBottom = extendBottom;
- }
-
- public float getExtendLeft() {
- return extendLeft;
- }
-
- public void setExtendLeft(float extendLeft) {
- this.extendLeft = extendLeft;
- }
-
- public float getExtendRight() {
- return extendRight;
- }
-
- public void setExtendRight(float extendRight) {
- this.extendRight = extendRight;
- }
-
- public float getDepth() {
- return depth;
- }
-
- public void setDepth(float depth) {
- this.depth = depth;
- }
+ private Mesh3D mesh;
+ public ArchCreator() {
+ segments = 15;
+ radius = 1;
+ extendTop = 0.5f;
+ extendBottom = 2;
+ extendLeft = 1;
+ extendRight = 1;
+ depth = 1;
+ }
+
+ @Override
+ public Mesh3D create() {
+ initializeMesh();
+ createVertices();
+ createFaces();
+ createArc();
+ solidify();
+ snapToGroundAndCenterZ();
+ return mesh;
+ }
+
+ private void createVertices() {
+ createLeftVertices();
+ createRightVertices();
+ }
+
+ private void createFaces() {
+ createLeftFace();
+ createRightFace();
+ }
+
+ private void createArc() {
+ float extendStep = calculateWidth() / segments;
+ float offsetLeft = -radius - extendLeft;
+
+ for (int i = 0; i <= segments; i++) {
+ float x = offsetLeft + (i * extendStep);
+ Vector3f v1 = new Vector3f(x, -radius - extendTop, 0);
+ Vector3f v0 = createPointOnCircleAt(i);
+
+ if (i > 0 && i < segments) v1.setX(v0.getX());
+
+ addFaceAt(i);
+ mesh.add(v0);
+ mesh.add(v1);
+ }
+ }
+
+ private Vector3f createPointOnCircleAt(int i) {
+ float angle = Mathf.PI + (i * (Mathf.PI / segments));
+ return pointOnCircle(angle);
+ }
+
+ private Vector3f pointOnCircle(float angrad) {
+ float x = radius * Mathf.cos(angrad);
+ float y = radius * Mathf.sin(angrad);
+ return new Vector3f(x, y, 0);
+ }
+
+ private float calculateWidth() {
+ return radius + radius + extendLeft + extendRight;
+ }
+
+ private void snapToGroundAndCenterZ() {
+ mesh.apply(new TranslateModifier(0, -extendBottom, depth / 2f));
+ }
+
+ private void initializeMesh() {
+ mesh = new Mesh3D();
+ }
+
+ private void solidify() {
+ new SolidifyModifier(depth).modify(mesh);
+ }
+
+ private void createLeftFace() {
+ mesh.addFace(0, 1, 5, 4);
+ }
+
+ private void createRightFace() {
+ int a = 2 * (segments + 1) + 4;
+ addFace(3, 2, a - 2, a - 1);
+ }
+
+ private void addFace(int... indices) {
+ mesh.addFace(indices);
+ }
+
+ private void createLeftVertices() {
+ addVertex(-radius, extendBottom, 0);
+ addVertex(-radius - extendLeft, extendBottom, 0);
+ }
+
+ private void createRightVertices() {
+ addVertex(radius, extendBottom, 0);
+ addVertex(radius + extendRight, extendBottom, 0);
+ }
+
+ private void addFaceAt(int i) {
+ if (i >= segments) return;
+ int index = (i * 2) + 4;
+ int index0 = index;
+ int index1 = index + 1;
+ int index2 = index + 3;
+ int index3 = index + 2;
+ mesh.addFace(index0, index1, index2, index3);
+ }
+
+ private void addVertex(float x, float y, float z) {
+ mesh.addVertex(x, y, z);
+ }
+
+ public int getSegments() {
+ return segments;
+ }
+
+ public void setSegments(int segments) {
+ this.segments = segments;
+ }
+
+ public float getRadius() {
+ return radius;
+ }
+
+ public void setRadius(float radius) {
+ this.radius = radius;
+ }
+
+ public float getExtendTop() {
+ return extendTop;
+ }
+
+ public void setExtendTop(float extendTop) {
+ this.extendTop = extendTop;
+ }
+
+ public float getExtendBottom() {
+ return extendBottom;
+ }
+
+ public void setExtendBottom(float extendBottom) {
+ this.extendBottom = extendBottom;
+ }
+
+ public float getExtendLeft() {
+ return extendLeft;
+ }
+
+ public void setExtendLeft(float extendLeft) {
+ this.extendLeft = extendLeft;
+ }
+
+ public float getExtendRight() {
+ return extendRight;
+ }
+
+ public void setExtendRight(float extendRight) {
+ this.extendRight = extendRight;
+ }
+
+ public float getDepth() {
+ return depth;
+ }
+
+ public void setDepth(float depth) {
+ this.depth = depth;
+ }
}
diff --git a/src/main/java/mesh/creator/assets/ProArchCreator.java b/src/main/java/mesh/creator/assets/ProArchCreator.java
new file mode 100644
index 00000000..a78617ec
--- /dev/null
+++ b/src/main/java/mesh/creator/assets/ProArchCreator.java
@@ -0,0 +1,259 @@
+package mesh.creator.assets;
+
+import math.Mathf;
+import mesh.Mesh3D;
+import mesh.creator.IMeshCreator;
+import mesh.creator.primitives.SolidArcCreator;
+import mesh.creator.primitives.TubeCreator;
+import mesh.modifier.RotateXModifier;
+
+/**
+ * Creates a 3D arch shape, inspired by Unity's ProBuilder.
+ *
+ * This class provides methods to configure and generate a 3D arch with customizable parameters
+ * such as radius, thickness, arch angle, depth, and capping options.
+ */
+public class ProArchCreator implements IMeshCreator {
+
+ /** The number of sides for the arch's cross-section. Must be at least 3. */
+ private int sidesCount = 16;
+
+ /** The outer radius of the arch. */
+ private float radius = 1;
+
+ /** The thickness of the arch's wall. Must be greater than 0. */
+ private float thickness = 0.1f;
+
+ /** The angle of the arch in radians. Must be between 0 and 2*PI. */
+ private float archAngle = Mathf.PI;
+
+ /** The depth of the arch. */
+ private float depth = 0.5f;
+
+ /** Whether to cap the start of the arch. */
+ private boolean capStart = true;
+
+ /** Whether to cap the end of the arch. */
+ private boolean capEnd = true;
+
+ /**
+ * Creates a new 3D mesh representing the configured arch.
+ *
+ * @return The generated 3D mesh.
+ * @throws IllegalArgumentException if any of the parameters are invalid.
+ */
+ @Override
+ public Mesh3D create() {
+ validateParameters();
+ return (archAngle == Mathf.TWO_PI) ? createTube() : createArch();
+ }
+
+ /**
+ * Validates the current parameter values to ensure they are within acceptable ranges.
+ *
+ * @throws IllegalArgumentException if any of the parameters are invalid.
+ */
+ private void validateParameters() {
+ if (sidesCount < 3) {
+ throw new IllegalArgumentException("sidesCount must be at least 3");
+ }
+ if (thickness <= 0) {
+ throw new IllegalArgumentException("thickness must be greater than 0");
+ }
+ if (archAngle <= 0 || archAngle > Mathf.TWO_PI) {
+ throw new IllegalArgumentException("archAngle must be between 0 and 2*PI");
+ }
+ }
+
+ /**
+ * Creates a 3D arch shape with a specific angle.
+ *
+ * @return The generated 3D arch mesh.
+ */
+ private Mesh3D createArch() {
+ float innerRadius = calculateInnerRadius();
+ float outerRadius = calculateOuterRadius();
+
+ SolidArcCreator creator = new SolidArcCreator();
+
+ creator.setRotationSegments(sidesCount);
+ creator.setInnerRadius(innerRadius);
+ creator.setOuterRadius(outerRadius);
+ creator.setAngle(archAngle);
+ creator.setHeight(depth);
+ creator.setCapStart(capStart);
+ creator.setCapEnd(capEnd);
+
+ Mesh3D mesh = creator.create();
+ mesh.apply(new RotateXModifier(Mathf.HALF_PI));
+
+ return mesh;
+ }
+
+ /**
+ * Creates a 3D tube shape (full circle).
+ *
+ * @return The generated 3D tube mesh.
+ */
+ private Mesh3D createTube() {
+ float innerRadius = calculateInnerRadius();
+ float outerRadius = calculateOuterRadius();
+
+ TubeCreator creator = new TubeCreator();
+ creator.setVertices(sidesCount);
+ creator.setHeight(depth);
+ creator.setBottomOuterRadius(outerRadius);
+ creator.setTopOuterRadius(outerRadius);
+ creator.setBottomInnerRadius(innerRadius);
+ creator.setTopInnerRadius(innerRadius);
+
+ Mesh3D mesh = creator.create();
+ mesh.apply(new RotateXModifier(Mathf.HALF_PI));
+
+ return mesh;
+ }
+
+ /**
+ * Calculates the inner radius of the arch.
+ *
+ * @return The inner radius.
+ */
+ private float calculateInnerRadius() {
+ return radius - thickness;
+ }
+
+ /**
+ * Calculates the outer radius of the arch.
+ *
+ * @return The outer radius.
+ */
+ private float calculateOuterRadius() {
+ return radius;
+ }
+
+ /**
+ * Gets the number of sides for the arch's cross-section.
+ *
+ * @return The number of sides.
+ */
+ public int getSidesCount() {
+ return sidesCount;
+ }
+
+ /**
+ * Sets the number of sides for the arch's cross-section.
+ *
+ * @param sidesCount The new number of sides. Must be at least 3.
+ */
+ public void setSidesCount(int sidesCount) {
+ this.sidesCount = sidesCount;
+ }
+
+ /**
+ * Gets the outer radius of the arch.
+ *
+ * @return The outer radius.
+ */
+ public float getRadius() {
+ return radius;
+ }
+
+ /**
+ * Sets the outer radius of the arch.
+ *
+ * @param radius The new outer radius.
+ */
+ public void setRadius(float radius) {
+ this.radius = radius;
+ }
+
+ /**
+ * Gets the thickness of the arch's wall.
+ *
+ * @return The thickness.
+ */
+ public float getThickness() {
+ return thickness;
+ }
+
+ /**
+ * Sets the thickness of the arch's wall.
+ *
+ * @param thickness The new thickness. Must be greater than 0.
+ */
+ public void setThickness(float thickness) {
+ this.thickness = thickness;
+ }
+
+ /**
+ * Gets the angle of the arch in radians.
+ *
+ * @return The arch angle.
+ */
+ public float getArchAngle() {
+ return archAngle;
+ }
+
+ /**
+ * Sets the angle of the arch in radians.
+ *
+ * @param archAngle The new arch angle. Must be between 0 and 2*PI.
+ */
+ public void setArchAngle(float archAngle) {
+ this.archAngle = archAngle;
+ }
+
+ /**
+ * Gets the depth of the arch.
+ *
+ * @return The depth.
+ */
+ public float getDepth() {
+ return depth;
+ }
+
+ /**
+ * Sets the depth of the arch.
+ *
+ * @param depth The new depth.
+ */
+ public void setDepth(float depth) {
+ this.depth = depth;
+ }
+
+ /**
+ * Checks if the start of the arch is capped.
+ *
+ * @return True if the start is capped, false otherwise.
+ */
+ public boolean isCapStart() {
+ return capStart;
+ }
+
+ /**
+ * Sets whether to cap the start of the arch.
+ *
+ * @param capStart True to cap the start, false otherwise.
+ */
+ public void setCapStart(boolean capStart) {
+ this.capStart = capStart;
+ }
+
+ /**
+ * Checks if the end of the arch is capped.
+ *
+ * @return True if the end is capped, false otherwise.
+ */
+ public boolean isCapEnd() {
+ return capEnd;
+ }
+
+ /**
+ * Sets whether to cap the end of the arch.
+ *
+ * @param capEnd True to cap the end, false otherwise.
+ */
+ public void setCapEnd(boolean capEnd) {
+ this.capEnd = capEnd;
+ }
+}
diff --git a/src/main/java/mesh/creator/assets/RoundCornerPlaneCreator.java b/src/main/java/mesh/creator/assets/RoundCornerPlaneCreator.java
new file mode 100644
index 00000000..02a4e8e8
--- /dev/null
+++ b/src/main/java/mesh/creator/assets/RoundCornerPlaneCreator.java
@@ -0,0 +1,203 @@
+package mesh.creator.assets;
+
+import math.Mathf;
+import math.Vector3f;
+import mesh.Mesh3D;
+import mesh.creator.IMeshCreator;
+import mesh.creator.primitives.ArcCreator;
+import mesh.modifier.CenterAtModifier;
+import mesh.modifier.SolidifyModifier;
+import mesh.modifier.subdivision.QuadsToTrianglesModifier;
+
+public class RoundCornerPlaneCreator implements IMeshCreator {
+
+ private float width = 3;
+
+ private float height = 0;
+
+ private float depth = 6;
+
+ private float radius = 1f;
+
+ private int segments = 16;
+
+ private boolean triangulateFaces = true;
+
+ private Mesh3D mesh;
+
+ @Override
+ public Mesh3D create() {
+ mesh = new Mesh3D();
+ createVertices();
+ createAllSideFaces();
+ createAllCornerFaces();
+ createCenterFace();
+ solidify();
+ centerAtOrigin();
+ triangulateQuads();
+ return mesh;
+ }
+
+ private Vector3f createCornerVertex(float xSign, float zSign) {
+ float x = (xSign * (width / 2)) + (xSign * -radius);
+ float z = (zSign * (depth / 2)) + (zSign * -radius);
+
+ return new Vector3f(x, 0, z);
+ }
+
+ private void createVertices() {
+ Vector3f topLeft = createCornerVertex(-1, -1);
+ Vector3f topRight = createCornerVertex(1, -1);
+ Vector3f bottomRight = createCornerVertex(1, 1);
+ Vector3f bottomLeft = createCornerVertex(-1, 1);
+
+ createCornerVertices(topLeft, Mathf.PI, Mathf.PI + Mathf.HALF_PI);
+ createCornerVertices(topRight, Mathf.PI + Mathf.HALF_PI, Mathf.TWO_PI);
+ createCornerVertices(bottomRight, 0, Mathf.HALF_PI);
+ createCornerVertices(bottomLeft, Mathf.HALF_PI, Mathf.PI);
+
+ mesh.add(topLeft);
+ mesh.add(topRight);
+ mesh.add(bottomRight);
+ mesh.add(bottomLeft);
+ }
+
+ private void solidify() {
+ if (height == 0) return;
+ mesh.apply(new SolidifyModifier(height));
+ }
+
+ private void centerAtOrigin() {
+ mesh.apply(new CenterAtModifier());
+ }
+
+ private void triangulateQuads() {
+ if (!triangulateFaces) return;
+ mesh.apply(new QuadsToTrianglesModifier());
+ }
+
+ private void createAllCornerFaces() {
+ if (triangulateFaces) {
+ createAllCornerTriangles();
+ } else {
+ createAllCornerNGons();
+ }
+ }
+
+ private void createAllCornerTriangles() {
+ for (int i = 0; i < 4; i++) {
+ createCornerTriangles(i);
+ }
+ }
+
+ private void createAllCornerNGons() {
+ for (int i = 0; i < 4; i++) {
+ createCornerNGons(i);
+ }
+ }
+
+ private void createAllSideFaces() {
+ for (int i = 0; i < 4; i++) {
+ createSideFace(i);
+ }
+ }
+
+ private void createSideFace(int index) {
+ int index0 = calculateTotalVertexCount() - (4 - index);
+ int index1 = (((index + 1) * segments) + index) % (4 * segments + 4);
+ int index2 = (index1 + 1) % (4 * segments + 4);
+ int index3 = calculateTotalVertexCount() - (3 - index);
+ index3 = index3 == calculateTotalVertexCount() ? index3 - 4 : index3;
+
+ mesh.addFace(index0, index1, index2, index3);
+ }
+
+ private void createCornerNGons(int index) {
+ int[] indices = new int[segments + 2];
+ indices[0] = calculateTotalVertexCount() - (4 - index);
+ for (int i = 0; i < segments + 1; i++) {
+ indices[i + 1] = (index * (segments + 1)) + i;
+ }
+ mesh.addFace(indices);
+ }
+
+ private void createCornerTriangles(int index) {
+ int centerIndex = calculateTotalVertexCount() - (4 - index);
+ int baseIndex = index * (segments + 1);
+
+ for (int i = 0; i < segments; i++) {
+ int index1 = baseIndex + i;
+ int index2 = baseIndex + i + 1;
+ mesh.addFace(centerIndex, index1, index2);
+ }
+ }
+
+ private void createCenterFace() {
+ int index = calculateTotalVertexCount();
+ mesh.addFace(index - 4, index - 3, index - 2, index - 1);
+ }
+
+ private void createCornerVertices(Vector3f center, float startAngle, float endAngle) {
+ ArcCreator creator = new ArcCreator();
+ creator.setRadius(radius);
+ creator.setVertices(segments + 1);
+ creator.setStartAngle(startAngle);
+ creator.setEndAngle(endAngle);
+ creator.setCenter(center);
+
+ Mesh3D arc = creator.create();
+ mesh.addVertices(arc.getVertices());
+ }
+
+ private int calculateTotalVertexCount() {
+ return 4 + (4 * (segments + 1));
+ }
+
+ public float getWidth() {
+ return width;
+ }
+
+ public void setWidth(float width) {
+ this.width = width;
+ }
+
+ public float getHeight() {
+ return height;
+ }
+
+ public void setHeight(float height) {
+ this.height = height;
+ }
+
+ public float getDepth() {
+ return depth;
+ }
+
+ public void setDepth(float depth) {
+ this.depth = depth;
+ }
+
+ public float getRadius() {
+ return radius;
+ }
+
+ public void setRadius(float radius) {
+ this.radius = radius;
+ }
+
+ public int getSegments() {
+ return segments;
+ }
+
+ public void setSegments(int segments) {
+ this.segments = segments;
+ }
+
+ public boolean isTriangulateFaces() {
+ return triangulateFaces;
+ }
+
+ public void setTriangulateFaces(boolean triangulateFaces) {
+ this.triangulateFaces = triangulateFaces;
+ }
+}
diff --git a/src/main/java/mesh/creator/primitives/ArcCreator.java b/src/main/java/mesh/creator/primitives/ArcCreator.java
index 43c372a8..e43b088e 100644
--- a/src/main/java/mesh/creator/primitives/ArcCreator.java
+++ b/src/main/java/mesh/creator/primitives/ArcCreator.java
@@ -1,87 +1,202 @@
package mesh.creator.primitives;
import math.Mathf;
+import math.Vector3f;
import mesh.Mesh3D;
import mesh.creator.IMeshCreator;
+/**
+ * Creates a 3D arc mesh composed of a series of vertices arranged in a circular segment. The arc is
+ * defined by a start angle, end angle, radius, center, and the number of vertices.
+ *
+ * This class implements the {@link IMeshCreator} interface, providing a modular way to create
+ * arcs for use in 3D mesh generation. It can be customized to create arcs of different sizes,
+ * orientations, and resolutions.
+ *
+ * Example usage:
+ *
+ *
+ *
+ *
+ * @param other the target vector to interpolate towards.
+ * @param t the interpolation factor, which is not clamped.
+ * @return a new vector representing the interpolated or extrapolated result.
+ * @throws IllegalArgumentException if the {@code other} vector is {@code null}.
+ */
+ public Vector4f lerpUnclamped(Vector4f other, float t) {
if (other == null) {
throw new IllegalArgumentException("Other vector cannot be null.");
}
@@ -404,6 +468,46 @@ public Vector4f reflect(Vector4f normal) {
return this.subtract(normal.multiply(2 * this.dot(normal)));
}
+ /**
+ * Multiplies this vector by a 4x4 matrix and returns the resulting vector.
+ *
+ *
+ * [ newX ] [ m00 m01 m02 m03 ] [ x ]
+ * [ newY ] = [ m10 m11 m12 m13 ] * [ y ]
+ * [ newZ ] [ m20 m21 m22 m23 ] [ z ]
+ * [ newW ] [ m30 m31 m32 m33 ] [ w ]
+ *
+ *
+ * @param m the 4x4 matrix to multiply with. This matrix defines the transformation (e.g.,
+ * scaling, translation, rotation) to be applied to this vector. The matrix should be in
+ * row-major order for correct computation.
+ * @return a new {@code Vector4f} instance representing the transformed vector.
+ * @throws NullPointerException if {@code m} is {@code null}.
+ */
+ public Vector4f multiply(Matrix4 m) {
+ float newX = m.get(0, 0) * x + m.get(0, 1) * y + m.get(0, 2) * z + m.get(0, 3) * w;
+ float newY = m.get(1, 0) * x + m.get(1, 1) * y + m.get(1, 2) * z + m.get(1, 3) * w;
+ float newZ = m.get(2, 0) * x + m.get(2, 1) * y + m.get(2, 2) * z + m.get(2, 3) * w;
+ float newW = m.get(3, 0) * x + m.get(3, 1) * y + m.get(3, 2) * z + m.get(3, 3) * w;
+
+ return new Vector4f(newX, newY, newZ, newW);
+ }
+
+ /**
+ * Converts this 4D vector to a 3D vector by dropping the w-component.
+ *
+ * @return a new Vector3f instance containing the x, y, and z components of this Vector4f.
+ */
+ public Vector3f toVector3f() {
+ return new Vector3f(x, y, z);
+ }
+
/**
* Returns the x component of the vector.
*
diff --git a/src/main/java/mesh/Face3D.java b/src/main/java/mesh/Face3D.java
index 46a00af0..cd38bdfd 100644
--- a/src/main/java/mesh/Face3D.java
+++ b/src/main/java/mesh/Face3D.java
@@ -11,20 +11,27 @@ public class Face3D {
public int[] indices;
+ private int[] uvIndices;
+
public Vector3f normal;
public String tag;
public Face3D() {
- this(new int[0]);
+ this(new int[0], new int[0]);
}
public Face3D(int... indices) {
+ this(indices, new int[0]);
+ }
+
+ public Face3D(int[] indices, int[] uvIndices) {
this.color = new Color();
this.indices = new int[indices.length];
this.normal = new Vector3f();
this.tag = "";
- for (int i = 0; i < indices.length; i++) this.indices[i] = indices[i];
+ this.indices = Arrays.copyOf(indices, indices.length);
+ this.uvIndices = Arrays.copyOf(uvIndices, uvIndices.length);
}
public boolean sharesSameIndices(Face3D face) {
@@ -39,6 +46,14 @@ public int getIndexAt(int index) {
return indices[index % indices.length];
}
+ // Get UV index, return -1 if no UVs are available
+ public int getUvIndexAt(int index) {
+ if (uvIndices == null || uvIndices.length == 0) {
+ return -1; // No UVs available
+ }
+ return uvIndices[index % uvIndices.length];
+ }
+
public int getVertexCount() {
return indices.length;
}
diff --git a/src/main/java/mesh/Mesh3D.java b/src/main/java/mesh/Mesh3D.java
index 914b0f4a..ede95ebd 100644
--- a/src/main/java/mesh/Mesh3D.java
+++ b/src/main/java/mesh/Mesh3D.java
@@ -5,6 +5,7 @@
import java.util.Collection;
import java.util.List;
+import math.Vector2f;
import math.Vector3f;
import mesh.modifier.IMeshModifier;
import mesh.modifier.RemoveDoubleVerticesModifier;
@@ -16,11 +17,18 @@
public class Mesh3D {
public ArrayList{@code
+ * ArcCreator arcCreator = new ArcCreator();
+ * arcCreator.setStartAngle(0);
+ * arcCreator.setEndAngle(Mathf.HALF_PI); // 90 degrees
+ * arcCreator.setRadius(2.0f);
+ * arcCreator.setVertices(16);
+ * arcCreator.setCenter(new Vector3f(1, 0, 1));
+ * Mesh3D arcMesh = arcCreator.create();
+ * }
+ */
public class ArcCreator implements IMeshCreator {
- private float startAngle;
-
- private float endAngle;
-
- private float radius;
-
- private int vertices;
-
- private Mesh3D mesh;
-
- public ArcCreator() {
- startAngle = 0;
- endAngle = Mathf.TWO_PI;
- radius = 1;
- vertices = 32;
- }
-
- @Override
- public Mesh3D create() {
- initializeMesh();
- createVertices();
- return mesh;
- }
-
- private void initializeMesh() {
- mesh = new Mesh3D();
- }
-
- private void createVertices() {
- float angleBetweenPoints = calculateAngleBetweenPoints();
- for (int i = 0; i < vertices; i++) {
- float currentAngle = angleBetweenPoints * i;
- float x = radius * Mathf.cos(currentAngle);
- float z = radius * Mathf.sin(currentAngle);
- addVertex(x, 0, z);
- }
- }
-
- private void addVertex(float x, float y, float z) {
- mesh.addVertex(x, y, z);
- }
-
- private float calculateAngleBetweenPoints() {
- return startAngle + ((endAngle - startAngle) / ((float) vertices - 1));
+ /** The starting angle of the arc in radians. Defaults to 0. */
+ private float startAngle;
+
+ /** The ending angle of the arc in radians. Defaults to {@link Mathf#TWO_PI}. */
+ private float endAngle;
+
+ /** The radius of the arc. Defaults to 1. */
+ private float radius;
+
+ /** The number of vertices that make up the arc. Defaults to 32. */
+ private int vertices;
+
+ /** The center position of the arc. Defaults to the origin (0, 0, 0). */
+ private Vector3f center;
+
+ /** The generated 3D mesh representing the arc. */
+ private Mesh3D mesh;
+
+ /** Constructs a new {@code ArcCreator} with default parameters. */
+ public ArcCreator() {
+ startAngle = 0;
+ endAngle = Mathf.TWO_PI;
+ radius = 1;
+ vertices = 32;
+ center = new Vector3f();
+ }
+
+ /**
+ * Creates the arc mesh based on the current configuration.
+ *
+ * @return A {@link Mesh3D} object representing the generated arc.
+ */
+ @Override
+ public Mesh3D create() {
+ initializeMesh();
+ createVertices();
+ return mesh;
+ }
+
+ /** Initializes the mesh object to store the arc's vertices. */
+ private void initializeMesh() {
+ mesh = new Mesh3D();
+ }
+
+ /** Generates the vertices of the arc based on the radius, angles, and center. */
+ private void createVertices() {
+ float angleBetweenPoints = calculateAngleBetweenPoints();
+ for (int i = 0; i < vertices; i++) {
+ float currentAngle = startAngle + angleBetweenPoints * i;
+ float x = center.x + radius * Mathf.cos(currentAngle);
+ float z = center.z + radius * Mathf.sin(currentAngle);
+ addVertex(x, center.y, z);
}
-
- public float getStartAngle() {
- return startAngle;
- }
-
- public void setStartAngle(float startAngle) {
- this.startAngle = startAngle;
- }
-
- public float getEndAngle() {
- return endAngle;
- }
-
- public void setEndAngle(float endAngle) {
- this.endAngle = endAngle;
+ }
+
+ /**
+ * Adds a vertex to the mesh at the specified position.
+ *
+ * @param x The x-coordinate of the vertex.
+ * @param y The y-coordinate of the vertex.
+ * @param z The z-coordinate of the vertex.
+ */
+ private void addVertex(float x, float y, float z) {
+ mesh.addVertex(x, y, z);
+ }
+
+ /**
+ * Calculates the angle between each pair of adjacent vertices.
+ *
+ * @return The angle between adjacent vertices in radians.
+ */
+ private float calculateAngleBetweenPoints() {
+ return (endAngle - startAngle) / ((float) vertices - 1);
+ }
+
+ /**
+ * Gets the starting angle of the arc in radians.
+ *
+ * @return The starting angle.
+ */
+ public float getStartAngle() {
+ return startAngle;
+ }
+
+ /**
+ * Sets the starting angle of the arc in radians.
+ *
+ * @param startAngle The starting angle.
+ */
+ public void setStartAngle(float startAngle) {
+ this.startAngle = startAngle;
+ }
+
+ /**
+ * Gets the ending angle of the arc in radians.
+ *
+ * @return The ending angle.
+ */
+ public float getEndAngle() {
+ return endAngle;
+ }
+
+ /**
+ * Sets the ending angle of the arc in radians.
+ *
+ * @param endAngle The ending angle.
+ */
+ public void setEndAngle(float endAngle) {
+ this.endAngle = endAngle;
+ }
+
+ /**
+ * Gets the radius of the arc.
+ *
+ * @return The radius.
+ */
+ public float getRadius() {
+ return radius;
+ }
+
+ /**
+ * Sets the radius of the arc.
+ *
+ * @param radius The radius.
+ */
+ public void setRadius(float radius) {
+ this.radius = radius;
+ }
+
+ /**
+ * Gets the number of vertices that make up the arc.
+ *
+ * @return The number of vertices.
+ */
+ public int getVertices() {
+ return vertices;
+ }
+
+ /**
+ * Sets the number of vertices that make up the arc. Must be at least 2.
+ *
+ * @param vertices The number of vertices.
+ * @throws IllegalArgumentException if {@code vertices} is less than 2.
+ */
+ public void setVertices(int vertices) {
+ if (vertices < 2) {
+ throw new IllegalArgumentException("Vertex count must be at least 2.");
}
-
- public float getRadius() {
- return radius;
+ this.vertices = vertices;
+ }
+
+ /**
+ * Gets the center position of the arc.
+ *
+ * @return A {@link Vector3f} representing the center position.
+ */
+ public Vector3f getCenter() {
+ return new Vector3f(center);
+ }
+
+ /**
+ * Sets the center position of the arc.
+ *
+ * @param center A {@link Vector3f} representing the new center position.
+ * @throws IllegalArgumentException if {@code center} is {@code null}.
+ */
+ public void setCenter(Vector3f center) {
+ if (center == null) {
+ throw new IllegalArgumentException("Center cannot be null.");
}
-
- public void setRadius(float radius) {
- this.radius = radius;
- }
-
- public int getVertices() {
- return vertices;
- }
-
- public void setVertices(int vertices) {
- this.vertices = vertices;
- }
-
+ this.center.set(center);
+ }
}
diff --git a/src/main/java/mesh/creator/primitives/SolidArcCreator.java b/src/main/java/mesh/creator/primitives/SolidArcCreator.java
index 8dc09317..11744fa6 100644
--- a/src/main/java/mesh/creator/primitives/SolidArcCreator.java
+++ b/src/main/java/mesh/creator/primitives/SolidArcCreator.java
@@ -7,187 +7,184 @@
public class SolidArcCreator implements IMeshCreator {
- private int index;
+ private int index;
- private int vertices;
+ private int vertices;
+
+ private float angle;
- private float angle;
+ private float outerRadius;
+
+ private float innerRadius;
- private float outerRadius;
+ private float height;
+
+ private boolean capStart;
- private float innerRadius;
+ private boolean capEnd;
+
+ private Mesh3D mesh;
- private float height;
+ private ArcCreator creator;
+
+ private Mesh3D outerArc;
- private boolean capStart;
+ private Mesh3D innerArc;
+
+ public SolidArcCreator() {
+ vertices = 17;
+ angle = Mathf.HALF_PI;
+ outerRadius = 3;
+ innerRadius = 2;
+ height = 1;
+ }
+
+ @Override
+ public Mesh3D create() {
+ initializeMesh();
+ initializeCreator();
+ createArcs();
+ createVertices();
+ createFaces();
+ capStart();
+ capEnd();
+ return mesh;
+ }
+
+ private void initializeMesh() {
+ mesh = new Mesh3D();
+ }
+
+ private void initializeCreator() {
+ creator = new ArcCreator();
+ creator.setStartAngle(0);
+ creator.setEndAngle(angle);
+ creator.setVertices(vertices);
+ }
- private boolean capEnd;
+ private void createArcs() {
+ creator.setRadius(outerRadius);
+ outerArc = creator.create();
+ creator.setRadius(innerRadius);
+ innerArc = creator.create();
+ }
- private Mesh3D mesh;
-
- private ArcCreator creator;
-
- private Mesh3D outerArc;
-
- private Mesh3D innerArc;
-
- public SolidArcCreator() {
- vertices = 17;
- angle = Mathf.HALF_PI;
- outerRadius = 3;
- innerRadius = 2;
- height = 1;
- }
-
- @Override
- public Mesh3D create() {
- initializeMesh();
- initializeCreator();
- createArcs();
- createVertices();
- createFaces();
- capStart();
- capEnd();
- return mesh;
- }
-
- private void initializeMesh() {
- mesh = new Mesh3D();
- }
-
- private void initializeCreator() {
- creator = new ArcCreator();
- creator.setStartAngle(0);
- creator.setEndAngle(angle);
- creator.setVertices(vertices);
- }
-
- private void createArcs() {
- creator.setRadius(outerRadius);
- outerArc = creator.create();
- creator.setRadius(innerRadius);
- innerArc = creator.create();
- }
-
- private void createVertices() {
- float halfHeight = height / 2f;
- for (int i = 0; i < vertices; i++) {
- addVertex(outerArc.getVertexAt(i).add(0, -halfHeight, 0));
- addVertex(innerArc.getVertexAt(i).add(0, -halfHeight, 0));
- addVertex(innerArc.getVertexAt(i).add(0, +halfHeight, 0));
- addVertex(outerArc.getVertexAt(i).add(0, +halfHeight, 0));
- }
- }
-
- private void addVertex(Vector3f v) {
- mesh.add(v);
- }
-
- private void capStart() {
- if (!capStart)
- return;
- mesh.addFace(0, 1, 2, 3);
- }
-
- private void capEnd() {
- if (!capEnd)
- return;
- int index = mesh.vertices.size();
- mesh.addFace(index - 1, index - 2, index - 3, index - 4);
- }
-
- private void createFaces() {
- for (int i = 0; i < (vertices) * 4 - 4; i += 4) {
- setIndex(i);
- addTopFaceAtIndex();
- addOuterFaceAtIndex();
- addBottomFaceAtIndex();
- addInnerFaceAtIndex();
- }
- }
-
- private void addTopFaceAtIndex() {
- addFaceAtIndex(1, 0, 4, 5);
- }
-
- private void addOuterFaceAtIndex() {
- addFaceAtIndex(4, 0, 3, 7);
- }
-
- private void addBottomFaceAtIndex() {
- addFaceAtIndex(2, 6, 7, 3);
- }
-
- private void addInnerFaceAtIndex() {
- addFaceAtIndex(6, 2, 1, 5);
- }
-
- private void addFaceAtIndex(int... indices) {
- int[] indices1 = new int[indices.length];
- for (int i = 0; i < indices.length; i++) {
- indices1[i] = index + indices[i];
- }
- mesh.addFace(indices1);
+ private void createVertices() {
+ float halfHeight = height / 2f;
+ for (int i = 0; i < vertices; i++) {
+ addVertex(outerArc.getVertexAt(i).add(0, -halfHeight, 0));
+ addVertex(innerArc.getVertexAt(i).add(0, -halfHeight, 0));
+ addVertex(innerArc.getVertexAt(i).add(0, +halfHeight, 0));
+ addVertex(outerArc.getVertexAt(i).add(0, +halfHeight, 0));
}
-
- private void setIndex(int index) {
- this.index = index;
- }
-
- public float getAngle() {
- return angle;
- }
-
- public void setAngle(float angle) {
- this.angle = angle;
- }
-
- public float getInnerRadius() {
- return innerRadius;
- }
-
- public void setInnerRadius(float innerRadius) {
- this.innerRadius = innerRadius;
- }
-
- public float getOuterRadius() {
- return outerRadius;
- }
-
- public void setOuterRadius(float outerRadius) {
- this.outerRadius = outerRadius;
- }
-
- public int getRotationSegments() {
- return vertices - 1;
- }
-
- public void setRotationSegments(int rotationSegments) {
- this.vertices = rotationSegments + 1;
- }
-
- public float getHeight() {
- return height;
- }
-
- public void setHeight(float height) {
- this.height = height;
- }
-
- public boolean isCapStart() {
- return capStart;
- }
-
- public void setCapStart(boolean capStart) {
- this.capStart = capStart;
- }
-
- public boolean isCapEnd() {
- return capEnd;
- }
-
- public void setCapEnd(boolean capEnd) {
- this.capEnd = capEnd;
- }
-
+ }
+
+ private void addVertex(Vector3f v) {
+ mesh.add(v);
+ }
+
+ private void capStart() {
+ if (!capStart) return;
+ mesh.addFace(0, 1, 2, 3);
+ }
+
+ private void capEnd() {
+ if (!capEnd) return;
+ int index = mesh.vertices.size();
+ mesh.addFace(index - 1, index - 2, index - 3, index - 4);
+ }
+
+ private void createFaces() {
+ for (int i = 0; i < (vertices) * 4 - 4; i += 4) {
+ setIndex(i);
+ addTopFaceAtIndex();
+ addOuterFaceAtIndex();
+ addBottomFaceAtIndex();
+ addInnerFaceAtIndex();
+ }
+ }
+
+ private void addTopFaceAtIndex() {
+ addFaceAtIndex(1, 0, 4, 5);
+ }
+
+ private void addOuterFaceAtIndex() {
+ addFaceAtIndex(4, 0, 3, 7);
+ }
+
+ private void addBottomFaceAtIndex() {
+ addFaceAtIndex(2, 6, 7, 3);
+ }
+
+ private void addInnerFaceAtIndex() {
+ addFaceAtIndex(6, 2, 1, 5);
+ }
+
+ private void addFaceAtIndex(int... indices) {
+ int[] indices1 = new int[indices.length];
+ for (int i = 0; i < indices.length; i++) {
+ indices1[i] = index + indices[i];
+ }
+ mesh.addFace(indices1);
+ }
+
+ private void setIndex(int index) {
+ this.index = index;
+ }
+
+ public float getAngle() {
+ return angle;
+ }
+
+ public void setAngle(float angle) {
+ this.angle = angle;
+ }
+
+ public float getInnerRadius() {
+ return innerRadius;
+ }
+
+ public void setInnerRadius(float innerRadius) {
+ this.innerRadius = innerRadius;
+ }
+
+ public float getOuterRadius() {
+ return outerRadius;
+ }
+
+ public void setOuterRadius(float outerRadius) {
+ this.outerRadius = outerRadius;
+ }
+
+ public int getRotationSegments() {
+ return vertices - 1;
+ }
+
+ public void setRotationSegments(int rotationSegments) {
+ this.vertices = rotationSegments + 1;
+ }
+
+ public float getHeight() {
+ return height;
+ }
+
+ public void setHeight(float height) {
+ this.height = height;
+ }
+
+ public boolean isCapStart() {
+ return capStart;
+ }
+
+ public void setCapStart(boolean capStart) {
+ this.capStart = capStart;
+ }
+
+ public boolean isCapEnd() {
+ return capEnd;
+ }
+
+ public void setCapEnd(boolean capEnd) {
+ this.capEnd = capEnd;
+ }
}
diff --git a/src/main/java/workspace/GraphicsPImpl.java b/src/main/java/workspace/GraphicsPImpl.java
index 3d7d28da..e0ec545f 100644
--- a/src/main/java/workspace/GraphicsPImpl.java
+++ b/src/main/java/workspace/GraphicsPImpl.java
@@ -4,12 +4,15 @@
import engine.processing.LightGizmoRenderer;
import engine.processing.LightRendererImpl;
+import engine.processing.ProcessingTexture;
import engine.render.Material;
import engine.resources.Image;
+import engine.resources.Texture;
import engine.scene.camera.Camera;
import engine.scene.light.Light;
import engine.scene.light.LightRenderer;
import math.Matrix4f;
+import math.Vector2f;
import math.Vector3f;
import mesh.Face3D;
import mesh.Mesh3D;
@@ -32,6 +35,8 @@ public class GraphicsPImpl implements Graphics {
private PGraphics g;
+ private PApplet p;
+
private Mesh3DRenderer renderer;
private LightRenderer lightRenderer;
@@ -42,6 +47,8 @@ public class GraphicsPImpl implements Graphics {
public static int vertexCount = 0;
+ private PImage texture;
+
@Override
public void setAmbientColor(math.Color ambientColor) {
this.ambientColor = ambientColor;
@@ -59,6 +66,7 @@ public void setWireframeMode(boolean wireframeMode) {
public GraphicsPImpl(PApplet p) {
this.g = p.g;
+ this.p = p;
renderer = new Mesh3DRenderer(p);
lightRenderer = new LightRendererImpl(p);
@@ -78,11 +86,12 @@ public void fillFaces(Mesh3D mesh) {
if (wireframeMode) {
g.noFill();
stroke();
- renderer.drawFaces(mesh);
+ // renderer.drawFaces(mesh);
+ drawMeshFaces(mesh);
} else {
g.noStroke();
fill();
- renderer.drawFaces(mesh);
+ drawMeshFaces(mesh);
}
}
@@ -133,11 +142,24 @@ private void drawMeshFaces(Mesh3D mesh) {
} else {
g.beginShape(PApplet.POLYGON);
}
- for (int index : f.indices) {
- Vector3f v = mesh.vertices.get(index);
- g.vertex(v.getX(), v.getY(), v.getZ());
+
+ if (texture != null) {
+ g.texture(texture);
+ g.textureMode(PApplet.NORMAL);
+ }
+
+ int[] indices = f.indices;
+ for (int i = 0; i < indices.length; i++) {
+ Vector3f v = mesh.vertices.get(f.indices[i]);
+ int uvIndex = f.getUvIndexAt(i);
+ if (uvIndex != -1) {
+ Vector2f uv = mesh.getUvAt(uvIndex);
+ g.vertex(v.getX(), v.getY(), v.getZ(), uv.getX(), 1 - uv.getY());
+ } else {
+ g.vertex(v.getX(), v.getY(), v.getZ());
+ }
}
- g.endShape(PApplet.CLOSE);
+ g.endShape();
}
}
@@ -367,6 +389,8 @@ public void setMaterial(Material material) {
return;
}
+ this.texture = null;
+
// Extract material properties
math.Color color = material.getColor();
float[] ambient = material.getAmbient();
@@ -411,6 +435,19 @@ public void setMaterial(Material material) {
g.shininess(shininess);
}
+ @Override
+ public void bindTexture(Texture texture, int unit) { // TODO Auto-generated method stub
+ // if (unit == 1) {
+ // g.textureMode(PApplet.NORMAL);
+ // }
+ ProcessingTexture texture2 = (ProcessingTexture) texture;
+ this.texture = texture2.getImage();
+ }
+
+ @Override
+ public void unbindTexture(int unit) { // TODO Auto-generated method stub
+ }
+
@Override
public void setShader(String vertexShaderName, String fragmentShaderName) {
try {
diff --git a/src/main/java/workspace/ui/Graphics3D.java b/src/main/java/workspace/ui/Graphics3D.java
index 878eb07c..c0c0621e 100644
--- a/src/main/java/workspace/ui/Graphics3D.java
+++ b/src/main/java/workspace/ui/Graphics3D.java
@@ -3,6 +3,7 @@
import java.util.List;
import engine.render.Material;
+import engine.resources.Texture;
import engine.scene.camera.Camera;
import engine.scene.light.Light;
import math.Matrix4f;
@@ -19,7 +20,7 @@ public interface Graphics3D extends Graphics2D {
void rotateY(float angle);
void rotateZ(float angle);
-
+
void rotate(float rx, float ry, float rz);
void render(Light light);
@@ -44,6 +45,10 @@ public interface Graphics3D extends Graphics2D {
void setWireframeMode(boolean wireframeMode);
+ void bindTexture(Texture texture, int unit);
+
+ void unbindTexture(int unit);
+
/**
* Sets the global ambient light color for the scene.
*
diff --git a/src/test/java/math/Vector4fTest.java b/src/test/java/math/Vector4fTest.java
index f236d609..90233941 100644
--- a/src/test/java/math/Vector4fTest.java
+++ b/src/test/java/math/Vector4fTest.java
@@ -1668,17 +1668,17 @@ public void testIsEqualNegativeInfinity() {
}
// ----------------------------------------------------------------------------------------------
- // Lerp
+ // Lerp Unclamped
// ----------------------------------------------------------------------------------------------
@Test
- public void testLerpWithTZero() {
+ public void testLerpUnclampedWithTZero() {
// Arrange
Vector4f start = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
Vector4f end = new Vector4f(5.0f, 6.0f, 7.0f, 8.0f);
// Act
- Vector4f result = start.lerp(end, 0.0f);
+ Vector4f result = start.lerpUnclamped(end, 0.0f);
// Assert
assertEquals(start.getX(), result.getX(), 0.0001f);
@@ -1688,13 +1688,13 @@ public void testLerpWithTZero() {
}
@Test
- public void testLerpWithTOne() {
+ public void testLerpUnclampedWithTOne() {
// Arrange
Vector4f start = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
Vector4f end = new Vector4f(5.0f, 6.0f, 7.0f, 8.0f);
// Act
- Vector4f result = start.lerp(end, 1.0f);
+ Vector4f result = start.lerpUnclamped(end, 1.0f);
// Assert
assertEquals(end.getX(), result.getX(), 0.0001f);
@@ -1704,13 +1704,13 @@ public void testLerpWithTOne() {
}
@Test
- public void testLerpWithHalfwayT() {
+ public void testLerpUnclampedWithHalfwayT() {
// Arrange
Vector4f start = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
Vector4f end = new Vector4f(5.0f, 6.0f, 7.0f, 8.0f);
// Act
- Vector4f result = start.lerp(end, 0.5f);
+ Vector4f result = start.lerpUnclamped(end, 0.5f);
// Assert
assertEquals(3.0f, result.getX(), 0.0001f);
@@ -1720,13 +1720,13 @@ public void testLerpWithHalfwayT() {
}
@Test
- public void testLerpWithNegativeT() {
+ public void testLerpUnclampedWithNegativeT() {
// Arrange
Vector4f start = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
Vector4f end = new Vector4f(5.0f, 6.0f, 7.0f, 8.0f);
// Act
- Vector4f result = start.lerp(end, -0.5f);
+ Vector4f result = start.lerpUnclamped(end, -0.5f);
// Assert
assertEquals(-1.0f, result.getX(), 0.0001f);
@@ -1736,13 +1736,13 @@ public void testLerpWithNegativeT() {
}
@Test
- public void testLerpWithOverOneT() {
+ public void testLerpUnclampedWithOverOneT() {
// Arrange
Vector4f start = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
Vector4f end = new Vector4f(5.0f, 6.0f, 7.0f, 8.0f);
// Act
- Vector4f result = start.lerp(end, 1.5f);
+ Vector4f result = start.lerpUnclamped(end, 1.5f);
// Assert
assertEquals(7.0f, result.getX(), 0.0001f);
@@ -1752,7 +1752,150 @@ public void testLerpWithOverOneT() {
}
@Test
- public void testLerpWithIdenticalStartAndEnd() {
+ public void testLerpUnclampedWithIdenticalStartAndEnd() {
+ // Arrange
+ Vector4f start = new Vector4f(3.0f, 3.0f, 3.0f, 3.0f);
+ Vector4f end = new Vector4f(3.0f, 3.0f, 3.0f, 3.0f);
+
+ // Act
+ Vector4f result = start.lerpUnclamped(end, 0.5f);
+
+ // Assert
+ assertEquals(3.0f, result.getX(), 0.0001f);
+ assertEquals(3.0f, result.getY(), 0.0001f);
+ assertEquals(3.0f, result.getZ(), 0.0001f);
+ assertEquals(3.0f, result.getW(), 0.0001f);
+ }
+
+ @Test
+ public void testLerpUnclampedReturnsNewInstanceAndOriginalVectorsUntouched() {
+ // Arrange
+ Vector4f start = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f end = new Vector4f(5.0f, 6.0f, 7.0f, 8.0f);
+ Vector4f startCopy = start.clone();
+ Vector4f endCopy = end.clone();
+
+ // Act
+ Vector4f result = start.lerpUnclamped(end, 0.5f);
+
+ // Assert
+ // Verify that a new instance is returned
+ assertNotSame(start, result);
+ assertNotSame(end, result);
+
+ // Verify that the original start vector is untouched
+ assertEquals(startCopy.getX(), start.getX(), 0.0001f);
+ assertEquals(startCopy.getY(), start.getY(), 0.0001f);
+ assertEquals(startCopy.getZ(), start.getZ(), 0.0001f);
+ assertEquals(startCopy.getW(), start.getW(), 0.0001f);
+
+ // Verify that the original end vector is untouched
+ assertEquals(endCopy.getX(), end.getX(), 0.0001f);
+ assertEquals(endCopy.getY(), end.getY(), 0.0001f);
+ assertEquals(endCopy.getZ(), end.getZ(), 0.0001f);
+ assertEquals(endCopy.getW(), end.getW(), 0.0001f);
+ }
+
+ @Test
+ public void testLerpUnclampedWithNullThrowsException() {
+ // Arrange
+ Vector4f start = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f other = null;
+ float t = 0.5f;
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ start.lerpUnclamped(other, t);
+ });
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Lerp (Clamped)
+ // ----------------------------------------------------------------------------------------------
+
+ @Test
+ public void testLerpClampedWithTZero() {
+ // Arrange
+ Vector4f start = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f end = new Vector4f(5.0f, 6.0f, 7.0f, 8.0f);
+
+ // Act
+ Vector4f result = start.lerp(end, 0.0f);
+
+ // Assert
+ assertEquals(start.getX(), result.getX(), 0.0001f);
+ assertEquals(start.getY(), result.getY(), 0.0001f);
+ assertEquals(start.getZ(), result.getZ(), 0.0001f);
+ assertEquals(start.getW(), result.getW(), 0.0001f);
+ }
+
+ @Test
+ public void testLerpClampedWithTOne() {
+ // Arrange
+ Vector4f start = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f end = new Vector4f(5.0f, 6.0f, 7.0f, 8.0f);
+
+ // Act
+ Vector4f result = start.lerp(end, 1.0f);
+
+ // Assert
+ assertEquals(end.getX(), result.getX(), 0.0001f);
+ assertEquals(end.getY(), result.getY(), 0.0001f);
+ assertEquals(end.getZ(), result.getZ(), 0.0001f);
+ assertEquals(end.getW(), result.getW(), 0.0001f);
+ }
+
+ @Test
+ public void testLerpClampedWithHalfwayT() {
+ // Arrange
+ Vector4f start = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f end = new Vector4f(5.0f, 6.0f, 7.0f, 8.0f);
+
+ // Act
+ Vector4f result = start.lerp(end, 0.5f);
+
+ // Assert
+ assertEquals(3.0f, result.getX(), 0.0001f);
+ assertEquals(4.0f, result.getY(), 0.0001f);
+ assertEquals(5.0f, result.getZ(), 0.0001f);
+ assertEquals(6.0f, result.getW(), 0.0001f);
+ }
+
+ @Test
+ public void testLerpClampedWithNegativeT() {
+ // Arrange
+ Vector4f start = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f end = new Vector4f(5.0f, 6.0f, 7.0f, 8.0f);
+
+ // Act
+ Vector4f result = start.lerp(end, -0.5f);
+
+ // Assert
+ assertEquals(start.getX(), result.getX(), 0.0001f); // Clamped to 0
+ assertEquals(start.getY(), result.getY(), 0.0001f);
+ assertEquals(start.getZ(), result.getZ(), 0.0001f);
+ assertEquals(start.getW(), result.getW(), 0.0001f);
+ }
+
+ @Test
+ public void testLerpClampedWithOverOneT() {
+ // Arrange
+ Vector4f start = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f end = new Vector4f(5.0f, 6.0f, 7.0f, 8.0f);
+
+ // Act
+ Vector4f result = start.lerp(end, 1.5f);
+
+ // Assert
+ assertEquals(end.getX(), result.getX(), 0.0001f); // Clamped to 1
+ assertEquals(end.getY(), result.getY(), 0.0001f);
+ assertEquals(end.getZ(), result.getZ(), 0.0001f);
+ assertEquals(end.getW(), result.getW(), 0.0001f);
+ }
+
+ @Test
+ public void testLerpClampedWithIdenticalStartAndEnd() {
// Arrange
Vector4f start = new Vector4f(3.0f, 3.0f, 3.0f, 3.0f);
Vector4f end = new Vector4f(3.0f, 3.0f, 3.0f, 3.0f);
@@ -1768,7 +1911,7 @@ public void testLerpWithIdenticalStartAndEnd() {
}
@Test
- public void testLerpReturnsNewInstanceAndOriginalVectorsUntouched() {
+ public void testLerpClampedReturnsNewInstanceAndOriginalVectorsUntouched() {
// Arrange
Vector4f start = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
Vector4f end = new Vector4f(5.0f, 6.0f, 7.0f, 8.0f);
@@ -1797,7 +1940,7 @@ public void testLerpReturnsNewInstanceAndOriginalVectorsUntouched() {
}
@Test
- public void testLerpWithNullThrowsException() {
+ public void testLerpClampedWithNullThrowsException() {
// Arrange
Vector4f start = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
Vector4f other = null;
@@ -2674,4 +2817,273 @@ public void testSetAndGetW() {
vector.setW(8.0f);
assertEquals(8.0f, vector.getW(), 0.0001);
}
+
+ // ----------------------------------------------------------------------------------------------
+ // Multiply Matrix4
+ // ----------------------------------------------------------------------------------------------
+
+ @Test
+ void testMultiplyWithIdentityMatrix() {
+ Vector4f vector = new Vector4f(1.0f, 2.0f, 3.0f, 1.0f);
+ Matrix4 identityMatrix =
+ new Matrix4(
+ new float[] {
+ 1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f
+ });
+
+ Vector4f result = vector.multiply(identityMatrix);
+
+ assertEquals(1.0f, result.getX(), 1e-6, "X component should remain unchanged");
+ assertEquals(2.0f, result.getY(), 1e-6, "Y component should remain unchanged");
+ assertEquals(3.0f, result.getZ(), 1e-6, "Z component should remain unchanged");
+ assertEquals(1.0f, result.getW(), 1e-6, "W component should remain unchanged");
+ }
+
+ @Test
+ void testMultiplyWithZeroMatrix() {
+ Vector4f vector = new Vector4f(1.0f, 2.0f, 3.0f, 1.0f);
+ Matrix4 zeroMatrix =
+ new Matrix4(
+ new float[] {
+ 0.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 0.0f
+ });
+
+ Vector4f result = vector.multiply(zeroMatrix);
+
+ assertEquals(0.0f, result.getX(), 1e-6, "X component should be 0");
+ assertEquals(0.0f, result.getY(), 1e-6, "Y component should be 0");
+ assertEquals(0.0f, result.getZ(), 1e-6, "Z component should be 0");
+ assertEquals(0.0f, result.getW(), 1e-6, "W component should be 0");
+ }
+
+ @Test
+ void testMultiplyWithTranslationMatrix() {
+ Vector4f vector = new Vector4f(1.0f, 2.0f, 3.0f, 1.0f);
+ Matrix4 translationMatrix =
+ new Matrix4(
+ new float[] {
+ 1.0f, 0.0f, 0.0f, 5.0f,
+ 0.0f, 1.0f, 0.0f, 10.0f,
+ 0.0f, 0.0f, 1.0f, 15.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f
+ });
+
+ Vector4f result = vector.multiply(translationMatrix);
+
+ assertEquals(6.0f, result.getX(), 1e-6, "X component should include translation");
+ assertEquals(12.0f, result.getY(), 1e-6, "Y component should include translation");
+ assertEquals(18.0f, result.getZ(), 1e-6, "Z component should include translation");
+ assertEquals(1.0f, result.getW(), 1e-6, "W component should remain unchanged");
+ }
+
+ @Test
+ void testMultiplyWithScalingMatrix() {
+ Vector4f vector = new Vector4f(1.0f, 2.0f, 3.0f, 1.0f);
+ Matrix4 scalingMatrix =
+ new Matrix4(
+ new float[] {
+ 2.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 3.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 4.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f
+ });
+
+ Vector4f result = vector.multiply(scalingMatrix);
+
+ assertEquals(2.0f, result.getX(), 1e-6, "X component should be scaled");
+ assertEquals(6.0f, result.getY(), 1e-6, "Y component should be scaled");
+ assertEquals(12.0f, result.getZ(), 1e-6, "Z component should be scaled");
+ assertEquals(1.0f, result.getW(), 1e-6, "W component should remain unchanged");
+ }
+
+ @Test
+ void testMultiplyWithRotationMatrix() {
+ Vector4f vector = new Vector4f(1.0f, 0.0f, 0.0f, 1.0f);
+ Matrix4 rotationMatrix =
+ new Matrix4(
+ new float[] {
+ 0.0f, -1.0f, 0.0f, 0.0f,
+ 1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f
+ });
+
+ Vector4f result = vector.multiply(rotationMatrix);
+
+ assertEquals(0.0f, result.getX(), 1e-6, "X component after rotation");
+ assertEquals(1.0f, result.getY(), 1e-6, "Y component after rotation");
+ assertEquals(0.0f, result.getZ(), 1e-6, "Z component should remain unchanged");
+ assertEquals(1.0f, result.getW(), 1e-6, "W component should remain unchanged");
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // To Vector3f
+ // ----------------------------------------------------------------------------------------------
+
+ @Test
+ public void testToVector3f() {
+ // Create a Vector4f instance
+ Vector4f vector4f = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+
+ // Convert to Vector3f
+ Vector3f result = vector4f.toVector3f();
+
+ // Assert the components are correctly converted
+ assertEquals(1.0f, result.getX(), 1e-6, "X component should match");
+ assertEquals(2.0f, result.getY(), 1e-6, "Y component should match");
+ assertEquals(3.0f, result.getZ(), 1e-6, "Z component should match");
+ }
+
+ @Test
+ public void testToVector3fNegativeValues() {
+ // Create a Vector4f instance with negative values
+ Vector4f vector4f = new Vector4f(-5.5f, -6.6f, -7.7f, -8.8f);
+
+ // Convert to Vector3f
+ Vector3f result = vector4f.toVector3f();
+
+ // Assert the components are correctly converted
+ assertEquals(-5.5f, result.getX(), 1e-6, "X component should match");
+ assertEquals(-6.6f, result.getY(), 1e-6, "Y component should match");
+ assertEquals(-7.7f, result.getZ(), 1e-6, "Z component should match");
+ }
+
+ @Test
+ public void testToVector3fZeroValues() {
+ // Create a Vector4f instance with zero values
+ Vector4f vector4f = new Vector4f(0.0f, 0.0f, 0.0f, 0.0f);
+
+ // Convert to Vector3f
+ Vector3f result = vector4f.toVector3f();
+
+ // Assert the components are correctly converted
+ assertEquals(0.0f, result.getX(), 1e-6, "X component should match");
+ assertEquals(0.0f, result.getY(), 1e-6, "Y component should match");
+ assertEquals(0.0f, result.getZ(), 1e-6, "Z component should match");
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Divide By W
+ // ----------------------------------------------------------------------------------------------
+
+ @Test
+ public void testDivideByW_validW() {
+ // Test case where w is non-zero
+ Vector4f vector = new Vector4f(4.0f, 8.0f, 12.0f, 2.0f);
+
+ Vector4f result = vector.divideByW();
+
+ // Expected result after division by w = 2.0f
+ Vector4f expected = new Vector4f(2.0f, 4.0f, 6.0f, 1.0f);
+
+ assertEquals(expected, result, "The vector should be correctly divided by w.");
+ }
+
+ @Test
+ public void testDivideByW_wIsZero() {
+ // Test case where w is zero, expecting an ArithmeticException
+ Vector4f vector = new Vector4f(4.0f, 8.0f, 12.0f, 0.0f);
+
+ ArithmeticException exception =
+ assertThrows(
+ ArithmeticException.class,
+ () -> {
+ vector.divideByW();
+ });
+
+ assertEquals(
+ "Division by zero.", exception.getMessage(), "Exception message should be correct.");
+ }
+
+ @Test
+ public void testDivideByW_wIsNegative() {
+ // Test case where w is negative
+ Vector4f vector = new Vector4f(4.0f, 8.0f, 12.0f, -2.0f);
+
+ Vector4f result = vector.divideByW();
+
+ // Expected result after division by w = -2.0f
+ Vector4f expected = new Vector4f(-2.0f, -4.0f, -6.0f, 1.0f);
+
+ assertEquals(expected, result, "The vector should be correctly divided by w.");
+ }
+
+ @Test
+ public void testDivideByW_createsNewInstance() {
+ // Create an original vector
+ Vector4f originalVector = new Vector4f(4.0f, 8.0f, 12.0f, 2.0f);
+
+ // Call divideByW on the original vector
+ Vector4f newVector = originalVector.divideByW();
+
+ // Check that the original vector is not modified
+ assertEquals(
+ new Vector4f(4.0f, 8.0f, 12.0f, 2.0f),
+ originalVector,
+ "The original vector should remain unchanged.");
+
+ // Check that a new vector is returned with the correct values
+ Vector4f expectedNewVector = new Vector4f(2.0f, 4.0f, 6.0f, 1.0f);
+ assertEquals(
+ expectedNewVector,
+ newVector,
+ "The returned vector should be a new instance with the expected values.");
+
+ // Check that the original and new vectors are different instances
+ assertNotSame(originalVector, newVector);
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Divide By W Local
+ // ----------------------------------------------------------------------------------------------
+
+ @Test
+ public void testDivideByWLocal_updatesOriginalVector() {
+ // Create a vector
+ Vector4f vector = new Vector4f(4.0f, 8.0f, 12.0f, 2.0f);
+
+ // Call divideByWLocal on the vector
+ Vector4f result = vector.divideByWLocal();
+
+ // Check that the original vector has been modified
+ assertEquals(
+ new Vector4f(2.0f, 4.0f, 6.0f, 1.0f),
+ vector,
+ "The original vector should be modified in place.");
+
+ // Check that the method returns the same instance (this) with the modified values
+ assertSame(vector, result);
+ }
+
+ @Test
+ public void testDivideByWLocal_throwsArithmeticException_whenWIsZero() {
+ // Create a vector with w = 0
+ Vector4f vector = new Vector4f(4.0f, 8.0f, 12.0f, 0.0f);
+
+ // Assert that calling divideByWLocal throws ArithmeticException
+ assertThrows(
+ ArithmeticException.class,
+ () -> {
+ vector.divideByWLocal();
+ },
+ "Division by zero should throw an ArithmeticException.");
+ }
+
+ @Test
+ public void testDivideByWLocal_doesNotCreateNewInstance() {
+ // Create a vector
+ Vector4f vector = new Vector4f(4.0f, 8.0f, 12.0f, 2.0f);
+
+ // Call divideByWLocal on the vector
+ vector.divideByWLocal();
+
+ // Check that the method does not create a new instance, but modifies the original instance
+ assertSame(vector, vector.divideByWLocal());
+ }
}