diff --git a/src/main/java/engine/application/BasicApplication.java b/src/main/java/engine/application/BasicApplication.java index fc85406f..3c3dab78 100644 --- a/src/main/java/engine/application/BasicApplication.java +++ b/src/main/java/engine/application/BasicApplication.java @@ -1,6 +1,7 @@ package engine.application; import engine.Timer; +import engine.components.FlyByCameraControl; import engine.debug.DebugInfoUpdater; import engine.debug.DebugOverlay; import engine.debug.FpsGraph; @@ -10,13 +11,14 @@ import engine.processing.ProcessingApplication; import engine.scene.Scene; import engine.scene.SceneNode; +import engine.scene.camera.PerspectiveCamera; import workspace.ui.Graphics; public abstract class BasicApplication implements Application { private boolean launched; - private boolean displayInfoText = true; + private boolean displayInfo = true; private boolean isPaused = false; @@ -74,6 +76,18 @@ public void initialize() { initializeDebugOverlay(); fpsGraph = new FpsGraph(new FpsHistory()); onInitialize(); + setupDefaultCamera(); + } + + private void setupDefaultCamera() { + if (activeScene == null) return; + if (activeScene.getActiveCamera() != null) return; + + PerspectiveCamera defaultCamera = new PerspectiveCamera(); + activeScene.setActiveCamera(defaultCamera); + SceneNode cameraNode = new SceneNode("DefaultCamera"); + cameraNode.addComponent(new FlyByCameraControl(input, defaultCamera)); + activeScene.addNode(cameraNode); } private void initializeDebugOverlay() { @@ -110,7 +124,7 @@ public void update() { } @Override - public void render(Graphics g) { + public void render(Graphics g) { if (activeScene != null) { activeScene.render(g); } @@ -124,7 +138,7 @@ public void render(Graphics g) { g.strokeWeight(1); renderUi(g); renderDebugUi(g); - fpsGraph.render(g); + g.enableDepthTest(); } @@ -134,8 +148,9 @@ private void renderUi(Graphics g) { } private void renderDebugUi(Graphics g) { - if (!displayInfoText) return; + if (!displayInfo) return; debugOverlay.render(g); + fpsGraph.render(g); } @Override @@ -170,8 +185,8 @@ public Scene getActiveScene() { public void setActiveScene(Scene activeScene) { this.activeScene = activeScene; } - - public void setDisplayInfoText(boolean displayInfoText) { - this.displayInfoText = displayInfoText; + + public void setDisplayInfo(boolean displayInfo) { + this.displayInfo = displayInfo; } } diff --git a/src/main/java/engine/processing/ProcessingTexture.java b/src/main/java/engine/processing/ProcessingTexture.java index b350bc2a..585e6b31 100644 --- a/src/main/java/engine/processing/ProcessingTexture.java +++ b/src/main/java/engine/processing/ProcessingTexture.java @@ -24,7 +24,6 @@ public int getHeight() { @Override public void bind(int unit) { // Processing doesn't use texture units in the same way, just bind globally - image.loadPixels(); } @Override diff --git a/src/main/java/engine/render/Material.java b/src/main/java/engine/render/Material.java index 40debe56..17a1e5d5 100644 --- a/src/main/java/engine/render/Material.java +++ b/src/main/java/engine/render/Material.java @@ -68,8 +68,7 @@ public class Material { /** Shininess factor for specular highlights. */ private final float shininess; - private Texture normalTexture; - + /** The diffuse texture map (map_Kd) of the material. */ private Texture diffuseTexture; /** @@ -88,7 +87,6 @@ private Material(Builder builder) { this.diffuse = builder.diffuse; this.specular = builder.specular; this.shininess = builder.shininess; - this.normalTexture = builder.normalTexture; this.diffuseTexture = builder.diffuseTexture; } @@ -112,8 +110,6 @@ public static class Builder { private Texture diffuseTexture = null; - private Texture normalTexture = null; - /** * Sets the base color of the material. * @@ -174,17 +170,6 @@ 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. * @@ -217,9 +202,6 @@ public void apply(Graphics g) { if (diffuseTexture != null) { g.bindTexture(diffuseTexture, 0); // Bind to texture unit 0 } - if (normalTexture != null) { - g.bindTexture(normalTexture, 1); // Bind to texture unit 1 - } } /** @@ -281,11 +263,19 @@ public float getShininess() { return shininess; } + /** + * Returns the diffuse texture map (map_Kd) of the material. + * + *
The diffuse texture is a 2D image used to define the base color and pattern of the surface, + * simulating the appearance of the material under diffuse lighting. This texture is typically + * applied using UV mapping to wrap the image onto the geometry of a 3D model. + * + *
In the context of material definition files (e.g., MTL for OBJ models), this corresponds to + * the `map_Kd` property, which specifies the file path to the texture image. + * + * @return The diffuse texture map as {@link Texture}. + */ public Texture getDiffuseTexture() { return diffuseTexture; } - - public Texture getNormalTexture() { - return normalTexture; - } } diff --git a/src/main/java/engine/scene/Scene.java b/src/main/java/engine/scene/Scene.java index d8ccdd64..9348a9b7 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 math.Color; import workspace.GraphicsPImpl; import workspace.ui.Graphics; @@ -37,6 +38,9 @@ public class Scene { /** Name of the scene. Used for identification or debugging purposes. */ private final String name; + /** The background color of the scene. Defaults to black if not explicitly set. */ + private Color background; + /** The currently active camera that determines the scene's view transformation. */ private Camera activeCamera; @@ -56,6 +60,7 @@ public Scene(String name) { throw new IllegalArgumentException("Name cannot be null."); } this.name = name; + this.background = new Color(0, 0, 0, 1); } /** @@ -116,6 +121,8 @@ public void update(float deltaTime) { * compatibility with most rendering APIs. */ public void render(Graphics g) { + g.clear(background); + if (activeCamera != null) { g.applyCamera(activeCamera); } @@ -295,4 +302,30 @@ public boolean isWireframeMode() { public void setWireframeMode(boolean wireframeMode) { this.wireframeMode = wireframeMode; } + + /** + * Retrieves the background color of the scene. + * + *
The background color is used to clear the rendering surface before drawing the scene. + * + * @return The current background color of the scene. + */ + public Color getBackground() { + return background; + } + + /** + * Sets the background color of the scene. + * + *
The background color is used to clear the rendering surface before drawing the scene. + * + * @param background The new background color for the scene. Must not be {@code null}. + * @throws IllegalArgumentException if the provided background color is {@code null}. + */ + public void setBackground(Color background) { + if (background == null) { + throw new IllegalArgumentException("Background color cannot be null."); + } + this.background = background; + } } diff --git a/src/main/java/engine/scene/light/DirectionalLight.java b/src/main/java/engine/scene/light/DirectionalLight.java index b1399d92..9be2fd29 100644 --- a/src/main/java/engine/scene/light/DirectionalLight.java +++ b/src/main/java/engine/scene/light/DirectionalLight.java @@ -45,6 +45,25 @@ public DirectionalLight() { this(Color.WHITE, new Vector3f(0, 1, 0), 1.0f); } + /** + * Creates a new DirectionalLight instance with the specified color and direction. + * + *
This constructor initializes the light with the given color and direction, and a default + * intensity of 1.0. The provided direction vector is normalized during initialization to ensure + * consistent light behavior. + * + * @param color The color of the light emitted by the directional light source. Represents the RGB + * values of the light's color. This parameter cannot be null. + * @param direction The direction of the light source. This vector determines the direction in + * which the light rays travel. The provided vector is automatically normalized during + * construction, ensuring the direction's magnitude is always 1. This parameter cannot be + * null. + * @throws IllegalArgumentException if the direction or color is null. + */ + public DirectionalLight(Color color, Vector3f direction) { + this(color, direction, 1.0f); + } + /** * Creates a new DirectionalLight instance. * diff --git a/src/main/java/math/Plane.java b/src/main/java/math/Plane.java index 3dba9b2a..ae8d5b28 100644 --- a/src/main/java/math/Plane.java +++ b/src/main/java/math/Plane.java @@ -75,7 +75,7 @@ public float distanceToPoint(Vector3f point) { * @return the normal vector of the plane. */ public Vector3f getNormal() { - return normal; + return new Vector3f(normal); } /** diff --git a/src/main/java/math/Ray3f.java b/src/main/java/math/Ray3f.java index d3eaf9b5..e417af79 100644 --- a/src/main/java/math/Ray3f.java +++ b/src/main/java/math/Ray3f.java @@ -36,12 +36,69 @@ public Ray3f(Vector3f origin, Vector3f direction) { if (direction == null) { throw new IllegalArgumentException("Direction cannot be null."); } - this.origin = origin; - this.direction = direction; + this.origin = new Vector3f(origin); + this.direction = new Vector3f(direction); this.direction.normalizeLocal(); this.directionInv = direction.reciprocal(); } + /** + * Computes the shortest distance from the ray to a point in 3D space. + * + *
This method calculates the perpendicular distance from the given point to the ray. If the + * point lies behind the origin of the ray, the distance from the point to the ray's origin is + * returned. If the point lies along the ray's path (in the direction of the ray), the + * perpendicular distance is calculated. The ray is considered to extend infinitely in both + * directions from the origin. + * + * @param point The point in 3D space to compute the distance to (non-null). + * @return The shortest distance from the ray to the point. If the point is behind the ray's + * origin, the distance from the point to the origin is returned. + * @throws IllegalArgumentException if the point is null. + */ + public float distanceToPoint(Vector3f point) { + if (point == null) { + throw new IllegalArgumentException("Point cannot be null."); + } + + // Calculate vector from ray origin to the point + Vector3f toPoint = point.subtract(origin); + + // Project the vector to the ray's direction (dot product normalized) + float projection = toPoint.dot(direction); + + // If the projection is negative, the point is behind the ray's origin + if (projection < 0) { + // Return the distance from the origin to the point + return toPoint.length(); + } + + // Return the perpendicular distance (calculate as the distance from the point to the closest + // point on the ray) + Vector3f closestPoint = getPointAt(projection); + return closestPoint.subtract(point).length(); + } + + /** + * Computes the point along the ray at a given parameter {@code t}. + * + *
The formula for the point is: + * + *
{@code
+ * point = origin + t * direction
+ * }
+ *
+ * where {@code t} is a scalar representing the distance along the ray from the origin. Positive
+ * values of {@code t} will give points in the direction the ray is pointing, while negative
+ * values will give points in the opposite direction.
+ *
+ * @param t The parameter along the ray (can be negative, zero, or positive).
+ * @return The point at parameter {@code t}.
+ */
+ public Vector3f getPointAt(float t) {
+ return origin.add(direction.mult(t));
+ }
+
/**
* Returns the origin of the ray.
*
@@ -50,7 +107,7 @@ public Ray3f(Vector3f origin, Vector3f direction) {
* @return The origin of the ray.
*/
public Vector3f getOrigin() {
- return origin;
+ return new Vector3f(origin);
}
/**
@@ -62,7 +119,7 @@ public Vector3f getOrigin() {
* @return The direction vector of the ray.
*/
public Vector3f getDirection() {
- return direction;
+ return new Vector3f(direction);
}
/**
@@ -74,26 +131,6 @@ public Vector3f getDirection() {
* @return The reciprocal of the direction vector of the ray.
*/
public Vector3f getDirectionInv() {
- return directionInv;
- }
-
- /**
- * Computes the point along the ray at a given parameter {@code t}.
- *
- * The formula for the point is: - * - *
{@code
- * point = origin + t * direction
- * }
- *
- * where {@code t} is a scalar representing the distance along the ray from the origin. Positive
- * values of {@code t} will give points in the direction the ray is pointing, while negative
- * values will give points in the opposite direction.
- *
- * @param t The parameter along the ray (can be negative, zero, or positive).
- * @return The point at parameter {@code t}.
- */
- public Vector3f getPointAt(float t) {
- return origin.add(direction.mult(t));
+ return new Vector3f(directionInv);
}
}
diff --git a/src/main/java/mesh/creator/special/HoneyCombCreator.java b/src/main/java/mesh/creator/special/HoneyCombCreator.java
index 0b3f8b07..d11b10ae 100644
--- a/src/main/java/mesh/creator/special/HoneyCombCreator.java
+++ b/src/main/java/mesh/creator/special/HoneyCombCreator.java
@@ -1,145 +1,143 @@
package mesh.creator.special;
+import math.Bounds;
import math.Mathf;
import mesh.Mesh3D;
import mesh.creator.FillType;
import mesh.creator.IMeshCreator;
import mesh.creator.primitives.CircleCreator;
+import mesh.modifier.CenterAtModifier;
import mesh.modifier.ExtrudeModifier;
+import mesh.modifier.RotateYModifier;
import mesh.modifier.SolidifyModifier;
import mesh.selection.FaceSelection;
import mesh.util.Bounds3;
+import mesh.util.MeshBoundsCalculator;
public class HoneyCombCreator implements IMeshCreator {
- private int rowCount;
-
- private int colCount;
-
- private float cellRadius;
-
- private float height;
-
- private float innerScale;
-
- private Mesh3D mesh;
-
- public HoneyCombCreator() {
- rowCount = 2;
- colCount = 2;
- cellRadius = 0.5f;
- height = 0.2f;
- innerScale = 0.9f;
- }
-
- @Override
- public Mesh3D create() {
- initializeMesh();
- createSegments();
- removeDoubleVertices();
- createInsets();
- solidify();
- center();
- return mesh;
- }
-
- private void solidify() {
- if (height == 0)
- return;
- new SolidifyModifier(height).modify(mesh);
- }
-
- private void center() {
- Bounds3 bounds = mesh.calculateBounds();
- mesh.translateX(-bounds.getCenterX());
- mesh.translateZ(-bounds.getCenterZ());
- mesh.translateY(-height / 2f);
- }
-
- private void createInsets() {
- ExtrudeModifier modifier = new ExtrudeModifier(innerScale, 0);
- FaceSelection selection = new FaceSelection(mesh);
- selection.selectAll();
- modifier.modify(mesh, selection.getFaces());
- mesh.removeFaces(selection.getFaces());
- }
-
- private void removeDoubleVertices() {
- mesh.removeDoubles(4);
- }
-
- private void createSegments() {
- for (int i = 0; i < colCount; i++) {
- for (int j = 0; j < rowCount; j++) {
- Mesh3D segment = createHexSegment();
- Bounds3 bounds = segment.calculateBounds();
- float width = bounds.getWidth();
- float depth = bounds.getDepth();
- segment.translateX(i * width);
- segment.translateZ(j * (depth - cellRadius / 2f));
- if (j % 2 == 1)
- segment.translateX(width / 2.0f);
- mesh.append(segment);
- }
- }
- }
-
- private CircleCreator createCircleCreator() {
- CircleCreator creator = new CircleCreator();
- creator.setFillType(FillType.N_GON);
- creator.setRadius(cellRadius);
- creator.setVertices(6);
- return creator;
- }
-
- private Mesh3D createHexSegment() {
- Mesh3D segment = createCircleCreator().create();
- segment.rotateY(Mathf.HALF_PI);
- return segment;
- }
-
- private void initializeMesh() {
- mesh = new Mesh3D();
- }
-
- public int getRowCount() {
- return rowCount;
- }
-
- public void setRowCount(int rowCount) {
- this.rowCount = rowCount;
- }
-
- public int getColCount() {
- return colCount;
- }
-
- public void setColCount(int colCount) {
- this.colCount = colCount;
- }
-
- public float getCellRadius() {
- return cellRadius;
- }
-
- public void setCellRadius(float cellRadius) {
- this.cellRadius = cellRadius;
- }
-
- public float getHeight() {
- return height;
- }
-
- public void setHeight(float height) {
- this.height = height;
- }
-
- public float getInnerScale() {
- return innerScale;
- }
-
- public void setInnerScale(float innerScale) {
- this.innerScale = innerScale;
- }
-
+ private int rowCount;
+
+ private int colCount;
+
+ private float cellRadius;
+
+ private float height;
+
+ private float innerScale;
+
+ private Mesh3D mesh;
+
+ public HoneyCombCreator() {
+ rowCount = 2;
+ colCount = 2;
+ cellRadius = 0.5f;
+ height = 0.2f;
+ innerScale = 0.9f;
+ }
+
+ @Override
+ public Mesh3D create() {
+ initializeMesh();
+ createSegments();
+ removeDoubleVertices();
+ createInsets();
+ solidify();
+ centerAtOrigin();
+ return mesh;
+ }
+
+ private void solidify() {
+ if (height == 0) return;
+ new SolidifyModifier(height).modify(mesh);
+ }
+
+ private void centerAtOrigin() {
+ mesh.apply(new CenterAtModifier());
+ }
+
+ private void createInsets() {
+ ExtrudeModifier modifier = new ExtrudeModifier(innerScale, 0);
+ FaceSelection selection = new FaceSelection(mesh);
+ selection.selectAll();
+ modifier.modify(mesh, selection.getFaces());
+ mesh.removeFaces(selection.getFaces());
+ }
+
+ private void removeDoubleVertices() {
+ mesh.removeDoubles(4);
+ }
+
+ private void createSegments() {
+ for (int i = 0; i < colCount; i++) {
+ for (int j = 0; j < rowCount; j++) {
+ Mesh3D segment = createHexSegment();
+ Bounds3 bounds = segment.calculateBounds();
+ float width = bounds.getWidth();
+ float depth = bounds.getDepth();
+ segment.translateX(i * width);
+ segment.translateZ(j * (depth - cellRadius / 2f));
+ if (j % 2 == 1) segment.translateX(width / 2.0f);
+ mesh.append(segment);
+ }
+ }
+ }
+
+ private CircleCreator createCircleCreator() {
+ CircleCreator creator = new CircleCreator();
+ creator.setFillType(FillType.N_GON);
+ creator.setRadius(cellRadius);
+ creator.setVertices(6);
+ return creator;
+ }
+
+ private Mesh3D createHexSegment() {
+ Mesh3D segment = createCircleCreator().create();
+ segment.apply(new RotateYModifier(Mathf.HALF_PI));
+ return segment;
+ }
+
+ private void initializeMesh() {
+ mesh = new Mesh3D();
+ }
+
+ public int getRowCount() {
+ return rowCount;
+ }
+
+ public void setRowCount(int rowCount) {
+ this.rowCount = rowCount;
+ }
+
+ public int getColCount() {
+ return colCount;
+ }
+
+ public void setColCount(int colCount) {
+ this.colCount = colCount;
+ }
+
+ public float getCellRadius() {
+ return cellRadius;
+ }
+
+ public void setCellRadius(float cellRadius) {
+ this.cellRadius = cellRadius;
+ }
+
+ public float getHeight() {
+ return height;
+ }
+
+ public void setHeight(float height) {
+ this.height = height;
+ }
+
+ public float getInnerScale() {
+ return innerScale;
+ }
+
+ public void setInnerScale(float innerScale) {
+ this.innerScale = innerScale;
+ }
}
diff --git a/src/main/java/workspace/GraphicsPImpl.java b/src/main/java/workspace/GraphicsPImpl.java
index e0ec545f..35a234bf 100644
--- a/src/main/java/workspace/GraphicsPImpl.java
+++ b/src/main/java/workspace/GraphicsPImpl.java
@@ -550,4 +550,9 @@ public void drawImage(Image image, float x, float y, float width, float height)
throw new IllegalArgumentException("Unsupported image backend.");
}
}
+
+ @Override
+ public void clear(math.Color color) {
+ g.background(color.getRedInt(), color.getGreenInt(), color.getBlueInt(), color.getAlphaInt());
+ }
}
diff --git a/src/main/java/workspace/ui/Graphics2D.java b/src/main/java/workspace/ui/Graphics2D.java
index 1b155c54..a1cbf700 100644
--- a/src/main/java/workspace/ui/Graphics2D.java
+++ b/src/main/java/workspace/ui/Graphics2D.java
@@ -226,6 +226,17 @@ public interface Graphics2D {
*/
void text(String text, float x, float y);
+ /**
+ * Clears the rendering context with the specified color.
+ *
+ * This method fills the entire viewport with the provided color, effectively resetting the + * drawing surface to a blank state. Any previously drawn content is overwritten. + * + * @param color The {@link math.Color} to use for clearing the viewport. The color is applied + * uniformly across the entire rendering surface. + */ + void clear(math.Color color); + void drawImage(Image image, float x, float y); void drawImage(Image image, float x, float y, float width, float height);