From 06ffe8ec75590ea61895ae5f048cec074cd7208a Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 14 Dec 2024 16:23:01 +0100 Subject: [PATCH 01/59] Refactoring - Removed clutter - Changed visibility of renderSelf to protected - Added getter and setter for positioning to the ui element interface --- src/main/java/workspace/ui/UiComponent.java | 85 ++++++++----------- .../java/workspace/ui/elements/UiElement.java | 80 +++++++++++------ 2 files changed, 93 insertions(+), 72 deletions(-) diff --git a/src/main/java/workspace/ui/UiComponent.java b/src/main/java/workspace/ui/UiComponent.java index 39c72eca..01d09453 100644 --- a/src/main/java/workspace/ui/UiComponent.java +++ b/src/main/java/workspace/ui/UiComponent.java @@ -27,12 +27,12 @@ public class UiComponent implements UiElement { protected Border border; - protected List components; - protected Layout layout; - + private Renderer renderer; + protected List components; + public UiComponent() { this(0, 0, 0, 0, true, Color.BLACK, Color.GRAY); } @@ -48,29 +48,6 @@ public UiComponent(int x, int y, int width, int height, boolean visible, this.background = background; this.components = new ArrayList(); } - - @Override - public void setRenderer(Renderer renderer) { - this.renderer = renderer; - } - - @Override - public Renderer getRenderer() { - return renderer; - } - - @Override - public Insets getInsets() { - if (border == null) - return new Insets(); - return border.getInsets(); - } - - - @Override - public void setBorder(Border border) { - this.border = border; - } @Override public void render(Graphics g) { @@ -101,12 +78,40 @@ protected void renderBorder(Graphics g) { border.renderBorder(g, 0, 0, getWidth(), getHeight()); } + protected void renderSelf(Graphics g) { + if (renderer == null) + return; + renderer.render(g, this); + } + protected void renderChildren(Graphics g) { for (UiComponent component : components) { component.render(g); } } + @Override + public void setRenderer(Renderer renderer) { + this.renderer = renderer; + } + + @Override + public Renderer getRenderer() { + return renderer; + } + + @Override + public Insets getInsets() { + if (border == null) + return new Insets(); + return border.getInsets(); + } + + @Override + public void setBorder(Border border) { + this.border = border; + } + @Override public void setLayout(Layout layout) { this.layout = layout; @@ -128,12 +133,6 @@ public boolean isVisible() { return visible; } - public void renderSelf(Graphics g) { - if (renderer == null) - return; - renderer.render(g, this); - } - protected void layout() { if (layout == null) return; @@ -187,34 +186,23 @@ public void add(UiComponent component) { components.add(component); layout(); } -// -// public void remove(UiComponent component) { -// if (component == null) -// return; -// components.remove(component); -// layout(); -// } -// -// public int getComponentCount() { -// return components.size(); -// } -// -// public UiComponent getComponentAt(int index) { -// return components.get(index); -// } + @Override public int getX() { return x; } + @Override public void setX(int x) { this.x = x; } + @Override public int getY() { return y; } + @Override public void setY(int y) { this.y = y; } @@ -224,6 +212,7 @@ public int getWidth() { return width; } + @Override public void setWidth(int width) { this.width = width; } @@ -233,6 +222,7 @@ public int getHeight() { return height; } + @Override public void setHeight(int height) { this.height = height; } @@ -253,5 +243,4 @@ public void setBackground(Color background) { this.background = background; } - } diff --git a/src/main/java/workspace/ui/elements/UiElement.java b/src/main/java/workspace/ui/elements/UiElement.java index 4f44f24e..90d149dd 100644 --- a/src/main/java/workspace/ui/elements/UiElement.java +++ b/src/main/java/workspace/ui/elements/UiElement.java @@ -18,6 +18,62 @@ */ public interface UiElement { + /** + * Gets the X-coordinate of this element. + * + * @return The X-coordinate in pixels. + */ + int getX(); + + /** + * Sets the X-coordinate of this element. + * + * @param x The new X-coordinate in pixels. + */ + void setX(int x); + + /** + * Gets the Y-coordinate of this element. + * + * @return The Y-coordinate in pixels. + */ + int getY(); + + /** + * Sets the Y-coordinate of this element. + * + * @param y The new Y-coordinate in pixels. + */ + void setY(int y); + + /** + * Retrieves the width of this {@code UiElement}. + *

+ * The width represents the horizontal size of the UI element. It is used + * during rendering and layout calculations to determine how much horizontal + * space the element occupies. + *

+ * + * @return The width of this UI element in pixels. + */ + int getWidth(); + + void setWidth(int width); + + /** + * Retrieves the height of this {@code UiElement}. + *

+ * The height represents the vertical size of the UI element. It is used + * during rendering and layout calculations to determine how much vertical + * space the element occupies. + *

+ * + * @return The height of this UI element in pixels. + */ + int getHeight(); + + void setHeight(int height); + /** * Renders this {@code UiElement} using its specific rendering logic. *

@@ -130,30 +186,6 @@ public interface UiElement { */ Insets getInsets(); - /** - * Retrieves the width of this {@code UiElement}. - *

- * The width represents the horizontal size of the UI element. It is used - * during rendering and layout calculations to determine how much horizontal - * space the element occupies. - *

- * - * @return The width of this UI element in pixels. - */ - int getWidth(); - - /** - * Retrieves the height of this {@code UiElement}. - *

- * The height represents the vertical size of the UI element. It is used - * during rendering and layout calculations to determine how much vertical - * space the element occupies. - *

- * - * @return The height of this UI element in pixels. - */ - int getHeight(); - /** * Retrieves the background color of this {@code UiElement}. *

From ef6487297ffbe0361681a9c7d4be88faa18b1b58 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 14 Dec 2024 16:26:35 +0100 Subject: [PATCH 02/59] Completed java doc. --- .../java/workspace/ui/elements/UiElement.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/main/java/workspace/ui/elements/UiElement.java b/src/main/java/workspace/ui/elements/UiElement.java index 90d149dd..b488f1f2 100644 --- a/src/main/java/workspace/ui/elements/UiElement.java +++ b/src/main/java/workspace/ui/elements/UiElement.java @@ -57,7 +57,13 @@ public interface UiElement { * @return The width of this UI element in pixels. */ int getWidth(); - + + /** + * Sets the width of this {@link UiElement} to the specified new width. + * + * @see #getWidth() + * @param width The new width in pixels. + */ void setWidth(int width); /** @@ -71,7 +77,13 @@ public interface UiElement { * @return The height of this UI element in pixels. */ int getHeight(); - + + /** + * Sets the height of this {@link UiElement} to the specified new height. + * + * @see #getHeight() + * @param height The new height in pixels. + */ void setHeight(int height); /** From f1d9d035958e0aaa74e8cc04d0657bca043575b3 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 14 Dec 2024 16:55:00 +0100 Subject: [PATCH 03/59] feat: Add anchor support to UiElement interface - Introduced `Anchor getAnchor()` and `void setAnchor(Anchor anchor)` methods to the UiElement interface. - These methods allow UI elements to define alignment behavior relative to their container or layout logic. - Ensured compatibility with layout systems by exposing anchor management functionality. --- src/main/java/workspace/ui/UiComponent.java | 13 ++++++++++ .../java/workspace/ui/elements/UiElement.java | 25 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/main/java/workspace/ui/UiComponent.java b/src/main/java/workspace/ui/UiComponent.java index 01d09453..62971e00 100644 --- a/src/main/java/workspace/ui/UiComponent.java +++ b/src/main/java/workspace/ui/UiComponent.java @@ -6,6 +6,7 @@ import workspace.ui.border.Border; import workspace.ui.border.Insets; import workspace.ui.elements.UiElement; +import workspace.ui.layout.Anchor; import workspace.ui.layout.Layout; import workspace.ui.renderer.Renderer; @@ -29,6 +30,8 @@ public class UiComponent implements UiElement { protected Layout layout; + protected Anchor anchor; + private Renderer renderer; protected List components; @@ -243,4 +246,14 @@ public void setBackground(Color background) { this.background = background; } + @Override + public Anchor getAnchor() { + return anchor; + } + + @Override + public void setAnchor(Anchor anchor) { + this.anchor = anchor; + } + } diff --git a/src/main/java/workspace/ui/elements/UiElement.java b/src/main/java/workspace/ui/elements/UiElement.java index b488f1f2..ba2abd57 100644 --- a/src/main/java/workspace/ui/elements/UiElement.java +++ b/src/main/java/workspace/ui/elements/UiElement.java @@ -4,6 +4,7 @@ import workspace.ui.Graphics; import workspace.ui.border.Border; import workspace.ui.border.Insets; +import workspace.ui.layout.Anchor; import workspace.ui.layout.Layout; import workspace.ui.renderer.Renderer; @@ -128,6 +129,30 @@ public interface UiElement { */ void setLayout(Layout layout); + /** + * Retrieves the current anchor type of this UI element. + *

+ * Anchors define how this element is aligned relative to its parent or + * specific layout logic. Examples include {@code TOP_LEFT}, {@code CENTER}, + * and {@code BOTTOM_RIGHT}. + *

+ * + * @return The current {@link Anchor} setting for this element. + */ + Anchor getAnchor(); + + /** + * Sets the anchor type for this UI element. + *

+ * The anchor determines how this element aligns in its container based on the + * defined anchor type, such as centering, top-left, or bottom-right + * alignment. + *

+ * + * @param anchor The {@link Anchor} to set for this element. + */ + void setAnchor(Anchor anchor); + /** * Determines if the specified coordinates are within the bounds of this * {@code UiElement}. From 66efa836cbe5f6420e6e4c1851c1ea6c1ea0bc85 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 14 Dec 2024 18:28:43 +0100 Subject: [PATCH 04/59] Refactoring: Extracted Grid3D class from the Workspace. - Decoupled Grid3D from the Processing environment. - Improved modularity by creating a standalone 3D grid visualization class. - Added flexibility for reuse in other rendering contexts. --- src/main/java/workspace/Workspace.java | 27 ++--- src/main/java/workspace/ui/ui3d/Grid3D.java | 107 ++++++++++++++++++++ 2 files changed, 115 insertions(+), 19 deletions(-) create mode 100644 src/main/java/workspace/ui/ui3d/Grid3D.java diff --git a/src/main/java/workspace/Workspace.java b/src/main/java/workspace/Workspace.java index 075f248d..238a58f5 100644 --- a/src/main/java/workspace/Workspace.java +++ b/src/main/java/workspace/Workspace.java @@ -12,6 +12,7 @@ import workspace.render.ObjectSelectionRender; import workspace.render.Shading; import workspace.ui.Color; +import workspace.ui.ui3d.Grid3D; public class Workspace extends Editor implements ModelListener { @@ -30,10 +31,13 @@ public class Workspace extends Editor implements ModelListener { private SceneObject selectedObject; private boolean select; + + private Grid3D grid; private GraphicsPImpl gImpl; public Workspace(PApplet p) { + grid = new Grid3D(32, 32, 1); this.p = p; registerMethods(); firstPersonView = new FirstPersonView(p); @@ -88,24 +92,9 @@ public void applyCamera() { firstPersonView.apply(); } - public void drawGrid(int rows, int cols, float size) { - if (!isGridVisible()) - return; - - p.stroke(UiValues.getColor(UiConstants.KEY_GRID_COLOR).getRGBA()); - p.noFill(); - - p.pushMatrix(); - p.rotateX(PApplet.radians(-90)); - p.translate(-cols / 2, -rows / 2); - - for (int i = 0; i < rows; i++) { - for (int j = 0; j < cols; j++) { - p.rect(j * size, i * size, size, size); - } - } - - p.popMatrix(); + public void drawGrid() { + grid.setVisible(model.isGridVisible()); + grid.render(gImpl); } protected void drawAxis(float size) { @@ -145,7 +134,7 @@ public void pre() { p.lights(); applyTransformations(); p.strokeWeight(1 / getScale()); - drawGrid(32, 32, 1); + drawGrid(); drawAxis(2000); } diff --git a/src/main/java/workspace/ui/ui3d/Grid3D.java b/src/main/java/workspace/ui/ui3d/Grid3D.java new file mode 100644 index 00000000..a21f8f52 --- /dev/null +++ b/src/main/java/workspace/ui/ui3d/Grid3D.java @@ -0,0 +1,107 @@ +package workspace.ui.ui3d; + +import math.Mathf; +import workspace.laf.UiConstants; +import workspace.laf.UiValues; +import workspace.ui.Graphics; + +/** + * Represents a 3D grid visualization that can be rendered in a 3D UI space. + *

+ * This class provides the logic for creating and rendering a 3D spatial grid + * with a given number of rows, columns, and cell size. It is designed to work + * with a custom 3D graphics rendering context, allowing visualization of UI + * layouts or spatial relationships. + *

+ */ +public class Grid3D { + + /** The number of rows in the 3D grid. */ + private int rows; + + /** The number of columns in the 3D grid. */ + private int cols; + + /** The size of each individual cell in the 3D grid. */ + private float size; + + /** + * Indicates whether the 3D grid should currently be visible when rendered. + */ + private boolean visible; + + /** + * Constructs a new {@code Grid3D} instance with the specified number of rows, + * columns, and cell size. + * + * @param rows The number of rows to create in the 3D grid. + * @param cols The number of columns to create in the 3D grid. + * @param size The size (width/height) of each individual cell in the 3D grid. + */ + public Grid3D(int rows, int cols, float size) { + this.rows = rows; + this.cols = cols; + this.size = size; + this.visible = true; + } + + /** + * Renders the 3D grid visualization using the provided graphics context. + *

+ * This method only performs rendering if the grid is currently visible. The + * rendering logic draws a series of rectangles in a 3D space to represent the + * cells of the grid. It applies necessary transformations for proper + * alignment and orientation in a 3D context. + *

+ * + * @param g The graphics context used for rendering the 3D grid. + */ + public void render(Graphics g) { + if (!isVisible()) { + return; + } + g.setColor(UiValues.getColor(UiConstants.KEY_GRID_COLOR)); + g.pushMatrix(); + g.rotateX(-Mathf.HALF_PI); + g.translate(-cols / 2.0f, -rows / 2.0f); + renderGridLines(g); + g.popMatrix(); + } + + /** + * Renders the grid lines. + * + * @param g The graphics context used for rendering. + */ + private void renderGridLines(Graphics g) { + for (int i = 0; i < rows; i++) { + for (int j = 0; j < cols; j++) { + g.drawRect(j * size, i * size, size, size); + } + } + } + + /** + * Checks whether the 3D grid visualization is currently visible. + * + * @return {@code true} if the 3D grid is visible; {@code false} otherwise. + */ + public boolean isVisible() { + return visible; + } + + /** + * Sets the visibility state of the 3D grid visualization. + *

+ * When set to {@code true}, the grid will be rendered during the next + * invocation of the {@code render} method. If set to {@code false}, rendering + * will be skipped. + *

+ * + * @param visible The new visibility state of the 3D grid. + */ + public void setVisible(boolean visible) { + this.visible = visible; + } + +} \ No newline at end of file From 50ec2484c419354dc920a20a5423595867ebde6d Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 14 Dec 2024 18:50:15 +0100 Subject: [PATCH 05/59] Refactoring: Extracted Axis3D class from Workspace. Decoupled the axis visualization logic from the Workspace class. Created a standalone Axis3D class for managing and rendering 3D axes. Enhanced modularity and reusability of axis rendering functionality. Added 3d line rendering to the graphics context. --- src/main/java/workspace/GraphicsPImpl.java | 10 + src/main/java/workspace/Workspace.java | 35 +--- src/main/java/workspace/ui/Graphics.java | 2 + src/main/java/workspace/ui/ui3d/Axis3D.java | 196 ++++++++++++++++++++ 4 files changed, 217 insertions(+), 26 deletions(-) create mode 100644 src/main/java/workspace/ui/ui3d/Axis3D.java diff --git a/src/main/java/workspace/GraphicsPImpl.java b/src/main/java/workspace/GraphicsPImpl.java index 4d871a78..f667c56c 100644 --- a/src/main/java/workspace/GraphicsPImpl.java +++ b/src/main/java/workspace/GraphicsPImpl.java @@ -100,6 +100,16 @@ public void drawLine(float x1, float y1, float x2, float y2) { g.popStyle(); } + @Override + public void drawLine(float x1, float y1, float z1, float x2, float y2, + float z2) { + g.pushStyle(); + g.noFill(); + stroke(); + g.line(x1, y1, z1, x2, y2, z2); + g.popStyle(); + } + @Override public void fillRect(float x, float y, float width, float height) { g.pushStyle(); diff --git a/src/main/java/workspace/Workspace.java b/src/main/java/workspace/Workspace.java index 238a58f5..266a8333 100644 --- a/src/main/java/workspace/Workspace.java +++ b/src/main/java/workspace/Workspace.java @@ -12,6 +12,7 @@ import workspace.render.ObjectSelectionRender; import workspace.render.Shading; import workspace.ui.Color; +import workspace.ui.ui3d.Axis3D; import workspace.ui.ui3d.Grid3D; public class Workspace extends Editor implements ModelListener { @@ -33,11 +34,14 @@ public class Workspace extends Editor implements ModelListener { private boolean select; private Grid3D grid; + + private Axis3D axis; private GraphicsPImpl gImpl; public Workspace(PApplet p) { grid = new Grid3D(32, 32, 1); + axis = new Axis3D(1); this.p = p; registerMethods(); firstPersonView = new FirstPersonView(p); @@ -98,32 +102,11 @@ public void drawGrid() { } protected void drawAxis(float size) { - p.pushStyle(); - p.pushMatrix(); - - p.noFill(); - p.strokeWeight(1.5f / getScale()); - - if (isxAxisVisible()) { - p.stroke(UiValues.getColor(UiConstants.KEY_AXIS_X_COLOR).getRGBA()); - p.line(size, 0, 0, 0, 0, 0); - p.line(-size, 0, 0, 0, 0, 0); - } - - if (isyAxisVisible()) { - p.stroke(UiValues.getColor(UiConstants.KEY_AXIS_Y_COLOR).getRGBA()); - p.line(0, size, 0, 0, 0, 0); - p.line(0, -size, 0, 0, 0, 0); - } - - if (iszAxisVisible()) { - p.stroke(UiValues.getColor(UiConstants.KEY_AXIS_Z_COLOR).getRGBA()); - p.line(0, 0, size, 0, 0, 0); - p.line(0, 0, -size, 0, 0, 0); - } - - p.popStyle(); - p.popMatrix(); + axis.setSize(size); + axis.setXAxisVisible(isxAxisVisible()); + axis.setYAxisVisible(isyAxisVisible()); + axis.setZAxisVisible(iszAxisVisible()); + axis.render(gImpl, getScale()); } public void pre() { diff --git a/src/main/java/workspace/ui/Graphics.java b/src/main/java/workspace/ui/Graphics.java index 1d7d95dc..e8d33a67 100644 --- a/src/main/java/workspace/ui/Graphics.java +++ b/src/main/java/workspace/ui/Graphics.java @@ -31,6 +31,8 @@ public interface Graphics { void fillOval(float x, float y, float width, float height); void drawLine(float x1, float y1, float x2, float y2); + + void drawLine(float x1, float y1, float z1, float x2, float y2, float z2); void fillFaces(Mesh3D mesh); diff --git a/src/main/java/workspace/ui/ui3d/Axis3D.java b/src/main/java/workspace/ui/ui3d/Axis3D.java new file mode 100644 index 00000000..375ccc27 --- /dev/null +++ b/src/main/java/workspace/ui/ui3d/Axis3D.java @@ -0,0 +1,196 @@ +package workspace.ui.ui3d; + +import workspace.laf.UiConstants; +import workspace.laf.UiValues; +import workspace.ui.Graphics; + +/** + * Represents a 3D axis visualization that can be rendered with customizable + * visibility for each axis. Each axis is drawn with its corresponding color, as + * defined by the UI constants, and can be toggled on or off individually. + *

+ * The axis lines are rendered in the X, Y, and Z directions, with their lengths + * determined by the specified size parameter. + *

+ */ +public class Axis3D { + + /** + * Visibility flag for the X-axis. + */ + private boolean xAxisVisible; + + /** + * Visibility flag for the Y-axis. + */ + private boolean yAxisVisible; + + /** + * Visibility flag for the Z-axis. + */ + private boolean zAxisVisible; + + /** + * The length of each axis, extending equally in both positive and negative + * directions. + */ + private float size; + + /** + * Constructs an {@code Axis3D} with the specified size. All axes are visible + * by default. + * + * @param size the length of each axis; must be positive + * @throws IllegalArgumentException if the size is not positive + */ + public Axis3D(float size) { + if (size <= 0) { + throw new IllegalArgumentException("Size must be positive."); + } + this.size = size; + this.xAxisVisible = true; + this.yAxisVisible = true; + this.zAxisVisible = true; + } + + /** + * Renders the 3D axis using the provided {@link Graphics} context. The axis + * lines are drawn based on their visibility flags and scaled according to the + * specified stroke weight factor. + * + * @param g the {@link Graphics} context to draw on + * @param scale the scale factor for adjusting the stroke weight; must be + * positive + */ + public void render(Graphics g, float scale) { + if (scale <= 0) { + throw new IllegalArgumentException("Scale must be positive."); + } + g.pushMatrix(); + g.strokeWeight(1.5f / scale); + renderXAxis(g); + renderYAxis(g); + renderZAxis(g); + g.popMatrix(); + } + + /** + * Renders the X-axis if it is visible. The X-axis is drawn in the positive + * and negative X directions with the color defined by + * {@link UiConstants#KEY_AXIS_X_COLOR}. + * + * @param g the {@link Graphics} context to draw on + */ + private void renderXAxis(Graphics g) { + if (!xAxisVisible) { + return; + } + g.setColor(UiValues.getColor(UiConstants.KEY_AXIS_X_COLOR)); + g.drawLine(size, 0, 0, 0, 0, 0); + g.drawLine(-size, 0, 0, 0, 0, 0); + } + + /** + * Renders the Y-axis if it is visible. The Y-axis is drawn in the positive + * and negative Y directions with the color defined by + * {@link UiConstants#KEY_AXIS_Y_COLOR}. + * + * @param g the {@link Graphics} context to draw on + */ + private void renderYAxis(Graphics g) { + if (!yAxisVisible) { + return; + } + g.setColor(UiValues.getColor(UiConstants.KEY_AXIS_Y_COLOR)); + g.drawLine(0, size, 0, 0, 0, 0); + g.drawLine(0, -size, 0, 0, 0, 0); + } + + /** + * Renders the Z-axis if it is visible. The Z-axis is drawn in the positive + * and negative Z directions with the color defined by + * {@link UiConstants#KEY_AXIS_Z_COLOR}. + * + * @param g the {@link Graphics} context to draw on + */ + private void renderZAxis(Graphics g) { + if (!zAxisVisible) { + return; + } + g.setColor(UiValues.getColor(UiConstants.KEY_AXIS_Z_COLOR)); + g.drawLine(0, 0, size, 0, 0, 0); + g.drawLine(0, 0, -size, 0, 0, 0); + } + + /** + * Returns whether the X-axis is visible. + * + * @return {@code true} if the X-axis is visible; {@code false} otherwise + */ + public boolean isXAxisVisible() { + return xAxisVisible; + } + + /** + * Sets the visibility of the X-axis. + * + * @param xAxisVisible {@code true} to make the X-axis visible; {@code false} + * to hide it + */ + public void setXAxisVisible(boolean xAxisVisible) { + this.xAxisVisible = xAxisVisible; + } + + /** + * Returns whether the Y-axis is visible. + * + * @return {@code true} if the Y-axis is visible; {@code false} otherwise + */ + public boolean isYAxisVisible() { + return yAxisVisible; + } + + /** + * Sets the visibility of the Y-axis. + * + * @param yAxisVisible {@code true} to make the Y-axis visible; {@code false} + * to hide it + */ + public void setYAxisVisible(boolean yAxisVisible) { + this.yAxisVisible = yAxisVisible; + } + + /** + * Returns whether the Z-axis is visible. + * + * @return {@code true} if the Z-axis is visible; {@code false} otherwise + */ + public boolean isZAxisVisible() { + return zAxisVisible; + } + + /** + * Sets the visibility of the Z-axis. + * + * @param zAxisVisible {@code true} to make the Z-axis visible; {@code false} + * to hide it + */ + public void setZAxisVisible(boolean zAxisVisible) { + this.zAxisVisible = zAxisVisible; + } + + /** + * Sets the length of each axis, extending equally in both positive and + * negative directions. + * + * @param newSize the new size of the axes; must be positive + * @throws IllegalArgumentException if the new size is not positive + */ + public void setSize(float newSize) { + if (newSize <= 0) { + throw new IllegalArgumentException("Size must be positive."); + } + this.size = newSize; + } + +} \ No newline at end of file From 859222e1eabdfec6dca1ba1ed29aeca756c9311e Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 14 Dec 2024 19:44:15 +0100 Subject: [PATCH 06/59] Added 3d translation to the graphics context. --- src/main/java/workspace/GraphicsPImpl.java | 5 +++++ src/main/java/workspace/ui/Graphics.java | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/main/java/workspace/GraphicsPImpl.java b/src/main/java/workspace/GraphicsPImpl.java index f667c56c..9771dd19 100644 --- a/src/main/java/workspace/GraphicsPImpl.java +++ b/src/main/java/workspace/GraphicsPImpl.java @@ -61,6 +61,11 @@ public void translate(float x, float y) { g.translate(x, y); } + @Override + public void translate(float x, float y, float z) { + g.translate(x, y, z); + } + @Override public void strokeWeight(float weight) { g.strokeWeight(weight); diff --git a/src/main/java/workspace/ui/Graphics.java b/src/main/java/workspace/ui/Graphics.java index e8d33a67..47983cad 100644 --- a/src/main/java/workspace/ui/Graphics.java +++ b/src/main/java/workspace/ui/Graphics.java @@ -13,6 +13,8 @@ public interface Graphics { void popMatrix(); void translate(float x, float y); + + void translate(float x, float y, float z); void strokeWeight(float weight); From cc47646bc1af336b6f04b179f3add6a023502a43 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 14 Dec 2024 21:44:01 +0100 Subject: [PATCH 07/59] Added scale(x,y,z) to the graphics context. --- src/main/java/workspace/GraphicsPImpl.java | 5 +++++ src/main/java/workspace/ui/Graphics.java | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/main/java/workspace/GraphicsPImpl.java b/src/main/java/workspace/GraphicsPImpl.java index 9771dd19..583342c3 100644 --- a/src/main/java/workspace/GraphicsPImpl.java +++ b/src/main/java/workspace/GraphicsPImpl.java @@ -56,6 +56,11 @@ public void popMatrix() { g.popMatrix(); } + @Override + public void scale(float sx, float sy, float sz) { + g.scale(sx, sy, sz); + } + @Override public void translate(float x, float y) { g.translate(x, y); diff --git a/src/main/java/workspace/ui/Graphics.java b/src/main/java/workspace/ui/Graphics.java index 47983cad..458f6d38 100644 --- a/src/main/java/workspace/ui/Graphics.java +++ b/src/main/java/workspace/ui/Graphics.java @@ -11,6 +11,8 @@ public interface Graphics { void pushMatrix(); void popMatrix(); + + void scale(float sx, float sy, float sz); void translate(float x, float y); From 2c2d9893fd7e76bbdcfd4a4bdfd9dac4939a7cbc Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 14 Dec 2024 21:49:09 +0100 Subject: [PATCH 08/59] Added default color. --- src/main/java/workspace/GraphicsPImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/workspace/GraphicsPImpl.java b/src/main/java/workspace/GraphicsPImpl.java index 583342c3..161358d5 100644 --- a/src/main/java/workspace/GraphicsPImpl.java +++ b/src/main/java/workspace/GraphicsPImpl.java @@ -18,6 +18,7 @@ public class GraphicsPImpl implements Graphics { public GraphicsPImpl(PApplet p) { this.g = p.g; renderer = new Mesh3DRenderer(p); + color = Color.BLACK; } @Override From 8ed2cf48508a5f8129ae406dec388f1fe0a2109f Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Mon, 16 Dec 2024 08:51:34 +0100 Subject: [PATCH 09/59] Refactor Matrix4f class for enhanced functionality and maintainability **Future considerations:** - For loops in critical matrix operations, consider eventual roll-out of values for performance gains in specific use cases. This will be evaluated based on profiling results and project needs. - Replaced static constants `ZERO`, `UNIT`, and `ONE` with `ZERO` and `IDENTITY` matrices for simplicity. - Added a constructor to initialize matrices with 16 elements directly. - Refactored `identity()` method for concise reinitialization of matrices. - Improved clarity in `add`, `addLocal`, and `multiply` methods through simplifications. - Doc comprehensive documentation for `fpsViewRH` - Added utility methods like `createTranslation` for constructing translation matrices efficiently. - Ensured immutability for getter methods by returning safe copies of internal data. - Rewrote `toString` for improved matrix visualization. - Streamlined code by reducing redundant operations and adopting modern practices. --- src/main/java/math/Matrix4f.java | 529 +++++++++---------------------- 1 file changed, 143 insertions(+), 386 deletions(-) diff --git a/src/main/java/math/Matrix4f.java b/src/main/java/math/Matrix4f.java index 00a282fe..43027730 100644 --- a/src/main/java/math/Matrix4f.java +++ b/src/main/java/math/Matrix4f.java @@ -4,389 +4,146 @@ public class Matrix4f { - public static final Matrix4f ZERO = new Matrix4f(0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0); - - public static final Matrix4f UNIT = new Matrix4f(1, 0, 0, 0, 0, 1, 0, 0, 0, - 0, 1, 0, 0, 0, 0, 1); - - public static final Matrix4f ONE = new Matrix4f(1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1); - - private static final int M00 = 0; - - private static final int M01 = 1; - - private static final int M02 = 2; - - private static final int M03 = 3; - - private static final int M10 = 4; - - private static final int M11 = 5; - - private static final int M12 = 6; - - private static final int M13 = 7; - - private static final int M20 = 8; - - private static final int M21 = 9; - - private static final int M22 = 10; - - private static final int M23 = 11; - - private static final int M30 = 12; - - private static final int M31 = 13; - - private static final int M32 = 14; - - private static final int M33 = 15; - - private float[] values; - - public Matrix4f() { - values = new float[16]; - } - - public Matrix4f(Matrix4f m) { - values = new float[16]; - for (int i = 0; i < m.values.length; i++) { - this.values[i] = m.values[i]; - } - } - - public Matrix4f(float m00, float m01, float m02, float m03, float m10, - float m11, float m12, float m13, float m20, float m21, float m22, - float m23, float m30, float m31, float m32, float m33) { - values = new float[16]; - - values[M00] = m00; - values[M01] = m01; - values[M02] = m02; - values[M03] = m03; - - values[M10] = m10; - values[M11] = m11; - values[M12] = m12; - values[M13] = m13; - - values[M20] = m20; - values[M21] = m21; - values[M22] = m22; - values[M23] = m23; - - values[M30] = m30; - values[M31] = m31; - values[M32] = m32; - values[M33] = m33; - } - - public Matrix4f transpose() { - Matrix4f tranpose = new Matrix4f(values[M00], values[M10], values[M20], - values[M30], - - values[M01], values[M11], values[M21], values[M31], - - values[M02], values[M12], values[M22], values[M32], - - values[M03], values[M13], values[M23], values[M33]); - - return tranpose; - } - - public Matrix4f add(Matrix4f m) { - Matrix4f result = new Matrix4f(); - float[] a = values; - float[] b = m.values; - float[] c = result.values; - - c[M00] = a[M00] + b[M00]; - c[M01] = a[M01] + b[M01]; - c[M02] = a[M02] + b[M02]; - c[M03] = a[M03] + b[M03]; - - c[M10] = a[M10] + b[M10]; - c[M11] = a[M11] + b[M11]; - c[M12] = a[M12] + b[M12]; - c[M13] = a[M13] + b[M13]; - - c[M20] = a[M20] + b[M20]; - c[M21] = a[M21] + b[M21]; - c[M22] = a[M22] + b[M22]; - c[M23] = a[M23] + b[M23]; - - c[M30] = a[M30] + b[M30]; - c[M31] = a[M31] + b[M31]; - c[M32] = a[M32] + b[M32]; - c[M33] = a[M33] + b[M33]; - - return result; - } - - public Matrix4f addLocal(Matrix4f m) { - values[M00] += m.values[M00]; - values[M01] += m.values[M01]; - values[M02] += m.values[M02]; - values[M03] += m.values[M03]; - - values[M10] += m.values[M10]; - values[M11] += m.values[M11]; - values[M12] += m.values[M12]; - values[M13] += m.values[M13]; - - values[M20] += m.values[M20]; - values[M21] += m.values[M21]; - values[M22] += m.values[M22]; - values[M23] += m.values[M23]; - - values[M30] += m.values[M30]; - values[M31] += m.values[M31]; - values[M32] += m.values[M32]; - values[M33] += m.values[M33]; - - return this; - } - - public Matrix4f multLocal(float scalar) { - values[M00] *= scalar; - values[M01] *= scalar; - values[M02] *= scalar; - values[M03] *= scalar; - - values[M10] *= scalar; - values[M11] *= scalar; - values[M12] *= scalar; - values[M13] *= scalar; - - values[M20] *= scalar; - values[M21] *= scalar; - values[M22] *= scalar; - values[M23] *= scalar; - - values[M30] *= scalar; - values[M31] *= scalar; - values[M32] *= scalar; - values[M33] *= scalar; - - return this; - } - - public Matrix4f mult(Matrix4f other) { - Matrix4f store = new Matrix4f(); - float[] m = new float[16]; - - m[0] = values[M00] * other.values[M00] + values[M01] * other.values[M10] - + values[M02] * other.values[M20] - + values[M03] * other.values[M30]; - m[1] = values[M00] * other.values[M01] + values[M01] * other.values[M11] - + values[M02] * other.values[M21] - + values[M03] * other.values[M31]; - m[2] = values[M00] * other.values[M02] + values[M01] * other.values[M12] - + values[M02] * other.values[M22] - + values[M03] * other.values[M32]; - m[3] = values[M00] * other.values[M03] + values[M01] * other.values[M13] - + values[M02] * other.values[M23] - + values[M03] * other.values[M33]; - - m[4] = values[M10] * other.values[M00] + values[M11] * other.values[M10] - + values[M12] * other.values[M20] - + values[M13] * other.values[M30]; - m[5] = values[M10] * other.values[M01] + values[M11] * other.values[M11] - + values[M12] * other.values[M21] - + values[M13] * other.values[M31]; - m[6] = values[M10] * other.values[M02] + values[M11] * other.values[M12] - + values[M12] * other.values[M22] - + values[M13] * other.values[M32]; - m[7] = values[M10] * other.values[M03] + values[M11] * other.values[M13] - + values[M12] * other.values[M23] - + values[M13] * other.values[M33]; - - m[8] = values[M20] * other.values[M00] + values[M21] * other.values[M10] - + values[M22] * other.values[M20] - + values[M23] * other.values[M30]; - m[9] = values[M20] * other.values[M01] + values[M21] * other.values[M11] - + values[M22] * other.values[M21] - + values[M23] * other.values[M31]; - m[10] = values[M20] * other.values[M02] - + values[M21] * other.values[M12] - + values[M22] * other.values[M22] - + values[M23] * other.values[M32]; - m[11] = values[M20] * other.values[M03] - + values[M21] * other.values[M13] - + values[M22] * other.values[M23] - + values[M23] * other.values[M33]; - - m[12] = values[M30] * other.values[M00] - + values[M31] * other.values[M10] - + values[M32] * other.values[M20] - + values[M33] * other.values[M30]; - m[13] = values[M30] * other.values[M01] - + values[M31] * other.values[M11] - + values[M32] * other.values[M21] - + values[M33] * other.values[M31]; - m[14] = values[M30] * other.values[M02] - + values[M31] * other.values[M12] - + values[M32] * other.values[M22] - + values[M33] * other.values[M32]; - m[15] = values[M30] * other.values[M03] - + values[M31] * other.values[M13] - + values[M32] * other.values[M23] - + values[M33] * other.values[M33]; - - store.values[M00] = m[0]; - store.values[M01] = m[1]; - store.values[M02] = m[2]; - store.values[M03] = m[3]; - store.values[M10] = m[4]; - store.values[M11] = m[5]; - store.values[M12] = m[6]; - store.values[M13] = m[7]; - store.values[M20] = m[8]; - store.values[M21] = m[9]; - store.values[M22] = m[10]; - store.values[M23] = m[11]; - store.values[M30] = m[12]; - store.values[M31] = m[13]; - store.values[M32] = m[14]; - store.values[M33] = m[15]; - - return store; - } - - public Vector3f mult(Vector3f v) { - Vector3f result = new Vector3f(); - float vx = v.x, vy = v.y, vz = v.z; - result.x = values[M00] * vx + values[M01] * vy + values[M02] * vz - + values[M03]; - result.y = values[M10] * vx + values[M11] * vy + values[M12] * vz - + values[M13]; - result.z = values[M20] * vx + values[M21] * vy + values[M22] * vz - + values[M23]; - return result; - } - - /** - * Look At, right-handed coordinate system. - * - * @param from - * @param to - * @param up - * @return the resulting view matrix - */ - public static Matrix4f lookAtRH(Vector3f eye, Vector3f target, - Vector3f up) { - // https://www.3dgep.com/understanding-the-view-matrix/ - Vector3f zaxis = eye.subtract(target).normalize(); // The "forward" - // vector. - Vector3f xaxis = up.cross(zaxis).normalize();// The "right" vector. - Vector3f yaxis = zaxis.cross(xaxis); // The "up" vector. - - Matrix4f viewMatrix = new Matrix4f(xaxis.x, yaxis.x, zaxis.x, 0, - xaxis.y, yaxis.y, zaxis.y, 0, xaxis.z, yaxis.z, zaxis.z, 0, - -xaxis.dot(eye), -yaxis.dot(eye), -zaxis.dot(eye), 1); - - return viewMatrix; - } - - /** - * FPS camera, right-handed coordinate system. - * - * @param eye - * @param pitch in radians - * @param yaw in radians - * @return the resulting view matrix - */ - public static Matrix4f fpsViewRH(Vector3f eye, float pitch, float yaw) { - // https://www.3dgep.com/understanding-the-view-matrix/ - float cosPitch = Mathf.cos(pitch); - float sinPitch = Mathf.sin(pitch); - float cosYaw = Mathf.cos(yaw); - float sinYaw = Mathf.sin(yaw); - - Vector3f xaxis = new Vector3f(cosYaw, 0, -sinYaw); - Vector3f yaxis = new Vector3f(sinYaw * sinPitch, cosPitch, - cosYaw * sinPitch); - Vector3f zaxis = new Vector3f(sinYaw * cosPitch, -sinPitch, - cosPitch * cosYaw); - - // Create a 4x4 view matrix from the right, up, forward and eye position - // vectors - Matrix4f viewMatrix = new Matrix4f(xaxis.x, yaxis.x, zaxis.x, 0, - xaxis.y, yaxis.y, zaxis.y, 0, xaxis.z, yaxis.z, zaxis.z, 0, - -xaxis.dot(eye), -yaxis.dot(eye), -zaxis.dot(eye), 1); - - return viewMatrix; - } - - public float[] getValues() { - return values; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + Arrays.hashCode(values); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - Matrix4f other = (Matrix4f) obj; - if (!Arrays.equals(values, other.values)) - return false; - return true; - } - - @Override - public String toString() { - StringBuffer buffer = new StringBuffer(); - - buffer.append(values[M00]); - buffer.append("|"); - buffer.append(values[M01]); - buffer.append("|"); - buffer.append(values[M02]); - buffer.append("|"); - buffer.append(values[M03]); - buffer.append("\n"); - - buffer.append(values[M10]); - buffer.append("|"); - buffer.append(values[M11]); - buffer.append("|"); - buffer.append(values[M12]); - buffer.append("|"); - buffer.append(values[M13]); - buffer.append("\n"); - - buffer.append(values[M20]); - buffer.append("|"); - buffer.append(values[M21]); - buffer.append("|"); - buffer.append(values[M22]); - buffer.append("|"); - buffer.append(values[M23]); - buffer.append("\n"); - - buffer.append(values[M30]); - buffer.append("|"); - buffer.append(values[M31]); - buffer.append("|"); - buffer.append(values[M32]); - buffer.append("|"); - buffer.append(values[M33]); - - return buffer.toString(); - } - -} + public static final Matrix4f ZERO = new Matrix4f(); + + public static final Matrix4f IDENTITY = new Matrix4f().identity(); + + private final float[] values; + + public Matrix4f() { + this.values = new float[16]; + } + + public Matrix4f(float... elements) { + if (elements.length != 16) { + throw new IllegalArgumentException("Matrix4f requires 16 elements."); + } + this.values = Arrays.copyOf(elements, 16); + } + + public Matrix4f(Matrix4f other) { + this.values = Arrays.copyOf(other.values, 16); + } + + public Matrix4f identity() { + Arrays.fill(values, 0); + values[0] = values[5] = values[10] = values[15] = 1; + return this; + } + + public Matrix4f transpose() { + return new Matrix4f(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 Matrix4f add(Matrix4f other) { + float[] result = new float[16]; + for (int i = 0; i < 16; i++) { + result[i] = this.values[i] + other.values[i]; + } + return new Matrix4f(result); + } + + public Matrix4f addLocal(Matrix4f other) { + for (int i = 0; i < 16; i++) { + this.values[i] += other.values[i]; + } + return this; + } + + public Matrix4f multiply(Matrix4f other) { + float[] m = new float[16]; + for (int row = 0; row < 4; row++) { + for (int col = 0; col < 4; col++) { + m[row * 4 + col] = values[row * 4 + 0] * other.values[0 * 4 + col] + + values[row * 4 + 1] * other.values[1 * 4 + col] + + values[row * 4 + 2] * other.values[2 * 4 + col] + + values[row * 4 + 3] * other.values[3 * 4 + col]; + } + } + return new Matrix4f(m); + } + + public float[] getValues() { + return Arrays.copyOf(values, values.length); + } + + public float get(int row, int col) { + return values[row * 4 + col]; + } + + public void set(int row, int col, float value) { + values[row * 4 + col] = value; + } + + public static Matrix4f createTranslation(float x, float y, float z) { + return new Matrix4f( + 1, 0, 0, x, + 0, 1, 0, y, + 0, 0, 1, z, + 0, 0, 0, 1); + } + + /** + * Constructs a right-handed view matrix for an FPS (First-Person Shooter) + * style camera. + *

+ * The view matrix is computed based on the camera's position (`eye`), pitch, + * and yaw angles. It assumes a right-handed coordinate system where: + *

    + *
  • +X points to the right
  • + *
  • +Y points up
  • + *
  • +Z points backward (into the screen)
  • + *
+ * This method is particularly useful for creating a camera that can move and + * rotate freely in a 3D scene, such as in games or visualization + * applications. + *

+ * + * @param eye the position of the camera in world space, represented as a + * {@link Vector3f}. + * @param pitch the pitch angle (rotation around the X-axis), in radians. A + * positive value tilts the camera upward. + * @param yaw the yaw angle (rotation around the Y-axis), in radians. A + * positive value rotates the camera to the right. + * @return a {@link Matrix4f} representing the view matrix for the specified + * position and orientation. + * + * @see + * Understanding the View Matrix + */ + public static Matrix4f fpsViewRH(Vector3f eye, float pitch, float yaw) { + float cosPitch = Mathf.cos(pitch); + float sinPitch = Mathf.sin(pitch); + float cosYaw = Mathf.cos(yaw); + float sinYaw = Mathf.sin(yaw); + + Vector3f right = new Vector3f(cosYaw, 0, -sinYaw); + Vector3f up = new Vector3f(sinYaw * sinPitch, cosPitch, cosYaw * sinPitch); + Vector3f forward = new Vector3f(sinYaw * cosPitch, -sinPitch, + cosPitch * cosYaw); + + Matrix4f viewMatrix = new Matrix4f( + right.x, up.x, forward.x, 0, + right.y, up.y, forward.y, 0, + right.z, up.z, forward.z, 0, + -right.dot(eye), -up.dot(eye), -forward.dot(eye), 1); + + return viewMatrix; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("Matrix4f:\n"); + for (int row = 0; row < 4; row++) { + sb.append("[ "); + for (int col = 0; col < 4; col++) { + sb.append(get(row, col)).append(" "); + } + sb.append("]\n"); + } + return sb.toString(); + } + +} \ No newline at end of file From f5ff1430b2b0b0cb824ea38f00d0bd530527d2b2 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Mon, 16 Dec 2024 13:37:45 +0100 Subject: [PATCH 10/59] feat(math): Implement Ray3f class for 3D ray representation - Added Ray3f class with origin and normalized direction vector. - Included getPointAt(t) method to compute points along the ray. - Ensured direction normalization upon initialization to maintain mathematical correctness. - Added validation to ensure origin and direction are non-null. - Documented class and methods with detailed Javadocs. --- src/main/java/math/Ray3f.java | 90 +++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 src/main/java/math/Ray3f.java diff --git a/src/main/java/math/Ray3f.java b/src/main/java/math/Ray3f.java new file mode 100644 index 00000000..a0f0b537 --- /dev/null +++ b/src/main/java/math/Ray3f.java @@ -0,0 +1,90 @@ +package math; + +/** + * Represents a ray in 3D space, defined by an origin point and a direction + * vector. + *

+ * A ray is a mathematical abstraction used in 3D graphics, physics simulations, + * and computational geometry. It extends infinitely from its origin in the + * specified direction. The direction vector is automatically normalized during + * initialization to ensure consistent calculations. + *

+ */ +public class Ray3f { + + /** + * The starting point of the ray. + */ + private final Vector3f origin; + + /** + * The normalized direction vector of the ray. + */ + private final Vector3f direction; + + /** + * Constructs a new {@code Ray3f} with the given origin and direction. + *

+ * The direction vector will be normalized internally to ensure correctness in + * calculations. Both {@code origin} and {@code direction} must be non-null. + *

+ * + * @param origin The starting point of the ray (non-null) + * @param direction The direction vector of the ray (non-null, normalized + * internally) + * @throws IllegalArgumentException if either {@code origin} or + * {@code direction} is null + */ + public Ray3f(Vector3f origin, Vector3f direction) { + if (origin == null) { + throw new IllegalArgumentException("Origin cannot be null."); + } + if (direction == null) { + throw new IllegalArgumentException("Direction cannot be null."); + } + this.origin = origin; + this.direction = direction; + this.direction.normalizeLocal(); + } + + /** + * Returns the origin of the ray. + * + * @return The origin of the ray. + */ + public Vector3f getOrigin() { + return origin; + } + + /** + * Returns the normalized direction vector of the ray. + * + * @return The direction vector of the ray. + */ + public Vector3f getDirection() { + return direction; + } + + /** + * 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. + *

+ * + * @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)); + } + +} \ No newline at end of file From fbf811aa240ebc4dad9814b508c9df333dc396e5 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Mon, 16 Dec 2024 13:53:49 +0100 Subject: [PATCH 11/59] feat: Define Camera interface with core properties and utilities - Added the `Camera` interface to abstract camera behaviors and properties for 3D rendering. - Includes methods for handling view and projection matrices, field of view, near/far clipping planes, and aspect ratio. - Added utility methods for screen-to-world ray generation and unprojection. - Provides a clean abstraction for camera-related computations in the rendering pipeline. --- src/main/java/engine/scene/camera/Camera.java | 200 ++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 src/main/java/engine/scene/camera/Camera.java diff --git a/src/main/java/engine/scene/camera/Camera.java b/src/main/java/engine/scene/camera/Camera.java new file mode 100644 index 00000000..e9030be3 --- /dev/null +++ b/src/main/java/engine/scene/camera/Camera.java @@ -0,0 +1,200 @@ +package engine.scene.camera; + +import engine.components.Transform; +import math.Matrix4f; +import math.Ray3f; +import math.Vector3f; + +/** + * Represents a generic camera within a 3D scene. A camera defines the view and + * projection settings necessary for rendering a 3D scene from a specific + * perspective. This interface abstracts common properties and functionalities + * that all camera types share, including transformation, view matrices, + * projection matrices, and interaction with screen-space coordinates. + *

+ * Cameras are responsible for defining how 3D scenes are projected onto a 2D + * screen and can be configured for various projection modes, such as + * perspective and orthographic projections. Implementations should define their + * specific logic for projection and view matrix handling, field of view + * adjustments, clipping plane configuration, and conversion between screen and + * world coordinates. + *

+ */ +public interface Camera { + + /** + * Retrieves the camera's transformation. + *

+ * The transformation defines the position, rotation, and scaling of the + * camera in the 3D world. This transformation is used to compute the camera's + * position and orientation in world space. + *

+ * + * @return The {@link Transform} representing the camera's position, rotation, + * and scaling. + */ + Transform getTransform(); + + /** + * Retrieves the current view matrix of the camera. + *

+ * The view matrix is used to transform world-space coordinates into camera + * (view) space. It is typically derived from the camera's position and + * orientation in the 3D world. + *

+ * + * @return The view matrix as a {@link Matrix4f}. + */ + Matrix4f getViewMatrix(); + + /** + * Retrieves the current projection matrix of the camera. + *

+ * The projection matrix defines how a 3D scene is projected onto a 2D + * viewport, depending on the camera's projection settings (perspective or + * orthographic). + *

+ * + * @return The projection matrix as a {@link Matrix4f}. + */ + Matrix4f getProjectionMatrix(); + + /** + * Updates the view matrix based on the current transformation. + *

+ * This method should recalculate the view matrix whenever the camera's + * position or orientation has changed. + *

+ */ + void updateViewMatrix(); + + /** + * Updates the projection matrix based on camera-specific settings. + *

+ * This method should be called whenever changes are made to parameters like + * the field of view, near or far clipping planes, or aspect ratio. + *

+ */ + void updateProjectionMatrix(); + + /** + * Retrieves the field of view (FOV) for perspective cameras. + *

+ * The field of view determines how wide or narrow the camera's view is and + * only applies to perspective projections. + *

+ * + * @return The current field of view in degrees. + */ + float getFieldOfView(); + + /** + * Sets the field of view (FOV) for perspective cameras. + *

+ * This only has an effect on cameras configured for perspective projection. + *

+ * + * @param fov The desired field of view in degrees. + */ + void setFieldOfView(float fov); + + /** + * Retrieves the near clipping plane distance. + *

+ * The near clipping plane defines the closest distance from the camera at + * which objects are rendered. Objects closer than this distance will not be + * visible. + *

+ * + * @return The near clipping plane distance. + */ + float getNearPlane(); + + /** + * Sets the near clipping plane distance. + *

+ * This modifies how close an object must be to the camera to be visible. + *

+ * + * @param nearPlane The desired near clipping plane distance. + */ + void setNearPlane(float nearPlane); + + /** + * Retrieves the far clipping plane distance. + *

+ * The far clipping plane defines the furthest distance from the camera at + * which objects are rendered. Objects farther away than this distance will + * not be visible. + *

+ * + * @return The far clipping plane distance. + */ + float getFarPlane(); + + /** + * Sets the far clipping plane distance. + *

+ * This modifies how far objects can be from the camera to remain visible. + *

+ * + * @param farPlane The desired far clipping plane distance. + */ + void setFarPlane(float farPlane); + + /** + * Retrieves the current aspect ratio of the camera's viewport. + *

+ * The aspect ratio is defined as the ratio of the viewport's width to its + * height and is used to adjust the projection matrix accordingly. + *

+ * + * @return The aspect ratio of the viewport. + */ + float getAspectRatio(); + + /** + * Sets the aspect ratio for the camera's viewport. + *

+ * Changing the aspect ratio should trigger an update to the projection + * matrix. + *

+ * + * @param aspectRatio The desired aspect ratio. + */ + void setAspectRatio(float aspectRatio); + + /** + * Converts 2D screen coordinates to a 3D ray in world space. + *

+ * This method is essential for raycasting operations, such as determining + * which objects in the scene correspond to a given 2D screen-space click. + *

+ * + * @param screenX The x-coordinate on the screen. + * @param screenY The y-coordinate on the screen. + * @param viewportWidth The width of the viewport in pixels. + * @param viewportHeight The height of the viewport in pixels. + * @return A {@link Ray3f} representing the computed ray in 3D world space. + */ + Ray3f createRay(float screenX, float screenY, int viewportWidth, + int viewportHeight); + + /** + * Converts 2D screen-space coordinates to their corresponding world-space + * coordinates. + *

+ * This is the inverse of projection and can be used for operations like + * object picking or determining intersections between screen-space inputs and + * 3D objects in the world. + *

+ * + * @param screenCoords The 2D screen-space coordinates to unproject. + * @param viewportWidth The width of the viewport in pixels. + * @param viewportHeight The height of the viewport in pixels. + * @return The corresponding 3D world-space coordinates as a {@link Vector3f}. + */ + Vector3f unproject(Vector3f screenCoords, int viewportWidth, + int viewportHeight); + +} \ No newline at end of file From b4ced13ce79eedd201ee4e488a10c80965de2aa7 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Mon, 16 Dec 2024 14:12:53 +0100 Subject: [PATCH 12/59] dd setPerspective method to Matrix4f class with input validation and documentation. --- src/main/java/math/Matrix4f.java | 64 ++++++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 11 deletions(-) diff --git a/src/main/java/math/Matrix4f.java b/src/main/java/math/Matrix4f.java index 43027730..f148193e 100644 --- a/src/main/java/math/Matrix4f.java +++ b/src/main/java/math/Matrix4f.java @@ -5,7 +5,7 @@ public class Matrix4f { public static final Matrix4f ZERO = new Matrix4f(); - + public static final Matrix4f IDENTITY = new Matrix4f().identity(); private final float[] values; @@ -78,11 +78,55 @@ public void set(int row, int col, float value) { } public static Matrix4f createTranslation(float x, float y, float z) { - return new Matrix4f( - 1, 0, 0, x, - 0, 1, 0, y, - 0, 0, 1, z, - 0, 0, 0, 1); + return new Matrix4f(1, 0, 0, x, 0, 1, 0, y, 0, 0, 1, z, 0, 0, 0, 1); + } + + /** + * Sets the matrix to represent a perspective projection matrix. + * + *

+ * This method computes a standard perspective projection matrix based on the + * provided field of view, aspect ratio, near clipping plane, and far clipping + * plane. The resulting matrix transforms 3D points into normalized device + * coordinates for rendering with perspective projection. + *

+ * + * @param fov Field of view in radians (vertical field of view). Must be + * between 0 and π radians. + * @param aspect Aspect ratio of the viewport (width / height). Must be + * positive. + * @param nearPlane Distance to the near clipping plane. Must be less than + * `farPlane`. + * @param farPlane Distance to the far clipping plane. Must be greater than + * `nearPlane`. + * @return This matrix for chaining calls. + * @throws IllegalArgumentException if the input parameters are invalid. + */ + public Matrix4f setPerspective(float fov, float aspect, float nearPlane, + float farPlane) { + if (nearPlane > farPlane) { + throw new IllegalArgumentException(String.format( + "Near plane (%.2f) cannot be greater than far plane (%.2f).", + nearPlane, farPlane)); + } + if (aspect <= 0) { + throw new IllegalArgumentException( + "Aspect ratio must be a positive number."); + } + if (fov <= 0.0 || fov >= Math.PI) { + throw new IllegalArgumentException( + "Field of view must be between 0 and π radians."); + } + + float f = (float) (1.0 / Math.tan(fov / 2.0)); + Arrays.fill(values, 0); + values[0] = f / aspect; + values[5] = f; + values[10] = (farPlane + nearPlane) / (nearPlane - farPlane); + values[11] = -1; + values[14] = (2 * farPlane * nearPlane) / (nearPlane - farPlane); + + return this; } /** @@ -124,11 +168,9 @@ public static Matrix4f fpsViewRH(Vector3f eye, float pitch, float yaw) { Vector3f forward = new Vector3f(sinYaw * cosPitch, -sinPitch, cosPitch * cosYaw); - Matrix4f viewMatrix = new Matrix4f( - right.x, up.x, forward.x, 0, - right.y, up.y, forward.y, 0, - right.z, up.z, forward.z, 0, - -right.dot(eye), -up.dot(eye), -forward.dot(eye), 1); + Matrix4f viewMatrix = new Matrix4f(right.x, up.x, forward.x, 0, right.y, + up.y, forward.y, 0, right.z, up.z, forward.z, 0, -right.dot(eye), + -up.dot(eye), -forward.dot(eye), 1); return viewMatrix; } From aad5b850cb7df5b69678cce9ca1deface55cb5c6 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Mon, 16 Dec 2024 19:29:19 +0100 Subject: [PATCH 13/59] Initial commit: Added legacy Timer class implementation. - Implements a basic Timer for frame rate calculation, time scaling, and elapsed time tracking. - Provides support for features such as FPS calculation, time-per-frame, and formatted time outputs. - Includes methods for slow motion and speed-up effects via time scaling. This version serves as the foundation for further improvements. --- src/main/java/engine/Timer.java | 215 ++++++++++++++++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 src/main/java/engine/Timer.java diff --git a/src/main/java/engine/Timer.java b/src/main/java/engine/Timer.java new file mode 100644 index 00000000..a607d3a5 --- /dev/null +++ b/src/main/java/engine/Timer.java @@ -0,0 +1,215 @@ +package engine; + +/** + * This class implements a simple timer. The timer calculates the total time (in + * seconds) since the first update of this timer, the number of frames per + * second, the time per frame and the total number of frames since the first + * update. Furthermore it provides time scaling for slow motion effects and + * speed up the game. + * + * @author - Simon + * @version 0.2, 5 December 2014 + */ +public class Timer { + + // Used to calculate the frames per second. + private long lastTime; + + // Used to calculate the frames per second. + private float time; + + // Used to calculate the frames per second. + private long secondCount; + + // Used to calculate the frames per second. + private int lastFrameCount; + + /** + * Frames per second. + */ + private int fps; + + /** + * The time in milliseconds that has passed since the first update of this + * timer. + */ + private long totalTime; + + /** + * The scale at which the time is passing. This can be used for slow motion + * effects. + */ + private float timeScale; + + /** + * The total number of frames that have passed since the first update of + * this timer. + */ + private int frameCount; + + /** + * Constructs a new instance of this timer. + */ + public Timer() { + this.lastTime = System.currentTimeMillis(); + this.time = 0; + this.totalTime = 0; + this.timeScale = 1f; + this.frameCount = 0; + } + + /** + * Returns the frames per second. + * + * @return the frames per second + */ + public float getFrameRate() { + return fps; + } + + /** + * Updates the frames per second value. + */ + private void updateFPS() { + secondCount += time; + if (secondCount >= 1000) { + secondCount = 0; + fps = frameCount - lastFrameCount; + lastFrameCount = frameCount; + } + } + + /** + * Updates this timer. This method must invoked each frame to ensure + * accurate timer functionality. + */ + public void update() { + time = System.currentTimeMillis() - lastTime; + lastTime = System.currentTimeMillis(); + totalTime += time; + frameCount++; + updateFPS(); + } + + /** + * Returns the scaled time in seconds that has passed since the first update + * of this timer. This is usually the time in seconds since the start of the + * game multiplied by the time scale. + * + * @return the scaled time in seconds that has passed since the first update + * of this timer + * @see #getUnscaledTotalTime() + */ + public float getTotalTime() { + return totalTime / 1000.0f * timeScale; + } + + /** + * Returns the time scale independent time in seconds that has passed since + * the first update of this timer. This is usually the time in seconds since + * the start of the game. + * + * @return the time in seconds that has passes since the first update of + * this timer + * @see #getTotalTime() + */ + public float getUnscaledTotalTime() { + return totalTime / 1000.0f; + } + + /** + * Returns a string representation of the scaled total time with the + * following format: hours:minutes:seconds. The total time is the + * time that has passed since the first update of this timer. + * + * @return a formatted string representation of totalTime + * @see #getUnscaledFormattedTotalTime() + * @see #getTotalTime() + * @see #getUnscaledTotalTime() + */ + public String getFormattedTotalTime() { + int s = (int) (this.totalTime * timeScale / 1000); + String result = String.format("%d:%02d:%02d", s / 3600, + (s % 3600) / 60, (s % 60)); + return result; + } + + /** + * Returns a time scale independent string representation of the total time + * with the following format: hours:minutes:seconds. The total time + * is the time that has passed since the first update of this timer. + * + * @return a formatted, time scale independent string representation of + * total time + * @see #getFormattedTotalTime() + * @see #getTotalTime() + * @see #getUnscaledTotalTime() + */ + public String getUnscaledFormattedTotalTime() { + int s = (int) (this.totalTime / 1000); + String result = String.format("%d:%02d:%02d", s / 3600, + (s % 3600) / 60, (s % 60)); + return result; + } + + /** + * Returns the time in seconds it took to complete the last frame. + * + * @return the time in seconds it took to complete the last frame + */ + public float getTimePerFrame() { + return time / 1000.0f * timeScale; + } + + /** + * Returns the time scale independent time in seconds it took to complete the last frame. + * + * @return the time scale independent time in seconds it took to complete the last frame + */ + public float getUnscaledTimePerFrame() { + return time / 1000.0f; + } + + /** + * Returns the scale at which time is passing. + * + * @return the scale at which time is passing + */ + public float getTimeScale() { + return timeScale; + } + + /** + * Sets the scale at which time is passing to the specified new value. This + * can be used for slow motion effects. + * + * @param timeScale + * the specified new value + */ + public void setTimeScale(float timeScale) { + this.timeScale = timeScale; + } + + /** + * Returns the total number of frames that have passed since the first + * update of this timer. + * + * @return the total number of frames that have passed + */ + public int getFrameCount() { + return frameCount; + } + + /** + * Returns a string representation of this timer. + */ + @Override + public String toString() { + return "Timer [secondCount=" + secondCount + ", lastFrameCount=" + + lastFrameCount + ", fps=" + fps + ", lastTime=" + lastTime + + ", time=" + time + ", totalTime=" + totalTime + + ", timeScale=" + timeScale + ", frameCount=" + frameCount + + "]"; + } + +} From 0f7e3a6dc23558a69c677c5aca475d138eacdecc Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Mon, 16 Dec 2024 19:36:07 +0100 Subject: [PATCH 14/59] Refactor Timer class with improved time tracking, scaling, and formatting Enhanced precision and readability by using nanoseconds for timekeeping. Added methods for formatted time representation (HH:MM:SS) for both scaled and unscaled total times. Improved FPS calculation and ensured it updates accurately each frame. Refined methods for time-per-frame calculations, both scaled and unscaled. Introduced robust time scaling to support slow-motion and speed-up effects. Simplified class structure for better maintainability and extensibility. This refactor improves the usability and performance of the Timer class for game loops and real-time applications. --- src/main/java/engine/Timer.java | 206 ++++++++++++++++---------------- 1 file changed, 103 insertions(+), 103 deletions(-) diff --git a/src/main/java/engine/Timer.java b/src/main/java/engine/Timer.java index a607d3a5..402a226e 100644 --- a/src/main/java/engine/Timer.java +++ b/src/main/java/engine/Timer.java @@ -1,57 +1,55 @@ package engine; /** - * This class implements a simple timer. The timer calculates the total time (in - * seconds) since the first update of this timer, the number of frames per - * second, the time per frame and the total number of frames since the first - * update. Furthermore it provides time scaling for slow motion effects and - * speed up the game. + * The {@code Timer} class provides a utility for tracking elapsed time, frames + * per second (FPS), and time scaling for games or applications. It uses + * nanosecond precision for timekeeping and offers features such as formatted + * time representation, time-per-frame calculation, and slow-motion or speed-up + * effects via time scaling. + * + *

+ * Key features include: + *

    + *
  • Tracking total elapsed time (scaled and unscaled).
  • + *
  • Calculating frames per second (FPS).
  • + *
  • Formatting time as hours:minutes:seconds.
  • + *
  • Adjustable time scaling for slow-motion or fast-forward effects.
  • + *
* - * @author - Simon - * @version 0.2, 5 December 2014 + * This class is designed to be updated on every frame of an application or + * game. */ public class Timer { - // Used to calculate the frames per second. + /** The last recorded time in nanoseconds. */ private long lastTime; - // Used to calculate the frames per second. + /** The time in milliseconds taken for the last frame. */ private float time; - // Used to calculate the frames per second. - private long secondCount; + /** Accumulates milliseconds for FPS calculation. */ + private long millisecondCounter; - // Used to calculate the frames per second. + /** The frame count during the last FPS update. */ private int lastFrameCount; - /** - * Frames per second. - */ + /** The calculated frames per second (FPS). */ private int fps; - /** - * The time in milliseconds that has passed since the first update of this - * timer. - */ + /** Total elapsed time in milliseconds. */ private long totalTime; - /** - * The scale at which the time is passing. This can be used for slow motion - * effects. - */ + /** Scaling factor for time (default is 1.0 for real-time). */ private float timeScale; - /** - * The total number of frames that have passed since the first update of - * this timer. - */ + /** Total number of frames since the Timer started. */ private int frameCount; /** - * Constructs a new instance of this timer. + * Constructs a {@code Timer} with a default time scale of 1.0. */ public Timer() { - this.lastTime = System.currentTimeMillis(); + this.lastTime = System.nanoTime(); this.time = 0; this.totalTime = 0; this.timeScale = 1f; @@ -59,8 +57,18 @@ public Timer() { } /** - * Returns the frames per second. - * + * Constructs a {@code Timer} with the specified initial time scale. + * + * @param initialTimeScale the initial time scaling factor + */ + public Timer(float initialTimeScale) { + this(); + this.timeScale = initialTimeScale; + } + + /** + * Returns the current frames per second (FPS). + * * @return the frames per second */ public float getFrameRate() { @@ -68,148 +76,140 @@ public float getFrameRate() { } /** - * Updates the frames per second value. + * Updates the FPS calculation based on the accumulated milliseconds. */ private void updateFPS() { - secondCount += time; - if (secondCount >= 1000) { - secondCount = 0; + millisecondCounter += time; + if (millisecondCounter >= 1000) { + millisecondCounter = 0; fps = frameCount - lastFrameCount; lastFrameCount = frameCount; } } /** - * Updates this timer. This method must invoked each frame to ensure - * accurate timer functionality. + * Updates the Timer. This method must be called once per frame to ensure + * accurate time tracking. */ public void update() { - time = System.currentTimeMillis() - lastTime; - lastTime = System.currentTimeMillis(); + long currentTime = System.nanoTime(); + time = (currentTime - lastTime) / 1_000_000.0f; // Convert to milliseconds + lastTime = currentTime; totalTime += time; frameCount++; updateFPS(); } /** - * Returns the scaled time in seconds that has passed since the first update - * of this timer. This is usually the time in seconds since the start of the - * game multiplied by the time scale. - * - * @return the scaled time in seconds that has passed since the first update - * of this timer - * @see #getUnscaledTotalTime() + * Returns the total elapsed time in seconds, scaled by the current time + * scale. + * + * @return the scaled total elapsed time in seconds */ public float getTotalTime() { return totalTime / 1000.0f * timeScale; } /** - * Returns the time scale independent time in seconds that has passed since - * the first update of this timer. This is usually the time in seconds since - * the start of the game. - * - * @return the time in seconds that has passes since the first update of - * this timer - * @see #getTotalTime() + * Returns the total elapsed time in seconds, independent of the time scale. + * + * @return the unscaled total elapsed time in seconds */ public float getUnscaledTotalTime() { return totalTime / 1000.0f; } /** - * Returns a string representation of the scaled total time with the - * following format: hours:minutes:seconds. The total time is the - * time that has passed since the first update of this timer. - * - * @return a formatted string representation of totalTime - * @see #getUnscaledFormattedTotalTime() - * @see #getTotalTime() - * @see #getUnscaledTotalTime() + * Returns a formatted string representing the scaled total time in the format + * HH:MM:SS. + * + * @return the formatted scaled total time */ public String getFormattedTotalTime() { - int s = (int) (this.totalTime * timeScale / 1000); - String result = String.format("%d:%02d:%02d", s / 3600, - (s % 3600) / 60, (s % 60)); - return result; + return formatTime(getTotalTime()); } /** - * Returns a time scale independent string representation of the total time - * with the following format: hours:minutes:seconds. The total time - * is the time that has passed since the first update of this timer. - * - * @return a formatted, time scale independent string representation of - * total time - * @see #getFormattedTotalTime() - * @see #getTotalTime() - * @see #getUnscaledTotalTime() + * Returns a formatted string representing the unscaled total time in the + * format HH:MM:SS. + * + * @return the formatted unscaled total time */ public String getUnscaledFormattedTotalTime() { - int s = (int) (this.totalTime / 1000); - String result = String.format("%d:%02d:%02d", s / 3600, - (s % 3600) / 60, (s % 60)); - return result; + return formatTime(getUnscaledTotalTime()); } /** - * Returns the time in seconds it took to complete the last frame. - * - * @return the time in seconds it took to complete the last frame + * Returns the time it took to complete the last frame in seconds, scaled by + * the current time scale. + * + * @return the scaled time per frame in seconds */ public float getTimePerFrame() { return time / 1000.0f * timeScale; } - + /** - * Returns the time scale independent time in seconds it took to complete the last frame. - * - * @return the time scale independent time in seconds it took to complete the last frame + * Returns the time it took to complete the last frame in seconds, independent + * of the time scale. + * + * @return the unscaled time per frame in seconds */ public float getUnscaledTimePerFrame() { return time / 1000.0f; } /** - * Returns the scale at which time is passing. - * - * @return the scale at which time is passing + * Returns the current time scaling factor. + * + * @return the time scale */ public float getTimeScale() { return timeScale; } /** - * Sets the scale at which time is passing to the specified new value. This - * can be used for slow motion effects. - * - * @param timeScale - * the specified new value + * Sets the time scaling factor. A value of 1.0 represents real-time, values + * less than 1.0 slow down time, and values greater than 1.0 speed up time. + * + * @param timeScale the new time scaling factor */ public void setTimeScale(float timeScale) { this.timeScale = timeScale; } /** - * Returns the total number of frames that have passed since the first - * update of this timer. - * - * @return the total number of frames that have passed + * Returns the total number of frames that have passed since the Timer + * started. + * + * @return the total frame count */ public int getFrameCount() { return frameCount; } + + /** + * Formats a time value in seconds into a string in the format HH:MM:SS. + * + * @param timeInSeconds the time in seconds to format + * @return the formatted time string + */ + private String formatTime(float timeInSeconds) { + int s = (int) timeInSeconds; + return String.format("%d:%02d:%02d", s / 3600, (s % 3600) / 60, s % 60); + } /** - * Returns a string representation of this timer. + * Returns a string representation of this Timer, showing its current state. + * + * @return a string representation of the Timer */ @Override public String toString() { - return "Timer [secondCount=" + secondCount + ", lastFrameCount=" - + lastFrameCount + ", fps=" + fps + ", lastTime=" + lastTime - + ", time=" + time + ", totalTime=" + totalTime - + ", timeScale=" + timeScale + ", frameCount=" + frameCount - + "]"; + return "Timer [millisecondCounter=" + millisecondCounter + + ", lastFrameCount=" + lastFrameCount + ", fps=" + fps + ", lastTime=" + + lastTime + ", time=" + time + ", totalTime=" + totalTime + + ", timeScale=" + timeScale + ", frameCount=" + frameCount + "]"; } } From 5d98d7b280e4f9ec8cd140a0a7cae21bcf5d9540 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Mon, 16 Dec 2024 20:14:47 +0100 Subject: [PATCH 15/59] refactor: move light classes from scene.light to engine.scene.light - Updated package structure to place light-related classes under `engine.scene.light`. - Improves organization and aligns with the overall framework's naming conventions. - Ensures a consistent structure for scene-related components within the `engine.scene` package. Affected classes: - `DirectionalLight` - `Light` (interface) - `LightRenderer` (interface) - `LightType` (enum) - `PointLight` - `Spotlight` --- src/main/java/{ => engine}/scene/light/DirectionalLight.java | 2 +- src/main/java/{ => engine}/scene/light/Light.java | 2 +- src/main/java/{ => engine}/scene/light/LightRenderer.java | 2 +- src/main/java/{ => engine}/scene/light/LightType.java | 2 +- src/main/java/{ => engine}/scene/light/PointLight.java | 2 +- src/main/java/{ => engine}/scene/light/SpotLight.java | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) rename src/main/java/{ => engine}/scene/light/DirectionalLight.java (99%) rename src/main/java/{ => engine}/scene/light/Light.java (98%) rename src/main/java/{ => engine}/scene/light/LightRenderer.java (98%) rename src/main/java/{ => engine}/scene/light/LightType.java (94%) rename src/main/java/{ => engine}/scene/light/PointLight.java (99%) rename src/main/java/{ => engine}/scene/light/SpotLight.java (99%) diff --git a/src/main/java/scene/light/DirectionalLight.java b/src/main/java/engine/scene/light/DirectionalLight.java similarity index 99% rename from src/main/java/scene/light/DirectionalLight.java rename to src/main/java/engine/scene/light/DirectionalLight.java index 29ed5bf6..ccfd8661 100644 --- a/src/main/java/scene/light/DirectionalLight.java +++ b/src/main/java/engine/scene/light/DirectionalLight.java @@ -1,4 +1,4 @@ -package scene.light; +package engine.scene.light; import math.Color; import math.Vector3f; diff --git a/src/main/java/scene/light/Light.java b/src/main/java/engine/scene/light/Light.java similarity index 98% rename from src/main/java/scene/light/Light.java rename to src/main/java/engine/scene/light/Light.java index 002ffddd..18765910 100644 --- a/src/main/java/scene/light/Light.java +++ b/src/main/java/engine/scene/light/Light.java @@ -1,4 +1,4 @@ -package scene.light; +package engine.scene.light; import math.Color; diff --git a/src/main/java/scene/light/LightRenderer.java b/src/main/java/engine/scene/light/LightRenderer.java similarity index 98% rename from src/main/java/scene/light/LightRenderer.java rename to src/main/java/engine/scene/light/LightRenderer.java index 3490d213..d307b12e 100644 --- a/src/main/java/scene/light/LightRenderer.java +++ b/src/main/java/engine/scene/light/LightRenderer.java @@ -1,4 +1,4 @@ -package scene.light; +package engine.scene.light; /** * Interface for rendering various light sources in a 3D scene. diff --git a/src/main/java/scene/light/LightType.java b/src/main/java/engine/scene/light/LightType.java similarity index 94% rename from src/main/java/scene/light/LightType.java rename to src/main/java/engine/scene/light/LightType.java index 051300a9..50912c4d 100644 --- a/src/main/java/scene/light/LightType.java +++ b/src/main/java/engine/scene/light/LightType.java @@ -1,4 +1,4 @@ -package scene.light; +package engine.scene.light; /** * Enum representing different types of lights. diff --git a/src/main/java/scene/light/PointLight.java b/src/main/java/engine/scene/light/PointLight.java similarity index 99% rename from src/main/java/scene/light/PointLight.java rename to src/main/java/engine/scene/light/PointLight.java index f37a81df..6262f402 100644 --- a/src/main/java/scene/light/PointLight.java +++ b/src/main/java/engine/scene/light/PointLight.java @@ -1,4 +1,4 @@ -package scene.light; +package engine.scene.light; import math.Color; import math.Vector3f; diff --git a/src/main/java/scene/light/SpotLight.java b/src/main/java/engine/scene/light/SpotLight.java similarity index 99% rename from src/main/java/scene/light/SpotLight.java rename to src/main/java/engine/scene/light/SpotLight.java index c6d17e16..72bc0cf1 100644 --- a/src/main/java/scene/light/SpotLight.java +++ b/src/main/java/engine/scene/light/SpotLight.java @@ -1,4 +1,4 @@ -package scene.light; +package engine.scene.light; import math.Color; import math.Vector3f; From 63e10e82a914c777f010fa453958a29d7452b396 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Tue, 17 Dec 2024 00:09:44 +0100 Subject: [PATCH 16/59] Refactor Graphics Context: Split into Graphics2D, Graphics3D, and Graphics Interfaces - Split rendering interfaces into three layers: - **Graphics2D**: Handles 2D rendering operations like shapes, text, and simple transformations. - **Graphics3D**: Extends Graphics2D and adds 3D rendering operations such as view/projection matrix handling and 3D transformations. - **Graphics**: Extends Graphics3D to provide a unified abstraction layer for rendering pipelines. - Improved adherence to SOLID principles by separating concerns. - Prepared architecture for potential rendering pipeline swaps (e.g., LibGDX, JOGL). --- src/main/java/workspace/ui/Graphics.java | 64 +--------------------- src/main/java/workspace/ui/Graphics2D.java | 49 +++++++++++++++++ src/main/java/workspace/ui/Graphics3D.java | 60 ++++++++++++++++++++ 3 files changed, 110 insertions(+), 63 deletions(-) create mode 100644 src/main/java/workspace/ui/Graphics2D.java create mode 100644 src/main/java/workspace/ui/Graphics3D.java diff --git a/src/main/java/workspace/ui/Graphics.java b/src/main/java/workspace/ui/Graphics.java index 458f6d38..d3d986c2 100644 --- a/src/main/java/workspace/ui/Graphics.java +++ b/src/main/java/workspace/ui/Graphics.java @@ -1,67 +1,5 @@ package workspace.ui; -import mesh.Mesh3D; - -public interface Graphics { - - int getWidth(); - - int getHeight(); - - void pushMatrix(); - - void popMatrix(); - - void scale(float sx, float sy, float sz); - - void translate(float x, float y); - - void translate(float x, float y, float z); - - void strokeWeight(float weight); - - void setColor(Color color); - - void setColor(math.Color color); - - void setColor(int red, int green, int blue); - - void drawRect(float x, float y, float width, float height); - - void fillRect(float x, float y, float width, float height); - - void drawOval(float x, float y, float width, float height); - - void fillOval(float x, float y, float width, float height); - - void drawLine(float x1, float y1, float x2, float y2); - - void drawLine(float x1, float y1, float z1, float x2, float y2, float z2); - - void fillFaces(Mesh3D mesh); - - void textSize(float size); - - float getTextSize(); - - float textWidth(String text); - - float textAscent(); - - float textDescent(); - - void text(String text, float x, float y); - - void enableDepthTest(); - - void disableDepthTest(); - - void rotate(float angle); - - void rotateX(float angle); - - void rotateY(float angle); - - void rotateZ(float angle); +public interface Graphics extends Graphics3D { } diff --git a/src/main/java/workspace/ui/Graphics2D.java b/src/main/java/workspace/ui/Graphics2D.java new file mode 100644 index 00000000..d90a1479 --- /dev/null +++ b/src/main/java/workspace/ui/Graphics2D.java @@ -0,0 +1,49 @@ +package workspace.ui; + +public interface Graphics2D { + + int getWidth(); + + int getHeight(); + + void setColor(Color color); + + void setColor(math.Color color); + + void setColor(int red, int green, int blue); + + void strokeWeight(float weight); + + void pushMatrix(); + + void popMatrix(); + + void translate(float x, float y); + + void scale(float sx, float sy); + + void rotate(float angle); + + void drawRect(float x, float y, float width, float height); + + void fillRect(float x, float y, float width, float height); + + void drawOval(float x, float y, float width, float height); + + void fillOval(float x, float y, float width, float height); + + void drawLine(float x1, float y1, float x2, float y2); + + void textSize(float size); + + float getTextSize(); + + float textWidth(String text); + + float textAscent(); + + float textDescent(); + + void text(String text, float x, float y); + +} diff --git a/src/main/java/workspace/ui/Graphics3D.java b/src/main/java/workspace/ui/Graphics3D.java new file mode 100644 index 00000000..138e6a18 --- /dev/null +++ b/src/main/java/workspace/ui/Graphics3D.java @@ -0,0 +1,60 @@ +package workspace.ui; + +import java.util.List; + +import engine.render.Material; +import engine.scene.light.Light; +import math.Matrix4f; +import mesh.Mesh3D; + +public interface Graphics3D extends Graphics2D { + + void translate(float x, float y, float z); + + void scale(float sx, float sy, float sz); + + void rotateX(float angle); + + void rotateY(float angle); + + void rotateZ(float angle); + + void render(Light light); + + void fillFaces(Mesh3D mesh); + + void renderInstances(Mesh3D mesh, List instanceTransforms); + + void setShader(String vertexShaderName, String fragmentShaderName); + + void enableDepthTest(); + + void disableDepthTest(); + + void setMaterial(Material material); + + void drawLine(float x1, float y1, float z1, float x2, float y2, float z2); + + void camera(); + + void lightsOff(); + + /** + * Sets the current view matrix for rendering. The view matrix transforms + * coordinates from world space to camera (view) space. + * + * @param viewMatrix The 4x4 view matrix to be applied for rendering. + */ + void setViewMatrix(Matrix4f viewMatrix); + + /** + * Sets the current projection matrix for rendering. The projection matrix + * defines how 3D coordinates are projected into the 2D viewport for rendering + * purposes. + * + * @param projectionMatrix The 4x4 projection matrix to be applied for + * rendering. + */ + void setProjectionMatrix(Matrix4f projectionMatrix); + +} From 4f24521df71628fb41befdc206360602ea84e7e8 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Tue, 17 Dec 2024 00:13:52 +0100 Subject: [PATCH 17/59] Add comprehensive Javadoc to Graphics2D interface - Provided detailed documentation for all methods in the `Graphics2D` interface. - Improved clarity on purpose, parameters, return values, and usage for each method. - Enhanced maintainability and developer understanding by including context for the interface itself. --- src/main/java/workspace/ui/Graphics2D.java | 178 +++++++++++++++++++-- 1 file changed, 169 insertions(+), 9 deletions(-) diff --git a/src/main/java/workspace/ui/Graphics2D.java b/src/main/java/workspace/ui/Graphics2D.java index d90a1479..f8a7ab78 100644 --- a/src/main/java/workspace/ui/Graphics2D.java +++ b/src/main/java/workspace/ui/Graphics2D.java @@ -1,49 +1,209 @@ package workspace.ui; +/** + * Defines the 2D rendering context and operations for a 2D rendering system. + *

+ * This interface provides methods for basic 2D graphics rendering operations + * such as drawing geometric shapes (rectangles, ovals, and lines), text + * rendering, color setting, transformations (translate, scale, rotate), and + * text metrics calculations. It serves as the foundation for implementing 2D + * rendering capabilities in a graphics pipeline. + *

+ * + *

+ * Implementations of this interface are responsible for providing concrete + * rendering logic with support for transformations and state management (e.g., + * push and pop matrix operations). + *

+ */ public interface Graphics2D { - + + /** + * Retrieves the current width of the rendering context's viewport. + * + * @return The width in pixels. + */ int getWidth(); + /** + * Retrieves the current height of the rendering context's viewport. + * + * @return The height in pixels. + */ int getHeight(); - + + /** + * Sets the current drawing color using a {@link Color}. + * + * @param color The color to set for rendering operations. + */ void setColor(Color color); + /** + * Sets the current drawing color using a math-defined {@link math.Color}. + * + * @param color The math-defined color to use for rendering operations. + */ void setColor(math.Color color); - + + /** + * Sets the current drawing color using RGB integer values. + * + * @param red The red channel value (0-255). + * @param green The green channel value (0-255). + * @param blue The blue channel value (0-255). + */ void setColor(int red, int green, int blue); - + + /** + * Sets the thickness of strokes (lines) used in subsequent drawing commands. + * + * @param weight The stroke weight to apply (e.g., 1.0 for standard line + * thickness). + */ void strokeWeight(float weight); - + + /** + * Saves the current transformation matrix onto a stack for future + * restoration. + * + * This allows temporary transformations without permanently altering the + * rendering state. + */ void pushMatrix(); + /** + * Restores the last saved transformation matrix from the stack. + * + * This undoes any temporary transformations applied since the last + * pushMatrix(). + */ void popMatrix(); - + + /** + * Translates the rendering context by a specified distance in 2D space. + * + * @param x The amount to translate along the x-axis. + * @param y The amount to translate along the y-axis. + */ void translate(float x, float y); - + + /** + * Scales the rendering context by specified scaling factors along the x and y + * axes. + * + * @param sx The scaling factor along the x-axis. + * @param sy The scaling factor along the y-axis. + */ void scale(float sx, float sy); - + + /** + * Rotates the rendering context by the given angle in radians. + * + * @param angle The angle to rotate by, in radians. + */ void rotate(float angle); + /** + * Draws an unfilled rectangle at the specified coordinates with the given + * dimensions. + * + * @param x The x-coordinate of the top-left corner of the rectangle. + * @param y The y-coordinate of the top-left corner of the rectangle. + * @param width The width of the rectangle. + * @param height The height of the rectangle. + */ void drawRect(float x, float y, float width, float height); + /** + * Draws a filled rectangle at the specified coordinates with the given + * dimensions. + * + * @param x The x-coordinate of the top-left corner of the rectangle. + * @param y The y-coordinate of the top-left corner of the rectangle. + * @param width The width of the rectangle. + * @param height The height of the rectangle. + */ void fillRect(float x, float y, float width, float height); + /** + * Draws an unfilled oval at the specified coordinates with the given + * dimensions. + * + * @param x The x-coordinate of the top-left corner of the bounding box + * of the oval. + * @param y The y-coordinate of the top-left corner of the bounding box + * of the oval. + * @param width The width of the bounding box. + * @param height The height of the bounding box. + */ void drawOval(float x, float y, float width, float height); + /** + * Draws a filled oval at the specified coordinates with the given dimensions. + * + * @param x The x-coordinate of the top-left corner of the bounding box + * of the oval. + * @param y The y-coordinate of the top-left corner of the bounding box + * of the oval. + * @param width The width of the bounding box. + * @param height The height of the bounding box. + */ void fillOval(float x, float y, float width, float height); + /** + * Draws a line from (x1, y1) to (x2, y2). + * + * @param x1 Starting x-coordinate. + * @param y1 Starting y-coordinate. + * @param x2 Ending x-coordinate. + * @param y2 Ending y-coordinate. + */ void drawLine(float x1, float y1, float x2, float y2); + /** + * Sets the size of text to render for subsequent text rendering operations. + * + * @param size The desired text size. + */ void textSize(float size); + /** + * Retrieves the current text size used by text rendering operations. + * + * @return The current text size. + */ float getTextSize(); + /** + * Computes the width of the given text string at the current text size. + * + * @param text The text to compute the width for. + * @return The width of the rendered text. + */ float textWidth(String text); + /** + * Retrieves the ascent of text (the portion of text above the baseline). + * + * @return The ascent value of the text. + */ float textAscent(); + /** + * Retrieves the descent of text (the portion of text below the baseline). + * + * @return The descent value of the text. + */ float textDescent(); + /** + * Renders text at the given screen coordinates. + * + * @param text The text to render. + * @param x The x-coordinate to start rendering the text. + * @param y The y-coordinate to start rendering the text. + */ void text(String text, float x, float y); -} +} \ No newline at end of file From ff12ecf6071bacc13cd9217738c00b52a225a36b Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Tue, 17 Dec 2024 00:44:04 +0100 Subject: [PATCH 18/59] feat(Graphics2D): add drawRoundRect and fillRoundRect methods - Introduced `drawRoundRect` for drawing the outline of a rounded rectangle. - Added `fillRoundRect` to draw a filled rounded rectangle. - Both methods include parameters for position, dimensions, and corner radius. - Updated the interface to support rounded rectangle rendering. --- src/main/java/workspace/ui/Graphics2D.java | 40 +++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/main/java/workspace/ui/Graphics2D.java b/src/main/java/workspace/ui/Graphics2D.java index f8a7ab78..f957c87a 100644 --- a/src/main/java/workspace/ui/Graphics2D.java +++ b/src/main/java/workspace/ui/Graphics2D.java @@ -126,6 +126,44 @@ public interface Graphics2D { */ void fillRect(float x, float y, float width, float height); + /** + * Draws the outline of a rounded rectangle with specified position, + * dimensions, and corner radius. + * + *

+ * The rectangle is defined by its top-left corner (x, y), its width, and its + * height. The "radii" parameter specifies the corner radius, which determines + * how rounded the corners appear. If the corner radius is 0, this method + * behaves like {@link #drawRect(float, float, float, float)}. + * + * @param x The x-coordinate of the top-left corner of the rectangle. + * @param y The y-coordinate of the top-left corner of the rectangle. + * @param width The width of the rectangle. + * @param height The height of the rectangle. + * @param radii The corner radius for rounding the corners of the rectangle. + * A larger value results in more rounded corners. + */ + void drawRoundRect(float x, float y, float width, float height, float radii); + + /** + * Draws a filled rounded rectangle with specified position, dimensions, and + * corner radius. + * + *

+ * The rectangle is defined by its top-left corner (x, y), its width, and its + * height. The "radii" parameter specifies the corner radius, which determines + * how rounded the corners appear. If the corner radius is 0, this method + * behaves like {@link #fillRect(float, float, float, float)}. + * + * @param x The x-coordinate of the top-left corner of the rectangle. + * @param y The y-coordinate of the top-left corner of the rectangle. + * @param width The width of the rectangle. + * @param height The height of the rectangle. + * @param radii The corner radius for rounding the corners of the rectangle. + * A larger value results in more rounded corners. + */ + void fillRoundRect(float x, float y, float width, float height, float radii); + /** * Draws an unfilled oval at the specified coordinates with the given * dimensions. @@ -205,5 +243,5 @@ public interface Graphics2D { * @param y The y-coordinate to start rendering the text. */ void text(String text, float x, float y); - + } \ No newline at end of file From 888d8f31462e6955b170a31bfcef2b00eb47428f Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Tue, 17 Dec 2024 20:33:13 +0100 Subject: [PATCH 19/59] feat: Add Material class with predefined and customizable lighting properties - Created `Material` class to define 3D mesh visual properties under lighting conditions. - Includes properties like base color, ambient, diffuse, specular coefficients, and shininess. - Added predefined materials (white, black, red, green, blue, metallic silver, gold, glass, stone grey, water). - Added functionality to apply and release materials to/from the rendering context using `Graphics`. - Provided flexible constructors for custom and default material creation. - Included getter methods for accessing material properties. This class sets the foundation for realistic rendering effects with dynamic lighting. --- src/main/java/engine/render/Material.java | 231 ++++++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 src/main/java/engine/render/Material.java diff --git a/src/main/java/engine/render/Material.java b/src/main/java/engine/render/Material.java new file mode 100644 index 00000000..7d67e2a4 --- /dev/null +++ b/src/main/java/engine/render/Material.java @@ -0,0 +1,231 @@ +package engine.render; + +import math.Color; +import workspace.ui.Graphics; + +/** + * Represents a material with lighting and shader properties for rendering 3D + * meshes. + *

+ * A Material defines the visual appearance of a 3D object under lighting + * conditions. It includes attributes such as base color, ambient, diffuse, and + * specular light coefficients, and shininess, which together determine how + * light interacts with the object's surface. + *

+ *

+ * This class provides predefined materials for common use cases (e.g., default + * white, metallic, glass) and allows custom material creation with specific + * properties. + *

+ */ +public class Material { + + /** + * Default white material with a base white color and standard light + * coefficients. + */ + public static final Material DEFAULT_WHITE = new Material(Color.WHITE); + + /** + * Default black material with a base black color and standard light + * coefficients. + */ + public static final Material DEFAULT_BLACK = new Material(Color.BLACK); + + /** + * Default red material with a base red color and standard light coefficients. + */ + public static final Material DEFAULT_RED = new Material(Color.RED); + + /** + * Default green material with a base green color and standard light + * coefficients. + */ + public static final Material DEFAULT_GREEN = new Material(Color.GREEN); + + /** + * Default blue material with a base blue color and standard light + * coefficients. + */ + public static final Material DEFAULT_BLUE = new Material(Color.BLUE); + + /** + * Metallic silver material with a shiny silver appearance. + */ + public static final Material METALLIC_SILVER_MATERIAL = new Material( + new Color(0.75f, 0.75f, 0.75f), + new float[] { 0.2f, 0.2f, 0.2f }, + new float[] { 1.0f, 1.0f, 1.0f }, + new float[] { 1.0f, 1.0f, 1.0f }, + 50.0f); + + /** + * Metallic gold material with a shiny gold appearance. + */ + public static final Material GOLDEN_METALLIC_MATERIAL = new Material( + new Color(1.0f, 0.84f, 0.0f), + new float[] { 0.3f, 0.3f, 0.3f }, + new float[] { 1.0f, 0.84f, 0.0f }, + new float[] { 1.0f, 1.0f, 0.5f }, + 100.0f); + + /** + * Clear glass material with a transparent appearance. + */ + public static final Material GLASS_CLEAR_MATERIAL = new Material( + new Color(0.7f, 0.9f, 1.0f), + new float[] { 0.3f, 0.3f, 0.3f }, + new float[] { 0.8f, 0.8f, 0.8f }, + new float[] { 1.0f, 1.0f, 1.0f }, 5.0f); + + /** + * Stone grey material with a matte grey appearance. + */ + public static final Material STONE_GREY_MATERIAL = + new Material( + new Color(0.5f, 0.5f, 0.5f), + new float[] { 0.4f, 0.4f, 0.4f }, + new float[] { 0.6f, 0.6f, 0.6f }, + new float[] { 0.2f, 0.2f, 0.2f }, + 10.0f); + + /** + * Water material with a reflective blue appearance. + */ + public static final Material WATER_MATERIAL = new Material( + new Color(0.0f, 0.5f, 1.0f), + new float[] { 0.1f, 0.3f, 0.5f }, + new float[] { 0.3f, 0.5f, 0.7f }, + new float[] { 0.2f, 0.2f, 0.6f }, 2.0f); + + /** + * Base color for the material. + */ + private Color color; + + /** + * Ambient light coefficient (R, G, B). + */ + private float[] ambient; + + /** + * Diffuse light coefficient (R, G, B). + */ + private float[] diffuse; + + /** + * Specular light coefficient (R, G, B). + */ + private float[] specular; + + /** + * Shininess factor for specular highlights. + */ + private float shininess; + + /** + * Private constructor for creating a material with specific properties. + * + * @param color The base color of the material. + * @param ambient The ambient light coefficient (R, G, B). + * @param diffuse The diffuse light coefficient (R, G, B). + * @param specular The specular light coefficient (R, G, B). + * @param shininess The shininess factor for specular highlights. + */ + private Material(Color color, float[] ambient, float[] diffuse, + float[] specular, float shininess) { + this.color = color; + this.ambient = ambient; + this.diffuse = diffuse; + this.specular = specular; + this.shininess = shininess; + } + + /** + * Default constructor initializes the material with default lighting + * properties. + */ + public Material() { + this.color = new Color(1, 1, 1); + this.ambient = new float[] { 0.2f, 0.2f, 0.2f }; + this.diffuse = new float[] { 1.0f, 1.0f, 1.0f }; + this.specular = new float[] { 1.0f, 1.0f, 1.0f }; + this.shininess = 10.0f; + } + + /** + * Constructor to set the base color of the material. + * + * @param color The base color of the material. + */ + public Material(Color color) { + this(); + this.color = color; + } + + /** + * Applies this material's properties to the given rendering context. + * + * @param g The {@link Graphics} instance to which the material will be + * applied. + */ + public void apply(Graphics g) { + g.setMaterial(this); + } + + /** + * Releases the material or resets properties in the rendering context. Useful + * for unbinding shaders or clearing material-specific settings. + * + * @param g The {@link Graphics} instance from which the material is released. + */ + public void release(Graphics g) { + // Logic to unbind shader or reset material-related properties + } + + /** + * Gets the base color of the material. + * + * @return The base {@link Color} of the material. + */ + public Color getColor() { + return color; + } + + /** + * Gets the ambient light coefficient of the material. + * + * @return An array representing the ambient light coefficient (R, G, B). + */ + public float[] getAmbient() { + return ambient; + } + + /** + * Gets the diffuse light coefficient of the material. + * + * @return An array representing the diffuse light coefficient (R, G, B). + */ + public float[] getDiffuse() { + return diffuse; + } + + /** + * Gets the specular light coefficient of the material. + * + * @return An array representing the specular light coefficient (R, G, B). + */ + public float[] getSpecular() { + return specular; + } + + /** + * Gets the shininess factor of the material. + * + * @return The shininess factor for specular highlights. + */ + public float getShininess() { + return shininess; + } + +} \ No newline at end of file From d37abc19431b0a8b64cd33c018884bf1befab50a Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Tue, 17 Dec 2024 20:46:33 +0100 Subject: [PATCH 20/59] feat: Implement SceneNode class with scene graph hierarchy, component management, and rendering support - Added `SceneNode` class to represent nodes in the scene graph. - Enabled parent-child relationships with methods for adding and removing children. - Introduced component system with methods to add, remove, and query components. - Implemented rendering logic with recursive traversal of children and rendering components. - Added update and cleanup mechanisms for nodes and their associated components. - Ensured proper resource management during child/component cleanup to prevent memory leaks. - Added utilities for root node retrieval and leaf node checks. - Added helper methods to handle `Transform` and `RenderComponent` lookups. --- src/main/java/engine/scene/SceneNode.java | 374 ++++++++++++++++++++++ 1 file changed, 374 insertions(+) create mode 100644 src/main/java/engine/scene/SceneNode.java diff --git a/src/main/java/engine/scene/SceneNode.java b/src/main/java/engine/scene/SceneNode.java new file mode 100644 index 00000000..c19e52b9 --- /dev/null +++ b/src/main/java/engine/scene/SceneNode.java @@ -0,0 +1,374 @@ +package engine.scene; + +import java.util.ArrayList; +import java.util.List; + +import engine.components.Component; +import engine.components.RenderComponent; +import engine.components.Transform; +import workspace.ui.Graphics; + +/** + * Represents a single node within the scene graph. + *

+ * A {@code SceneNode} is a fundamental building block in a hierarchical scene + * graph structure, managing child nodes, components, and transformations. Scene + * nodes are organized in a parent-child relationship, where a parent node can + * have multiple children, and each child can have its own components and + * transformations. + *

+ *

+ * The {@code SceneNode} manages its own transformation through a + * {@link Transform} object, handles rendering, updates logic for itself and its + * children, and provides methods for managing components like + * {@link RenderComponent}. + *

+ *

+ * Example use cases include: + *

    + *
  • Modeling a hierarchy of objects (e.g., parts of a character or modular + * environment pieces).
  • + *
  • Managing rendering logic and transformations in a scene graph.
  • + *
  • Composing behavior with reusable components for modular design.
  • + *
+ *

+ * + * @see Transform + * @see Component + * @see RenderComponent + */ +public class SceneNode { + + /** + * The default name assigned to a scene node if no name is provided. + */ + private static final String DEFAULT_NAME = "Untitled-Node"; + + /** + * The name of this node, primarily intended for debugging and identification + * purposes. + */ + private String name; + + /** The parent node in the scene graph hierarchy. */ + private SceneNode parent; + + /** List of child nodes attached to this node. */ + private List children; + + /** List of components (logic/rendering behavior) attached to this node. */ + private List components; + + /** + * Creates a new {@code SceneNode} with the specified name. + * + * @param name The name to assign to the scene node. + * @throws IllegalArgumentException if the name is {@code null}. + */ + public SceneNode(String name) { + if (name == null) { + throw new IllegalArgumentException("Name cannot be null."); + } + this.name = name; + this.children = new ArrayList(); + this.components = new ArrayList(); + // Add a default Transform component + this.components.add(new Transform()); + } + + /** + * Constructs a new, empty {@code SceneNode} with default transformations, an + * empty list of children, and an empty list of components. + */ + public SceneNode() { + this(DEFAULT_NAME); + } + + /** + * Renders this node and all its children recursively. + *

+ * This method applies the local transformation, renders components, and + * traverses through all child nodes to render them as well. This ensures the + * entire subtree rooted at this node is rendered properly. + *

+ * + * @param g The graphics context used for rendering this node and its + * children. + */ + public void render(Graphics g) { + g.pushMatrix(); + + applyLocalTransform(g); + renderComponents(g); + + for (SceneNode child : children) { + child.render(g); + } + + g.popMatrix(); + } + + /** + * Applies the local transformation to the graphics context. + */ + private void applyLocalTransform(Graphics g) { + getTransform().apply(g); + } + + /** + * Renders all associated {@link RenderComponent} instances attached to this + * node. + *

+ * This method iterates through all render components and calls their + * respective rendering logic. + *

+ * + * @param g The graphics context used for rendering. + */ + protected void renderComponents(Graphics g) { + for (RenderComponent renderer : getRenderComponents()) { + renderer.render(g); + } + } + + /** + * Updates this node's logic and propagates updates to children nodes. + * + * @param tpf The time per frame in seconds (delta time). + */ + public void update(float tpf) { + updateComponents(tpf); + updateChildren(tpf); + } + + /** + * Updates all components attached to this node. + * + * @param tpf The time per frame in seconds. + */ + protected void updateComponents(float tpf) { + for (Component component : components) { + component.update(tpf); + } + } + + /** + * Updates all child nodes of this node recursively. + * + * @param tpf The time per frame in seconds. + */ + protected void updateChildren(float tpf) { + for (SceneNode child : children) { + child.update(tpf); + } + } + + /** + * Cleans up this node's resources, components, and children recursively. + *

+ * Each component and child is cleaned up to ensure no resources are left + * hanging, preventing memory leaks or unwanted behavior. + *

+ */ + public void cleanup() { + for (Component component : components) { + try { + component.cleanup(); + } catch (Exception e) { + System.err.println("Error cleaning up component: " + e.getMessage()); + } + } + + for (SceneNode child : children) { + child.cleanup(); + } + + components.clear(); + children.clear(); + } + + /** + * Adds a child node to this node's hierarchy. + *

+ * Prevents the addition of null nodes and ensures no duplicate child is + * added. + *

+ * + * @param child The child {@code SceneNode} to add. + */ + public void addChild(SceneNode child) { + if (child == null) { + throw new IllegalArgumentException("Child node cannot be null."); + } + if (children.contains(child)) { + return; + } + child.parent = this; + children.add(child); + } + + /** + * Removes a child node from this node's hierarchy. + *

+ * Cleans up the child before removing it to ensure no resources are leaked. + *

+ * + * @param child The child {@code SceneNode} to remove. + */ + public void removeChild(SceneNode child) { + if (child == null) { + return; + } + if (!children.contains(child)) { + return; + } + child.cleanup(); + child.parent = null; + children.remove(child); + } + + /** + * Adds a component to this node. + *

+ * Ensures that duplicate components of the same instance are not added. + *

+ * + * @param component The {@link Component} to add. + * @throws IllegalArgumentException if the component is null. + */ + public void addComponent(Component component) { + if (component == null) { + throw new IllegalArgumentException("Component cannot be null."); + } + if (!components.contains(component)) { + components.add(component); + component.setOwner(this); + } + } + + /** + * Removes a component from this node. + *

+ * If the component is found, it is cleaned up before removal. + *

+ * + * @param component The {@link Component} to remove. + * @throws IllegalArgumentException if the component is null. + */ + public void removeComponent(Component component) { + if (component == null) { + throw new IllegalArgumentException("Component cannot be null."); + } + if (components.contains(component)) { + components.remove(component); + component.cleanup(); + component.setOwner(null); + } + } + + /** + * Retrieves the first component of the specified type attached to this node. + * + * @param componentClass The class type of the component to retrieve. + * @param The type of the component. + * @return The first matching component, or {@code null} if none exists. + */ + public T getComponent(Class componentClass) { + for (Component component : components) { + if (componentClass.isInstance(component)) { + return componentClass.cast(component); + } + } + return null; + } + + /** + * Retrieves a list of components of a specific type attached to this node. + *

+ * Enables querying for specific types of behavior or functionality attached + * to a node. + *

+ * + * @param componentClass The class type of the component to retrieve. + * @param The type of component to search for. + * @return A list of components matching the specified type. + */ + public List getComponents(Class componentClass) { + List result = new ArrayList<>(); + for (Component component : components) { + if (componentClass.isInstance(component)) { + result.add(componentClass.cast(component)); + } + } + return result; + } + + /** + * Retrieves all render components for this node. + * + * @return A list of {@link RenderComponent} instances associated with this + * node. + */ + public List getRenderComponents() { + return getComponents(RenderComponent.class); + } + + /** + * Retrieves the root node in the scene graph hierarchy. + * + * @return The root {@code SceneNode} in the hierarchy. + */ + public SceneNode getRoot() { + if (parent == null) { + return this; + } + return parent.getRoot(); + } + + /** + * Checks whether this node is the root node in the hierarchy. + * + * @return {@code true} if this node is the root; {@code false} otherwise. + */ + public boolean isRoot() { + return parent == null; + } + + /** + * Checks whether this node is a leaf node (has no children). + * + * @return {@code true} if this node has no children; {@code false} otherwise. + */ + public boolean isLeaf() { + return children.isEmpty(); + } + + /** Retrieves the Transform component associated with this node. */ + public Transform getTransform() { + return getComponents(Transform.class).stream().findFirst().orElseThrow( + () -> new IllegalStateException("Transform component is missing.")); + } + + /** + * Retrieves the name of this {@code SceneNode}. + * + * @return The name of the node. + */ + public String getName() { + return name; + } + + /** + * Sets the name of this {@code SceneNode}. + * + * @param name The new name to assign to the node. + * @throws IllegalArgumentException if the name is {@code null}. + */ + public void setName(String name) { + if (name == null) { + throw new IllegalArgumentException("Name cannot be null."); + } + this.name = name; + } + +} \ No newline at end of file From 1eac84cc77d1458a0dbcf9d20fe20cb66aa9cc93 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Tue, 17 Dec 2024 20:50:37 +0100 Subject: [PATCH 21/59] Implement Scene Management with Thread-Safe Operations and Parallel Updates Added the Scene class to manage a hierarchy of SceneNode instances for efficient rendering and updates. Introduced thread-safe operations for managing root nodes and lights using synchronized blocks. Added a thread pool with ExecutorService to enable parallel updates for better performance during simulation logic calculations. Implemented methods for adding/removing nodes and lights safely, supporting dynamic scene graph manipulation. Added methods for rendering lights and nodes, integrating a simple wireframe rendering mode toggle. Included cleanup logic to ensure proper resource management by shutting down worker threads and clearing references. Added utility methods to query root nodes and lights safely, with copies to prevent race conditions during access. Integrated methods for setting and querying the active camera for dynamic view transformations. --- src/main/java/engine/scene/Scene.java | 319 ++++++++++++++++++++++++++ 1 file changed, 319 insertions(+) create mode 100644 src/main/java/engine/scene/Scene.java diff --git a/src/main/java/engine/scene/Scene.java b/src/main/java/engine/scene/Scene.java new file mode 100644 index 00000000..f0669283 --- /dev/null +++ b/src/main/java/engine/scene/Scene.java @@ -0,0 +1,319 @@ +package engine.scene; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import engine.scene.camera.Camera; +import engine.scene.light.Light; +import workspace.ui.Graphics; + +/** + * The {@code Scene} class manages a hierarchy of {@code SceneNode}s for + * rendering and updating. It handles root-level scene nodes, lighting, and a + * thread pool for parallel updates, offering a high-level interface for + * managing and rendering complex 3D scenes. + */ +public class Scene { + + /** Default name assigned to a newly created scene if no name is provided. */ + private static final String DEFAULT_NAME = "Untitled-Scene"; + + /** + * List of root-level nodes in the scene hierarchy for rendering and updates. + */ + private final List rootNodes = new ArrayList<>(); + + /** List of lights in the scene that are used for lighting calculations. */ + private final List lights = new ArrayList<>(); + + /** Thread pool used to parallelize updates for performance optimization. */ + private final ExecutorService updateExecutor = Executors + .newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + + /** Flag indicating whether the scene is rendered in wireframe mode. */ + private boolean wireframeMode; + + /** Name of the scene. Used for identification or debugging purposes. */ + private final String name; + + /** + * The currently active camera that determines the scene's view + * transformation. + */ + private Camera activeCamera; + + /** + * Constructs a {@code Scene} with a default name. + */ + public Scene() { + this(DEFAULT_NAME); + } + + /** + * Constructs a {@code Scene} with the specified name. + * + * @param name The name of the scene. + * @throws IllegalArgumentException if the name is {@code null}. + */ + public Scene(String name) { + if (name == null) { + throw new IllegalArgumentException("Name cannot be null."); + } + this.name = name; + } + + /** + * Adds a SceneNode to the root level of the scene graph. + * + * @param node The node to add to the root level. + */ + public void addNode(SceneNode node) { + if (node == null) { + throw new IllegalArgumentException("Node cannot be null."); + } + synchronized (rootNodes) { + rootNodes.add(node); + } + } + + /** + * Adds a light to the scene's list of lights for rendering and lighting + * calculations. Ensures thread-safe addition by synchronizing on the `lights` + * list. + * + * @param light The Light instance to be added to the scene. + * @throws IllegalArgumentException if the provided light is null. + */ + public void addLight(Light light) { + if (light == null) { + throw new IllegalArgumentException("Light cannot be null."); + } + synchronized (lights) { + lights.add(light); + } + } + + /** + * Removes a SceneNode from the root level. + * + * @param node The node to remove from the root level. + */ + public void removeNode(SceneNode node) { + if (node == null) + return; + synchronized (rootNodes) { + rootNodes.remove(node); + } + } + + /** + * Perform parallel updates on all nodes in the scene graph. + * + * @param deltaTime The time step for simulation logic updates. + */ + public void update(float deltaTime) { + List nodesCopy; + synchronized (rootNodes) { + nodesCopy = new ArrayList<>(rootNodes); + } + + // Submit updates to worker threads + for (SceneNode node : nodesCopy) { + updateExecutor.submit(() -> node.update(deltaTime)); + } + } + + /** + * Render lights and nodes concurrently. However, rendering must still run on + * the main thread for compatibility with most rendering APIs. + */ + public void render(Graphics g) { + g.setWireframeMode(wireframeMode); + renderLights(g); + synchronized (rootNodes) { + if (activeCamera != null) { + // TODO FIXME: Implement + } + for (SceneNode node : rootNodes) { + node.render(g); + } + } + } + + /** + * Renders all lights in the scene safely by synchronizing access to the + * lights list. This ensures thread-safe iteration and rendering, especially + * when lights are added or removed concurrently. + * + * @param g The graphics context used for rendering the lights. + */ + private void renderLights(Graphics g) { + synchronized (lights) { + for (Light light : lights) { + g.render(light); + } + } + } + + /** + * Cleans up resources and shuts down the executor safely. + */ + public void cleanup() { + // Shutdown thread pool properly + updateExecutor.shutdown(); + synchronized (rootNodes) { + for (SceneNode node : rootNodes) { + node.cleanup(); + } + rootNodes.clear(); + } + } + + /** + * Retrieves the number of root nodes in the scene. + * + * @return The count of root nodes currently present in the scene graph. + */ + public int getRootCount() { + synchronized (rootNodes) { + return rootNodes.size(); + } + } + + /** + * Performs a complete cleanup of all lights and nodes in the scene, in + * addition to shutting down worker threads. This ensures no memory leaks or + * dangling references persist after the scene is no longer needed. + */ + public void cleanupAllResources() { + synchronized (lights) { + lights.clear(); + } + synchronized (rootNodes) { + for (SceneNode node : rootNodes) { + node.cleanup(); + } + rootNodes.clear(); + } + updateExecutor.shutdown(); + } + + /** + * Retrieves all lights that are currently active in the scene. This allows + * querying of lights for dynamic light management features. + * + * @return A thread-safe copy of the current lights list. + */ + public List getAllLights() { + synchronized (lights) { + return new ArrayList<>(lights); + } + } + + /** + * Fetches all the nodes at the root level in a thread-safe way. Useful for + * debugging, visualization, or debugging purposes to monitor scene nodes in + * real-time. + * + * @return A list of root SceneNodes currently in the scene graph. + */ + public List getRootNodes() { + synchronized (rootNodes) { + return new ArrayList<>(rootNodes); + } + } + + /** + * Finds and removes all SceneNodes matching a particular condition. For + * example, it can remove nodes based on type or other predicates. + * + * @param predicate The condition to test nodes against. + * @return The number of nodes removed. + */ + public int removeNodesIf(java.util.function.Predicate predicate) { + int count = 0; + synchronized (rootNodes) { + for (SceneNode node : new ArrayList<>(rootNodes)) { + if (predicate.test(node)) { + node.cleanup(); + rootNodes.remove(node); + count++; + } + } + } + return count; + } + + /** + * Sets the currently active camera for the scene. The active camera + * determines the view and projection matrices used during rendering. If no + * active camera is set, rendering will proceed without camera + * transformations. + * + * @param camera The camera to set as the active camera. May be null to + * disable camera-based rendering logic. + */ + public void setActiveCamera(Camera camera) { + this.activeCamera = camera; + } + + /** + * Retrieves the currently active camera used for rendering the scene. The + * active camera's view and projection matrices define the perspective and + * viewport used during rendering. + * + * @return The currently active camera, or {@code null} if no active camera + * has been set. + */ + public Camera getActiveCamera() { + return this.activeCamera; + } + + /** + * Retrieves the number of lights currently managed by the scene. + *

+ * This method provides the total count of lights that are part of the scene's + * lighting system. It operates in a thread-safe manner by synchronizing + * access to the shared lights list to ensure accurate and safe read + * operations, even when lights are being added or removed concurrently. + *

+ * + * @return The total number of lights currently in the scene. + */ + public int getLightCount() { + synchronized (lights) { + return lights.size(); + } + } + + /** + * Retrieves the name of the scene. + * + * @return The name of the scene. + */ + public String getName() { + return name; + } + + /** + * Checks if the scene is in wireframe rendering mode. + * + * @return {@code true} if wireframe mode is enabled, {@code false} otherwise. + */ + public boolean isWireframeMode() { + return wireframeMode; + } + + /** + * Enables or disables wireframe rendering mode for the scene. + * + * @param wireframeMode {@code true} to enable wireframe mode, {@code false} + * to disable it. + */ + public void setWireframeMode(boolean wireframeMode) { + this.wireframeMode = wireframeMode; + } + +} \ No newline at end of file From c19ad7dc68ffb0950878d5876b305a50bc979d6d Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Tue, 17 Dec 2024 20:52:05 +0100 Subject: [PATCH 22/59] feat: Implement Particle class for physics-based effects - Added `Particle` class to represent individual particles with properties like position, velocity, acceleration, damping, and lifetime. - Included methods for applying environmental forces, damping, and simulating motion over time. - Implemented lifecycle management with `isAlive()` to determine if a particle is still active. - Integrated utility methods for position tracking (current and old) for effects like trails and motion blur. - Designed to support visual effects such as trails, smoke, explosions, and other particle-based phenomena. --- .../java/engine/render/effects/Particle.java | 168 ++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 src/main/java/engine/render/effects/Particle.java diff --git a/src/main/java/engine/render/effects/Particle.java b/src/main/java/engine/render/effects/Particle.java new file mode 100644 index 00000000..7e413908 --- /dev/null +++ b/src/main/java/engine/render/effects/Particle.java @@ -0,0 +1,168 @@ +package engine.render.effects; + +import math.Vector3f; + +/** + * Represents a single particle with physical properties and lifecycle tracking. + * This class models particles' motion using physics principles like velocity, + * acceleration, damping (drag), and lifetime constraints. + *

+ * Designed to support effects like trails, smoke, explosions, or other particle + * effects by integrating old and new positions for rendering purposes. + *

+ * + * @author Simon Dietz + */ +public class Particle { + + /** The current position of the particle in world-space coordinates. */ + private Vector3f position; + + /** + * The previous position of the particle; used for effects like trails or + * motion blur. + */ + private Vector3f oldPosition; + + /** + * The velocity vector of the particle, representing how it moves over time. + */ + private Vector3f velocity; + + /** The acceleration vector affecting the particle's motion. */ + private Vector3f acceleration; + + /** Total duration (in seconds) for which the particle will live. */ + private float lifetime; + + /** Tracks how much time has elapsed since the particle was created. */ + private float elapsedTime; + + /** The damping factor simulates air resistance or drag on the particle. */ + private float dampingFactor = 0.98f; + + /** + * Constructs a new particle with the specified initial position, velocity, + * acceleration, and lifetime. Initializes the old position to match the + * initial position. + * + * @param position Initial position of the particle in 3D space. + * @param velocity Initial velocity vector of the particle. + * @param acceleration Acceleration vector affecting the particle. + * @param lifetime Total time (seconds) this particle will live. + */ + public Particle(Vector3f position, Vector3f velocity, Vector3f acceleration, + float lifetime) { + this.position = new Vector3f(position); + this.oldPosition = new Vector3f(position); + this.velocity = velocity; + this.acceleration = acceleration; + this.lifetime = lifetime; + this.elapsedTime = 0f; + } + + /** + * Applies a force to the particle by adding it to the acceleration vector. + * Useful for simulating environmental effects like wind or gravity. + *

+ * Assumes that mass is constant and equals 1 for simplicity. + *

+ * + * @param force The force vector to apply to the particle's acceleration. + */ + public void applyForce(Vector3f force) { + acceleration.addLocal(force); + } + + /** + * Applies damping to simulate drag or resistance, slowing down the particle's + * motion over time. + */ + public void applyDamping() { + velocity.multLocal(dampingFactor); + } + + /** + * Updates the particle's position, velocity, and applies damping effects over + * a given time step. Resets acceleration after each update to ensure isolated + * force application each frame. + * + * @param deltaTime The time elapsed since the last frame (in seconds). + */ + public void update(float deltaTime) { + elapsedTime += deltaTime; + + oldPosition.set(position); + + // Apply physics: velocity changes due to acceleration + velocity.addLocal(acceleration.mult(deltaTime)); + + // Apply environmental drag/damping + applyDamping(); + + // Update position based on the new velocity + position.addLocal(velocity.mult(deltaTime)); + + // Reset acceleration for the next simulation step + acceleration.set(0, 0, 0); + } + + /** + * Checks if the particle is still alive (i.e., has not exceeded its + * lifetime). + * + * @return {@code true} if the particle's elapsed time is less than its total + * lifetime, otherwise {@code false}. + */ + public boolean isAlive() { + return elapsedTime < lifetime; + } + + /** + * Gets the current position of the particle in 3D space. + * + * @return The current position vector of the particle. + */ + public Vector3f getPosition() { + return position; + } + + /** + * Gets the previous position of the particle. Useful for rendering trails or + * other visual effects. + * + * @return The old position vector of the particle. + */ + public Vector3f getOldPosition() { + return oldPosition; + } + + /** + * Gets the amount of time that has elapsed since the particle was created. + * + * @return The elapsed time in seconds. + */ + public float getElapsedTime() { + return elapsedTime; + } + + /** + * Gets the total lifetime of the particle. + * + * @return The total lifetime of the particle in seconds. + */ + public float getLifetime() { + return lifetime; + } + + /** + * Sets the particle's lifetime to a new value. This can extend or shorten how + * long the particle will exist. + * + * @param lifetime New lifetime value in seconds. + */ + public void setLifetime(float lifetime) { + this.lifetime = lifetime; + } + +} From 69f8704646502e45713d0900b01834406f106c50 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Tue, 17 Dec 2024 22:03:06 +0100 Subject: [PATCH 23/59] Add MaterialFactory with Builder Pattern and JavaDocs - Implemented a `MaterialFactory` class using the Builder pattern for cleaner and more modular material creation. - Added predefined materials: metallic silver, golden metallic, clear glass, stone grey, and water. - Provided JavaDocs for the class and all factory methods to improve readability and developer onboarding. - Encapsulated material properties like ambient, diffuse, specular, and shininess configuration in the Builder pattern. --- src/main/java/engine/render/Material.java | 223 +++++++++++------- .../java/engine/render/MaterialFactory.java | 121 ++++++++++ 2 files changed, 254 insertions(+), 90 deletions(-) create mode 100644 src/main/java/engine/render/MaterialFactory.java diff --git a/src/main/java/engine/render/Material.java b/src/main/java/engine/render/Material.java index 7d67e2a4..ff005ed9 100644 --- a/src/main/java/engine/render/Material.java +++ b/src/main/java/engine/render/Material.java @@ -4,18 +4,23 @@ import workspace.ui.Graphics; /** - * Represents a material with lighting and shader properties for rendering 3D - * meshes. + * Represents a material with lighting and shader properties for 3D rendering. *

- * A Material defines the visual appearance of a 3D object under lighting - * conditions. It includes attributes such as base color, ambient, diffuse, and - * specular light coefficients, and shininess, which together determine how - * light interacts with the object's surface. + * A Material defines how a 3D object interacts with light, determining its + * visual appearance under various lighting conditions. It encapsulates + * attributes such as ambient, diffuse, and specular light coefficients, + * shininess, and base color, which collectively define how light reflects off + * the object's surface. *

*

- * This class provides predefined materials for common use cases (e.g., default - * white, metallic, glass) and allows custom material creation with specific - * properties. + * This class includes predefined materials for common use cases (e.g., default + * white, black, or other colors) and supports the creation of custom materials + * with specific properties through the use of {@link Builder}. + *

+ *

+ * The material properties can control effects like reflectivity, surface + * roughness, and color, enabling diverse visual representations for 3D meshes + * in a rendering engine. *

*/ public class Material { @@ -52,148 +57,186 @@ public class Material { /** * Metallic silver material with a shiny silver appearance. */ - public static final Material METALLIC_SILVER_MATERIAL = new Material( - new Color(0.75f, 0.75f, 0.75f), - new float[] { 0.2f, 0.2f, 0.2f }, - new float[] { 1.0f, 1.0f, 1.0f }, - new float[] { 1.0f, 1.0f, 1.0f }, - 50.0f); + public static final Material METALLIC_SILVER_MATERIAL = MaterialFactory + .createMetallicSilver(); /** * Metallic gold material with a shiny gold appearance. */ - public static final Material GOLDEN_METALLIC_MATERIAL = new Material( - new Color(1.0f, 0.84f, 0.0f), - new float[] { 0.3f, 0.3f, 0.3f }, - new float[] { 1.0f, 0.84f, 0.0f }, - new float[] { 1.0f, 1.0f, 0.5f }, - 100.0f); + public static final Material GOLDEN_METALLIC_MATERIAL = MaterialFactory + .createGoldenMetallic(); /** * Clear glass material with a transparent appearance. */ - public static final Material GLASS_CLEAR_MATERIAL = new Material( - new Color(0.7f, 0.9f, 1.0f), - new float[] { 0.3f, 0.3f, 0.3f }, - new float[] { 0.8f, 0.8f, 0.8f }, - new float[] { 1.0f, 1.0f, 1.0f }, 5.0f); + public static final Material GLASS_CLEAR_MATERIAL = MaterialFactory + .createGlassClear(); /** * Stone grey material with a matte grey appearance. */ - public static final Material STONE_GREY_MATERIAL = - new Material( - new Color(0.5f, 0.5f, 0.5f), - new float[] { 0.4f, 0.4f, 0.4f }, - new float[] { 0.6f, 0.6f, 0.6f }, - new float[] { 0.2f, 0.2f, 0.2f }, - 10.0f); + public static final Material STONE_GREY_MATERIAL = MaterialFactory + .createStoneGrey(); /** * Water material with a reflective blue appearance. */ - public static final Material WATER_MATERIAL = new Material( - new Color(0.0f, 0.5f, 1.0f), - new float[] { 0.1f, 0.3f, 0.5f }, - new float[] { 0.3f, 0.5f, 0.7f }, - new float[] { 0.2f, 0.2f, 0.6f }, 2.0f); + public static final Material WATER_MATERIAL = MaterialFactory.createWater(); /** * Base color for the material. */ - private Color color; + private final Color color; /** * Ambient light coefficient (R, G, B). */ - private float[] ambient; + private final float[] ambient; /** * Diffuse light coefficient (R, G, B). */ - private float[] diffuse; + private final float[] diffuse; /** - * Specular light coefficient (R, G, B). + * Specular light coefficient (R, G, B). */ - private float[] specular; + private final float[] specular; /** * Shininess factor for specular highlights. */ - private float shininess; - + private final float shininess; + /** - * Private constructor for creating a material with specific properties. + * Constructor to set the base color of the material. * - * @param color The base color of the material. - * @param ambient The ambient light coefficient (R, G, B). - * @param diffuse The diffuse light coefficient (R, G, B). - * @param specular The specular light coefficient (R, G, B). - * @param shininess The shininess factor for specular highlights. - */ - private Material(Color color, float[] ambient, float[] diffuse, - float[] specular, float shininess) { - this.color = color; - this.ambient = ambient; - this.diffuse = diffuse; - this.specular = specular; - this.shininess = shininess; + * @param color The base color of the material. + */ + public Material(Color color) { + this(new Builder().setColor(color)); } - /** - * Default constructor initializes the material with default lighting - * properties. - */ - public Material() { - this.color = new Color(1, 1, 1); - this.ambient = new float[] { 0.2f, 0.2f, 0.2f }; - this.diffuse = new float[] { 1.0f, 1.0f, 1.0f }; - this.specular = new float[] { 1.0f, 1.0f, 1.0f }; - this.shininess = 10.0f; + private Material(Builder builder) { + this.color = builder.color; + this.ambient = builder.ambient; + this.diffuse = builder.diffuse; + this.specular = builder.specular; + this.shininess = builder.shininess; } /** - * Constructor to set the base color of the material. - * - * @param color The base color of the material. + * Builder class to facilitate the creation of custom materials with specific + * lighting and shader properties. */ - public Material(Color color) { - this(); - this.color = color; + public static class Builder { + + private Color color = new Color(1, 1, 1); // Default color is white + + private float[] ambient = new float[] { 0.2f, 0.2f, 0.2f }; + + private float[] diffuse = new float[] { 1.0f, 1.0f, 1.0f }; + + private float[] specular = new float[] { 1.0f, 1.0f, 1.0f }; + + private float shininess = 10.0f; + + /** + * Sets the base color of the material. + * + * @param color The desired base color. + * @return The builder instance for chaining. + */ + public Builder setColor(Color color) { + this.color = color; + return this; + } + + /** + * Sets the ambient light coefficient of the material. + * + * @param ambient The desired ambient light coefficient (R, G, B). + * @return The builder instance for chaining. + */ + public Builder setAmbient(float[] ambient) { + this.ambient = ambient; + return this; + } + + /** + * Sets the diffuse light coefficient of the material. + * + * @param diffuse The desired diffuse light coefficient (R, G, B). + * @return The builder instance for chaining. + */ + public Builder setDiffuse(float[] diffuse) { + this.diffuse = diffuse; + return this; + } + + /** + * Sets the specular light coefficient of the material. + * + * @param specular The desired specular light coefficient (R, G, B). + * @return The builder instance for chaining. + */ + public Builder setSpecular(float[] specular) { + this.specular = specular; + return this; + } + + /** + * Sets the shininess value of the material. + * + * @param shininess The shininess factor for specular highlights. + * @return The builder instance for chaining. + */ + public Builder setShininess(float shininess) { + this.shininess = shininess; + return this; + } + + /** + * Builds and returns the Material instance with the set properties. + * + * @return A new instance of {@link Material}. + */ + public Material build() { + return new Material(this); + } } /** - * Applies this material's properties to the given rendering context. + * Applies this material's properties to the provided rendering context. * - * @param g The {@link Graphics} instance to which the material will be - * applied. + * @param g The {@link Graphics} instance to apply this material to. */ public void apply(Graphics g) { g.setMaterial(this); } /** - * Releases the material or resets properties in the rendering context. Useful - * for unbinding shaders or clearing material-specific settings. + * Releases this material's properties from the rendering context, useful for + * cleaning up shaders or material-specific settings. * - * @param g The {@link Graphics} instance from which the material is released. + * @param g The {@link Graphics} instance from which this material will be + * unbound. */ public void release(Graphics g) { - // Logic to unbind shader or reset material-related properties + // Logic for releasing or resetting rendering context goes here } /** - * Gets the base color of the material. + * Retrieves the base color of the material. * - * @return The base {@link Color} of the material. + * @return The {@link Color} representing the base color of the material. */ public Color getColor() { return color; } /** - * Gets the ambient light coefficient of the material. + * Retrieves the ambient light coefficient of the material. * * @return An array representing the ambient light coefficient (R, G, B). */ @@ -202,7 +245,7 @@ public float[] getAmbient() { } /** - * Gets the diffuse light coefficient of the material. + * Retrieves the diffuse light coefficient of the material. * * @return An array representing the diffuse light coefficient (R, G, B). */ @@ -211,7 +254,7 @@ public float[] getDiffuse() { } /** - * Gets the specular light coefficient of the material. + * Retrieves the specular light coefficient of the material. * * @return An array representing the specular light coefficient (R, G, B). */ @@ -220,12 +263,12 @@ public float[] getSpecular() { } /** - * Gets the shininess factor of the material. + * Retrieves the shininess factor of the material. * - * @return The shininess factor for specular highlights. + * @return The shininess factor that controls specular highlights. */ public float getShininess() { return shininess; } - + } \ No newline at end of file diff --git a/src/main/java/engine/render/MaterialFactory.java b/src/main/java/engine/render/MaterialFactory.java new file mode 100644 index 00000000..0360b0a0 --- /dev/null +++ b/src/main/java/engine/render/MaterialFactory.java @@ -0,0 +1,121 @@ +package engine.render; + +import math.Color; + +/** + * Factory class for creating predefined Material instances using a builder + * pattern. + *

+ * This class provides various predefined material configurations, such as + * metallic silver, golden metallic, clear glass, stone grey, and water effects. + * Each material can be created through the builder pattern to encapsulate + * lighting properties like ambient, diffuse, specular colors, and shininess + * levels. + *

+ *

+ * The goal of these predefined materials is to simplify common rendering use + * cases, such as simulating metals, glass, stone, and water effects, by + * abstracting their creation and configuration logic. + *

+ *

+ * Each static method in this class returns a fully built Material object, ready + * for use in rendering pipelines or graphics engines. + *

+ */ +public class MaterialFactory { + + /** + * Creates a metallic silver material with preset properties. + *

+ * This material has a diffuse color representing silver, with ambient, + * diffuse, and specular properties tuned for a metallic effect. + *

+ * + * @return A {@link Material} instance configured as metallic silver. + */ + public static Material createMetallicSilver() { + return new Material.Builder() + .setColor(new Color(0.75f, 0.75f, 0.75f)) + .setAmbient(new float[] { 0.2f, 0.2f, 0.2f }) + .setDiffuse(new float[] { 1.0f, 1.0f, 1.0f }) + .setSpecular(new float[] { 1.0f, 1.0f, 1.0f }) + .setShininess(50.0f) + .build(); + } + + /** + * Creates a golden metallic material with preset properties. + *

+ * This material mimics the reflective, shiny properties of gold. + *

+ * + * @return A {@link Material} instance configured as golden metallic. + */ + public static Material createGoldenMetallic() { + return new Material.Builder() + .setColor(new Color(1.0f, 0.84f, 0.0f)) + .setAmbient(new float[] { 0.3f, 0.3f, 0.3f }) + .setDiffuse(new float[] { 1.0f, 0.84f, 0.0f }) + .setSpecular(new float[] { 1.0f, 1.0f, 0.5f }) + .setShininess(100.0f) + .build(); + } + + /** + * Creates a clear glass-like material with preset properties. + *

+ * This material simulates the transparency and reflection characteristics of + * clear glass. + *

+ * + * @return A {@link Material} instance configured as clear glass. + */ + public static Material createGlassClear() { + return new Material.Builder() + .setColor(new Color(0.7f, 0.9f, 1.0f)) + .setAmbient(new float[] { 0.3f, 0.3f, 0.3f }) + .setDiffuse(new float[] { 0.8f, 0.8f, 0.8f }) + .setSpecular(new float[] { 1.0f, 1.0f, 1.0f }) + .setShininess(5.0f) + .build(); + } + + /** + * Creates a stone grey material with preset properties. + *

+ * This material has a matte stone-like appearance, suitable for terrain, + * rocky surfaces, or architectural models. + *

+ * + * @return A {@link Material} instance configured as stone grey. + */ + public static Material createStoneGrey() { + return new Material.Builder() + .setColor(new Color(0.5f, 0.5f, 0.5f)) + .setAmbient(new float[] { 0.4f, 0.4f, 0.4f }) + .setDiffuse(new float[] { 0.6f, 0.6f, 0.6f }) + .setSpecular(new float[] { 0.2f, 0.2f, 0.2f }) + .setShininess(10.0f) + .build(); + } + + /** + * Creates a water-like material with preset properties. + *

+ * This material simulates the transparency and light-reflective properties of + * water, incorporating a mix of blue and subtle reflective colors. + *

+ * + * @return A {@link Material} instance configured as water. + */ + public static Material createWater() { + return new Material.Builder() + .setColor(new Color(0.0f, 0.5f, 1.0f)) + .setAmbient(new float[] { 0.1f, 0.3f, 0.5f }) + .setDiffuse(new float[] { 0.3f, 0.5f, 0.7f }) + .setSpecular(new float[] { 0.2f, 0.2f, 0.6f }) + .setShininess(2.0f) + .build(); + } + +} From ac48d67ac43660e1ccd1a379e65fdfd9862e9cba Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Tue, 17 Dec 2024 22:14:15 +0100 Subject: [PATCH 24/59] Add Plane class for 3D geometric calculations Introduced the Plane class in the math package to represent a geometric plane in 3D space. Implemented methods to define the plane using coefficients (set method) and calculate the signed distance from a point to the plane (distanceToPoint). Included getters for the plane's normal vector and distance from the origin. Normalization of the plane's normal vector is handled during initialization to ensure consistency. Added comprehensive JavaDoc for better usability and understanding of the class functionality. --- src/main/java/math/Plane.java | 95 +++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 src/main/java/math/Plane.java diff --git a/src/main/java/math/Plane.java b/src/main/java/math/Plane.java new file mode 100644 index 00000000..31000126 --- /dev/null +++ b/src/main/java/math/Plane.java @@ -0,0 +1,95 @@ +package math; + +/** + * Represents a geometric plane in 3D space defined by a normal vector and a + * distance from the origin. + *

+ * The equation of the plane is represented as:
+ * Ax + By + Cz + D = 0, where: + *

    + *
  • (A, B, C) is the normalized normal vector of the plane.
  • + *
  • D is the distance of the plane from the origin, along the + * normal direction.
  • + *
+ *

+ */ +public class Plane { + + /** The normal vector of the plane, representing its orientation. */ + private Vector3f normal; + + /** The distance of the plane from the origin along the normal vector. */ + private float distance; + + /** + * Constructs a plane with a default normal vector (0, 0, 0) and a distance of + * 0. + *

+ * The resulting plane is uninitialized and must be configured using the + * {@link #set(float, float, float, float)} method. + *

+ */ + public Plane() { + this.normal = new Vector3f(); + this.distance = 0; + } + + /** + * Sets the plane parameters using its coefficients. + *

+ * The coefficients (A, B, C, D) define the plane equation + * Ax + By + Cz + D = 0. The normal vector is automatically + * normalized during this operation. + *

+ * + * @param a the x-component of the normal vector. + * @param b the y-component of the normal vector. + * @param c the z-component of the normal vector. + * @param d the distance from the origin along the plane's normal vector. + */ + public void set(float a, float b, float c, float d) { + this.normal.set(a, b, c); + normal.normalizeLocal(); + this.distance = d; + } + + /** + * Calculates the signed distance from a given point to the plane. + *

+ * The signed distance is computed as:
+ * distance = dot(normal, point) + D, where: + *

    + *
  • normal is the plane's normal vector.
  • + *
  • point is the 3D point to test.
  • + *
  • D is the distance parameter of the plane.
  • + *
+ *

+ * + * @param point the point to calculate the distance from. + * @return the signed distance from the point to the plane. A positive value + * indicates the point is in the direction of the normal vector, and a + * negative value indicates it is on the opposite side. + */ + public float distanceToPoint(Vector3f point) { + return normal.dot(point) + distance; + } + + /** + * Gets the normal vector of the plane. + * + * @return the normal vector of the plane. + */ + public Vector3f getNormal() { + return normal; + } + + /** + * Gets the distance of the plane from the origin along its normal vector. + * + * @return the distance of the plane from the origin. + */ + public float getDistance() { + return distance; + } + +} \ No newline at end of file From 158ef9cb256ab9c862b0e3749db43c8fc3a7beeb Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Tue, 17 Dec 2024 23:21:01 +0100 Subject: [PATCH 25/59] Add ParticleRenderer interface for modular particle rendering system - Defined a flexible interface `ParticleRenderer` to handle particle rendering. - Methods include: - `render(Graphics g, Collection particles)` to draw particles. - `initialize()` for resource setup. - `cleanup()` for efficient resource management. - Promotes separation of concerns and allows for multiple rendering strategies (e.g., sprites, points, trails). --- .../render/effects/ParticleRenderer.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/main/java/engine/render/effects/ParticleRenderer.java diff --git a/src/main/java/engine/render/effects/ParticleRenderer.java b/src/main/java/engine/render/effects/ParticleRenderer.java new file mode 100644 index 00000000..90b6cfea --- /dev/null +++ b/src/main/java/engine/render/effects/ParticleRenderer.java @@ -0,0 +1,36 @@ +package engine.render.effects; + +import java.util.Collection; + +import workspace.ui.Graphics; + +/** + * Interface for rendering particles in a particle system. Implementations of + * this interface define how particles are visually represented, such as using + * sprites, points, or other rendering techniques. + * + * @author Simon Dietz + */ +public interface ParticleRenderer { + + /** + * Renders a batch of particles using the provided graphics context. + * + * @param g The graphics context used for rendering. + * @param particles The collection of particles to render. + */ + void render(Graphics g, Collection particles); + + /** + * Initializes any resources or setup required for rendering particles. This + * could include shaders, textures, or other rendering assets. + */ + void initialize(); + + /** + * Cleans up resources used by the renderer when it is no longer needed. This + * ensures efficient memory and resource management. + */ + void cleanup(); + +} \ No newline at end of file From 08f1755a7571fb85c44a52e4f1ce743f83da1fc2 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Tue, 17 Dec 2024 23:25:29 +0100 Subject: [PATCH 26/59] Implement ParticleComponent with emitter and renderer lifecycle management --- .../render/effects/ParticleComponent.java | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 src/main/java/engine/render/effects/ParticleComponent.java diff --git a/src/main/java/engine/render/effects/ParticleComponent.java b/src/main/java/engine/render/effects/ParticleComponent.java new file mode 100644 index 00000000..6735c6a2 --- /dev/null +++ b/src/main/java/engine/render/effects/ParticleComponent.java @@ -0,0 +1,119 @@ +package engine.render.effects; + +import engine.components.AbstractComponent; +import engine.components.RenderComponent; +import workspace.ui.Graphics; + +/** + * A component responsible for managing and rendering particles using a + * specified particle emitter and renderer. + *

+ * This class implements the {@link RenderComponent} interface, allowing it to + * integrate seamlessly with the rendering system. It uses a + * {@link ParticleEmitter} to handle the logic of particle spawning and updates, + * while delegating rendering operations to a {@link ParticleRenderer}. + *

+ *

+ * The ParticleComponent ensures proper lifecycle management by handling + * initialization, updates, rendering, and cleanup for both the emitter and + * renderer components. + *

+ * + * @author Simon Dietz + */ +public class ParticleComponent extends AbstractComponent + implements RenderComponent { + + private ParticleEmitter emitter; + + private ParticleRenderer renderer; + + /** + * Creates a new ParticleComponent with the given particle emitter and + * renderer. + * + * @param emitter The particle emitter responsible for spawning and managing + * particle lifecycles. + * @param renderer The particle renderer responsible for drawing particles on + * the provided graphics context. + */ + public ParticleComponent(ParticleEmitter emitter, ParticleRenderer renderer) { + this.emitter = emitter; + this.renderer = renderer; + } + + /** + * Initializes the renderer resources necessary for drawing particles. + */ + @Override + public void initialize() { + renderer.initialize(); + } + + /** + * Updates the particle emitter with the time-per-frame value to spawn and + * manage particles over time. + */ + @Override + public void update(float tpf) { + emitter.update(tpf); + } + + /** + * Delegates the rendering of particles to the renderer, passing the current + * particles to visualize. + */ + @Override + public void render(Graphics g) { + renderer.render(g, emitter.getParticles()); + } + + /** + * Cleans up any resources used by the particle renderer. + */ + @Override + public void cleanup() { + renderer.cleanup(); + } + + /** + * Retrieves the particle emitter associated with this component. + * + * @return The ParticleEmitter instance used for spawning and updating + * particles. + */ + public ParticleEmitter getEmitter() { + return emitter; + } + + /** + * Sets a new particle emitter for this component. This can be used to + * dynamically change the emitter's behavior or particle spawning logic at + * runtime. + * + * @param emitter The new ParticleEmitter instance. + */ + public void setEmitter(ParticleEmitter emitter) { + this.emitter = emitter; + } + + /** + * Retrieves the particle renderer associated with this component. + * + * @return The ParticleRenderer responsible for drawing particles. + */ + public ParticleRenderer getRenderer() { + return renderer; + } + + /** + * Sets a new particle renderer for this component. This allows for swapping + * rendering strategies or visualizations dynamically at runtime. + * + * @param renderer The new ParticleRenderer instance. + */ + public void setRenderer(ParticleRenderer renderer) { + this.renderer = renderer; + } + +} From 4ed26700e672d2611bc44e3e2a2a22536199d2a4 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Tue, 17 Dec 2024 23:41:22 +0100 Subject: [PATCH 27/59] Initial commit: Implement ParticleEmitter class - Added ParticleEmitter to handle continuous and burst-based particle emission. - Introduced configurable parameters such as velocity range, acceleration range, and lifetime range. - Implemented randomization logic for particle properties within specified ranges. - Added support for continuous emission and burst emission modes. - Integrated a thread-safe particle queue (`ConcurrentLinkedQueue`) for efficient particle management. - Provided methods for dynamic configuration of emitter properties and emission behavior. --- .../render/effects/ParticleEmitter.java | 210 ++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 src/main/java/engine/render/effects/ParticleEmitter.java diff --git a/src/main/java/engine/render/effects/ParticleEmitter.java b/src/main/java/engine/render/effects/ParticleEmitter.java new file mode 100644 index 00000000..b6aaf710 --- /dev/null +++ b/src/main/java/engine/render/effects/ParticleEmitter.java @@ -0,0 +1,210 @@ +package engine.render.effects; + +import java.util.concurrent.ConcurrentLinkedQueue; + +import math.Vector3f; + +/** + * Represents a particle emitter responsible for generating and managing + * particles based on defined parameters like velocity, acceleration, and + * lifetime ranges. This class supports both continuous particle emission and + * burst-based emission modes. + *

+ * The emitter allows for dynamic configuration of properties such as initial + * velocity ranges, acceleration ranges, particle lifetime ranges, and the + * emission rate. It uses a thread-safe queue to manage particles for efficient + * access and updates. + *

+ * + *

+ * Key Features: + *

+ *
    + *
  • Supports continuous and burst particle emission modes.
  • + *
  • Randomizes particle properties like initial velocity, acceleration, and + * lifetime within defined ranges.
  • + *
  • Handles particle cleanup by removing expired particles from the + * queue.
  • + *
+ * + * @author Simon Dietz + */ +public class ParticleEmitter { + + /** The world-space origin of the particle emitter. */ + private Vector3f position; + + /** The range for randomizing initial particle velocities. */ + private Vector3f velocityRange; + + /** The range for randomizing initial particle accelerations. */ + private Vector3f accelerationRange; + + /** The range of possible particle lifetimes. */ + private float lifetimeRange; + + /** The rate at which particles are emitted (particles per second). */ + private int particlesPerSecond; + + /** Whether the emitter is currently configured for burst emission mode. */ + private boolean burstMode; + + /** Number of particles to emit during each burst. */ + private int burstCount; + + /** + * Tracks elapsed time to determine when particles should be emitted during + * continuous mode. + */ + private float timeSinceLastEmission = 0f; + + /** A thread-safe queue storing active particles. */ + private ConcurrentLinkedQueue particles; + + /** + * Constructs a new ParticleEmitter with a specified position and emission + * rate. + * + * @param position The initial world-space position of the emitter. + * @param particlesPerSecond The rate at which particles are emitted in + * continuous mode (particles per second). + */ + public ParticleEmitter(Vector3f position, int particlesPerSecond) { + this.position = position; + this.particlesPerSecond = particlesPerSecond; + this.velocityRange = new Vector3f(1f, 1f, 1f); + this.accelerationRange = new Vector3f(0f, 0f, 0f); + this.lifetimeRange = 5f; // Default particle lifetime of 5 seconds + this.particles = new ConcurrentLinkedQueue<>(); + this.burstMode = false; // Default mode is continuous particle emission + this.burstCount = 0; + } + + /** + * Updates particles and performs emission logic based on elapsed time. + * Handles both continuous emission and burst emission logic. Cleans up + * expired particles from the particle queue. + * + * @param deltaTime Time elapsed since the last frame, in seconds. + */ + public void update(float deltaTime) { + if (burstMode) { + emitBurst(); + } else { + timeSinceLastEmission += deltaTime; + float emissionInterval = 1f / particlesPerSecond; + + // Emit particles continuously based on elapsed time + while (timeSinceLastEmission >= emissionInterval) { + emitParticle(); + timeSinceLastEmission -= emissionInterval; + } + } + + // Update and clean expired particles + for (Particle particle : particles) { + particle.update(deltaTime); + if (!particle.isAlive()) { + particles.remove(particle); + } + } + } + + /** + * Emits a single particle with randomized properties (velocity, acceleration, + * and lifetime) within their configured ranges. + */ + private void emitParticle() { + Vector3f initialPosition = new Vector3f(position); + Vector3f initialVelocity = randomizeVector(velocityRange); + Vector3f initialAcceleration = randomizeVector(accelerationRange); + float lifetime = randomizeFloat(lifetimeRange); + + Particle particle = new Particle(initialPosition, initialVelocity, + initialAcceleration, lifetime); + particles.add(particle); + } + + /** + * Emits a burst of particles, the number of which is defined by the + * `burstCount`. After completing a burst, burst mode is disabled + * automatically. + */ + private void emitBurst() { + for (int i = 0; i < burstCount; i++) { + emitParticle(); + } + burstMode = false; // Disable burst mode after the burst is emitted. + } + + /** + * Randomizes a vector's x, y, z components within their respective ranges. + * + * @param range The range to randomize values within. + * @return A new randomized vector. + */ + private Vector3f randomizeVector(Vector3f range) { + return new Vector3f((float) (Math.random() * range.x * 2 - range.x), + (float) (Math.random() * range.y * 2 - range.y), + (float) (Math.random() * range.z * 2 - range.z)); + } + + /** + * Randomizes a float value within a range [0, range). + * + * @param range The range to randomize values within. + * @return A randomized float value. + */ + private float randomizeFloat(float range) { + return (float) (Math.random() * range); + } + + /** + * Configures the emitter to use burst mode with a specified number of + * particles to emit. After enabling, particles will only emit in bursts until + * reset. + * + * @param burstCount Number of particles to emit during each burst. + */ + public void setBurstMode(int burstCount) { + this.burstMode = true; + this.burstCount = burstCount; + } + + /** + * Updates the range for randomizing initial particle velocities. + * + * @param velocityRange The new velocity range. + */ + public void setVelocityRange(Vector3f velocityRange) { + this.velocityRange = velocityRange; + } + + /** + * Updates the range for randomizing initial particle accelerations. + * + * @param accelerationRange The new acceleration range. + */ + public void setAccelerationRange(Vector3f accelerationRange) { + this.accelerationRange = accelerationRange; + } + + /** + * Updates the range of particle lifetimes. + * + * @param lifetimeRange The new lifetime range. + */ + public void setLifetimeRange(float lifetimeRange) { + this.lifetimeRange = lifetimeRange; + } + + /** + * Retrieves all currently active particles managed by this emitter. + * + * @return A concurrent queue containing the currently active particles. + */ + public ConcurrentLinkedQueue getParticles() { + return particles; + } + +} \ No newline at end of file From 9a3c62daa4b0860e413c2d1cd555f915053bf72d Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Tue, 17 Dec 2024 23:46:37 +0100 Subject: [PATCH 28/59] Initial commit: Define Component interface for scene graph architecture - Added `Component` interface to represent modular, reusable behaviors in the scene graph system. - Defined lifecycle methods: `initialize()`, `update(float tpf)`, and `cleanup()` for managing component states. - Added `setOwner(SceneNode owner)` to associate components with their owning scene nodes. - Established a clear contract for components' interaction with `SceneNode` instances. - Provided detailed Javadoc to explain the purpose, lifecycle, and usage of components in the system. --- .../java/engine/components/Component.java | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 src/main/java/engine/components/Component.java diff --git a/src/main/java/engine/components/Component.java b/src/main/java/engine/components/Component.java new file mode 100644 index 00000000..c216d6fc --- /dev/null +++ b/src/main/java/engine/components/Component.java @@ -0,0 +1,67 @@ +package engine.components; + +import engine.scene.SceneNode; + +/** + * Represents a generic component within the scene graph architecture. + *

+ * Components are modular pieces of behavior or functionality that can be added + * to {@link SceneNode} instances. They encapsulate logic, rendering, or other + * behaviors, following a component-based design pattern. + *

+ *

+ * Each component should manage its lifecycle, with {@code initialize()}, + * {@code update()}, and {@code cleanup()} methods, allowing nodes to manage + * their behavior lifecycle cleanly. + *

+ */ +public interface Component { + + /** + * Sets the owning {@link SceneNode} for this component. + *

+ * This is called when the component is added to a {@link SceneNode}. The + * owning node serves as the context within which this component operates, + * allowing it to interact with other components or node transformations. + *

+ * + * @param owner The {@link SceneNode} that owns this component; cannot be + * null. + */ + void setOwner(SceneNode owner); + + /** + * Updates the component's logic every frame. + *

+ * Called once per frame during the scene's update cycle. The time-per-frame + * (tpf) is passed in to allow for time-based animations or logic that depends + * on frame timing. + *

+ * + * @param tpf The time per frame in seconds (time delta) since the last + * update. + */ + void update(float tpf); + + /** + * Initializes the component before it becomes active. + *

+ * This is called once after the component is attached to a {@link SceneNode}. + * It allows the component to set up necessary resources, state, or perform + * other preparatory work. + *

+ */ + void initialize(); + + /** + * Cleans up resources and performs necessary teardown when the component is + * removed or the scene is unloaded. + *

+ * This ensures no memory is leaked, threads are terminated, or other + * resources are left hanging by cleaning up internal state and releasing + * references. + *

+ */ + void cleanup(); + +} \ No newline at end of file From 0dd10798c82657d3aaf75bc90d2eae86b1ae28db Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Tue, 17 Dec 2024 23:47:14 +0100 Subject: [PATCH 29/59] Add AbstractComponent as a base class for reusable component logic - Implemented `AbstractComponent` to serve as a shared base class for all components in the scene graph. - Centralized common functionality, including the ownership management logic via `setOwner(SceneNode owner)`. - Added a convenience method `getOwner()` to retrieve the owning `SceneNode`. - Reduced boilerplate code by providing shared logic to all components. - Included clear Javadoc for better maintainability and clarity. --- .../engine/components/AbstractComponent.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/main/java/engine/components/AbstractComponent.java diff --git a/src/main/java/engine/components/AbstractComponent.java b/src/main/java/engine/components/AbstractComponent.java new file mode 100644 index 00000000..8c04f309 --- /dev/null +++ b/src/main/java/engine/components/AbstractComponent.java @@ -0,0 +1,41 @@ +package engine.components; + +import engine.scene.SceneNode; + +/** + * Abstract base class for all components in the scene graph. + *

+ * This class provides a shared implementation of common functionality across + * all components, reducing boilerplate and centralizing shared logic for ease + * of maintenance. + *

+ */ +public abstract class AbstractComponent implements Component { + + /** Reference to the owning SceneNode */ + protected SceneNode owner; + + /** + * Sets the owner (parent node) of this component. + *

+ * This is common logic provided by the abstract class to ensure consistency + * among all components. + *

+ * + * @param owner The SceneNode that owns this component. + */ + @Override + public void setOwner(SceneNode owner) { + this.owner = owner; + } + + /** + * Retrieves the owning node for convenience. + * + * @return The owning SceneNode instance. + */ + public SceneNode getOwner() { + return owner; + } + +} \ No newline at end of file From fc281420bd735ab7281480adcfb052a78b2d1453 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Tue, 17 Dec 2024 23:47:54 +0100 Subject: [PATCH 30/59] Implement Transform component for 3D transformations with position, rotation, and scale - Added `Transform` class implementing `AbstractComponent` and `Component` interface. - Encapsulates position, rotation, and scale properties for spatial transformations in 3D space. - Provided methods to apply transformations (`translate`, `rotate`, `scale`) incrementally. - Implemented `apply(Graphics g)` to apply transformations to a provided graphics context. - Added getter and setter methods with null-checks for robustness. - Included convenience methods to directly set transformations by coordinates or vectors. - Prepared `initialize`, `update`, and `cleanup` for future lifecycle logic if needed. - Documented each method and class for clarity and maintainability. --- .../java/engine/components/Transform.java | 248 ++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 src/main/java/engine/components/Transform.java diff --git a/src/main/java/engine/components/Transform.java b/src/main/java/engine/components/Transform.java new file mode 100644 index 00000000..b4786599 --- /dev/null +++ b/src/main/java/engine/components/Transform.java @@ -0,0 +1,248 @@ +package engine.components; + +import math.Vector3f; +import workspace.ui.Graphics; + +/** + * Represents a transformation in 3D space, encapsulating position, rotation, + * and scale. Implements the Component interface. + *

+ * The {@code Transform} class provides functionality to manipulate and apply + * transformations such as translation, rotation, and scaling to objects or + * nodes in a 3D scene. It acts as a helper utility to represent and modify the + * spatial properties of a 3D object or a scene node. + *

+ *

+ * The transformations are defined by: + *

    + *
  • Position: The location of the object in 3D space.
  • + *
  • Rotation: The orientation of the object around the X, Y, and Z + * axes.
  • + *
  • Scale: The size multiplier along the X, Y, and Z axes.
  • + *
+ *

+ *

+ * This class provides methods for applying transformations to a rendering + * context, modifying transformations incrementally, and setting transformation + * properties explicitly. + *

+ * + * @see Vector3f + * @see Graphics + */ +public class Transform extends AbstractComponent { + + /** The position of this transform in 3D space. */ + private Vector3f position; + + /** + * The rotation (in radians) of this transform around the X, Y, and Z axes. + */ + private Vector3f rotation; + + /** The scaling factors along the X, Y, and Z axes. */ + private Vector3f scale; + + /** + * Constructs a new {@code Transform} with default position, rotation, and + * scale values. + *

+ * The default position is set to (0, 0, 0), the default rotation is (0, 0, + * 0), and the default scale is (1, 1, 1). + *

+ */ + public Transform() { + this.position = new Vector3f(); + this.rotation = new Vector3f(); + this.scale = new Vector3f(1, 1, 1); + } + + /** + * Applies this transformation to the given graphics context. + *

+ * This method translates the context to the object's position, applies + * rotations around the X, Y, and Z axes, and scales the object using the + * defined scale values. + *

+ * + * @param g The graphics context to which this transformation is applied. + */ + public void apply(Graphics g) { + g.translate(position.x, position.y, position.z); + g.rotateX(rotation.x); + g.rotateY(rotation.y); + g.rotateZ(rotation.z); + g.scale(scale.x, scale.y, scale.z); + } + + /** + * Translates this transformation by the given delta vector. + *

+ * This modifies the position of the transform by adding the provided vector + * to the current position. + *

+ * + * @param delta The vector representing the change in position. + */ + public void translate(Vector3f delta) { + this.position.addLocal(delta); + } + + /** + * Rotates this transformation by the given delta vector (in radians). + *

+ * This modifies the rotation of the transform by adding the provided vector's + * values to the current rotation. + *

+ * + * @param delta The vector representing the change in rotation (in radians). + */ + public void rotate(Vector3f delta) { + this.rotation.addLocal(delta); + } + + /** + * Scales this transformation by the provided scaling factors. + *

+ * This modifies the scale of the transform by multiplying the current scale + * values by the provided factors. + *

+ * + * @param factor The vector representing the scale factors to apply along each + * axis. + */ + public void scale(Vector3f factor) { + this.scale.multLocal(factor); + } + + /** + * Retrieves a copy of the position vector. + * + * @return A new Vector3f instance representing the current position. + */ + public Vector3f getPosition() { + return new Vector3f(position); + } + + /** + * Sets the position of this transform to a specific value. + * + * @param position The new position vector to set. Must not be null. + * @throws IllegalArgumentException if the provided vector is null. + */ + public void setPosition(Vector3f position) { + if (position == null) { + throw new IllegalArgumentException("Position cannot be null."); + } + this.position.set(position); + } + + /** + * Sets the position of this transform to the specified coordinates. + *

+ * This method updates the position vector to the provided (x, y, z) values, + * effectively moving the object to the given location in 3D space. + *

+ * + * @param x The X-coordinate of the new position. + * @param y The Y-coordinate of the new position. + * @param z The Z-coordinate of the new position. + */ + public void setPosition(float x, float y, float z) { + this.position.set(x, y, z); + } + + /** + * Retrieves a copy of the rotation vector. + * + * @return A new Vector3f instance representing the current rotation. + */ + public Vector3f getRotation() { + return new Vector3f(rotation); + } + + /** + * Sets the rotation of this transform to a specific value. + * + * @param rotation The new rotation vector (in radians) to set. Must not be + * null. + * @throws IllegalArgumentException if the provided vector is null. + */ + public void setRotation(Vector3f rotation) { + if (rotation == null) { + throw new IllegalArgumentException("Rotation cannot be null."); + } + this.rotation.set(rotation); + } + + /** + * Sets the rotation of this transform to the specified angles in radians. + *

+ * This method updates the rotation vector to the provided (rx, ry, rz) + * values, which represent the rotation of the object around the X, Y, and Z + * axes, respectively. + *

+ * + * @param rx The rotation angle around the X-axis, in radians. + * @param ry The rotation angle around the Y-axis, in radians. + * @param rz The rotation angle around the Z-axis, in radians. + */ + public void setRotation(float rx, float ry, float rz) { + this.rotation.set(rx, ry, rz); + } + + /** + * Retrieves a copy of the scale vector. + * + * @return A new Vector3f instance representing the current scale. + */ + public Vector3f getScale() { + return new Vector3f(scale); + } + + /** + * Sets the scale of this transform to a specific value. + * + * @param scale The new scale vector to set. Must not be null. + * @throws IllegalArgumentException if the provided vector is null. + */ + public void setScale(Vector3f scale) { + if (scale == null) { + throw new IllegalArgumentException("Scale cannot be null."); + } + this.scale.set(scale); + } + + /** + * Sets the scale of this transform to the specified scale factors along each + * axis. + *

+ * This method updates the scale vector to the provided (sx, sy, sz) values, + * allowing the object to be scaled uniformly or non-uniformly along the X, Y, + * and Z axes. + *

+ * + * @param sx The scale factor along the X-axis. + * @param sy The scale factor along the Y-axis. + * @param sz The scale factor along the Z-axis. + */ + public void setScale(float sx, float sy, float sz) { + this.scale.set(sx, sy, sz); + } + + @Override + public void update(float tpf) { + // Logic for dynamic transform updates if needed. + } + + @Override + public void initialize() { + // Logic for additional initialization. + } + + @Override + public void cleanup() { + // Cleanup logic if required. + } + +} \ No newline at end of file From 506e41d51a75346dbcaae743c0c68528332f8c70 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Tue, 17 Dec 2024 23:48:31 +0100 Subject: [PATCH 31/59] Added CinematicBlackBarsRenderer for smooth fade-in/out cinematic effects. --- .../CinematicBlackBarsRenderer.java | 219 ++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 src/main/java/engine/components/CinematicBlackBarsRenderer.java diff --git a/src/main/java/engine/components/CinematicBlackBarsRenderer.java b/src/main/java/engine/components/CinematicBlackBarsRenderer.java new file mode 100644 index 00000000..83dcba9b --- /dev/null +++ b/src/main/java/engine/components/CinematicBlackBarsRenderer.java @@ -0,0 +1,219 @@ +package engine.components; + +import math.Color; +import workspace.ui.Graphics; + +/** + * CinematicBlackBarsRenderer is responsible for rendering cinematic black bars + * on the top and bottom of the screen. It supports smooth fade-in and fade-out + * animations for cinematic effects during transitions. + *

+ * This component renders black bars with configurable speed and handles dynamic + * animations like appearing or disappearing smoothly over time. The fade + * effects are controlled by the speed of transition and a constant bar size (in + * pixels). + *

+ */ +public class CinematicBlackBarsRenderer extends AbstractComponent + implements RenderComponent { + + /** Default height (in pixels) of the cinematic bars at full fade-in. */ + private static final int DEFAULT_TARGET_BAR_HEIGHT = 200; + + /** + * Default speed at which the fade-in and fade-out effects occur (in pixels + * per second). + */ + private static final float DEFAULT_FADE_SPEED = 70.0f; + + /** Indicates whether the black bars are currently fading in. */ + private boolean fadingIn; + + /** Indicates whether the black bars are currently fading out. */ + private boolean fadingOut; + + /** Current visual size of the black bars (interpolated over time). */ + private float currentSize; + + /** The speed at which fade animations are applied (in pixels/second). */ + private float fadeSpeed; + + /** The target bar height at full fade-in (in pixels). */ + private float targetBarHeight; + + /** + * Default constructor for initializing the renderer instance. + *

+ * This sets the default fade speed and target bar height to predefined + * constants. + *

+ */ + public CinematicBlackBarsRenderer() { + this.fadeSpeed = DEFAULT_FADE_SPEED; + this.targetBarHeight = DEFAULT_TARGET_BAR_HEIGHT; + } + + /** + * Constructor with configurable fade speed and target bar height. + * + * @param fadeSpeed Speed at which fade animations will play (in + * pixels/second). + * @param targetBarHeight The target height of the cinematic bars at full + * fade-in. + */ + public CinematicBlackBarsRenderer(float fadeSpeed, float targetBarHeight) { + this.fadeSpeed = fadeSpeed; + this.targetBarHeight = targetBarHeight; + } + + /** + * Updates the animation state over time. + *

+ * This method advances the fade-in and fade-out animations by incrementally + * updating the `currentSize` value based on elapsed time and speed. Stops + * animations when they reach their final states. + *

+ * + * @param tpf Time per frame (time elapsed since the last frame in seconds). + */ + @Override + public void update(float tpf) { + if (!isFading()) { + return; + } + if (fadingIn) { + animateFadeIn(tpf); + } + if (fadingOut) { + animateFaceOut(tpf); + } + } + + /** + * Handles the fade-in animation by incrementally increasing the size of the + * black bars over time until the target bar height is reached. + * + * @param tpf Time per frame (time elapsed since the last frame in seconds). + */ + private void animateFadeIn(float tpf) { + currentSize += fadeSpeed * tpf; + if (currentSize >= targetBarHeight) { + currentSize = targetBarHeight; + fadingIn = false; + } + } + + /** + * Handles the fade-out animation by decrementally decreasing the size of the + * black bars over time until their visual size is zero. + * + * @param tpf Time per frame (time elapsed since the last frame in seconds). + */ + private void animateFaceOut(float tpf) { + currentSize -= fadeSpeed * tpf; + if (currentSize <= 0) { + currentSize = 0; + fadingOut = false; + } + } + + /** + * Determines if a fade animation (either in or out) is currently active. + * + * @return {@code true} if either fading in or fading out is active, + * {@code false} otherwise. + */ + public boolean isFading() { + return fadingIn || fadingOut; + } + + /** + * Starts the fade-in animation. + *

+ * This stops any ongoing fade-out animation and sets the internal state to + * begin the fade-in effect from the current position. + *

+ */ + public void fadeIn() { + if (fadingIn) + return; + fadingIn = true; + fadingOut = false; + } + + /** + * Starts the fade-out animation. + *

+ * This stops any ongoing fade-in animation and sets the internal state to + * begin the fade-out effect from the current position. + *

+ */ + public void fadeOut() { + if (fadingOut) + return; + fadingOut = true; + fadingIn = false; + } + + /** + * Renders the cinematic black bars at the top and bottom of the screen. + *

+ * The rendering uses the current interpolated size value (`currentSize`) to + * draw smooth black bars transitioning in and out. This rendering respects + * the screen's current dimensions and is dynamically updated during + * animation. + *

+ * + * @param g The graphics context used to perform the rendering. + */ + @Override + public void render(Graphics g) { + g.setColor(Color.BLACK); + renderTopBar(g); + renderBottomBar(g); + } + + /** + * Renders the top cinematic black bar using the current animation state. + * + * @param g The graphics context used for rendering. + */ + private void renderTopBar(Graphics g) { + g.fillRect(0, 0, g.getWidth(), (int) currentSize); + } + + /** + * Renders the bottom cinematic black bar using the current animation state. + * + * @param g The graphics context used for rendering. + */ + private void renderBottomBar(Graphics g) { + g.fillRect(0, g.getHeight() - (int) currentSize, g.getWidth(), + (int) currentSize); + } + + /** + * Initialization logic for this renderer. + *

+ * Currently unimplemented but provided as a placeholder for any setup + * required when initializing this component in a larger system. + *

+ */ + @Override + public void initialize() { + // Placeholder for initialization logic. + } + + /** + * Cleans up resources when the renderer is no longer needed. + *

+ * Currently unimplemented but can be used to clean up graphics resources, + * listeners, or other allocated resources in a larger system. + *

+ */ + @Override + public void cleanup() { + // Placeholder for cleanup logic. + } + +} \ No newline at end of file From 94ced3c4849ee8686178ee721832710022511dae Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Tue, 17 Dec 2024 23:49:06 +0100 Subject: [PATCH 32/59] Added CircularAnimationComponent for smooth circular motion over the XZ plane. --- .../CircularAnimationComponent.java | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 src/main/java/engine/components/CircularAnimationComponent.java diff --git a/src/main/java/engine/components/CircularAnimationComponent.java b/src/main/java/engine/components/CircularAnimationComponent.java new file mode 100644 index 00000000..38e794e5 --- /dev/null +++ b/src/main/java/engine/components/CircularAnimationComponent.java @@ -0,0 +1,126 @@ +package engine.components; + +import engine.scene.SceneNode; +import math.Mathf; + +/** + * A simple animation component that makes the owner SceneNode move in circular + * motion over the XZ plane. + *

+ * This component calculates circular motion based on a defined radius and + * angular speed, updating the position of the owning SceneNode in real-time. + * The motion occurs on the horizontal plane (XZ plane) while keeping the + * Y-coordinate constant. + *

+ *

+ * Properties: + *

    + *
  • radius: The distance from the center point of the circular path to + * the moving node along the X and Z axes.
  • + *
  • angularSpeed: The speed of rotation defined in radians per + * second.
  • + *
  • timeElapsed: A counter tracking the elapsed time to calculate + * motion over time.
  • + *
+ *

+ *

+ * This component updates the position of the owning SceneNode every frame, + * creating smooth circular motion over time. + *

+ * + * @see SceneNode + * @see Transform + */ +public class CircularAnimationComponent extends AbstractComponent { + + /** + * Radius of circular motion. Determines how far from the center the object + * orbits. + */ + private float radius; + + /** + * Angular speed in radians per second that determines how fast the object + * orbits. + */ + private float angularSpeed; + + /** Tracks the total elapsed time since the start of the animation. */ + private float timeElapsed; + + /** + * Default constructor that initializes with default values: - radius = 30.0f + * - angularSpeed = 1.0f + */ + public CircularAnimationComponent() { + this.radius = 30.0f; + this.angularSpeed = 1.0f; + this.timeElapsed = 0.0f; + } + + /** + * Creates a CircularAnimationComponent with specified radius and angular + * speed. + * + * @param radius The distance from the center of circular motion to the + * object. + * @param angularSpeed The speed at which the object orbits, in radians per + * second. + */ + public CircularAnimationComponent(float radius, float angularSpeed) { + this.radius = radius; + this.angularSpeed = angularSpeed; + } + + /** + * Updates the animation logic each frame. + *

+ * This method calculates the new position of the owning SceneNode in a + * circular path using trigonometric functions. The position is updated on the + * XZ plane with constant Y-coordinate to simulate planar circular motion. + *

+ * + * @param tpf Time per frame (time in seconds since the last frame). + */ + @Override + public void update(float tpf) { + // Update the elapsed time based on the time per frame + timeElapsed += tpf; + + // Calculate the new X and Z positions using circular motion formulas + float x = radius * Mathf.cos(angularSpeed * timeElapsed); + float z = radius * Mathf.sin(angularSpeed * timeElapsed); + + // Retrieve the transform of the owning SceneNode + SceneNode node = getOwner(); + Transform transform = node.getTransform(); + + // Set the new position while maintaining the current Y-coordinate + transform.setPosition(x, transform.getPosition().y, z); + } + + /** + * Handles any initialization logic required before the animation starts. + *

+ * Currently, this is left blank but can be implemented if preconditions need + * to be set before the animation begins. + *

+ */ + @Override + public void initialize() { + // Placeholder for potential initialization logic + } + + /** + * Handles any cleanup logic when the animation component is no longer needed. + *

+ * Currently, this is left blank but can be implemented if resources need to + * be released or reset during cleanup. + *

+ */ + @Override + public void cleanup() { + // Placeholder for potential cleanup logic + } + +} \ No newline at end of file From 8b54a3fe291f6cd2d3f6a44a570ded661f84f1a5 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Tue, 17 Dec 2024 23:49:38 +0100 Subject: [PATCH 33/59] Added ControlWASD component for keyboard-based 3D navigation with WASD movement. --- .../java/engine/components/ControlWASD.java | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 src/main/java/engine/components/ControlWASD.java diff --git a/src/main/java/engine/components/ControlWASD.java b/src/main/java/engine/components/ControlWASD.java new file mode 100644 index 00000000..aa039778 --- /dev/null +++ b/src/main/java/engine/components/ControlWASD.java @@ -0,0 +1,170 @@ +package engine.components; + +import engine.input.Input; +import engine.input.Key; +import engine.scene.SceneNode; +import math.Vector3f; + +/** + * ControlWASD is a movement component that allows moving a node in the scene + * graph using keyboard input (W/A/S/D). It integrates with the scene's + * transformation system to control position changes, allowing basic 3D + * navigation in a scene graph. + * + *

+ * Movement is normalized to ensure consistent speed, even when moving + * diagonally. This class supports velocity adjustments via the speed multiplier + * and works by translating the owning node within the 3D world space. + *

+ * + *

+ * Example usage: Attach this component to a {@link SceneNode} to allow movement + * using WASD keyboard inputs. The position updates depend on the elapsed time + * per frame to ensure smooth and consistent movement over varying frame rates. + *

+ */ +public class ControlWASD extends AbstractComponent { + + /** + * The speed multiplier determines how fast the node moves per second. + */ + private float speed; + + /** + * The Input instance to monitor keyboard events (injected dependency). + */ + private Input input; + + /** + * Constructs a new ControlWASD component, injecting the Input logic needed + * for detecting key presses. + * + * @param input The Input system used to check for keyboard input events. Must + * not be null. + * @throws IllegalArgumentException if input is null. + */ + public ControlWASD(Input input) { + this(input, 1); + } + + /** + * Constructs a new ControlWASD component, injecting the Input logic needed + * for detecting key presses and allowing a custom movement speed multiplier. + * + *

+ * This constructor allows developers to specify custom speeds for different + * movement styles, debugging scenarios, or game mechanics adjustments. + *

+ * + * @param input The Input system used to monitor keyboard input events. Must + * not be null. + * @param speed The speed multiplier determining how fast the node moves per + * second. Must be non-negative. + * @throws IllegalArgumentException if input is null or speed is less than 0. + */ + public ControlWASD(Input input, float speed) { + if (input == null) { + throw new IllegalArgumentException("Input cannot be null."); + } + if (speed < 0) { + throw new IllegalArgumentException("Speed must be non-negative."); + } + this.input = input; + this.speed = speed; + } + + /** + * Initializes the component. Currently no initialization logic is required, + * but this is provided as a placeholder for future logic. + */ + @Override + public void initialize() { + // No additional initialization logic required at this stage. + } + + /** + * Updates the movement of the owning node based on keyboard input. + *

+ * This method calculates the velocity vector by processing the WASD input and + * applies it to move the node smoothly in the 3D space by adjusting its + * position over time with respect to `tpf` (time per frame). + *

+ * + * @param tpf Time per frame, used to ensure frame-rate-independent movement. + */ + @Override + public void update(float tpf) { + SceneNode node = getOwner(); + Vector3f velocity = handleInput(); + + Transform transform = node.getTransform(); + Vector3f position = transform.getPosition(); + + transform.setPosition(position.add(velocity.mult(tpf))); + } + + /** + * Processes keyboard input to determine velocity. Handles the W/A/S/D keys to + * allow movement in the 3D plane. Normalizes the vector to ensure diagonal + * movement doesn't lead to faster speeds. + * + * @return A velocity vector representing the computed movement direction and + * speed. + */ + private Vector3f handleInput() { + Vector3f velocity = new Vector3f(); + + // Check for movement inputs + if (input.isKeyPressed(Key.W)) velocity.addLocal(0, 0, -1); + if (input.isKeyPressed(Key.A)) velocity.addLocal(-1, 0, 0); + if (input.isKeyPressed(Key.S)) velocity.addLocal(0, 0, 1); + if (input.isKeyPressed(Key.D)) velocity.addLocal(1, 0, 0); + + // Normalize diagonal movement to prevent unintended speed boosts + if (velocity.length() > 0) { + velocity.normalizeLocal().multLocal(speed); + } + return velocity; + } + + /** + * Cleans up resources when the component is removed or the application is + * shutting down. Currently, there is no cleanup logic necessary, but this is + * a placeholder for extensibility. + */ + @Override + public void cleanup() { + // Cleanup logic placeholder + } + + /** + * Retrieves the current movement speed multiplier. + * + * @return The current speed value. + */ + public float getSpeed() { + return speed; + } + + /** + * Sets the movement speed multiplier for controlling how fast the node moves + * through the scene. + * + *

+ * A speed of 1.0 represents the default unit speed. Increasing this value + * will make the node move faster, while decreasing it will slow it down. + * Movement speed is scaled by elapsed time per frame to ensure + * frame-rate-independent movement. + *

+ * + * @param speed The new speed value to set. Must be a non-negative number. + * @throws IllegalArgumentException if the speed is less than 0. + */ + public void setSpeed(float speed) { + if (speed < 0) { + throw new IllegalArgumentException("Speed must be non-negative."); + } + this.speed = speed; + } + +} \ No newline at end of file From 4dbd02d4b91f5d1e2c90b93aa72b806d4bf49540 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Tue, 17 Dec 2024 23:50:19 +0100 Subject: [PATCH 34/59] Created RenderComponent interface for modular rendering logic in the scene graph. --- .../engine/components/RenderComponent.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/main/java/engine/components/RenderComponent.java diff --git a/src/main/java/engine/components/RenderComponent.java b/src/main/java/engine/components/RenderComponent.java new file mode 100644 index 00000000..1bcd4059 --- /dev/null +++ b/src/main/java/engine/components/RenderComponent.java @@ -0,0 +1,45 @@ +package engine.components; + +import engine.scene.SceneNode; +import workspace.ui.Graphics; + +/** + * Represents a renderable component within the scene graph architecture. + *

+ * RenderComponents are modular pieces of rendering behavior that can be added + * to {@link SceneNode} instances. They follow the component-based design + * pattern, encapsulating rendering logic and enabling flexible and reusable + * visual behavior within a scene. + *

+ *

+ * A RenderComponent should implement the rendering logic in its + * {@code render()} method, which is invoked during the rendering pass of a + * scene graph. It is designed to work with a graphics context (such as that + * represented by the {@link Graphics} class) to issue drawing commands. + *

+ *

+ * Example use cases include rendering meshes, particles, UI overlays, or other + * visual elements as part of a node's rendering pipeline. + *

+ * + * @see Component + * @see SceneNode + */ +public interface RenderComponent extends Component { + + /** + * Renders this component using the provided graphics context. + *

+ * This method is called during the rendering phase of the scene graph + * traversal. Implementations of this method should issue drawing commands + * using the provided {@link Graphics} instance. Components should encapsulate + * the logic necessary to visualize themselves or their state. + *

+ * + * @param g The graphics context to use for rendering. This context provides + * methods for transformations, shading, drawing primitives, and + * other rendering operations. + */ + void render(Graphics g); + +} \ No newline at end of file From 6d97f30bc640130ab985591e902d8cde27e8eee4 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Tue, 17 Dec 2024 23:50:51 +0100 Subject: [PATCH 35/59] Implemented RotationComponent for rotating SceneNodes around a specified axis at a constant angular speed. --- .../engine/components/RotationComponent.java | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 src/main/java/engine/components/RotationComponent.java diff --git a/src/main/java/engine/components/RotationComponent.java b/src/main/java/engine/components/RotationComponent.java new file mode 100644 index 00000000..3fcaa873 --- /dev/null +++ b/src/main/java/engine/components/RotationComponent.java @@ -0,0 +1,124 @@ +package engine.components; + +import engine.scene.SceneNode; +import math.Vector3f; + +/** + * A simple rotation component that rotates the owning SceneNode around a + * specified axis at a constant angular speed. + * + *

+ * Properties: + *

    + *
  • axis: The axis around which the rotation occurs.
  • + *
  • angularSpeed: The speed of rotation in radians per second.
  • + *
+ *

+ * + *

+ * The rotation is applied incrementally to the Transform component of the + * owning SceneNode during each frame update. + *

+ */ +public class RotationComponent extends AbstractComponent { + + /** The axis around which the node will rotate. */ + private Vector3f axis; + + /** The angular speed of the rotation in radians per second. */ + private float angularSpeed; + + /** + * Constructs a RotationComponent with default axis (0, 1, 0) and angular + * speed. Default settings will rotate around the Y-axis. + */ + public RotationComponent() { + this.axis = new Vector3f(0, 1, 0); // Default rotation axis: Y-axis + this.angularSpeed = 1.0f; // Default angular speed: 1 radian/second + } + + /** + * Constructs a RotationComponent with a specified axis and angular speed. + * + * @param axis The axis of rotation (must not be null). + * @param angularSpeed The angular speed in radians per second. + */ + public RotationComponent(Vector3f axis, float angularSpeed) { + if (axis == null || axis.length() == 0) { + throw new IllegalArgumentException( + "Rotation axis cannot be null or zero-length."); + } + this.axis = axis.normalize(); // Normalize the axis to ensure proper + // rotation + this.angularSpeed = angularSpeed; + } + + /** + * Updates the rotation logic each frame. + * + * @param tpf Time per frame (in seconds since the last frame). + */ + @Override + public void update(float tpf) { + SceneNode node = getOwner(); + if (node == null) + return; + + // Calculate the incremental rotation angle + float angleIncrement = angularSpeed * tpf; + + // Apply rotation to the owning SceneNode's Transform + node.getTransform().rotate(axis.mult(angleIncrement)); + } + + /** + * Sets a new rotation axis for the component. + * + * @param axis The new axis of rotation. + */ + public void setAxis(Vector3f axis) { + if (axis == null || axis.length() == 0) { + throw new IllegalArgumentException( + "Rotation axis cannot be null or zero-length."); + } + this.axis = axis.normalize(); + } + + /** + * Sets the angular speed of the rotation. + * + * @param angularSpeed The new angular speed in radians per second. + */ + public void setAngularSpeed(float angularSpeed) { + this.angularSpeed = angularSpeed; + } + + /** + * Gets the current rotation axis. + * + * @return The axis of rotation. + */ + public Vector3f getAxis() { + return axis; + } + + /** + * Gets the current angular speed. + * + * @return The angular speed in radians per second. + */ + public float getAngularSpeed() { + return angularSpeed; + } + + @Override + public void initialize() { + // Initialization logic if needed + } + + @Override + public void cleanup() { + // Cleanup logic if needed + } + +} \ No newline at end of file From 77a8aef82b11feea1fd0ca02cf71a31ce27ae841 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Tue, 17 Dec 2024 23:52:58 +0100 Subject: [PATCH 36/59] feat: Implement Geometry component with mesh and material rendering support --- src/main/java/engine/components/Geometry.java | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 src/main/java/engine/components/Geometry.java diff --git a/src/main/java/engine/components/Geometry.java b/src/main/java/engine/components/Geometry.java new file mode 100644 index 00000000..1c602329 --- /dev/null +++ b/src/main/java/engine/components/Geometry.java @@ -0,0 +1,126 @@ +package engine.components; + +import engine.render.Material; +import mesh.Mesh3D; +import workspace.ui.Graphics; + +/** + * Represents a renderable geometry component within the scene graph. + *

+ * The Geometry class is a component that encapsulates a 3D mesh and its + * associated material. It implements rendering behavior through the + * {@link RenderComponent} interface, allowing it to be drawn during the + * rendering pass of the scene graph traversal. + *

+ *

+ * This class is responsible for applying the provided {@link Material} during + * rendering, ensuring proper visualization of the associated {@link Mesh3D}. + *

+ * + *

Key Features

+ *
    + *
  • Supports a default white material if no custom material is provided.
  • + *
  • Handles cleanup by releasing references to the mesh and material.
  • + *
  • Integrates with the graphics context to issue rendering commands via the + * {@link Graphics} class.
  • + *
+ */ +public class Geometry extends AbstractComponent implements RenderComponent { + + /** The 3D mesh to be rendered by this component. */ + private Mesh3D mesh; + + /** The material associated with this geometry for rendering purposes. */ + private Material material; + + /** + * Constructs a Geometry component with a provided mesh and the default white + * material. + * + * @param mesh The mesh to associate with this geometry. + */ + public Geometry(Mesh3D mesh) { + this(mesh, Material.DEFAULT_WHITE); + } + + /** + * Constructs a Geometry component with a specific mesh and material. + * + * @param mesh The 3D mesh to associate with this geometry. Must not be + * null. + * @param material The material to use for rendering. Must not be null. + * @throws IllegalArgumentException if either mesh or material is null. + */ + public Geometry(Mesh3D mesh, Material material) { + validate(mesh, material); + this.mesh = mesh; + this.material = material; + } + + /** + * Validates the provided mesh and material to ensure they are non-null. + * + * @param mesh The 3D mesh to validate. + * @param material The material to validate. + * @throws IllegalArgumentException if either mesh or material is null. + */ + private void validate(Mesh3D mesh, Material material) { + if (mesh == null) { + throw new IllegalArgumentException("Mesh cannot be null."); + } + if (material == null) { + throw new IllegalArgumentException("Material cannot be null."); + } + } + + /** + * Cleans up resources associated with this component by nullifying references + * to the mesh and material. This is called when the component is no longer + * needed or during application shutdown. + */ + @Override + public void cleanup() { + mesh = null; + material = null; + } + + /** + * Handles rendering of the mesh with its material using the provided graphics + * context. + * + *

+ * This method applies the material, renders the associated mesh's faces using + * the {@link Graphics} instance, and then releases the material's state + * afterward. + *

+ * + * @param g The graphics context to use for rendering the geometry. + */ + @Override + public void render(Graphics g) { + material.apply(g); + g.fillFaces(mesh); + material.release(g); + } + + /** + * Placeholder for initialization logic if needed in future development. + * Currently, no specific initialization is necessary. + */ + @Override + public void initialize() { + // Initialization logic, if needed + } + + /** + * Updates geometry's state over time, if necessary. Currently, this is a + * placeholder for potential future logic. + * + * @param tpf Time per frame, used to synchronize updates across frames. + */ + @Override + public void update(float tpf) { + // Placeholder for potential mesh state updates + } + +} \ No newline at end of file From db20f76042736794e8ee9f36d9977329d97e4340 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Wed, 18 Dec 2024 11:31:44 +0100 Subject: [PATCH 37/59] Bugfix: Color add(Color color) used subtract. --- src/main/java/math/Color.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/main/java/math/Color.java b/src/main/java/math/Color.java index cc1613bf..3d40b1d0 100644 --- a/src/main/java/math/Color.java +++ b/src/main/java/math/Color.java @@ -166,7 +166,7 @@ public static Color getColorFromInt(int r, int g, int b) { * @return the resultant color */ public Color add(Color color) { - return subtract(color, null); + return add(color, null); } /** @@ -331,6 +331,18 @@ public Color divideLocal(float a) { this.a /= a; return this; } + + /** + * @param a + * @return + */ + public Color multLocal(float r, float g, float b, float a) { + this.r *= a; + this.g *= g; + this.b *= b; + this.a *= a; + return this; + } /** * Clamps the components of this color between 0.0f and 1.0f internally, and From 20f48b25240c14d15aa6da9d3519ddd83b3e90aa Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Wed, 18 Dec 2024 11:34:04 +0100 Subject: [PATCH 38/59] Format changes. Removed version. --- src/main/java/math/Color.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/math/Color.java b/src/main/java/math/Color.java index 3d40b1d0..3c936517 100644 --- a/src/main/java/math/Color.java +++ b/src/main/java/math/Color.java @@ -3,10 +3,6 @@ /** * Representation of RGBA colors. The components (r,g,b) define a color in RGB * color space. - * - * @author Simon - * @version 0.3, 11 December 2017 - * */ public class Color { From 41a93701339c7c288a7599ed76eb759a161a5336 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Wed, 18 Dec 2024 11:43:11 +0100 Subject: [PATCH 39/59] Add AmbientLight support and update LightRenderer for rendering - Implemented `AmbientLight` with a customizable color property and default white color. - Integrated `AmbientLight` into `LightType` enum to represent global, non-directional lighting. - Updated `LightRenderer` interface to include a new method for rendering `AmbientLight`. --- .../java/engine/scene/light/AmbientLight.java | 99 +++++++++++++++++++ .../engine/scene/light/LightRenderer.java | 31 ++++++ .../java/engine/scene/light/LightType.java | 9 +- 3 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 src/main/java/engine/scene/light/AmbientLight.java diff --git a/src/main/java/engine/scene/light/AmbientLight.java b/src/main/java/engine/scene/light/AmbientLight.java new file mode 100644 index 00000000..b6e52e9b --- /dev/null +++ b/src/main/java/engine/scene/light/AmbientLight.java @@ -0,0 +1,99 @@ +package engine.scene.light; + +import math.Color; + +/** + * Represents an ambient light source in a 3D scene. + * + *

+ * Ambient light provides uniform, non-directional illumination throughout the + * entire 3D scene. Unlike other types of lights, such as point lights or + * directional lights, ambient light does not have a specific position or + * direction, and it affects all objects equally, regardless of their location + * or orientation. This is ideal for simulating indirect or global lighting + * effects that ensure objects are visible even when not directly illuminated by + * other light sources. + *

+ * + *
+ * Key Characteristics of an ambient light:
+ * - Color: Defined by an instance of {@link math.Color},
+ *   representing the light's hue.
+ * - No position: Ambient light is global, meaning it affects all parts of the
+ *   scene uniformly.
+ * - Non-directional: Ambient light lacks a specific directional focus and has
+ *   equal influence everywhere.
+ * 
+ * + * Usage: This class allows you to dynamically configure the ambient light's + * color during runtime. Integration with rendering systems can be achieved via + * the {@link LightRenderer}. + */ +public class AmbientLight implements Light { + + /** + * Ambient color. + */ + private Color color; + + /** + * Constructs a new AmbientLight with a default color of white. + */ + public AmbientLight() { + this(Color.WHITE); + } + + /** + * Constructs a new AmbientLight with the specified color. + * + * @param color the color of the ambient light. Must not be null. + * @throws IllegalArgumentException if the color is null. + */ + public AmbientLight(Color color) { + setColor(color); + } + + /** + * Gets the type of the light. + * + * @return the light type, which is {@link LightType#AMBIENT}. + */ + @Override + public LightType getType() { + return LightType.AMBIENT; + } + + /** + * Renders the ambient light using the specified {@link LightRenderer}. + * + * @param renderer the renderer to use for rendering the light. + */ + @Override + public void render(LightRenderer renderer) { + renderer.render(this); + } + + /** + * Gets the color of the ambient light. + * + * @return the color of the light. Never null. + */ + @Override + public Color getColor() { + return color; + } + + /** + * Sets the color of the ambient light. + * + * @param color the new color for the ambient light. Must not be null. + * @throws IllegalArgumentException if the color is null. + */ + public void setColor(Color color) { + if (color == null) { + throw new IllegalArgumentException("Color cannot be null."); + } + this.color = color; + } + +} \ No newline at end of file diff --git a/src/main/java/engine/scene/light/LightRenderer.java b/src/main/java/engine/scene/light/LightRenderer.java index d307b12e..4e519fef 100644 --- a/src/main/java/engine/scene/light/LightRenderer.java +++ b/src/main/java/engine/scene/light/LightRenderer.java @@ -1,5 +1,7 @@ package engine.scene.light; +import workspace.ui.Graphics; + /** * Interface for rendering various light sources in a 3D scene. *

@@ -12,6 +14,22 @@ */ public interface LightRenderer { + /** + * Sets the graphics context for the light renderer. + *

+ * This method initializes the rendering environment by associating the given + * {@link Graphics} instance with the light renderer. The graphics context is + * responsible for rendering commands, shader bindings, and light + * computations. Implementations can use this context to issue rendering + * commands for different light types or configure the rendering pipeline as + * needed. + *

+ * + * @param g The {@link Graphics} instance to be used by the light renderer. + * Must not be null. + */ + void setGraphics(Graphics g); + /** * Renders a generic light source. *

@@ -63,4 +81,17 @@ public interface LightRenderer { */ void render(DirectionalLight light); + /** + * Renders an ambient light source. + *

+ * This method handles the rendering of ambient light, which provides uniform + * illumination across the entire scene without directionality or position. + * Ambient light is used to simulate indirect lighting and ensures that + * objects are visible even when not directly lit by other light sources. + *

+ * + * @param light The ambient light source to render. Must not be null. + */ + void render(AmbientLight light); + } \ No newline at end of file diff --git a/src/main/java/engine/scene/light/LightType.java b/src/main/java/engine/scene/light/LightType.java index 50912c4d..8d641565 100644 --- a/src/main/java/engine/scene/light/LightType.java +++ b/src/main/java/engine/scene/light/LightType.java @@ -3,7 +3,7 @@ /** * Enum representing different types of lights. * - * This enum defines the three primary types of lights commonly used in 3D + * This enum defines the four primary types of lights commonly used in 3D * graphics: * *
@@ -12,10 +12,13 @@
  *   a specific direction.
  * - SPOT: A spotlight emits light in a cone shape, with a specific 
  *   direction and angle.
+ * - AMBIENT: An ambient light provides uniform illumination across the 
+ *   entire scene, simulating indirect lighting with no specific direction 
+ *   or position.
  * 
*/ public enum LightType { - POINT, DIRECTIONAL, SPOT + POINT, DIRECTIONAL, SPOT, AMBIENT -} +} \ No newline at end of file From 06ab221ed8c9d24bb0c20e3ba1b1972638244322 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Thu, 19 Dec 2024 13:20:13 +0100 Subject: [PATCH 40/59] Add getRealtimeSinceStartup() and getRealtimeSinceStartupAsDouble() methods to Timer class to provide accurate real-time measurements since game startup in both float and double precision. --- src/main/java/engine/Timer.java | 40 ++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/main/java/engine/Timer.java b/src/main/java/engine/Timer.java index 402a226e..546c9b57 100644 --- a/src/main/java/engine/Timer.java +++ b/src/main/java/engine/Timer.java @@ -21,6 +21,11 @@ */ public class Timer { + /** + * Real world time when the timer started. + */ + private long startTime; + /** The last recorded time in nanoseconds. */ private long lastTime; @@ -49,6 +54,7 @@ public class Timer { * Constructs a {@code Timer} with a default time scale of 1.0. */ public Timer() { + this.startTime = System.nanoTime(); this.lastTime = System.nanoTime(); this.time = 0; this.totalTime = 0; @@ -178,6 +184,38 @@ public void setTimeScale(float timeScale) { this.timeScale = timeScale; } + /** + * Returns the real-time elapsed since the game / application started, + * measured in seconds. + * + *

+ * This method uses `System.nanoTime()` to obtain a high-precision timestamp. + * The returned value is a `float` representing the elapsed time in seconds. + *

+ * + * @return The real-time elapsed since the game started, in seconds. + */ + public float getRealtimeSinceStartup() { + return (System.nanoTime() - startTime) / 1_000_000_000.0f; + } + + /** + * Returns the real-time elapsed since the game / application started, + * measured in seconds, as a `double` value. + * + *

+ * This method uses `System.nanoTime()` to obtain a high-precision timestamp. + * The returned value is a `double` representing the elapsed time in seconds, + * providing higher precision than the `float` version. + *

+ * + * @return The real-time elapsed since the game started, in seconds, as a + * `double`. + */ + public double getRealtimeSinceStartupAsDouble() { + return (System.nanoTime() - startTime) / 1_000_000_000.0; + } + /** * Returns the total number of frames that have passed since the Timer * started. @@ -187,7 +225,7 @@ public void setTimeScale(float timeScale) { public int getFrameCount() { return frameCount; } - + /** * Formats a time value in seconds into a string in the format HH:MM:SS. * From c7a0a2f2d15da22e154c99fe3eb75b3256c3b9fd Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Thu, 19 Dec 2024 22:25:00 +0100 Subject: [PATCH 41/59] Add. Initialize component when attached. --- src/main/java/engine/scene/SceneNode.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/engine/scene/SceneNode.java b/src/main/java/engine/scene/SceneNode.java index c19e52b9..80c5ef2c 100644 --- a/src/main/java/engine/scene/SceneNode.java +++ b/src/main/java/engine/scene/SceneNode.java @@ -35,7 +35,7 @@ * * @see Transform * @see Component - * @see RenderComponent + * @see RenderComponents */ public class SceneNode { @@ -243,6 +243,7 @@ public void addComponent(Component component) { if (!components.contains(component)) { components.add(component); component.setOwner(this); + component.initialize(); } } @@ -370,5 +371,5 @@ public void setName(String name) { } this.name = name; } - + } \ No newline at end of file From cda016f2b09da00473f63f5f0dd1910b4b861923 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 21 Dec 2024 07:32:50 +0100 Subject: [PATCH 42/59] Changed interface name from RenderComponent to RenderableComponent --- .../components/CinematicBlackBarsRenderer.java | 2 +- src/main/java/engine/components/Geometry.java | 4 ++-- ...rComponent.java => RenderableComponent.java} | 2 +- src/main/java/engine/render/Material.java | 14 ++++++++++++++ .../render/effects/ParticleComponent.java | 6 +++--- src/main/java/engine/scene/Scene.java | 17 ++++++++++++++--- src/main/java/engine/scene/SceneNode.java | 14 +++++++------- src/main/java/engine/scene/camera/Camera.java | 2 ++ 8 files changed, 44 insertions(+), 17 deletions(-) rename src/main/java/engine/components/{RenderComponent.java => RenderableComponent.java} (96%) diff --git a/src/main/java/engine/components/CinematicBlackBarsRenderer.java b/src/main/java/engine/components/CinematicBlackBarsRenderer.java index 83dcba9b..94841370 100644 --- a/src/main/java/engine/components/CinematicBlackBarsRenderer.java +++ b/src/main/java/engine/components/CinematicBlackBarsRenderer.java @@ -15,7 +15,7 @@ *

*/ public class CinematicBlackBarsRenderer extends AbstractComponent - implements RenderComponent { + implements RenderableComponent { /** Default height (in pixels) of the cinematic bars at full fade-in. */ private static final int DEFAULT_TARGET_BAR_HEIGHT = 200; diff --git a/src/main/java/engine/components/Geometry.java b/src/main/java/engine/components/Geometry.java index 1c602329..660026c0 100644 --- a/src/main/java/engine/components/Geometry.java +++ b/src/main/java/engine/components/Geometry.java @@ -9,7 +9,7 @@ *

* The Geometry class is a component that encapsulates a 3D mesh and its * associated material. It implements rendering behavior through the - * {@link RenderComponent} interface, allowing it to be drawn during the + * {@link RenderableComponent} interface, allowing it to be drawn during the * rendering pass of the scene graph traversal. *

*

@@ -25,7 +25,7 @@ * {@link Graphics} class. * */ -public class Geometry extends AbstractComponent implements RenderComponent { +public class Geometry extends AbstractComponent implements RenderableComponent { /** The 3D mesh to be rendered by this component. */ private Mesh3D mesh; diff --git a/src/main/java/engine/components/RenderComponent.java b/src/main/java/engine/components/RenderableComponent.java similarity index 96% rename from src/main/java/engine/components/RenderComponent.java rename to src/main/java/engine/components/RenderableComponent.java index 1bcd4059..b4a66416 100644 --- a/src/main/java/engine/components/RenderComponent.java +++ b/src/main/java/engine/components/RenderableComponent.java @@ -25,7 +25,7 @@ * @see Component * @see SceneNode */ -public interface RenderComponent extends Component { +public interface RenderableComponent extends Component { /** * Renders this component using the provided graphics context. diff --git a/src/main/java/engine/render/Material.java b/src/main/java/engine/render/Material.java index ff005ed9..af8206f7 100644 --- a/src/main/java/engine/render/Material.java +++ b/src/main/java/engine/render/Material.java @@ -83,6 +83,8 @@ public class Material { */ public static final Material WATER_MATERIAL = MaterialFactory.createWater(); + private boolean useLighting; + /** * Base color for the material. */ @@ -118,6 +120,7 @@ public Material(Color color) { } private Material(Builder builder) { + this.useLighting = builder.useLighting; this.color = builder.color; this.ambient = builder.ambient; this.diffuse = builder.diffuse; @@ -131,6 +134,8 @@ private Material(Builder builder) { */ public static class Builder { + private boolean useLighting = true; + private Color color = new Color(1, 1, 1); // Default color is white private float[] ambient = new float[] { 0.2f, 0.2f, 0.2f }; @@ -196,6 +201,11 @@ public Builder setShininess(float shininess) { return this; } + public Builder setUseLighting(boolean useLighting) { + this.useLighting = useLighting; + return this; + } + /** * Builds and returns the Material instance with the set properties. * @@ -226,6 +236,10 @@ public void release(Graphics g) { // Logic for releasing or resetting rendering context goes here } + public boolean isUseLighting() { + return useLighting; + } + /** * Retrieves the base color of the material. * diff --git a/src/main/java/engine/render/effects/ParticleComponent.java b/src/main/java/engine/render/effects/ParticleComponent.java index 6735c6a2..991d164a 100644 --- a/src/main/java/engine/render/effects/ParticleComponent.java +++ b/src/main/java/engine/render/effects/ParticleComponent.java @@ -1,14 +1,14 @@ package engine.render.effects; import engine.components.AbstractComponent; -import engine.components.RenderComponent; +import engine.components.RenderableComponent; import workspace.ui.Graphics; /** * A component responsible for managing and rendering particles using a * specified particle emitter and renderer. *

- * This class implements the {@link RenderComponent} interface, allowing it to + * This class implements the {@link RenderableComponent} interface, allowing it to * integrate seamlessly with the rendering system. It uses a * {@link ParticleEmitter} to handle the logic of particle spawning and updates, * while delegating rendering operations to a {@link ParticleRenderer}. @@ -22,7 +22,7 @@ * @author Simon Dietz */ public class ParticleComponent extends AbstractComponent - implements RenderComponent { + implements RenderableComponent { private ParticleEmitter emitter; diff --git a/src/main/java/engine/scene/Scene.java b/src/main/java/engine/scene/Scene.java index f0669283..7cf7940e 100644 --- a/src/main/java/engine/scene/Scene.java +++ b/src/main/java/engine/scene/Scene.java @@ -130,16 +130,27 @@ public void update(float deltaTime) { * the main thread for compatibility with most rendering APIs. */ public void render(Graphics g) { + + if (activeCamera != null) { +// Vector3f position = activeCamera.getTransform().getPosition(); +// g.applyMatrix(activeCamera.getProjectionMatrix()); +// g.applyMatrix(activeCamera.getViewMatrix()); + g.applyCamera(activeCamera); +// if (position != null) +// g.translate(position.x, position.y, position.z); +// System.out.println(position); + } + g.setWireframeMode(wireframeMode); renderLights(g); + synchronized (rootNodes) { - if (activeCamera != null) { - // TODO FIXME: Implement - } + for (SceneNode node : rootNodes) { node.render(g); } } + } /** diff --git a/src/main/java/engine/scene/SceneNode.java b/src/main/java/engine/scene/SceneNode.java index 80c5ef2c..5a5a6550 100644 --- a/src/main/java/engine/scene/SceneNode.java +++ b/src/main/java/engine/scene/SceneNode.java @@ -4,7 +4,7 @@ import java.util.List; import engine.components.Component; -import engine.components.RenderComponent; +import engine.components.RenderableComponent; import engine.components.Transform; import workspace.ui.Graphics; @@ -21,7 +21,7 @@ * The {@code SceneNode} manages its own transformation through a * {@link Transform} object, handles rendering, updates logic for itself and its * children, and provides methods for managing components like - * {@link RenderComponent}. + * {@link RenderableComponent}. *

*

* Example use cases include: @@ -116,7 +116,7 @@ private void applyLocalTransform(Graphics g) { } /** - * Renders all associated {@link RenderComponent} instances attached to this + * Renders all associated {@link RenderableComponent} instances attached to this * node. *

* This method iterates through all render components and calls their @@ -126,7 +126,7 @@ private void applyLocalTransform(Graphics g) { * @param g The graphics context used for rendering. */ protected void renderComponents(Graphics g) { - for (RenderComponent renderer : getRenderComponents()) { + for (RenderableComponent renderer : getRenderComponents()) { renderer.render(g); } } @@ -307,11 +307,11 @@ public List getComponents(Class componentClass) { /** * Retrieves all render components for this node. * - * @return A list of {@link RenderComponent} instances associated with this + * @return A list of {@link RenderableComponent} instances associated with this * node. */ - public List getRenderComponents() { - return getComponents(RenderComponent.class); + public List getRenderComponents() { + return getComponents(RenderableComponent.class); } /** diff --git a/src/main/java/engine/scene/camera/Camera.java b/src/main/java/engine/scene/camera/Camera.java index e9030be3..2c1e14a8 100644 --- a/src/main/java/engine/scene/camera/Camera.java +++ b/src/main/java/engine/scene/camera/Camera.java @@ -22,6 +22,8 @@ */ public interface Camera { + Vector3f getTarget(); + /** * Retrieves the camera's transformation. *

From 548ce453c084cf2b2191dbe041105735b18f348c Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 21 Dec 2024 08:11:20 +0100 Subject: [PATCH 43/59] Refactoring. Removed clutter. --- src/main/java/engine/scene/Scene.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/main/java/engine/scene/Scene.java b/src/main/java/engine/scene/Scene.java index 7cf7940e..f225c9fd 100644 --- a/src/main/java/engine/scene/Scene.java +++ b/src/main/java/engine/scene/Scene.java @@ -130,15 +130,8 @@ public void update(float deltaTime) { * the main thread for compatibility with most rendering APIs. */ public void render(Graphics g) { - if (activeCamera != null) { -// Vector3f position = activeCamera.getTransform().getPosition(); -// g.applyMatrix(activeCamera.getProjectionMatrix()); -// g.applyMatrix(activeCamera.getViewMatrix()); g.applyCamera(activeCamera); -// if (position != null) -// g.translate(position.x, position.y, position.z); -// System.out.println(position); } g.setWireframeMode(wireframeMode); From cf4f41fa8ebb52cb24cdcdba34396465495b0c05 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 21 Dec 2024 08:34:15 +0100 Subject: [PATCH 44/59] Use Read-Only Access with Collections.unmodifiableList --- src/main/java/engine/scene/Scene.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/main/java/engine/scene/Scene.java b/src/main/java/engine/scene/Scene.java index f225c9fd..af0935e4 100644 --- a/src/main/java/engine/scene/Scene.java +++ b/src/main/java/engine/scene/Scene.java @@ -1,6 +1,7 @@ package engine.scene; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -114,14 +115,11 @@ public void removeNode(SceneNode node) { * @param deltaTime The time step for simulation logic updates. */ public void update(float deltaTime) { - List nodesCopy; synchronized (rootNodes) { - nodesCopy = new ArrayList<>(rootNodes); - } - - // Submit updates to worker threads - for (SceneNode node : nodesCopy) { - updateExecutor.submit(() -> node.update(deltaTime)); + for (SceneNode node : Collections.unmodifiableList(rootNodes)) { + // Submit updates to worker threads + updateExecutor.submit(() -> node.update(deltaTime)); + } } } From 6128d500e54cad17941e30aee82e76529e3b8b91 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 21 Dec 2024 12:53:08 +0100 Subject: [PATCH 45/59] Removed clutter. --- src/main/java/engine/Timer.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/engine/Timer.java b/src/main/java/engine/Timer.java index 546c9b57..135e3d83 100644 --- a/src/main/java/engine/Timer.java +++ b/src/main/java/engine/Timer.java @@ -68,7 +68,6 @@ public Timer() { * @param initialTimeScale the initial time scaling factor */ public Timer(float initialTimeScale) { - this(); this.timeScale = initialTimeScale; } From 22ac27efc7b0d89b93eccd9fecdd382ca12f7708 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 21 Dec 2024 13:20:34 +0100 Subject: [PATCH 46/59] Feat: Added reciprocal() --- src/main/java/math/Vector3f.java | 1058 +++++++++++++++--------------- 1 file changed, 531 insertions(+), 527 deletions(-) diff --git a/src/main/java/math/Vector3f.java b/src/main/java/math/Vector3f.java index 7d756a56..591fb892 100644 --- a/src/main/java/math/Vector3f.java +++ b/src/main/java/math/Vector3f.java @@ -2,552 +2,556 @@ public class Vector3f { - public static final Vector3f BACK = new Vector3f(0, 0, -1); + public static final Vector3f BACK = new Vector3f(0, 0, -1); - public static final Vector3f DOWN = new Vector3f(0, -1, 0); + public static final Vector3f DOWN = new Vector3f(0, -1, 0); - public static final Vector3f FORWARD = new Vector3f(0, 0, 1); + public static final Vector3f FORWARD = new Vector3f(0, 0, 1); - public static final Vector3f LEFT = new Vector3f(-1, 0, 0); + public static final Vector3f LEFT = new Vector3f(-1, 0, 0); - public static final Vector3f MAX = new Vector3f(Float.MAX_VALUE, - Float.MAX_VALUE, Float.MAX_VALUE); + public static final Vector3f MAX = new Vector3f(Float.MAX_VALUE, + Float.MAX_VALUE, Float.MAX_VALUE); - public static final Vector3f MIN = new Vector3f(Float.MIN_VALUE, - Float.MIN_VALUE, Float.MIN_VALUE); + public static final Vector3f MIN = new Vector3f(Float.MIN_VALUE, + Float.MIN_VALUE, Float.MIN_VALUE); - public static final Vector3f NAN = new Vector3f(Float.NaN, Float.NaN, - Float.NaN); + public static final Vector3f NAN = new Vector3f(Float.NaN, Float.NaN, + Float.NaN); - public static final Vector3f NEGATIVE_INFINITY = new Vector3f( - Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, - Float.NEGATIVE_INFINITY); + public static final Vector3f NEGATIVE_INFINITY = new Vector3f( + Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, + Float.NEGATIVE_INFINITY); - public static final Vector3f ONE = new Vector3f(1, 1, 1); + public static final Vector3f ONE = new Vector3f(1, 1, 1); - public static final Vector3f POSITIVE_INFINITY = new Vector3f( - Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, - Float.POSITIVE_INFINITY); + public static final Vector3f POSITIVE_INFINITY = new Vector3f( + Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, + Float.POSITIVE_INFINITY); - public static final Vector3f RIGHT = new Vector3f(1, 0, 0); + public static final Vector3f RIGHT = new Vector3f(1, 0, 0); - public static final Vector3f UP = new Vector3f(0, 1, 0); + public static final Vector3f UP = new Vector3f(0, 1, 0); - public static final Vector3f ZERO = new Vector3f(0, 0, 0); + public static final Vector3f ZERO = new Vector3f(0, 0, 0); - public float x; + public float x; - public float y; + public float y; - public float z; + public float z; - public Vector3f() { - x = y = z = 0; - } + public Vector3f() { + x = y = z = 0; + } - public Vector3f(float value) { - x = y = z = value; - } + public Vector3f(float value) { + x = y = z = value; + } - public Vector3f(float x, float y) { - this(x, y, 0); - } + public Vector3f(float x, float y) { + this(x, y, 0); + } - public Vector3f(float x, float y, float z) { - this.x = x; - this.y = y; - this.z = z; - } - - public Vector3f(float[] values) { - x = values[0]; - y = values[1]; - z = values[2]; - } + public Vector3f(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + } + + public Vector3f(float[] values) { + x = values[0]; + y = values[1]; + z = values[2]; + } - public Vector3f(Vector3f v) { - set(v); - } - - /** - * Rounds the x, y, and z components of this vector to the specified number - * of decimal places. - * - * @param decimalPlaces The number of decimal places to round to. - */ - public void roundLocalDecimalPlaces(int decimalPlaces) { - float factor = Mathf.pow(10, decimalPlaces); - x = Mathf.round(x * factor) / factor; - y = Mathf.round(y * factor) / factor; - z = Mathf.round(z * factor) / factor; - } - - public boolean approximatelyEquals(Vector3f v, float threshold) { - if (threshold < 0.0f) - throw new IllegalArgumentException( - "Threshold must be greater or equal to 0.0f."); - - float diffX = Math.abs(x - v.x); - float diffY = Math.abs(y - v.y); - float diffZ = Math.abs(z - v.z); - - return (diffX <= threshold && diffY <= threshold && diffZ <= threshold); - } - - public float angle(Vector3f v) { - return (float) Math.acos(dot(v)); - } - - public float signedAngle(Vector3f v, Vector3f normal) { - float unsignedAngle = (float) Math.acos(dot(v)); - return unsignedAngle * Math.signum(normal.dot(cross(v))); - } - - public Vector3f project(Vector3f v) { - float scalar = dot(v) / v.lengthSquared(); - return v.mult(scalar); - } - - public Vector3f projectLocal(Vector3f v) { - float scalar = dot(v) / v.lengthSquared(); - set(v.mult(scalar)); - return this; - } - - public Vector3f projectOnPlane(Vector3f planeNormal) { - // FIXME Check if this implementation is correct. - float scalar = dot(planeNormal) / planeNormal.lengthSquared(); - return subtract(planeNormal.mult(scalar)); - } - - public Vector3f projectOnPlaneLocal(Vector3f planeNormal) { - // FIXME Check if this implementation is correct. - float scalar = dot(planeNormal) / planeNormal.lengthSquared(); - subtractLocal(planeNormal.mult(scalar)); - return this; - } - - public Vector3f clampLength(float maxLength) { - return normalize().mult(maxLength); - } - - public Vector3f clampLengthLocal(float maxLength) { - normalizeLocal(); - multLocal(maxLength); - return this; - } - - public float lengthSquared() { - return (x * x) + (y * y) + (z * z); - } - - public float length() { - return (float) Math.sqrt(lengthSquared()); - } - - public float distanceSquared(float x, float y, float z) { - float dx = this.x - x; - float dy = this.y - y; - float dz = this.z - z; - return (dx * dx) + (dy * dy) + (dz * dz); - } - - public float distance(float x, float y, float z) { - return (float) Math.sqrt(distanceSquared(x, y, z)); - } - - public float distanceSquared(Vector3f v) { - return distanceSquared(v.x, v.y, v.z); - } - - public float distance(Vector3f v) { - return (float) Math.sqrt(distanceSquared(v)); - } - - public Vector3f normalize() { - float length = length(); - if (length != 0) { - return divide(length); - } - return divide(1); - } - - public Vector3f normalizeLocal() { - float length = length(); - if (length != 0) { - return divideLocal(length); - } - return divideLocal(1); - } - - public float dot(Vector3f v) { - return (x * v.x) + (y * v.y) + z * (v.z); - } - - public Vector3f cross(float x, float y, float z) { - return new Vector3f((this.y * z) - (this.z * y), - (this.z * x) - (this.x * z), (this.x * y) - (this.y * x)); - } - - public Vector3f crossLocal(float x, float y, float z) { - this.x = (this.y * z) - (this.z * y); - this.y = (this.z * x) - (this.x * z); - this.z = (this.x * y) - (this.y * x); - return this; - } - - public Vector3f cross(Vector3f v) { - return new Vector3f((y * v.z) - (z * v.y), (z * v.x) - (x * v.z), - (x * v.y) - (y * v.x)); - } - - public Vector3f crossLocal(Vector3f v) { - x = (y * v.z) - (z * v.y); - y = (z * v.x) - (x * v.z); - z = (x * v.y) - (y * v.x); - return this; - } - - public Vector3f cross(Vector3f v, Vector3f result) { - if (result == null) - result = new Vector3f(); - return result.set(cross(v)); - } - - public Vector3f negate() { - return new Vector3f(-x, -y, -z); - } - - public Vector3f negateLocal() { - x = -x; - y = -y; - z = -z; - return this; - } - - public Vector3f add(float x, float y, float z) { - return new Vector3f(this.x + x, this.y + y, this.z + z); - } - - public Vector3f addLocal(float x, float y, float z) { - this.x += x; - this.y += y; - this.z += z; - return this; - } - - public Vector3f add(Vector3f v) { - return new Vector3f(x + v.x, y + v.y, z + v.z); - } - - public Vector3f addLocal(Vector3f v) { - x += v.x; - y += v.y; - z += v.z; - return this; - } - - public Vector3f add(Vector3f v, Vector3f result) { - if (result == null) - result = new Vector3f(); - return result.set(add(v)); - } - - public Vector3f subtract(float x, float y, float z) { - return new Vector3f(this.x - x, this.y - y, this.z - z); - } - - public Vector3f subtractLocal(float x, float y, float z) { - this.x -= x; - this.y -= y; - this.z -= z; - return this; - } - - public Vector3f subtract(Vector3f v) { - return new Vector3f(x - v.x, y - v.y, z - v.z); - } - - public Vector3f subtractLocal(Vector3f v) { - x -= v.x; - y -= v.y; - z -= v.z; - return this; - } - - public Vector3f subtract(Vector3f v, Vector3f result) { - if (result == null) - result = new Vector3f(); - return result.set(subtract(v)); - } - - public Vector3f mult(float x, float y, float z) { - return new Vector3f(this.x * x, this.y * y, this.z * z); - } - - public Vector3f multLocal(float x, float y, float z) { - this.x *= x; - this.y *= y; - this.z *= z; - return this; - } - - public Vector3f mult(Vector3f v) { - return new Vector3f(x * v.x, y * v.y, z * v.z); - } - - public Vector3f multLocal(Vector3f v) { - x *= v.x; - y *= v.y; - z *= v.z; - return this; - } - - public Vector3f mult(Vector3f v, Vector3f result) { - if (result == null) - result = new Vector3f(); - return result.set(mult(v)); - } - - public Vector3f divide(float x, float y, float z) { - return new Vector3f(this.x / x, this.y / y, this.z / z); - } - - public Vector3f divideLocal(float x, float y, float z) { - this.x /= x; - this.y /= y; - this.z /= z; - return this; - } - - public Vector3f divide(Vector3f v) { - return new Vector3f(x / v.x, y / v.y, z / v.z); - } - - public Vector3f divideLocal(Vector3f v) { - x /= v.x; - y /= v.y; - z /= v.z; - return this; - } - - public Vector3f mult(float scalar) { - return new Vector3f(x * scalar, y * scalar, z * scalar); - } - - public Vector3f multLocal(float scalar) { - x *= scalar; - y *= scalar; - z *= scalar; - return this; - } - - public Vector3f mult(Matrix3f m) { - float x0 = m.values[0] * x + m.values[1] * y + m.values[2] * z; - float y0 = m.values[3] * x + m.values[4] * y + m.values[5] * z; - float z0 = m.values[6] * x + m.values[7] * y + m.values[8] * z; - return new Vector3f(x0, y0, z0); - } - - public Vector3f multLocal(Matrix3f m) { - float x0 = m.values[0] * x + m.values[1] * y + m.values[2] * z; - float y0 = m.values[3] * x + m.values[4] * y + m.values[5] * z; - float z0 = m.values[6] * x + m.values[7] * y + m.values[8] * z; - set(x0, y0, z0); - return this; - } - - public Vector3f divide(float scalar) { - return new Vector3f(x / scalar, y / scalar, z / scalar); - } - - public Vector3f divideLocal(float scalar) { - x /= scalar; - y /= scalar; - z /= scalar; - return this; - } - - public Vector3f divide(Vector3f v, Vector3f result) { - if (result == null) - result = new Vector3f(); - return result.set(divide(v)); - } - - public Vector3f abs() { - return new Vector3f(Math.abs(x), Math.abs(y), Math.abs(z)); - } - - public Vector3f absLocal() { - x = Math.abs(x); - y = Math.abs(y); - z = Math.abs(z); - return this; - } - - public Vector3f min(Vector3f v) { - return new Vector3f(Math.min(x, v.x), Math.min(y, v.y), - Math.min(z, v.z)); - } - - public Vector3f minLocal(Vector3f v) { - x = Math.min(x, v.x); - y = Math.min(y, v.y); - z = Math.min(z, v.z); - return this; - } - - public Vector3f minLocal(Vector3f a, Vector3f b) { - x = Math.min(a.x, b.x); - y = Math.min(a.y, b.y); - z = Math.min(a.z, b.z); - return this; - } - - public Vector3f max(Vector3f v) { - return new Vector3f(Math.max(x, v.x), Math.max(y, v.y), - Math.max(z, v.z)); - } - - public Vector3f maxLocal(Vector3f v) { - x = Math.max(x, v.x); - y = Math.max(y, v.y); - z = Math.max(z, v.z); - return this; - } - - public Vector3f maxLocal(Vector3f a, Vector3f b) { - x = Math.max(a.x, b.x); - y = Math.max(a.y, b.y); - z = Math.max(a.z, b.z); - return this; - } - - public Vector3f lerpLocal(Vector3f finalVec, float changeAmnt) { - if (changeAmnt == 0) { - return this; - } - if (changeAmnt == 1) { - this.x = finalVec.x; - this.y = finalVec.y; - this.z = finalVec.z; - return this; - } - this.x = (1 - changeAmnt) * this.x + changeAmnt * finalVec.x; - this.y = (1 - changeAmnt) * this.y + changeAmnt * finalVec.y; - this.z = (1 - changeAmnt) * this.z + changeAmnt * finalVec.z; - return this; - } - - public Vector3f lerpLocal(Vector3f beginVec, Vector3f finalVec, float changeAmnt) { - if (changeAmnt == 0) { - this.x = beginVec.x; - this.y = beginVec.y; - this.z = beginVec.z; - return this; - } - if (changeAmnt == 1) { - this.x = finalVec.x; - this.y = finalVec.y; - this.z = finalVec.z; - return this; - } - this.x = (1 - changeAmnt) * beginVec.x + changeAmnt * finalVec.x; - this.y = (1 - changeAmnt) * beginVec.y + changeAmnt * finalVec.y; - this.z = (1 - changeAmnt) * beginVec.z + changeAmnt * finalVec.z; - return this; - } - - public static boolean isValid(Vector3f v) { - if (v == null) - return false; - if (Float.isNaN(v.x) || Float.isNaN(v.y) || Float.isNaN(v.z)) - return false; - if (Float.isInfinite(v.x) || Float.isInfinite(v.y) - || Float.isInfinite(v.z)) - return false; - return true; - } - - public Vector3f set(float x, float y, float z) { - this.x = x; - this.y = y; - this.z = z; - return this; - } - - public Vector3f set(Vector3f v) { - x = v.x; - y = v.y; - z = v.z; - return this; - } - - public Vector3f set(float[] values) { - x = values[0]; - y = values[1]; - z = values[2]; - return this; - } - - public float getX() { - return x; - } - - public Vector3f setX(float x) { - this.x = x; - return this; - } - - public float getY() { - return y; - } - - public Vector3f setY(float y) { - this.y = y; - return this; - } - - public float getZ() { - return z; - } - - public Vector3f setZ(float z) { - this.z = z; - return this; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + Float.floatToIntBits(x); - result = prime * result + Float.floatToIntBits(y); - result = prime * result + Float.floatToIntBits(z); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - Vector3f other = (Vector3f) obj; - if (Float.floatToIntBits(x) != Float.floatToIntBits(other.x)) - return false; - if (Float.floatToIntBits(y) != Float.floatToIntBits(other.y)) - return false; - if (Float.floatToIntBits(z) != Float.floatToIntBits(other.z)) - return false; - return true; - } - - @Override - public String toString() { - return "Vector3f [x=" + x + ", y=" + y + ", z=" + z + "]"; - } + public Vector3f(Vector3f v) { + set(v); + } + + /** + * Rounds the x, y, and z components of this vector to the specified number of + * decimal places. + * + * @param decimalPlaces The number of decimal places to round to. + */ + public void roundLocalDecimalPlaces(int decimalPlaces) { + float factor = Mathf.pow(10, decimalPlaces); + x = Mathf.round(x * factor) / factor; + y = Mathf.round(y * factor) / factor; + z = Mathf.round(z * factor) / factor; + } + + public boolean approximatelyEquals(Vector3f v, float threshold) { + if (threshold < 0.0f) + throw new IllegalArgumentException( + "Threshold must be greater or equal to 0.0f."); + + float diffX = Math.abs(x - v.x); + float diffY = Math.abs(y - v.y); + float diffZ = Math.abs(z - v.z); + + return (diffX <= threshold && diffY <= threshold && diffZ <= threshold); + } + + public float angle(Vector3f v) { + return (float) Math.acos(dot(v)); + } + + public float signedAngle(Vector3f v, Vector3f normal) { + float unsignedAngle = (float) Math.acos(dot(v)); + return unsignedAngle * Math.signum(normal.dot(cross(v))); + } + + public Vector3f project(Vector3f v) { + float scalar = dot(v) / v.lengthSquared(); + return v.mult(scalar); + } + + public Vector3f projectLocal(Vector3f v) { + float scalar = dot(v) / v.lengthSquared(); + set(v.mult(scalar)); + return this; + } + + public Vector3f projectOnPlane(Vector3f planeNormal) { + // FIXME Check if this implementation is correct. + float scalar = dot(planeNormal) / planeNormal.lengthSquared(); + return subtract(planeNormal.mult(scalar)); + } + + public Vector3f projectOnPlaneLocal(Vector3f planeNormal) { + // FIXME Check if this implementation is correct. + float scalar = dot(planeNormal) / planeNormal.lengthSquared(); + subtractLocal(planeNormal.mult(scalar)); + return this; + } + + public Vector3f clampLength(float maxLength) { + return normalize().mult(maxLength); + } + + public Vector3f clampLengthLocal(float maxLength) { + normalizeLocal(); + multLocal(maxLength); + return this; + } + + public float lengthSquared() { + return (x * x) + (y * y) + (z * z); + } + + public float length() { + return (float) Math.sqrt(lengthSquared()); + } + + public float distanceSquared(float x, float y, float z) { + float dx = this.x - x; + float dy = this.y - y; + float dz = this.z - z; + return (dx * dx) + (dy * dy) + (dz * dz); + } + + public float distance(float x, float y, float z) { + return (float) Math.sqrt(distanceSquared(x, y, z)); + } + + public float distanceSquared(Vector3f v) { + return distanceSquared(v.x, v.y, v.z); + } + + public float distance(Vector3f v) { + return (float) Math.sqrt(distanceSquared(v)); + } + + public Vector3f normalize() { + float length = length(); + if (length != 0) { + return divide(length); + } + return divide(1); + } + + public Vector3f normalizeLocal() { + float length = length(); + if (length != 0) { + return divideLocal(length); + } + return divideLocal(1); + } + + public float dot(Vector3f v) { + return (x * v.x) + (y * v.y) + z * (v.z); + } + + public Vector3f cross(float x, float y, float z) { + return new Vector3f((this.y * z) - (this.z * y), + (this.z * x) - (this.x * z), (this.x * y) - (this.y * x)); + } + + public Vector3f crossLocal(float x, float y, float z) { + this.x = (this.y * z) - (this.z * y); + this.y = (this.z * x) - (this.x * z); + this.z = (this.x * y) - (this.y * x); + return this; + } + + public Vector3f cross(Vector3f v) { + return new Vector3f((y * v.z) - (z * v.y), (z * v.x) - (x * v.z), + (x * v.y) - (y * v.x)); + } + + public Vector3f crossLocal(Vector3f v) { + x = (y * v.z) - (z * v.y); + y = (z * v.x) - (x * v.z); + z = (x * v.y) - (y * v.x); + return this; + } + + public Vector3f cross(Vector3f v, Vector3f result) { + if (result == null) + result = new Vector3f(); + return result.set(cross(v)); + } + + public Vector3f negate() { + return new Vector3f(-x, -y, -z); + } + + public Vector3f negateLocal() { + x = -x; + y = -y; + z = -z; + return this; + } + + public Vector3f add(float x, float y, float z) { + return new Vector3f(this.x + x, this.y + y, this.z + z); + } + + public Vector3f addLocal(float x, float y, float z) { + this.x += x; + this.y += y; + this.z += z; + return this; + } + + public Vector3f add(Vector3f v) { + return new Vector3f(x + v.x, y + v.y, z + v.z); + } + + public Vector3f addLocal(Vector3f v) { + x += v.x; + y += v.y; + z += v.z; + return this; + } + + public Vector3f add(Vector3f v, Vector3f result) { + if (result == null) + result = new Vector3f(); + return result.set(add(v)); + } + + public Vector3f subtract(float x, float y, float z) { + return new Vector3f(this.x - x, this.y - y, this.z - z); + } + + public Vector3f subtractLocal(float x, float y, float z) { + this.x -= x; + this.y -= y; + this.z -= z; + return this; + } + + public Vector3f subtract(Vector3f v) { + return new Vector3f(x - v.x, y - v.y, z - v.z); + } + + public Vector3f subtractLocal(Vector3f v) { + x -= v.x; + y -= v.y; + z -= v.z; + return this; + } + + public Vector3f subtract(Vector3f v, Vector3f result) { + if (result == null) + result = new Vector3f(); + return result.set(subtract(v)); + } + + public Vector3f mult(float x, float y, float z) { + return new Vector3f(this.x * x, this.y * y, this.z * z); + } + + public Vector3f multLocal(float x, float y, float z) { + this.x *= x; + this.y *= y; + this.z *= z; + return this; + } + + public Vector3f mult(Vector3f v) { + return new Vector3f(x * v.x, y * v.y, z * v.z); + } + + public Vector3f multLocal(Vector3f v) { + x *= v.x; + y *= v.y; + z *= v.z; + return this; + } + + public Vector3f mult(Vector3f v, Vector3f result) { + if (result == null) + result = new Vector3f(); + return result.set(mult(v)); + } + + public Vector3f divide(float x, float y, float z) { + return new Vector3f(this.x / x, this.y / y, this.z / z); + } + + public Vector3f divideLocal(float x, float y, float z) { + this.x /= x; + this.y /= y; + this.z /= z; + return this; + } + + public Vector3f divide(Vector3f v) { + return new Vector3f(x / v.x, y / v.y, z / v.z); + } + + public Vector3f divideLocal(Vector3f v) { + x /= v.x; + y /= v.y; + z /= v.z; + return this; + } + + public Vector3f mult(float scalar) { + return new Vector3f(x * scalar, y * scalar, z * scalar); + } + + public Vector3f multLocal(float scalar) { + x *= scalar; + y *= scalar; + z *= scalar; + return this; + } + + public Vector3f mult(Matrix3f m) { + float x0 = m.values[0] * x + m.values[1] * y + m.values[2] * z; + float y0 = m.values[3] * x + m.values[4] * y + m.values[5] * z; + float z0 = m.values[6] * x + m.values[7] * y + m.values[8] * z; + return new Vector3f(x0, y0, z0); + } + + public Vector3f multLocal(Matrix3f m) { + float x0 = m.values[0] * x + m.values[1] * y + m.values[2] * z; + float y0 = m.values[3] * x + m.values[4] * y + m.values[5] * z; + float z0 = m.values[6] * x + m.values[7] * y + m.values[8] * z; + set(x0, y0, z0); + return this; + } + + public Vector3f divide(float scalar) { + return new Vector3f(x / scalar, y / scalar, z / scalar); + } + + public Vector3f divideLocal(float scalar) { + x /= scalar; + y /= scalar; + z /= scalar; + return this; + } + + public Vector3f divide(Vector3f v, Vector3f result) { + if (result == null) + result = new Vector3f(); + return result.set(divide(v)); + } + + public Vector3f abs() { + return new Vector3f(Math.abs(x), Math.abs(y), Math.abs(z)); + } + + public Vector3f absLocal() { + x = Math.abs(x); + y = Math.abs(y); + z = Math.abs(z); + return this; + } + + public Vector3f min(Vector3f v) { + return new Vector3f(Math.min(x, v.x), Math.min(y, v.y), Math.min(z, v.z)); + } + + public Vector3f minLocal(Vector3f v) { + x = Math.min(x, v.x); + y = Math.min(y, v.y); + z = Math.min(z, v.z); + return this; + } + + public Vector3f minLocal(Vector3f a, Vector3f b) { + x = Math.min(a.x, b.x); + y = Math.min(a.y, b.y); + z = Math.min(a.z, b.z); + return this; + } + + public Vector3f max(Vector3f v) { + return new Vector3f(Math.max(x, v.x), Math.max(y, v.y), Math.max(z, v.z)); + } + + public Vector3f maxLocal(Vector3f v) { + x = Math.max(x, v.x); + y = Math.max(y, v.y); + z = Math.max(z, v.z); + return this; + } + + public Vector3f maxLocal(Vector3f a, Vector3f b) { + x = Math.max(a.x, b.x); + y = Math.max(a.y, b.y); + z = Math.max(a.z, b.z); + return this; + } + + public Vector3f reciprocal() { + return new Vector3f((x != 0 ? 1.0f / x : Float.POSITIVE_INFINITY), + (y != 0 ? 1.0f / y : Float.POSITIVE_INFINITY), + (z != 0 ? 1.0f / z : Float.POSITIVE_INFINITY)); + } + + public Vector3f lerpLocal(Vector3f finalVec, float changeAmnt) { + if (changeAmnt == 0) { + return this; + } + if (changeAmnt == 1) { + this.x = finalVec.x; + this.y = finalVec.y; + this.z = finalVec.z; + return this; + } + this.x = (1 - changeAmnt) * this.x + changeAmnt * finalVec.x; + this.y = (1 - changeAmnt) * this.y + changeAmnt * finalVec.y; + this.z = (1 - changeAmnt) * this.z + changeAmnt * finalVec.z; + return this; + } + + public Vector3f lerpLocal(Vector3f beginVec, Vector3f finalVec, + float changeAmnt) { + if (changeAmnt == 0) { + this.x = beginVec.x; + this.y = beginVec.y; + this.z = beginVec.z; + return this; + } + if (changeAmnt == 1) { + this.x = finalVec.x; + this.y = finalVec.y; + this.z = finalVec.z; + return this; + } + this.x = (1 - changeAmnt) * beginVec.x + changeAmnt * finalVec.x; + this.y = (1 - changeAmnt) * beginVec.y + changeAmnt * finalVec.y; + this.z = (1 - changeAmnt) * beginVec.z + changeAmnt * finalVec.z; + return this; + } + + public static boolean isValid(Vector3f v) { + if (v == null) + return false; + if (Float.isNaN(v.x) || Float.isNaN(v.y) || Float.isNaN(v.z)) + return false; + if (Float.isInfinite(v.x) || Float.isInfinite(v.y) || Float.isInfinite(v.z)) + return false; + return true; + } + + public Vector3f set(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + return this; + } + + public Vector3f set(Vector3f v) { + x = v.x; + y = v.y; + z = v.z; + return this; + } + + public Vector3f set(float[] values) { + x = values[0]; + y = values[1]; + z = values[2]; + return this; + } + + public float getX() { + return x; + } + + public Vector3f setX(float x) { + this.x = x; + return this; + } + + public float getY() { + return y; + } + + public Vector3f setY(float y) { + this.y = y; + return this; + } + + public float getZ() { + return z; + } + + public Vector3f setZ(float z) { + this.z = z; + return this; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Float.floatToIntBits(x); + result = prime * result + Float.floatToIntBits(y); + result = prime * result + Float.floatToIntBits(z); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Vector3f other = (Vector3f) obj; + if (Float.floatToIntBits(x) != Float.floatToIntBits(other.x)) + return false; + if (Float.floatToIntBits(y) != Float.floatToIntBits(other.y)) + return false; + if (Float.floatToIntBits(z) != Float.floatToIntBits(other.z)) + return false; + return true; + } + + @Override + public String toString() { + return "Vector3f [x=" + x + ", y=" + y + ", z=" + z + "]"; + } } From 1ac7fd7414a94213face4614d11a0027b103a4a9 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 21 Dec 2024 16:30:43 +0100 Subject: [PATCH 47/59] Feat: Added float get(int i) --- src/main/java/math/Vector3f.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/java/math/Vector3f.java b/src/main/java/math/Vector3f.java index 591fb892..935b0f5f 100644 --- a/src/main/java/math/Vector3f.java +++ b/src/main/java/math/Vector3f.java @@ -521,6 +521,19 @@ public Vector3f setZ(float z) { return this; } + public float get(int i) { + switch (i) { + case 0: + return x; + case 1: + return y; + case 2: + return z; + default: + throw new IndexOutOfBoundsException("Index must be 0, 1, or 2."); + } + } + @Override public int hashCode() { final int prime = 31; From 63346a42c1fd4937816455b87ca55d8724c0968a Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 21 Dec 2024 16:54:31 +0100 Subject: [PATCH 48/59] Add reset method to Timer class to reset all time and frame tracking values --- src/main/java/engine/Timer.java | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/main/java/engine/Timer.java b/src/main/java/engine/Timer.java index 135e3d83..e0c30fde 100644 --- a/src/main/java/engine/Timer.java +++ b/src/main/java/engine/Timer.java @@ -105,6 +105,34 @@ public void update() { updateFPS(); } + /** + * Resets the {@code Timer} to its initial state, clearing all accumulated + * time and frame count values. This includes resetting the following: + *

    + *
  • The start time to the current system time.
  • + *
  • The last time recorded to the current system time.
  • + *
  • The total elapsed time to zero.
  • + *
  • The frame count to zero.
  • + *
  • The frames per second (FPS) to zero.
  • + *
  • The millisecond counter to zero.
  • + *
  • The last frame count for FPS calculation to zero.
  • + *
+ *

+ * This method can be used when you need to restart the timer, such as for + * restarting the game / application or resetting the simulation state. + *

+ */ + public void reset() { + this.startTime = System.nanoTime(); + this.lastTime = startTime; + this.time = 0; + this.totalTime = 0; + this.frameCount = 0; + this.fps = 0; + this.millisecondCounter = 0; + this.lastFrameCount = 0; + } + /** * Returns the total elapsed time in seconds, scaled by the current time * scale. From af6b8ac39cf87bdc9d2415dd159899da4d3b2bd1 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 21 Dec 2024 17:08:23 +0100 Subject: [PATCH 49/59] Add Bounds class for 3D axis-aligned bounding box (AABB) calculations - Introduced the `Bounds` class to represent an axis-aligned bounding box, defined by minimum and maximum corners. - Implemented methods for checking point containment, expanding the box, encapsulating points, and calculating distances. - Added intersection methods for detecting ray and box intersections, as well as overlap between two bounding boxes. - Provided utility methods for getting and setting the minimum and maximum corners of the bounding box. This class will be useful for collision detection, frustum culling, and other spatial queries in 3D environments. --- src/main/java/math/Bounds.java | 189 +++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 src/main/java/math/Bounds.java diff --git a/src/main/java/math/Bounds.java b/src/main/java/math/Bounds.java new file mode 100644 index 00000000..989a7ea1 --- /dev/null +++ b/src/main/java/math/Bounds.java @@ -0,0 +1,189 @@ +package math; + +/** + * The {@code Bounds} class represents a 3D axis-aligned bounding box (AABB), + * defined by two {@link Vector3f} points: the minimum and maximum corners. This + * class provides various utility methods for manipulating and querying the + * bounding box, such as checking if a point is contained within the bounds, + * expanding the bounds, and testing for intersections with rays or other + * bounds. + * + *

+ * A bounding box is often used in 3D graphics for collision detection, frustum + * culling, and other spatial queries. + *

+ */ +public class Bounds { + + /** + * The minimum corner of the bounding box. + */ + private Vector3f min; + + /** + * The maximum corner of the bounding box. + */ + private Vector3f max; + + /** + * Constructs a new {@code Bounds} object with the specified minimum and + * maximum corners. + * + * @param min the minimum corner of the bounding box + * @param max the maximum corner of the bounding box + * @throws IllegalArgumentException if either {@code min} or {@code max} is + * {@code null} + */ + public Bounds(Vector3f min, Vector3f max) { + if (min == null) { + throw new IllegalArgumentException("Min cannot be null."); + } + if (max == null) { + throw new IllegalArgumentException("Max cannot be null."); + } + this.min = new Vector3f(min); + this.max = new Vector3f(max); + } + + /** + * Returns the closest point on the bounding box to the given {@code point}. + * The closest point is determined by clamping each coordinate of the point + * between the minimum and maximum bounds of the box. + * + * @param point the point to clamp to the bounding box + * @return a new {@code Vector3f} representing the closest point on the + * bounding box + */ + public Vector3f closestPoint(Vector3f point) { + float x = Math.max(min.x, Math.min(max.x, point.x)); + float y = Math.max(min.y, Math.min(max.y, point.y)); + float z = Math.max(min.z, Math.min(max.z, point.z)); + return new Vector3f(x, y, z); + } + + /** + * Checks if the given {@code point} is inside the bounding box. The point is + * considered inside if all of its coordinates are between the minimum and + * maximum coordinates of the box. + * + * @param point the point to check + * @return {@code true} if the point is inside the bounding box, {@code false} + * otherwise + */ + public boolean contains(Vector3f point) { + return point.x >= min.x && point.x <= max.x && point.y >= min.y + && point.y <= max.y && point.z >= min.z && point.z <= max.z; + } + + /** + * Expands the bounding box to encompass the given {@code point}. If the point + * is outside the current bounds, the box will be enlarged to include it. + * + * @param point the point to include in the bounding box + */ + public void encapsulate(Vector3f point) { + min = new Vector3f(Math.min(min.x, point.x), Math.min(min.y, point.y), + Math.min(min.z, point.z)); + max = new Vector3f(Math.max(max.x, point.x), Math.max(max.y, point.y), + Math.max(max.z, point.z)); + } + + /** + * Expands the bounding box by the given {@code amount}. The expansion is done + * uniformly along all axes, increasing the size of the bounding box by the + * specified amount. + * + * @param amount the amount to expand the bounding box by + */ + public void expand(float amount) { + Vector3f expansion = new Vector3f(amount / 2, amount / 2, amount / 2); + min = min.subtract(expansion); + max = max.add(expansion); + } + + /** + * Tests if the given {@code ray} intersects the bounding box. The + * intersection is checked using the slab method, which determines if the ray + * intersects the box along each axis. + * + * @param ray the ray to test for intersection + * @return {@code true} if the ray intersects the bounding box, {@code false} + * otherwise + */ + public boolean intersectRay(Ray3f ray) { + Vector3f invDir = ray.getDirection().reciprocal(); + Vector3f tMin = min.subtract(ray.getOrigin()).mult(invDir); + Vector3f tMax = max.subtract(ray.getOrigin()).mult(invDir); + + float t1 = Math.min(tMin.x, tMax.x); + float t2 = Math.max(tMin.x, tMax.x); + + for (int i = 1; i < 3; i++) { // For y and z axes + t1 = Math.max(t1, Math.min(tMin.get(i), tMax.get(i))); + t2 = Math.min(t2, Math.max(tMin.get(i), tMax.get(i))); + } + + return t1 <= t2 && t2 >= 0; + } + + /** + * Tests if this bounding box intersects another {@code Bounds}. The + * intersection is checked by comparing the min and max coordinates of both + * boxes. + * + * @param other the other bounding box to check for intersection + * @return {@code true} if the bounding boxes intersect, {@code false} + * otherwise + */ + public boolean intersects(Bounds other) { + return min.x <= other.max.x && max.x >= other.min.x && min.y <= other.max.y + && max.y >= other.min.y && min.z <= other.max.z && max.z >= other.min.z; + } + + /** + * Calculates the squared distance from the given {@code point} to the closest + * point on the bounding box. This method avoids calculating the square root + * for performance reasons, returning the squared distance instead. + * + * @param point the point to calculate the squared distance from + * @return the squared distance from the point to the closest point on the + * bounding box + */ + public float sqrDistance(Vector3f point) { + float dx = Math.max(0, Math.max(min.x - point.x, point.x - max.x)); + float dy = Math.max(0, Math.max(min.y - point.y, point.y - max.y)); + float dz = Math.max(0, Math.max(min.z - point.z, point.z - max.z)); + return dx * dx + dy * dy + dz * dz; + } + + /** + * Sets the minimum and maximum corners of the bounding box to the specified + * values. + * + * @param min the new minimum corner + * @param max the new maximum corner + */ + public void setMinMax(Vector3f min, Vector3f max) { + this.min = new Vector3f(min); + this.max = new Vector3f(max); + } + + /** + * Returns the minimum corner of the bounding box. + * + * @return the minimum corner of the bounding box + */ + public Vector3f getMin() { + return min; + } + + /** + * Returns the maximum corner of the bounding box. + * + * @return the maximum corner of the bounding box + */ + public Vector3f getMax() { + return max; + } + +} \ No newline at end of file From f430832aa4856daaf81de1f882d41be92dfd541f Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 21 Dec 2024 17:50:56 +0100 Subject: [PATCH 50/59] feat: add precomputed `directionInv` to `Ray3f` for optimized ray-box intersection - Introduced the `directionInv` field in the `Ray3f` class to store the reciprocal of the direction vector components. - Precomputing the reciprocal reduces computation overhead in operations like ray-box intersections, improving performance. - Updated the constructor to calculate and assign `directionInv` during ray initialization. --- src/main/java/math/Ray3f.java | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/main/java/math/Ray3f.java b/src/main/java/math/Ray3f.java index a0f0b537..1dd351dc 100644 --- a/src/main/java/math/Ray3f.java +++ b/src/main/java/math/Ray3f.java @@ -22,6 +22,12 @@ public class Ray3f { */ private final Vector3f direction; + /** + * The reciprocal of the direction vector, used for optimized ray-box + * intersection. + */ + private final Vector3f directionInv; + /** * Constructs a new {@code Ray3f} with the given origin and direction. *

@@ -45,10 +51,14 @@ public Ray3f(Vector3f origin, Vector3f direction) { this.origin = origin; this.direction = direction; this.direction.normalizeLocal(); + this.directionInv = direction.reciprocal(); } /** * Returns the origin of the ray. + *

+ * The origin is the starting point from which the ray emanates. + *

* * @return The origin of the ray. */ @@ -58,6 +68,11 @@ public Vector3f getOrigin() { /** * Returns the normalized direction vector of the ray. + *

+ * The direction vector defines the direction in which the ray travels. The + * vector is normalized, ensuring consistent calculations for operations like + * intersections. + *

* * @return The direction vector of the ray. */ @@ -65,6 +80,20 @@ public Vector3f getDirection() { return direction; } + /** + * Returns the reciprocal of the direction vector of the ray. + *

+ * The reciprocal of the direction vector is precomputed to optimize ray-box + * intersection tests, where division by components of the direction vector is + * required. + *

+ * + * @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}. *

@@ -77,10 +106,12 @@ public Vector3f getDirection() { * * * where {@code t} is a scalar representing the distance along the ray from - * the origin. + * 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)- + * @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) { From 9c68ba7f40929f4c41952fdc7f07d9a084a3f452 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 21 Dec 2024 19:07:58 +0100 Subject: [PATCH 51/59] Fix --- src/main/java/math/Bounds.java | 98 ++++++++++++++++++++++++---------- 1 file changed, 69 insertions(+), 29 deletions(-) diff --git a/src/main/java/math/Bounds.java b/src/main/java/math/Bounds.java index 989a7ea1..6e4931e0 100644 --- a/src/main/java/math/Bounds.java +++ b/src/main/java/math/Bounds.java @@ -66,26 +66,24 @@ public Vector3f closestPoint(Vector3f point) { * considered inside if all of its coordinates are between the minimum and * maximum coordinates of the box. * - * @param point the point to check + * @param p the point to check * @return {@code true} if the point is inside the bounding box, {@code false} * otherwise */ - public boolean contains(Vector3f point) { - return point.x >= min.x && point.x <= max.x && point.y >= min.y - && point.y <= max.y && point.z >= min.z && point.z <= max.z; + public boolean contains(Vector3f p) { + return p.x >= min.x && p.x <= max.x && p.y >= min.y + && p.y <= max.y && p.z >= min.z && p.z <= max.z; } /** * Expands the bounding box to encompass the given {@code point}. If the point * is outside the current bounds, the box will be enlarged to include it. * - * @param point the point to include in the bounding box + * @param p the point to include in the bounding box */ - public void encapsulate(Vector3f point) { - min = new Vector3f(Math.min(min.x, point.x), Math.min(min.y, point.y), - Math.min(min.z, point.z)); - max = new Vector3f(Math.max(max.x, point.x), Math.max(max.y, point.y), - Math.max(max.z, point.z)); + public void encapsulate(Vector3f p) { + min.set(Math.min(min.x, p.x), Math.min(min.y, p.y), Math.min(min.z, p.z)); + max.set(Math.max(max.x, p.x), Math.max(max.y, p.y), Math.max(max.z, p.z)); } /** @@ -96,34 +94,72 @@ public void encapsulate(Vector3f point) { * @param amount the amount to expand the bounding box by */ public void expand(float amount) { - Vector3f expansion = new Vector3f(amount / 2, amount / 2, amount / 2); - min = min.subtract(expansion); - max = max.add(expansion); + float halfAmount = amount / 2; + min.subtractLocal(halfAmount, halfAmount, halfAmount); + max.addLocal(halfAmount, halfAmount, halfAmount); } /** - * Tests if the given {@code ray} intersects the bounding box. The - * intersection is checked using the slab method, which determines if the ray - * intersects the box along each axis. + * Tests whether the given ray intersects this axis-aligned bounding box + * (AABB). + *

+ * The method uses the slab method to compute the intersection by checking the + * ray's position and direction relative to the box's bounds in each axis (x, + * y, z). It accounts for parallel rays and updates intersection intervals to + * determine if there is an overlap. + *

* - * @param ray the ray to test for intersection - * @return {@code true} if the ray intersects the bounding box, {@code false} - * otherwise + * @param ray the {@link Ray3f} to test for intersection with this AABB. The + * ray must have its inverse direction precomputed and accessible + * via {@code ray.getDirectionInv()} for optimal performance. + * @return {@code true} if the ray intersects the AABB, {@code false} + * otherwise. */ public boolean intersectRay(Ray3f ray) { - Vector3f invDir = ray.getDirection().reciprocal(); - Vector3f tMin = min.subtract(ray.getOrigin()).mult(invDir); - Vector3f tMax = max.subtract(ray.getOrigin()).mult(invDir); + if (ray.getDirection().isZero()) { + return false; // A ray with zero direction cannot intersect anything + } - float t1 = Math.min(tMin.x, tMax.x); - float t2 = Math.max(tMin.x, tMax.x); + if (min.equals(max)) { + return ray.getOrigin().equals(min); + } - for (int i = 1; i < 3; i++) { // For y and z axes - t1 = Math.max(t1, Math.min(tMin.get(i), tMax.get(i))); - t2 = Math.min(t2, Math.max(tMin.get(i), tMax.get(i))); + float tmin = 0.0f; + float tmax = Float.POSITIVE_INFINITY; + + for (int d = 0; d < 3; ++d) { + float invDir = ray.getDirectionInv().get(d); + + // Handle zero direction component + if (invDir == 0.0f) { + if (ray.getOrigin().get(d) < min.get(d) + || ray.getOrigin().get(d) > max.get(d)) { + return false; + } + continue; + } + + float bmin, bmax; + if (invDir < 0.0f) { + bmin = max.get(d); + bmax = min.get(d); + } else { + bmin = min.get(d); + bmax = max.get(d); + } + + float dmin = (bmin - ray.getOrigin().get(d)) * invDir; + float dmax = (bmax - ray.getOrigin().get(d)) * invDir; + + tmin = Math.max(tmin, dmin); + tmax = Math.min(tmax, dmax); + + if (tmin > tmax) { + return false; + } } - return t1 <= t2 && t2 >= 0; + return true; } /** @@ -162,8 +198,12 @@ public float sqrDistance(Vector3f point) { * * @param min the new minimum corner * @param max the new maximum corner + * @throws IllegalArgumentException if min or max is null. */ public void setMinMax(Vector3f min, Vector3f max) { + if (min == null || max == null) { + throw new IllegalArgumentException("Min and Max cannot be null."); + } this.min = new Vector3f(min); this.max = new Vector3f(max); } @@ -185,5 +225,5 @@ public Vector3f getMin() { public Vector3f getMax() { return max; } - + } \ No newline at end of file From a8d746bc6d49e6bd4e985cbe80f51a9ef4c7efbd Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 21 Dec 2024 19:12:02 +0100 Subject: [PATCH 52/59] Changes method name. --- src/main/java/math/Bounds.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/math/Bounds.java b/src/main/java/math/Bounds.java index 6e4931e0..57f62990 100644 --- a/src/main/java/math/Bounds.java +++ b/src/main/java/math/Bounds.java @@ -115,7 +115,7 @@ public void expand(float amount) { * @return {@code true} if the ray intersects the AABB, {@code false} * otherwise. */ - public boolean intersectRay(Ray3f ray) { + public boolean intersectsRay(Ray3f ray) { if (ray.getDirection().isZero()) { return false; // A ray with zero direction cannot intersect anything } From 1b24fc9efe79a347b55f16cd7c6afc6e4d923d3e Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 21 Dec 2024 19:48:55 +0100 Subject: [PATCH 53/59] Add bounding box to Geometry class for culling, spatial partitioning, and debugging - Implemented a bounding box calculation for the Geometry class, utilizing MeshBoundsCalculator. - Bounding box is now accessible for culling and spatial partitioning optimizations. - Updated debug rendering to visualize the bounding box in red for easier debugging of mesh extents. - Added documentation explaining the broader use of the bounding box beyond debugging (e.g., for frustum culling). --- src/main/java/engine/components/Geometry.java | 172 +++++++++++------- 1 file changed, 111 insertions(+), 61 deletions(-) diff --git a/src/main/java/engine/components/Geometry.java b/src/main/java/engine/components/Geometry.java index 660026c0..b54cb960 100644 --- a/src/main/java/engine/components/Geometry.java +++ b/src/main/java/engine/components/Geometry.java @@ -1,68 +1,75 @@ package engine.components; import engine.render.Material; +import math.Bounds; +import math.Color; import mesh.Mesh3D; +import mesh.util.MeshBoundsCalculator; import workspace.ui.Graphics; /** - * Represents a renderable geometry component within the scene graph. - *

- * The Geometry class is a component that encapsulates a 3D mesh and its - * associated material. It implements rendering behavior through the - * {@link RenderableComponent} interface, allowing it to be drawn during the - * rendering pass of the scene graph traversal. - *

- *

- * This class is responsible for applying the provided {@link Material} during - * rendering, ensuring proper visualization of the associated {@link Mesh3D}. - *

+ * The {@code Geometry} class represents a 3D object in a scene with a mesh and + * material applied to it. It is responsible for rendering the mesh and applying + * the appropriate material to it. The class also provides access to the mesh's + * bounding box, which is useful for purposes like culling, spatial + * partitioning, and debugging. * - *

Key Features

- *
    - *
  • Supports a default white material if no custom material is provided.
  • - *
  • Handles cleanup by releasing references to the mesh and material.
  • - *
  • Integrates with the graphics context to issue rendering commands via the - * {@link Graphics} class.
  • - *
+ * This class implements the {@link RenderableComponent} interface, indicating + * that it has a render method to be invoked during the render loop of the + * engine. + * + * @see RenderableComponent + * @see Material + * @see Mesh3D + * @see Bounds */ public class Geometry extends AbstractComponent implements RenderableComponent { - /** The 3D mesh to be rendered by this component. */ + /** The mesh representing the geometry of the object. */ private Mesh3D mesh; - /** The material associated with this geometry for rendering purposes. */ + /** The material applied to the mesh for rendering. */ private Material material; /** - * Constructs a Geometry component with a provided mesh and the default white + * The bounding box of the mesh used for culling, spatial partitioning, and + * debugging. + */ + private Bounds bounds; + + /** + * Constructs a {@code Geometry} with the specified mesh and a default * material. - * - * @param mesh The mesh to associate with this geometry. + * + * @param mesh The {@link Mesh3D} object representing the geometry of the + * object. + * @throws IllegalArgumentException If the mesh is {@code null}. */ public Geometry(Mesh3D mesh) { this(mesh, Material.DEFAULT_WHITE); } /** - * Constructs a Geometry component with a specific mesh and material. - * - * @param mesh The 3D mesh to associate with this geometry. Must not be - * null. - * @param material The material to use for rendering. Must not be null. - * @throws IllegalArgumentException if either mesh or material is null. + * Constructs a {@code Geometry} with the specified mesh and material. + * + * @param mesh The {@link Mesh3D} object representing the geometry of the + * object. + * @param material The {@link Material} to be applied to the mesh. + * @throws IllegalArgumentException If the mesh or material is {@code null}. */ public Geometry(Mesh3D mesh, Material material) { validate(mesh, material); this.mesh = mesh; this.material = material; + this.bounds = MeshBoundsCalculator.calculateBounds(mesh); } /** - * Validates the provided mesh and material to ensure they are non-null. - * - * @param mesh The 3D mesh to validate. - * @param material The material to validate. - * @throws IllegalArgumentException if either mesh or material is null. + * Validates the mesh and material to ensure they are not {@code null}. + * + * @param mesh The {@link Mesh3D} object to validate. + * @param material The {@link Material} to validate. + * @throws IllegalArgumentException If the mesh or material is {@code null}. */ private void validate(Mesh3D mesh, Material material) { if (mesh == null) { @@ -74,53 +81,96 @@ private void validate(Mesh3D mesh, Material material) { } /** - * Cleans up resources associated with this component by nullifying references - * to the mesh and material. This is called when the component is no longer - * needed or during application shutdown. + * Renders the geometry by applying the material and drawing the mesh using + * the specified graphics context. + * + * @param g The {@link Graphics} context used for rendering. */ @Override - public void cleanup() { - mesh = null; - material = null; + public void render(Graphics g) { + material.apply(g); + g.fillFaces(mesh); + material.release(g); + debugRenderBounds(g); } /** - * Handles rendering of the mesh with its material using the provided graphics - * context. + * Debugs the rendering by drawing the bounding box of the mesh using the + * specified graphics context. The bounding box is rendered in red to help + * visualize the mesh's extents. This method can be used for debugging + * purposes to ensure the mesh is properly positioned and scaled in the scene. * *

- * This method applies the material, renders the associated mesh's faces using - * the {@link Graphics} instance, and then releases the material's state - * afterward. + * Beyond debugging, the bounding box is useful for spatial partitioning + * techniques like frustum culling, as well as for determining the overall + * size and position of the mesh in the 3D world. *

- * - * @param g The graphics context to use for rendering the geometry. + * + * @param g The {@link Graphics} context used for rendering the debug bounding + * box. + */ + public void debugRenderBounds(Graphics g) { + if (bounds == null) { + return; + } + + g.setColor(Color.RED); + + // Extract corner points for readability + float minX = bounds.getMin().x; + float minY = bounds.getMin().y; + float minZ = bounds.getMin().z; + float maxX = bounds.getMax().x; + float maxY = bounds.getMax().y; + float maxZ = bounds.getMax().z; + + // Draw lines for each edge of the bounding box + g.drawLine(minX, minY, minZ, maxX, minY, minZ); + g.drawLine(minX, minY, minZ, minX, maxY, minZ); + g.drawLine(minX, minY, minZ, minX, minY, maxZ); + + g.drawLine(maxX, maxY, maxZ, minX, maxY, maxZ); + g.drawLine(maxX, maxY, maxZ, maxX, minY, maxZ); + g.drawLine(maxX, maxY, maxZ, maxX, maxY, minZ); + + g.drawLine(minX, maxY, minZ, maxX, maxY, minZ); + g.drawLine(maxX, minY, minZ, maxX, maxY, minZ); + g.drawLine(maxX, minY, minZ, maxX, minY, maxZ); + + g.drawLine(minX, maxY, maxZ, minX, minY, maxZ); + g.drawLine(maxX, minY, maxZ, minX, minY, maxZ); + g.drawLine(minX, maxY, maxZ, minX, maxY, minZ); + } + + /** + * Updates the state of the geometry. This method is a placeholder for + * potential updates to the mesh state over time. + * + * @param tpf The time per frame used for the update (in seconds). */ @Override - public void render(Graphics g) { - material.apply(g); - g.fillFaces(mesh); - material.release(g); + public void update(float tpf) { + // Placeholder for potential mesh state updates } /** - * Placeholder for initialization logic if needed in future development. - * Currently, no specific initialization is necessary. + * Called when the component is attached to a parent object in the scene. This + * method is a hook for additional initialization or setup when the component + * is added. */ @Override - public void initialize() { - // Initialization logic, if needed + public void onAttach() { + // Hook for additional setup on attachment } /** - * Updates geometry's state over time, if necessary. Currently, this is a - * placeholder for potential future logic. - * - * @param tpf Time per frame, used to synchronize updates across frames. + * Called when the component is detached from its parent object in the scene. + * This method is a hook for cleanup or other actions when the component is + * removed. */ @Override - public void update(float tpf) { - // Placeholder for potential mesh state updates + public void onDetach() { + // Hook for cleanup on detachment } } \ No newline at end of file From 0a801143dc07d335f0c9c5e944bad9a7530efaa9 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 21 Dec 2024 19:49:57 +0100 Subject: [PATCH 54/59] Format and name changes. --- src/main/java/engine/components/Transform.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/main/java/engine/components/Transform.java b/src/main/java/engine/components/Transform.java index b4786599..dd4a0d84 100644 --- a/src/main/java/engine/components/Transform.java +++ b/src/main/java/engine/components/Transform.java @@ -232,17 +232,14 @@ public void setScale(float sx, float sy, float sz) { @Override public void update(float tpf) { - // Logic for dynamic transform updates if needed. } @Override - public void initialize() { - // Logic for additional initialization. + public void onAttach() { } @Override - public void cleanup() { - // Cleanup logic if required. + public void onDetach() { } } \ No newline at end of file From c11cf48ff9a8b62c07a736ed4ddbdddcf117afec Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 21 Dec 2024 19:52:54 +0100 Subject: [PATCH 55/59] Method name changes. --- src/main/java/engine/components/Component.java | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/main/java/engine/components/Component.java b/src/main/java/engine/components/Component.java index c216d6fc..9cd5fc4c 100644 --- a/src/main/java/engine/components/Component.java +++ b/src/main/java/engine/components/Component.java @@ -10,8 +10,8 @@ * behaviors, following a component-based design pattern. *

*

- * Each component should manage its lifecycle, with {@code initialize()}, - * {@code update()}, and {@code cleanup()} methods, allowing nodes to manage + * Each component should manage its lifecycle, with {@code onAttach()}, + * {@code update()}, and {@code onDetach()} methods, allowing nodes to manage * their behavior lifecycle cleanly. *

*/ @@ -44,24 +44,22 @@ public interface Component { void update(float tpf); /** - * Initializes the component before it becomes active. + * Called when the component is attached to a {@link SceneNode}. *

- * This is called once after the component is attached to a {@link SceneNode}. - * It allows the component to set up necessary resources, state, or perform - * other preparatory work. + * This allows the component to set up necessary resources, state, or perform + * other preparatory work specific to being added to a node. *

*/ - void initialize(); + void onAttach(); /** - * Cleans up resources and performs necessary teardown when the component is - * removed or the scene is unloaded. + * Called when the component is detached from a {@link SceneNode}. *

* This ensures no memory is leaked, threads are terminated, or other * resources are left hanging by cleaning up internal state and releasing * references. *

*/ - void cleanup(); + void onDetach(); } \ No newline at end of file From 6e6fcc2e134693e3c24b8be98d08ebd1bfc42f01 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 21 Dec 2024 20:03:51 +0100 Subject: [PATCH 56/59] Changed init cleanup to attach detach --- .../engine/components/AbstractComponent.java | 2 +- .../CinematicBlackBarsRenderer.java | 20 +-------- .../CircularAnimationComponent.java | 20 +-------- .../java/engine/components/ControlWASD.java | 41 ++++++++----------- src/main/java/engine/components/Geometry.java | 12 ------ .../engine/components/RotationComponent.java | 8 ++-- 6 files changed, 25 insertions(+), 78 deletions(-) diff --git a/src/main/java/engine/components/AbstractComponent.java b/src/main/java/engine/components/AbstractComponent.java index 8c04f309..669586e0 100644 --- a/src/main/java/engine/components/AbstractComponent.java +++ b/src/main/java/engine/components/AbstractComponent.java @@ -37,5 +37,5 @@ public void setOwner(SceneNode owner) { public SceneNode getOwner() { return owner; } - + } \ No newline at end of file diff --git a/src/main/java/engine/components/CinematicBlackBarsRenderer.java b/src/main/java/engine/components/CinematicBlackBarsRenderer.java index 94841370..7a397239 100644 --- a/src/main/java/engine/components/CinematicBlackBarsRenderer.java +++ b/src/main/java/engine/components/CinematicBlackBarsRenderer.java @@ -192,28 +192,12 @@ private void renderBottomBar(Graphics g) { (int) currentSize); } - /** - * Initialization logic for this renderer. - *

- * Currently unimplemented but provided as a placeholder for any setup - * required when initializing this component in a larger system. - *

- */ @Override - public void initialize() { - // Placeholder for initialization logic. + public void onAttach() { } - /** - * Cleans up resources when the renderer is no longer needed. - *

- * Currently unimplemented but can be used to clean up graphics resources, - * listeners, or other allocated resources in a larger system. - *

- */ @Override - public void cleanup() { - // Placeholder for cleanup logic. + public void onDetach() { } } \ No newline at end of file diff --git a/src/main/java/engine/components/CircularAnimationComponent.java b/src/main/java/engine/components/CircularAnimationComponent.java index 38e794e5..4e4aa193 100644 --- a/src/main/java/engine/components/CircularAnimationComponent.java +++ b/src/main/java/engine/components/CircularAnimationComponent.java @@ -99,28 +99,12 @@ public void update(float tpf) { transform.setPosition(x, transform.getPosition().y, z); } - /** - * Handles any initialization logic required before the animation starts. - *

- * Currently, this is left blank but can be implemented if preconditions need - * to be set before the animation begins. - *

- */ @Override - public void initialize() { - // Placeholder for potential initialization logic + public void onAttach() { } - /** - * Handles any cleanup logic when the animation component is no longer needed. - *

- * Currently, this is left blank but can be implemented if resources need to - * be released or reset during cleanup. - *

- */ @Override - public void cleanup() { - // Placeholder for potential cleanup logic + public void onDetach() { } } \ No newline at end of file diff --git a/src/main/java/engine/components/ControlWASD.java b/src/main/java/engine/components/ControlWASD.java index aa039778..87b2d42e 100644 --- a/src/main/java/engine/components/ControlWASD.java +++ b/src/main/java/engine/components/ControlWASD.java @@ -73,15 +73,6 @@ public ControlWASD(Input input, float speed) { this.speed = speed; } - /** - * Initializes the component. Currently no initialization logic is required, - * but this is provided as a placeholder for future logic. - */ - @Override - public void initialize() { - // No additional initialization logic required at this stage. - } - /** * Updates the movement of the owning node based on keyboard input. *

@@ -113,12 +104,16 @@ public void update(float tpf) { */ private Vector3f handleInput() { Vector3f velocity = new Vector3f(); - + // Check for movement inputs - if (input.isKeyPressed(Key.W)) velocity.addLocal(0, 0, -1); - if (input.isKeyPressed(Key.A)) velocity.addLocal(-1, 0, 0); - if (input.isKeyPressed(Key.S)) velocity.addLocal(0, 0, 1); - if (input.isKeyPressed(Key.D)) velocity.addLocal(1, 0, 0); + if (input.isKeyPressed(Key.W)) + velocity.addLocal(0, 0, -1); + if (input.isKeyPressed(Key.A)) + velocity.addLocal(-1, 0, 0); + if (input.isKeyPressed(Key.S)) + velocity.addLocal(0, 0, 1); + if (input.isKeyPressed(Key.D)) + velocity.addLocal(1, 0, 0); // Normalize diagonal movement to prevent unintended speed boosts if (velocity.length() > 0) { @@ -127,16 +122,6 @@ private Vector3f handleInput() { return velocity; } - /** - * Cleans up resources when the component is removed or the application is - * shutting down. Currently, there is no cleanup logic necessary, but this is - * a placeholder for extensibility. - */ - @Override - public void cleanup() { - // Cleanup logic placeholder - } - /** * Retrieves the current movement speed multiplier. * @@ -167,4 +152,12 @@ public void setSpeed(float speed) { this.speed = speed; } + @Override + public void onAttach() { + } + + @Override + public void onDetach() { + } + } \ No newline at end of file diff --git a/src/main/java/engine/components/Geometry.java b/src/main/java/engine/components/Geometry.java index b54cb960..eb658fc6 100644 --- a/src/main/java/engine/components/Geometry.java +++ b/src/main/java/engine/components/Geometry.java @@ -153,24 +153,12 @@ public void update(float tpf) { // Placeholder for potential mesh state updates } - /** - * Called when the component is attached to a parent object in the scene. This - * method is a hook for additional initialization or setup when the component - * is added. - */ @Override public void onAttach() { - // Hook for additional setup on attachment } - /** - * Called when the component is detached from its parent object in the scene. - * This method is a hook for cleanup or other actions when the component is - * removed. - */ @Override public void onDetach() { - // Hook for cleanup on detachment } } \ No newline at end of file diff --git a/src/main/java/engine/components/RotationComponent.java b/src/main/java/engine/components/RotationComponent.java index 3fcaa873..dbdab16f 100644 --- a/src/main/java/engine/components/RotationComponent.java +++ b/src/main/java/engine/components/RotationComponent.java @@ -112,13 +112,11 @@ public float getAngularSpeed() { } @Override - public void initialize() { - // Initialization logic if needed + public void onAttach() { } @Override - public void cleanup() { - // Cleanup logic if needed + public void onDetach() { } - + } \ No newline at end of file From 770227ad48fadb871935c81fbe203d31d3f0f59d Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sun, 22 Dec 2024 11:25:53 +0100 Subject: [PATCH 57/59] Format changes. --- src/main/java/mesh/Edge3D.java | 80 +++--- src/main/java/mesh/Face3D.java | 92 +++--- src/main/java/mesh/Mesh3D.java | 507 ++++++++++++++++----------------- 3 files changed, 330 insertions(+), 349 deletions(-) diff --git a/src/main/java/mesh/Edge3D.java b/src/main/java/mesh/Edge3D.java index 8f9387f8..ea4d3262 100644 --- a/src/main/java/mesh/Edge3D.java +++ b/src/main/java/mesh/Edge3D.java @@ -2,47 +2,41 @@ public class Edge3D { - public int fromIndex; - - public int toIndex; - - public Edge3D(int fromIndex, int toIndex) { - this.fromIndex = fromIndex; - this.toIndex = toIndex; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + fromIndex; - result = prime * result + toIndex; - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - Edge3D other = (Edge3D) obj; - if (fromIndex != other.fromIndex) - return false; - if (toIndex != other.toIndex) - return false; - return true; - } - - public Edge3D createPair() { - return new Edge3D(toIndex, fromIndex); - } - - @Override - public String toString() { - return "Edge3D [fromIndex=" + fromIndex + ", toIndex=" + toIndex + "]"; - } - + public int fromIndex; + + public int toIndex; + + public Edge3D(int fromIndex, int toIndex) { + this.fromIndex = fromIndex; + this.toIndex = toIndex; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + fromIndex; + result = prime * result + toIndex; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + Edge3D other = (Edge3D) obj; + if (fromIndex != other.fromIndex) return false; + if (toIndex != other.toIndex) return false; + return true; + } + + public Edge3D createPair() { + return new Edge3D(toIndex, fromIndex); + } + + @Override + public String toString() { + return "Edge3D [fromIndex=" + fromIndex + ", toIndex=" + toIndex + "]"; + } } diff --git a/src/main/java/mesh/Face3D.java b/src/main/java/mesh/Face3D.java index 5a84154c..46a00af0 100644 --- a/src/main/java/mesh/Face3D.java +++ b/src/main/java/mesh/Face3D.java @@ -7,51 +7,49 @@ public class Face3D { - public Color color; - - public int[] indices; - - public Vector3f normal; - - public String tag; - - public Face3D() { - this(new int[0]); - } - - public Face3D(int... indices) { - 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]; - } - - public boolean sharesSameIndices(Face3D face) { - int[] indices0 = Arrays.copyOf(face.indices, face.indices.length); - int[] indices1 = Arrays.copyOf(indices, indices.length); - Arrays.sort(indices0); - Arrays.sort(indices1); - return Arrays.equals(indices0, indices1); - } - - public int getIndexAt(int index) { - return indices[index % indices.length]; - } - - public int getVertexCount() { - return indices.length; - } - - public Face3D(Face3D f) { - this(f.indices); - this.tag = new String(f.tag); - } - - @Override - public String toString() { - return "Face3D [indices=" + Arrays.toString(indices) + "]"; - } - + public Color color; + + public int[] indices; + + public Vector3f normal; + + public String tag; + + public Face3D() { + this(new int[0]); + } + + public Face3D(int... indices) { + 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]; + } + + public boolean sharesSameIndices(Face3D face) { + int[] indices0 = Arrays.copyOf(face.indices, face.indices.length); + int[] indices1 = Arrays.copyOf(indices, indices.length); + Arrays.sort(indices0); + Arrays.sort(indices1); + return Arrays.equals(indices0, indices1); + } + + public int getIndexAt(int index) { + return indices[index % indices.length]; + } + + public int getVertexCount() { + return indices.length; + } + + public Face3D(Face3D f) { + this(f.indices); + this.tag = new String(f.tag); + } + + @Override + public String toString() { + return "Face3D [indices=" + Arrays.toString(indices) + "]"; + } } diff --git a/src/main/java/mesh/Mesh3D.java b/src/main/java/mesh/Mesh3D.java index adf62b36..914b0f4a 100644 --- a/src/main/java/mesh/Mesh3D.java +++ b/src/main/java/mesh/Mesh3D.java @@ -15,263 +15,252 @@ public class Mesh3D { - public ArrayList vertices; - - public ArrayList faces; - - public Mesh3D() { - vertices = new ArrayList(); - faces = new ArrayList(); - } - - /** - * Applies the provided {@link IMeshModifier} to this mesh. This is congruent - * to {@link IMeshModifier#modify(Mesh3D)}. - * - * @param modifier The modifier to apply to this mesh. - * @return this - */ - public Mesh3D apply(IMeshModifier modifier) { - return modifier.modify(this); - } - - /** - * Rotates the mesh around the Y-axis. - * - * @deprecated Use {@link RotateYModifier} instead. - */ - public Mesh3D rotateY(float angle) { - return new RotateYModifier(angle).modify(this); - } - - /** - * Rotates the mesh around the Z-axis. - * - * @deprecated Use {@link RotateZModifier} instead. - */ - public Mesh3D rotateZ(float angle) { - return new RotateZModifier(angle).modify(this); - } - - /** - * Translates the mesh along the X-axis. - * - * @deprecated Use {@link TranslateModifier} instead. - */ - @Deprecated - public Mesh3D translateX(float tx) { - return new TranslateModifier(tx, 0, 0).modify(this); - } - - /** - * Translates the mesh along the Y-axis. - * - * @deprecated Use {@link TranslateModifier} instead. - */ - public Mesh3D translateY(float ty) { - return new TranslateModifier(0, ty, 0).modify(this); - } - - /** - * Translates the mesh along the Z-axis. - * - * @deprecated Use {@link TranslateModifier} instead. - */ - public Mesh3D translateZ(float tz) { - return new TranslateModifier(0, 0, tz).modify(this); - } - - /** - * Calculates the axis-aligned bounding box (AABB) for the 3D mesh based on - * its vertices. - *

- * The bounding box is defined by the minimum and maximum extents of the - * vertices along the X, Y, and Z axes. If there are no vertices in the mesh, - * an empty `Bounds3` is returned. - *

- * - * @return A {@link Bounds3} object representing the calculated bounding box - * of the mesh. The bounding box extends from the minimum vertex - * coordinate to the maximum vertex coordinate. - */ - public Bounds3 calculateBounds() { - if (vertices.isEmpty()) - return new Bounds3(); - - Vector3f min = new Vector3f(getVertexAt(0)); - Vector3f max = new Vector3f(getVertexAt(0)); - Bounds3 bounds = new Bounds3(); - for (Vector3f v : vertices) { - float minX = v.getX() < min.getX() ? v.getX() : min.getX(); - float minY = v.getY() < min.getY() ? v.getY() : min.getY(); - float minZ = v.getZ() < min.getZ() ? v.getZ() : min.getZ(); - float maxX = v.getX() > max.getX() ? v.getX() : max.getX(); - float maxY = v.getY() > max.getY() ? v.getY() : max.getY(); - float maxZ = v.getZ() > max.getZ() ? v.getZ() : max.getZ(); - min.set(minX, minY, minZ); - max.set(maxX, maxY, maxZ); - } - bounds.setMinMax(min, max); - return bounds; - } - - public Vector3f calculateFaceNormal(Face3D face) { - Vector3f faceNormal = new Vector3f(); - for (int i = 0; i < face.indices.length; i++) { - Vector3f currentVertex = vertices.get(face.indices[i]); - Vector3f nextVertex = vertices - .get(face.indices[(i + 1) % face.indices.length]); - float x = (currentVertex.getY() - nextVertex.getY()) - * (currentVertex.getZ() + nextVertex.getZ()); - float y = (currentVertex.getZ() - nextVertex.getZ()) - * (currentVertex.getX() + nextVertex.getX()); - float z = (currentVertex.getX() - nextVertex.getX()) - * (currentVertex.getY() + nextVertex.getY()); - faceNormal.addLocal(x, y, z); - } - return faceNormal.normalize(); - } - - public void removeDoubles(int decimalPlaces) { - for (Vector3f v : vertices) - v.roundLocalDecimalPlaces(decimalPlaces); - removeDoubles(); - } - - /** - * Removes duplicated vertices. - * - * @deprecated Use {@link RemoveDoubleVerticesModifier} instead. - */ - public void removeDoubles() { - new RemoveDoubleVerticesModifier().modify(this); - } - - public Mesh3D copy() { - Mesh3D copy = new Mesh3D(); - List vertices = copy.vertices; - List faces = copy.faces; - - for (Vector3f v : this.vertices) - vertices.add(new Vector3f(v)); - - for (Face3D f : this.faces) - faces.add(new Face3D(f)); - - return copy; - } - - public Vector3f calculateFaceCenter(Face3D face) { - Vector3f center = new Vector3f(); - for (int i = 0; i < face.indices.length; i++) { - center.addLocal(vertices.get(face.indices[i])); - } - return center.divideLocal(face.indices.length); - } - - public Mesh3D append(Mesh3D... meshes) { - Mesh3D result = new Mesh3D(); - - result = appendUtil(meshes); - result = appendUtil(this, result); - - vertices.clear(); - vertices.addAll(result.vertices); - - faces.clear(); - faces.addAll(result.faces); - - return this; - } - - private Mesh3D appendUtil(Mesh3D... meshes) { - // FIXME copy vertices and faces - int n = 0; - Mesh3D mesh = new Mesh3D(); - List vertices = mesh.vertices; - List faces = mesh.faces; - - for (int i = 0; i < meshes.length; i++) { - Mesh3D m = meshes[i]; - vertices.addAll(m.vertices); - faces.addAll(meshes[i].faces); - for (Face3D f : meshes[i].faces) { - for (int j = 0; j < f.indices.length; j++) { - f.indices[j] += n; - } - } - n += m.getVertexCount(); - } - - return mesh; - } - - public void clearVertices() { - vertices.clear(); - } - - public void addVertex(float x, float y, float z) { - vertices.add(new Vector3f(x, y, z)); - } - - public void addFace(int... indices) { - faces.add(new Face3D(indices)); - } - - public void addVertices(Collection vertices) { - this.vertices.addAll(vertices); - } - - public void addFaces(Collection faces) { - this.faces.addAll(faces); - } - - public void removeFace(Face3D face) { - faces.remove(face); - } - - public void removeFaces(Collection faces) { - this.faces.removeAll(faces); - } - - public void add(Vector3f... vertices) { - this.vertices.addAll(Arrays.asList(vertices)); - } - - public void add(Face3D... faces) { - this.faces.addAll(Arrays.asList(faces)); - } - - public int getVertexCount() { - return vertices.size(); - } - - public int getFaceCount() { - return faces.size(); - } - - public List getFaces() { - return new ArrayList(faces); - } - - public List getFaces(int from, int to) { - return new ArrayList<>(faces.subList(from, to)); - } - - public List getVertices(int from, int to) { - return new ArrayList<>(vertices.subList(from, to)); - } - - public List getVertices() { - return new ArrayList<>(vertices); - } - - public Vector3f getVertexAt(int index) { - return vertices.get(index); - } - - public Face3D getFaceAt(int index) { - return faces.get(index); - } - + public ArrayList vertices; + public ArrayList faces; + + public Mesh3D() { + vertices = new ArrayList(); + faces = new ArrayList(); + } + + /** + * Applies the provided {@link IMeshModifier} to this mesh. This is congruent to {@link + * IMeshModifier#modify(Mesh3D)}. + * + * @param modifier The modifier to apply to this mesh. + * @return this + */ + public Mesh3D apply(IMeshModifier modifier) { + return modifier.modify(this); + } + + /** + * Rotates the mesh around the Y-axis. + * + * @deprecated Use {@link RotateYModifier} instead. + */ + public Mesh3D rotateY(float angle) { + return new RotateYModifier(angle).modify(this); + } + + /** + * Rotates the mesh around the Z-axis. + * + * @deprecated Use {@link RotateZModifier} instead. + */ + public Mesh3D rotateZ(float angle) { + return new RotateZModifier(angle).modify(this); + } + + /** + * Translates the mesh along the X-axis. + * + * @deprecated Use {@link TranslateModifier} instead. + */ + @Deprecated + public Mesh3D translateX(float tx) { + return new TranslateModifier(tx, 0, 0).modify(this); + } + + /** + * Translates the mesh along the Y-axis. + * + * @deprecated Use {@link TranslateModifier} instead. + */ + public Mesh3D translateY(float ty) { + return new TranslateModifier(0, ty, 0).modify(this); + } + + /** + * Translates the mesh along the Z-axis. + * + * @deprecated Use {@link TranslateModifier} instead. + */ + public Mesh3D translateZ(float tz) { + return new TranslateModifier(0, 0, tz).modify(this); + } + + /** + * Calculates the axis-aligned bounding box (AABB) for the 3D mesh based on its vertices. + * + *

The bounding box is defined by the minimum and maximum extents of the vertices along the X, + * Y, and Z axes. If there are no vertices in the mesh, an empty `Bounds3` is returned. + * + * @return A {@link Bounds3} object representing the calculated bounding box of the mesh. The + * bounding box extends from the minimum vertex coordinate to the maximum vertex coordinate. + */ + public Bounds3 calculateBounds() { + if (vertices.isEmpty()) return new Bounds3(); + + Vector3f min = new Vector3f(getVertexAt(0)); + Vector3f max = new Vector3f(getVertexAt(0)); + Bounds3 bounds = new Bounds3(); + for (Vector3f v : vertices) { + float minX = v.getX() < min.getX() ? v.getX() : min.getX(); + float minY = v.getY() < min.getY() ? v.getY() : min.getY(); + float minZ = v.getZ() < min.getZ() ? v.getZ() : min.getZ(); + float maxX = v.getX() > max.getX() ? v.getX() : max.getX(); + float maxY = v.getY() > max.getY() ? v.getY() : max.getY(); + float maxZ = v.getZ() > max.getZ() ? v.getZ() : max.getZ(); + min.set(minX, minY, minZ); + max.set(maxX, maxY, maxZ); + } + bounds.setMinMax(min, max); + return bounds; + } + + public Vector3f calculateFaceNormal(Face3D face) { + Vector3f faceNormal = new Vector3f(); + for (int i = 0; i < face.indices.length; i++) { + Vector3f currentVertex = vertices.get(face.indices[i]); + Vector3f nextVertex = vertices.get(face.indices[(i + 1) % face.indices.length]); + float x = + (currentVertex.getY() - nextVertex.getY()) * (currentVertex.getZ() + nextVertex.getZ()); + float y = + (currentVertex.getZ() - nextVertex.getZ()) * (currentVertex.getX() + nextVertex.getX()); + float z = + (currentVertex.getX() - nextVertex.getX()) * (currentVertex.getY() + nextVertex.getY()); + faceNormal.addLocal(x, y, z); + } + return faceNormal.normalize(); + } + + public void removeDoubles(int decimalPlaces) { + for (Vector3f v : vertices) v.roundLocalDecimalPlaces(decimalPlaces); + removeDoubles(); + } + + /** + * Removes duplicated vertices. + * + * @deprecated Use {@link RemoveDoubleVerticesModifier} instead. + */ + public void removeDoubles() { + new RemoveDoubleVerticesModifier().modify(this); + } + + public Mesh3D copy() { + Mesh3D copy = new Mesh3D(); + List vertices = copy.vertices; + List faces = copy.faces; + + for (Vector3f v : this.vertices) vertices.add(new Vector3f(v)); + + for (Face3D f : this.faces) faces.add(new Face3D(f)); + + return copy; + } + + public Vector3f calculateFaceCenter(Face3D face) { + Vector3f center = new Vector3f(); + for (int i = 0; i < face.indices.length; i++) { + center.addLocal(vertices.get(face.indices[i])); + } + return center.divideLocal(face.indices.length); + } + + public Mesh3D append(Mesh3D... meshes) { + Mesh3D result = new Mesh3D(); + + result = appendUtil(meshes); + result = appendUtil(this, result); + + vertices.clear(); + vertices.addAll(result.vertices); + + faces.clear(); + faces.addAll(result.faces); + + return this; + } + + private Mesh3D appendUtil(Mesh3D... meshes) { + // FIXME copy vertices and faces + int n = 0; + Mesh3D mesh = new Mesh3D(); + List vertices = mesh.vertices; + List faces = mesh.faces; + + for (int i = 0; i < meshes.length; i++) { + Mesh3D m = meshes[i]; + vertices.addAll(m.vertices); + faces.addAll(meshes[i].faces); + for (Face3D f : meshes[i].faces) { + for (int j = 0; j < f.indices.length; j++) { + f.indices[j] += n; + } + } + n += m.getVertexCount(); + } + + return mesh; + } + + public void clearVertices() { + vertices.clear(); + } + + public void addVertex(float x, float y, float z) { + vertices.add(new Vector3f(x, y, z)); + } + + public void addFace(int... indices) { + faces.add(new Face3D(indices)); + } + + public void addVertices(Collection vertices) { + this.vertices.addAll(vertices); + } + + public void addFaces(Collection faces) { + this.faces.addAll(faces); + } + + public void removeFace(Face3D face) { + faces.remove(face); + } + + public void removeFaces(Collection faces) { + this.faces.removeAll(faces); + } + + public void add(Vector3f... vertices) { + this.vertices.addAll(Arrays.asList(vertices)); + } + + public void add(Face3D... faces) { + this.faces.addAll(Arrays.asList(faces)); + } + + public int getVertexCount() { + return vertices.size(); + } + + public int getFaceCount() { + return faces.size(); + } + + public List getFaces() { + return new ArrayList(faces); + } + + public List getFaces(int from, int to) { + return new ArrayList<>(faces.subList(from, to)); + } + + public List getVertices(int from, int to) { + return new ArrayList<>(vertices.subList(from, to)); + } + + public List getVertices() { + return new ArrayList<>(vertices); + } + + public Vector3f getVertexAt(int index) { + return vertices.get(index); + } + + public Face3D getFaceAt(int index) { + return faces.get(index); + } } From 85efbca7072d46ad038526db27b6eb493f516cfb Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sun, 22 Dec 2024 11:33:55 +0100 Subject: [PATCH 58/59] Fixed reasign paramters issue in getColorFromInt(int r, int g, int b) --- src/main/java/math/Color.java | 1074 ++++++++++++++++----------------- 1 file changed, 507 insertions(+), 567 deletions(-) diff --git a/src/main/java/math/Color.java b/src/main/java/math/Color.java index 3c936517..8af38db0 100644 --- a/src/main/java/math/Color.java +++ b/src/main/java/math/Color.java @@ -1,572 +1,512 @@ package math; -/** - * Representation of RGBA colors. The components (r,g,b) define a color in RGB - * color space. - */ +/** Representation of RGBA colors. The components (r,g,b) define a color in RGB color space. */ public class Color { - /** - * Solid black. RGBA is (0, 0, 0, 1). - */ - public static final Color BLACK = new Color(0f, 0f, 0f, 1f); - - /** - * Solid blue. RGBA is (0, 0, 1, 1). - */ - public static final Color BLUE = new Color(0f, 0f, 1f, 1f); - - /** - * Completely transparent. RGBA is (0, 0, 0, 0). - */ - public static final Color CLEAR = new Color(0f, 0f, 0f, 0f); - - /** - * Cyan. RGBA is (0, 1, 1, 1). - */ - public static final Color CYAN = new Color(0f, 1f, 1f, 1f); - - /** - * Dark gray. RGBA is (0.25, 0.25, 0.25, 1). - */ - public static final Color DARK_GRAY = new Color(0.25f, 0.25f, 0.25f, 1.0f); - - /** - * Gray. RGBA is (0.5, 0.5, 0.5, 1). - */ - public static final Color GRAY = new Color(0.5f, 0.5f, 0.5f, 1f); - - /** - * Solid green. RGBA is (0, 1, 0, 1). - */ - public static final Color GREEN = new Color(0f, 1f, 0f, 1f); - - /** - * English spelling for gray. RGBA is the same (0.5, 0.5, 0.5, 1). - */ - public static final Color GREY = GRAY; - - /** - * Light gray. RGBA is (0.75f, 0.75f, 0.75f, 1f). - */ - public static final Color LIGHT_GRAY = new Color(0.75f, 0.75f, 0.75f, 1f); - - /** - * Magenta. RGBA is (1, 0, 1, 1). - */ - public static final Color MAGENTA = new Color(1f, 0f, 1f, 1f); - - /** - * Solid red. RGBA is (1, 0, 0, 1). - */ - public static final Color RED = new Color(1f, 0f, 0f, 1f); - - /** - * Solid white. RGBA is (1, 1, 1, 1). - */ - public static final Color WHITE = new Color(1f, 1f, 1f, 1f); - - /** - * Yellow. RGBA is (1, 1, 0, 1). - */ - public static final Color YELLOW = new Color(1f, 1f, 0f, 1f); - - /** - * The red component of the color. - */ - private float r; - - /** - * The green component of the color. - */ - private float g; - - /** - * The blue component of the color. - */ - private float b; - - /** - * The alpha component of the color. - */ - private float a; - - /** - * Constructs a new instance of this color with r,g,b,a components set to 0. - */ - public Color() { - this(0, 0, 0, 0); - } - - /** - * Constructs a new instance of this color with r,g,b,a components set to the - * values provided by color c. - * - * @param c the color to copy from - */ - public Color(Color c) { - this(c.r, c.g, c.b, c.a); - } - - /** - * Constructs a new instance of this {@link Color} with the given r,g,b - * components and a alpha value set to 1f. - * - * @param r the red component of this color - * @param g the green component of this color - * @param b the blue component of this color - */ - public Color(float r, float g, float b) { - this(r, g, b, 1.0f); - } - - /** - * Constructs a new instance of this {@link Color} with the given r,g,b,a - * components. - * - * - * @param r the red component of this color - * @param g the green component of this color - * @param b the blue component of this color - * @param a the alpha component of this color - */ - public Color(float r, float g, float b, float a) { - this.r = r; - this.g = g; - this.b = b; - this.a = a; - } - - /** - * Returns a new color instance with the specified integer r,g,b values. - * - * @param r the red component for the color (0 to 255) - * @param g the green component for the color (0 to 255) - * @param b the blue component for the color (0 to 255) - * - * @return the newly created color - */ - public static Color getColorFromInt(int r, int g, int b) { - r = Mathf.clampInt(r, 0, 255); - g = Mathf.clampInt(g, 0, 255); - b = Mathf.clampInt(b, 0, 255); - return new Color(r / 255f, g / 255f, b / 255f); - } - - /** - * Adds the components of a given color to those of this color creating a new - * color object. Each component is added separately. If the provided color is - * null, an exception is thrown. - * - * @param color the color to add to this color - * @return the resultant color - */ - public Color add(Color color) { - return add(color, null); - } - - /** - * Adds the components of a given color to those of this color storing the - * result in the given result color. Each component is added separately. If - * the provided color c is null, an exception is thrown. If the provided - * result color is null, a new color is created. - * - * @param color the color to add to this color - * @param result the color to store the result in - * @return the resultant color - */ - public Color add(Color color, Color result) { - if (result == null) - result = new Color(); - result.r = this.r + color.r; - result.g = this.g + color.g; - result.b = this.b + color.b; - result.a = this.a + color.a; - return result; - } - - /** - * Adds the given r,g,b,a components to those of this color creating a new - * color object. Each component is added separately. - * - * @param r the red component to add - * @param g the green component to add - * @param b the blue component to add - * @param a the alpha component to add - * @return the resultant color - */ - public Color add(float r, float g, float b, float a) { - return new Color(this.r + r, this.g + g, this.b + b, this.a + a); - } - - /** - * Adds the color c to this color internally, and returns a handle to this - * color for easy chaining of calls. Each component is added separately. - * - * @param color the color to add to this color - * @return this - */ - public Color addLocal(Color color) { - this.r += color.r; - this.g += color.g; - this.b += color.b; - this.a += color.a; - return this; - } - - /** - * Adds the provided components to this color internally, and returns a handle - * to this color for easy chaining of calls. - * - * @param r the red component to add - * @param g the green component to add - * @param b the blue component to add - * @param a the alpha component to add - * - * @return this - */ - public Color addLocal(float r, float g, float b, float a) { - this.r += r; - this.g += g; - this.b += b; - this.a += a; - return this; - } - - /** - * Subtracts the components of a given color from those of this color creating - * a new color object. Each component is subtracted separately. If the - * provided color is null, an exception is thrown. - * - * @param color the color to subtract from this color - * @return the resultant color - */ - public Color subtract(Color color) { - return subtract(color, null); - } - - /** - * Subtracts the values of a given color from those of this color storing the - * result in the given color. Each component is subtracted separately. If the - * provided color c is null, an exception is thrown. If the provided result - * color is null, a new color is created. - * - * @param color the color to subtract from this color - * @param result the color to store the result in - * @return the resultant color - */ - public Color subtract(Color color, Color result) { - if (result == null) - result = new Color(); - result.r = this.r - color.r; - result.g = this.g - color.g; - result.b = this.b - color.b; - result.a = this.a - color.a; - return result; - } - - /** - * * Subtracts the given r,g,b,a components from those of this color creating - * a new color object. Each component is subtracted separately. - * - * @param r the red component to subtract - * @param g the green component to subtract - * @param b the blue component to subtract - * @param a the alpha component to subtract - * @return the resultant color - */ - public Color subtract(float r, float g, float b, float a) { - return new Color(this.r - r, this.g - g, this.b - b, this.a - a); - } - - /** - * Subtracts the color c from this color internally, and returns a handle to - * this color for easy chaining of calls. Each component is subtracted - * separately. - * - * @param color the color to subtract from this color - * @return this - */ - public Color subtractLocal(Color color) { - this.r -= color.r; - this.g -= color.g; - this.b -= color.b; - this.a -= color.a; - return this; - } - - /** - * Subtracts the provided components from this color internally, and returns a - * handle to this color for easy chaining of calls. - * - * @param r the red component to subtract - * @param g the green component to subtract - * @param b the blue component to subtract - * @param a the alpha component to subtract - * - * @return this - */ - public Color subtractLocal(float r, float g, float b, float a) { - this.r -= r; - this.g -= g; - this.b -= b; - this.a -= a; - return this; - } - - /** - * Divides this color by a internally. Each component is scaled - * separately - * - * @return this - */ - public Color divideLocal(float a) { - this.r /= a; - this.g /= a; - this.b /= a; - this.a /= a; - return this; - } - - /** - * @param a - * @return - */ - public Color multLocal(float r, float g, float b, float a) { - this.r *= a; - this.g *= g; - this.b *= b; - this.a *= a; - return this; - } - - /** - * Clamps the components of this color between 0.0f and 1.0f internally, and - * returns a handle to this color for easy chaining of calls. - * - * @return this - */ - public Color clampLocal() { - r = Mathf.clamp01(r); - g = Mathf.clamp01(g); - b = Mathf.clamp01(b); - a = Mathf.clamp01(a); - return this; - } - - /** - * Sets all components of this color to 0.0f internally, and returns a handle - * to this color for easy chaining of calls. - * - * @return this - */ - public Color clearLocal() { - r = g = b = a = 0.0f; - return this; - } - - /** - * Returns the maximum color component value: Max(r,g,b). This method does not - * consider the alpha component. - * - * @return the maximum color component - */ - public float maxComponent() { - return Mathf.max(new float[] { r, g, b }); - } - - /** - * Returns a new float array containing the r,g,b,a components of this color - * in that order. - * - * @return the components of this color as array - */ - public float[] toArray() { - return new float[] { r, g, b, a }; - } - - /** - * Stores the r,g,b,a components in the given array. If the provided store - * array is null a new array is created to store the components in. - * - * @param store the array to store the components into - * @return store - * @throws IndexOutOfBoundsException if store.length < 4 - */ - public float[] toArray(float[] store) { - if (store == null) - store = new float[4]; - store[0] = r; - store[1] = g; - store[2] = b; - store[3] = a; - return store; - } - - /** - * Sets the r,g,b,a components of this color to the specified new values. - * - * @param r the new red component for this color - * @param g the new green component for this color - * @param b the new blue component for this color - * @param a the new alpha component for this color - */ - public void set(float r, float g, float b, float a) { - this.r = r; - this.g = g; - this.b = b; - this.a = a; - } - - /** - * Sets the r,g,b components of this color to the specified new values. The - * alpha component is set to 1.0f. - * - * @param r the new red component for this color - * @param g the new green component for this color - * @param b the new blue component for this color - */ - public void set(float r, float g, float b) { - this.r = r; - this.g = g; - this.b = b; - this.a = 1.0f; - } - - /** - * Returns the red component of this color as float value (0f to 1f). - * - * @return the red component of this color (0f to 1f) - */ - public float getRed() { - return r; - } - - /** - * Returns the green component of this color as float value (0f to 1f). - * - * @return the green component of this color (0f to 1f) - */ - public float getGreen() { - return g; - } - - /** - * Returns the blue component of this color as float value (0f to 1f). - * - * @return the blue component of this color (0f to 1f) - */ - public float getBlue() { - return b; - } - - /** - * Returns the alpha component of this color as float value (0f to 1f). - * - * @return the alpha component of this color (0f to 1f) - */ - public float getAlpha() { - return a; - } - - /** - * Returns the red component of this color as an integer value (0 to 255). - * - * @return the red component of this color (0 to 255) - */ - public int getRedInt() { - return (int) (r * 255 + 0.5); - } - - /** - * Returns the green component of this color as an integer value (0 to 255). - * - * @return the green component of this color (0 to 255) - */ - public int getGreenInt() { - return (int) (g * 255 + 0.5); - } - - /** - * Returns the blue component of this color as an integer value (0 to 255). - * - * @return the blue component of this color (0 to 255) - */ - public int getBlueInt() { - return (int) (b * 255 + 0.5); - } - - /** - * Returns the alpha component of this color as an integer value (0 to 255). - * - * @return the alpha component of this color (0 to 255) - */ - public int getAlphaInt() { - return (int) (a * 255 + 0.5); - } - - /** - * Returns the RGBA value representing the color. (Bits 24-31 are alpha, 16-23 - * are red, 8-15 are green, 0-7 are blue). - * - * @return the RGBA value of this color as integer - */ - public int getRGBA() { - int r = getRedInt(); - int g = getGreenInt(); - int b = getBlueInt(); - int a = getAlphaInt(); - return ((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) - | ((b & 0xFF) << 0); - } - - /** - * Returns a unique hash code for this color object based on it's values. If - * two colors are logically equivalent, they will return the same hash code - * value. - * - * @return the hash code value of this color - */ - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + Float.floatToIntBits(a); - result = prime * result + Float.floatToIntBits(b); - result = prime * result + Float.floatToIntBits(g); - result = prime * result + Float.floatToIntBits(r); - return result; - } - - /** - * Determines if this color is equals to the given object obj. - * - * @param obj the object to compare for equality - * @return true if they are equal - */ - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - Color other = (Color) obj; - return Float.floatToIntBits(r) == Float.floatToIntBits(other.r) - && Float.floatToIntBits(g) == Float.floatToIntBits(other.g) - && Float.floatToIntBits(b) == Float.floatToIntBits(other.b) - && Float.floatToIntBits(a) == Float.floatToIntBits(other.a); - } - - /** - * Returns a string representation of this color. - * - * @return a string representation of this color - */ - @Override - public String toString() { - return "Color [r=" + r + ", g=" + g + ", b=" + b + ", a=" + a + "]"; - } - + /** Solid black. RGBA is (0, 0, 0, 1). */ + public static final Color BLACK = new Color(0f, 0f, 0f, 1f); + + /** Solid blue. RGBA is (0, 0, 1, 1). */ + public static final Color BLUE = new Color(0f, 0f, 1f, 1f); + + /** Completely transparent. RGBA is (0, 0, 0, 0). */ + public static final Color CLEAR = new Color(0f, 0f, 0f, 0f); + + /** Cyan. RGBA is (0, 1, 1, 1). */ + public static final Color CYAN = new Color(0f, 1f, 1f, 1f); + + /** Dark gray. RGBA is (0.25, 0.25, 0.25, 1). */ + public static final Color DARK_GRAY = new Color(0.25f, 0.25f, 0.25f, 1.0f); + + /** Gray. RGBA is (0.5, 0.5, 0.5, 1). */ + public static final Color GRAY = new Color(0.5f, 0.5f, 0.5f, 1f); + + /** Solid green. RGBA is (0, 1, 0, 1). */ + public static final Color GREEN = new Color(0f, 1f, 0f, 1f); + + /** English spelling for gray. RGBA is the same (0.5, 0.5, 0.5, 1). */ + public static final Color GREY = GRAY; + + /** Light gray. RGBA is (0.75f, 0.75f, 0.75f, 1f). */ + public static final Color LIGHT_GRAY = new Color(0.75f, 0.75f, 0.75f, 1f); + + /** Magenta. RGBA is (1, 0, 1, 1). */ + public static final Color MAGENTA = new Color(1f, 0f, 1f, 1f); + + /** Solid red. RGBA is (1, 0, 0, 1). */ + public static final Color RED = new Color(1f, 0f, 0f, 1f); + + /** Solid white. RGBA is (1, 1, 1, 1). */ + public static final Color WHITE = new Color(1f, 1f, 1f, 1f); + + /** Yellow. RGBA is (1, 1, 0, 1). */ + public static final Color YELLOW = new Color(1f, 1f, 0f, 1f); + + /** The red component of the color. */ + private float r; + + /** The green component of the color. */ + private float g; + + /** The blue component of the color. */ + private float b; + + /** The alpha component of the color. */ + private float a; + + /** Constructs a new instance of this color with r,g,b,a components set to 0. */ + public Color() { + this(0, 0, 0, 0); + } + + /** + * Constructs a new instance of this color with r,g,b,a components set to the values provided by + * color c. + * + * @param c the color to copy from + */ + public Color(Color c) { + this(c.r, c.g, c.b, c.a); + } + + /** + * Constructs a new instance of this {@link Color} with the given r,g,b components and a alpha + * value set to 1f. + * + * @param r the red component of this color + * @param g the green component of this color + * @param b the blue component of this color + */ + public Color(float r, float g, float b) { + this(r, g, b, 1.0f); + } + + /** + * Constructs a new instance of this {@link Color} with the given r,g,b,a components. + * + * @param r the red component of this color + * @param g the green component of this color + * @param b the blue component of this color + * @param a the alpha component of this color + */ + public Color(float r, float g, float b, float a) { + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } + + /** + * Returns a new color instance with the specified integer r,g,b values. + * + * @param r the red component for the color (0 to 255) + * @param g the green component for the color (0 to 255) + * @param b the blue component for the color (0 to 255) + * @return the newly created color + */ + public static Color getColorFromInt(int r, int g, int b) { + int clampR = Mathf.clampInt(r, 0, 255); + int clampG = Mathf.clampInt(g, 0, 255); + int clampB = Mathf.clampInt(b, 0, 255); + return new Color(clampR / 255f, clampG / 255f, clampB / 255f); + } + + /** + * Adds the components of a given color to those of this color creating a new color object. Each + * component is added separately. If the provided color is null, an exception is thrown. + * + * @param color the color to add to this color + * @return the resultant color + */ + public Color add(Color color) { + return add(color, null); + } + + /** + * Adds the components of a given color to those of this color storing the result in the given + * result color. Each component is added separately. If the provided color c is null, an exception + * is thrown. If the provided result color is null, a new color is created. + * + * @param color the color to add to this color + * @param result the color to store the result in + * @return the resultant color + */ + public Color add(Color color, Color result) { + if (result == null) result = new Color(); + result.r = this.r + color.r; + result.g = this.g + color.g; + result.b = this.b + color.b; + result.a = this.a + color.a; + return result; + } + + /** + * Adds the given r,g,b,a components to those of this color creating a new color object. Each + * component is added separately. + * + * @param r the red component to add + * @param g the green component to add + * @param b the blue component to add + * @param a the alpha component to add + * @return the resultant color + */ + public Color add(float r, float g, float b, float a) { + return new Color(this.r + r, this.g + g, this.b + b, this.a + a); + } + + /** + * Adds the color c to this color internally, and returns a handle to this color for easy chaining + * of calls. Each component is added separately. + * + * @param color the color to add to this color + * @return this + */ + public Color addLocal(Color color) { + this.r += color.r; + this.g += color.g; + this.b += color.b; + this.a += color.a; + return this; + } + + /** + * Adds the provided components to this color internally, and returns a handle to this color for + * easy chaining of calls. + * + * @param r the red component to add + * @param g the green component to add + * @param b the blue component to add + * @param a the alpha component to add + * @return this + */ + public Color addLocal(float r, float g, float b, float a) { + this.r += r; + this.g += g; + this.b += b; + this.a += a; + return this; + } + + /** + * Subtracts the components of a given color from those of this color creating a new color object. + * Each component is subtracted separately. If the provided color is null, an exception is thrown. + * + * @param color the color to subtract from this color + * @return the resultant color + */ + public Color subtract(Color color) { + return subtract(color, null); + } + + /** + * Subtracts the values of a given color from those of this color storing the result in the given + * color. Each component is subtracted separately. If the provided color c is null, an exception + * is thrown. If the provided result color is null, a new color is created. + * + * @param color the color to subtract from this color + * @param result the color to store the result in + * @return the resultant color + */ + public Color subtract(Color color, Color result) { + if (result == null) result = new Color(); + result.r = this.r - color.r; + result.g = this.g - color.g; + result.b = this.b - color.b; + result.a = this.a - color.a; + return result; + } + + /** + * * Subtracts the given r,g,b,a components from those of this color creating a new color object. + * Each component is subtracted separately. + * + * @param r the red component to subtract + * @param g the green component to subtract + * @param b the blue component to subtract + * @param a the alpha component to subtract + * @return the resultant color + */ + public Color subtract(float r, float g, float b, float a) { + return new Color(this.r - r, this.g - g, this.b - b, this.a - a); + } + + /** + * Subtracts the color c from this color internally, and returns a handle to this color for easy + * chaining of calls. Each component is subtracted separately. + * + * @param color the color to subtract from this color + * @return this + */ + public Color subtractLocal(Color color) { + this.r -= color.r; + this.g -= color.g; + this.b -= color.b; + this.a -= color.a; + return this; + } + + /** + * Subtracts the provided components from this color internally, and returns a handle to this + * color for easy chaining of calls. + * + * @param r the red component to subtract + * @param g the green component to subtract + * @param b the blue component to subtract + * @param a the alpha component to subtract + * @return this + */ + public Color subtractLocal(float r, float g, float b, float a) { + this.r -= r; + this.g -= g; + this.b -= b; + this.a -= a; + return this; + } + + /** + * Divides this color by a internally. Each component is scaled separately + * + * @return this + */ + public Color divideLocal(float a) { + this.r /= a; + this.g /= a; + this.b /= a; + this.a /= a; + return this; + } + + /** + * @param a + * @return + */ + public Color multLocal(float r, float g, float b, float a) { + this.r *= a; + this.g *= g; + this.b *= b; + this.a *= a; + return this; + } + + /** + * Clamps the components of this color between 0.0f and 1.0f internally, and returns a handle to + * this color for easy chaining of calls. + * + * @return this + */ + public Color clampLocal() { + r = Mathf.clamp01(r); + g = Mathf.clamp01(g); + b = Mathf.clamp01(b); + a = Mathf.clamp01(a); + return this; + } + + /** + * Sets all components of this color to 0.0f internally, and returns a handle to this color for + * easy chaining of calls. + * + * @return this + */ + public Color clearLocal() { + r = g = b = a = 0.0f; + return this; + } + + /** + * Returns the maximum color component value: Max(r,g,b). This method does not consider the alpha + * component. + * + * @return the maximum color component + */ + public float maxComponent() { + return Mathf.max(new float[] {r, g, b}); + } + + /** + * Returns a new float array containing the r,g,b,a components of this color in that order. + * + * @return the components of this color as array + */ + public float[] toArray() { + return new float[] {r, g, b, a}; + } + + /** + * Stores the r,g,b,a components in the given array. If the provided store array is null a new + * array is created to store the components in. + * + * @param store the array to store the components into + * @return store + * @throws IndexOutOfBoundsException if store.length < 4 + */ + public float[] toArray(float[] store) { + if (store == null) store = new float[4]; + store[0] = r; + store[1] = g; + store[2] = b; + store[3] = a; + return store; + } + + /** + * Sets the r,g,b,a components of this color to the specified new values. + * + * @param r the new red component for this color + * @param g the new green component for this color + * @param b the new blue component for this color + * @param a the new alpha component for this color + */ + public void set(float r, float g, float b, float a) { + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } + + /** + * Sets the r,g,b components of this color to the specified new values. The alpha component is set + * to 1.0f. + * + * @param r the new red component for this color + * @param g the new green component for this color + * @param b the new blue component for this color + */ + public void set(float r, float g, float b) { + this.r = r; + this.g = g; + this.b = b; + this.a = 1.0f; + } + + /** + * Returns the red component of this color as float value (0f to 1f). + * + * @return the red component of this color (0f to 1f) + */ + public float getRed() { + return r; + } + + /** + * Returns the green component of this color as float value (0f to 1f). + * + * @return the green component of this color (0f to 1f) + */ + public float getGreen() { + return g; + } + + /** + * Returns the blue component of this color as float value (0f to 1f). + * + * @return the blue component of this color (0f to 1f) + */ + public float getBlue() { + return b; + } + + /** + * Returns the alpha component of this color as float value (0f to 1f). + * + * @return the alpha component of this color (0f to 1f) + */ + public float getAlpha() { + return a; + } + + /** + * Returns the red component of this color as an integer value (0 to 255). + * + * @return the red component of this color (0 to 255) + */ + public int getRedInt() { + return (int) (r * 255 + 0.5); + } + + /** + * Returns the green component of this color as an integer value (0 to 255). + * + * @return the green component of this color (0 to 255) + */ + public int getGreenInt() { + return (int) (g * 255 + 0.5); + } + + /** + * Returns the blue component of this color as an integer value (0 to 255). + * + * @return the blue component of this color (0 to 255) + */ + public int getBlueInt() { + return (int) (b * 255 + 0.5); + } + + /** + * Returns the alpha component of this color as an integer value (0 to 255). + * + * @return the alpha component of this color (0 to 255) + */ + public int getAlphaInt() { + return (int) (a * 255 + 0.5); + } + + /** + * Returns the RGBA value representing the color. (Bits 24-31 are alpha, 16-23 are red, 8-15 are + * green, 0-7 are blue). + * + * @return the RGBA value of this color as integer + */ + public int getRGBA() { + int r = getRedInt(); + int g = getGreenInt(); + int b = getBlueInt(); + int a = getAlphaInt(); + return ((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | ((b & 0xFF) << 0); + } + + /** + * Returns a unique hash code for this color object based on it's values. If two colors are + * logically equivalent, they will return the same hash code value. + * + * @return the hash code value of this color + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Float.floatToIntBits(a); + result = prime * result + Float.floatToIntBits(b); + result = prime * result + Float.floatToIntBits(g); + result = prime * result + Float.floatToIntBits(r); + return result; + } + + /** + * Determines if this color is equals to the given object obj. + * + * @param obj the object to compare for equality + * @return true if they are equal + */ + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + Color other = (Color) obj; + return Float.floatToIntBits(r) == Float.floatToIntBits(other.r) + && Float.floatToIntBits(g) == Float.floatToIntBits(other.g) + && Float.floatToIntBits(b) == Float.floatToIntBits(other.b) + && Float.floatToIntBits(a) == Float.floatToIntBits(other.a); + } + + /** + * Returns a string representation of this color. + * + * @return a string representation of this color + */ + @Override + public String toString() { + return "Color [r=" + r + ", g=" + g + ", b=" + b + ", a=" + a + "]"; + } } From 41b2e22da4b234afc815571656c0dc02d83ccdbe Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sun, 22 Dec 2024 11:36:56 +0100 Subject: [PATCH 59/59] Format changes. --- src/main/java/engine/Timer.java | 478 ++++++++++++++++---------------- 1 file changed, 235 insertions(+), 243 deletions(-) diff --git a/src/main/java/engine/Timer.java b/src/main/java/engine/Timer.java index e0c30fde..d3572e22 100644 --- a/src/main/java/engine/Timer.java +++ b/src/main/java/engine/Timer.java @@ -1,280 +1,272 @@ package engine; /** - * The {@code Timer} class provides a utility for tracking elapsed time, frames - * per second (FPS), and time scaling for games or applications. It uses - * nanosecond precision for timekeeping and offers features such as formatted - * time representation, time-per-frame calculation, and slow-motion or speed-up - * effects via time scaling. + * The {@code Timer} class provides a utility for tracking elapsed time, frames per second (FPS), + * and time scaling for games or applications. It uses nanosecond precision for timekeeping and + * offers features such as formatted time representation, time-per-frame calculation, and + * slow-motion or speed-up effects via time scaling. + * + *

Key features include: * - *

- * Key features include: *

    - *
  • Tracking total elapsed time (scaled and unscaled).
  • - *
  • Calculating frames per second (FPS).
  • - *
  • Formatting time as hours:minutes:seconds.
  • - *
  • Adjustable time scaling for slow-motion or fast-forward effects.
  • + *
  • Tracking total elapsed time (scaled and unscaled). + *
  • Calculating frames per second (FPS). + *
  • Formatting time as hours:minutes:seconds. + *
  • Adjustable time scaling for slow-motion or fast-forward effects. *
- * - * This class is designed to be updated on every frame of an application or - * game. + * + * This class is designed to be updated on every frame of an application or game. */ public class Timer { - /** - * Real world time when the timer started. - */ - private long startTime; - - /** The last recorded time in nanoseconds. */ - private long lastTime; + /** Real world time when the timer started. */ + private long startTime; - /** The time in milliseconds taken for the last frame. */ - private float time; + /** The last recorded time in nanoseconds. */ + private long lastTime; - /** Accumulates milliseconds for FPS calculation. */ - private long millisecondCounter; + /** The time in milliseconds taken for the last frame. */ + private float time; - /** The frame count during the last FPS update. */ - private int lastFrameCount; + /** Accumulates milliseconds for FPS calculation. */ + private long millisecondCounter; - /** The calculated frames per second (FPS). */ - private int fps; + /** The frame count during the last FPS update. */ + private int lastFrameCount; - /** Total elapsed time in milliseconds. */ - private long totalTime; + /** The calculated frames per second (FPS). */ + private int fps; - /** Scaling factor for time (default is 1.0 for real-time). */ - private float timeScale; + /** Total elapsed time in milliseconds. */ + private long totalTime; - /** Total number of frames since the Timer started. */ - private int frameCount; + /** Scaling factor for time (default is 1.0 for real-time). */ + private float timeScale; - /** - * Constructs a {@code Timer} with a default time scale of 1.0. - */ - public Timer() { - this.startTime = System.nanoTime(); - this.lastTime = System.nanoTime(); - this.time = 0; - this.totalTime = 0; - this.timeScale = 1f; - this.frameCount = 0; - } + /** Total number of frames since the Timer started. */ + private int frameCount; - /** - * Constructs a {@code Timer} with the specified initial time scale. - * - * @param initialTimeScale the initial time scaling factor - */ - public Timer(float initialTimeScale) { - this.timeScale = initialTimeScale; - } + /** Constructs a {@code Timer} with a default time scale of 1.0. */ + public Timer() { + this.startTime = System.nanoTime(); + this.lastTime = System.nanoTime(); + this.time = 0; + this.totalTime = 0; + this.timeScale = 1f; + this.frameCount = 0; + } - /** - * Returns the current frames per second (FPS). - * - * @return the frames per second - */ - public float getFrameRate() { - return fps; - } + /** + * Constructs a {@code Timer} with the specified initial time scale. + * + * @param initialTimeScale the initial time scaling factor + */ + public Timer(float initialTimeScale) { + this.timeScale = initialTimeScale; + } - /** - * Updates the FPS calculation based on the accumulated milliseconds. - */ - private void updateFPS() { - millisecondCounter += time; - if (millisecondCounter >= 1000) { - millisecondCounter = 0; - fps = frameCount - lastFrameCount; - lastFrameCount = frameCount; - } - } + /** + * Returns the current frames per second (FPS). + * + * @return the frames per second + */ + public float getFrameRate() { + return fps; + } - /** - * Updates the Timer. This method must be called once per frame to ensure - * accurate time tracking. - */ - public void update() { - long currentTime = System.nanoTime(); - time = (currentTime - lastTime) / 1_000_000.0f; // Convert to milliseconds - lastTime = currentTime; - totalTime += time; - frameCount++; - updateFPS(); - } + /** Updates the FPS calculation based on the accumulated milliseconds. */ + private void updateFPS() { + millisecondCounter += time; + if (millisecondCounter >= 1000) { + millisecondCounter = 0; + fps = frameCount - lastFrameCount; + lastFrameCount = frameCount; + } + } - /** - * Resets the {@code Timer} to its initial state, clearing all accumulated - * time and frame count values. This includes resetting the following: - *
    - *
  • The start time to the current system time.
  • - *
  • The last time recorded to the current system time.
  • - *
  • The total elapsed time to zero.
  • - *
  • The frame count to zero.
  • - *
  • The frames per second (FPS) to zero.
  • - *
  • The millisecond counter to zero.
  • - *
  • The last frame count for FPS calculation to zero.
  • - *
- *

- * This method can be used when you need to restart the timer, such as for - * restarting the game / application or resetting the simulation state. - *

- */ - public void reset() { - this.startTime = System.nanoTime(); - this.lastTime = startTime; - this.time = 0; - this.totalTime = 0; - this.frameCount = 0; - this.fps = 0; - this.millisecondCounter = 0; - this.lastFrameCount = 0; - } + /** + * Updates the Timer. This method must be called once per frame to ensure accurate time tracking. + */ + public void update() { + long currentTime = System.nanoTime(); + time = (currentTime - lastTime) / 1_000_000.0f; // Convert to milliseconds + lastTime = currentTime; + totalTime += time; + frameCount++; + updateFPS(); + } - /** - * Returns the total elapsed time in seconds, scaled by the current time - * scale. - * - * @return the scaled total elapsed time in seconds - */ - public float getTotalTime() { - return totalTime / 1000.0f * timeScale; - } + /** + * Resets the {@code Timer} to its initial state, clearing all accumulated time and frame count + * values. This includes resetting the following: + * + *
    + *
  • The start time to the current system time. + *
  • The last time recorded to the current system time. + *
  • The total elapsed time to zero. + *
  • The frame count to zero. + *
  • The frames per second (FPS) to zero. + *
  • The millisecond counter to zero. + *
  • The last frame count for FPS calculation to zero. + *
+ * + *

This method can be used when you need to restart the timer, such as for restarting the game + * / application or resetting the simulation state. + */ + public void reset() { + this.startTime = System.nanoTime(); + this.lastTime = startTime; + this.time = 0; + this.totalTime = 0; + this.frameCount = 0; + this.fps = 0; + this.millisecondCounter = 0; + this.lastFrameCount = 0; + } - /** - * Returns the total elapsed time in seconds, independent of the time scale. - * - * @return the unscaled total elapsed time in seconds - */ - public float getUnscaledTotalTime() { - return totalTime / 1000.0f; - } + /** + * Returns the total elapsed time in seconds, scaled by the current time scale. + * + * @return the scaled total elapsed time in seconds + */ + public float getTotalTime() { + return totalTime / 1000.0f * timeScale; + } - /** - * Returns a formatted string representing the scaled total time in the format - * HH:MM:SS. - * - * @return the formatted scaled total time - */ - public String getFormattedTotalTime() { - return formatTime(getTotalTime()); - } + /** + * Returns the total elapsed time in seconds, independent of the time scale. + * + * @return the unscaled total elapsed time in seconds + */ + public float getUnscaledTotalTime() { + return totalTime / 1000.0f; + } - /** - * Returns a formatted string representing the unscaled total time in the - * format HH:MM:SS. - * - * @return the formatted unscaled total time - */ - public String getUnscaledFormattedTotalTime() { - return formatTime(getUnscaledTotalTime()); - } + /** + * Returns a formatted string representing the scaled total time in the format HH:MM:SS. + * + * @return the formatted scaled total time + */ + public String getFormattedTotalTime() { + return formatTime(getTotalTime()); + } - /** - * Returns the time it took to complete the last frame in seconds, scaled by - * the current time scale. - * - * @return the scaled time per frame in seconds - */ - public float getTimePerFrame() { - return time / 1000.0f * timeScale; - } + /** + * Returns a formatted string representing the unscaled total time in the format HH:MM:SS. + * + * @return the formatted unscaled total time + */ + public String getUnscaledFormattedTotalTime() { + return formatTime(getUnscaledTotalTime()); + } - /** - * Returns the time it took to complete the last frame in seconds, independent - * of the time scale. - * - * @return the unscaled time per frame in seconds - */ - public float getUnscaledTimePerFrame() { - return time / 1000.0f; - } + /** + * Returns the time it took to complete the last frame in seconds, scaled by the current time + * scale. + * + * @return the scaled time per frame in seconds + */ + public float getTimePerFrame() { + return time / 1000.0f * timeScale; + } - /** - * Returns the current time scaling factor. - * - * @return the time scale - */ - public float getTimeScale() { - return timeScale; - } + /** + * Returns the time it took to complete the last frame in seconds, independent of the time scale. + * + * @return the unscaled time per frame in seconds + */ + public float getUnscaledTimePerFrame() { + return time / 1000.0f; + } - /** - * Sets the time scaling factor. A value of 1.0 represents real-time, values - * less than 1.0 slow down time, and values greater than 1.0 speed up time. - * - * @param timeScale the new time scaling factor - */ - public void setTimeScale(float timeScale) { - this.timeScale = timeScale; - } + /** + * Returns the current time scaling factor. + * + * @return the time scale + */ + public float getTimeScale() { + return timeScale; + } - /** - * Returns the real-time elapsed since the game / application started, - * measured in seconds. - * - *

- * This method uses `System.nanoTime()` to obtain a high-precision timestamp. - * The returned value is a `float` representing the elapsed time in seconds. - *

- * - * @return The real-time elapsed since the game started, in seconds. - */ - public float getRealtimeSinceStartup() { - return (System.nanoTime() - startTime) / 1_000_000_000.0f; - } + /** + * Sets the time scaling factor. A value of 1.0 represents real-time, values less than 1.0 slow + * down time, and values greater than 1.0 speed up time. + * + * @param timeScale the new time scaling factor + */ + public void setTimeScale(float timeScale) { + this.timeScale = timeScale; + } - /** - * Returns the real-time elapsed since the game / application started, - * measured in seconds, as a `double` value. - * - *

- * This method uses `System.nanoTime()` to obtain a high-precision timestamp. - * The returned value is a `double` representing the elapsed time in seconds, - * providing higher precision than the `float` version. - *

- * - * @return The real-time elapsed since the game started, in seconds, as a - * `double`. - */ - public double getRealtimeSinceStartupAsDouble() { - return (System.nanoTime() - startTime) / 1_000_000_000.0; - } + /** + * Returns the real-time elapsed since the game / application started, measured in seconds. + * + *

This method uses `System.nanoTime()` to obtain a high-precision timestamp. The returned + * value is a `float` representing the elapsed time in seconds. + * + * @return The real-time elapsed since the game started, in seconds. + */ + public float getRealtimeSinceStartup() { + return (System.nanoTime() - startTime) / 1_000_000_000.0f; + } - /** - * Returns the total number of frames that have passed since the Timer - * started. - * - * @return the total frame count - */ - public int getFrameCount() { - return frameCount; - } + /** + * Returns the real-time elapsed since the game / application started, measured in seconds, as a + * `double` value. + * + *

This method uses `System.nanoTime()` to obtain a high-precision timestamp. The returned + * value is a `double` representing the elapsed time in seconds, providing higher precision than + * the `float` version. + * + * @return The real-time elapsed since the game started, in seconds, as a `double`. + */ + public double getRealtimeSinceStartupAsDouble() { + return (System.nanoTime() - startTime) / 1_000_000_000.0; + } - /** - * Formats a time value in seconds into a string in the format HH:MM:SS. - * - * @param timeInSeconds the time in seconds to format - * @return the formatted time string - */ - private String formatTime(float timeInSeconds) { - int s = (int) timeInSeconds; - return String.format("%d:%02d:%02d", s / 3600, (s % 3600) / 60, s % 60); - } + /** + * Returns the total number of frames that have passed since the Timer started. + * + * @return the total frame count + */ + public int getFrameCount() { + return frameCount; + } - /** - * Returns a string representation of this Timer, showing its current state. - * - * @return a string representation of the Timer - */ - @Override - public String toString() { - return "Timer [millisecondCounter=" + millisecondCounter - + ", lastFrameCount=" + lastFrameCount + ", fps=" + fps + ", lastTime=" - + lastTime + ", time=" + time + ", totalTime=" + totalTime - + ", timeScale=" + timeScale + ", frameCount=" + frameCount + "]"; - } + /** + * Formats a time value in seconds into a string in the format HH:MM:SS. + * + * @param timeInSeconds the time in seconds to format + * @return the formatted time string + */ + private String formatTime(float timeInSeconds) { + int s = (int) timeInSeconds; + return String.format("%d:%02d:%02d", s / 3600, (s % 3600) / 60, s % 60); + } + /** + * Returns a string representation of this Timer, showing its current state. + * + * @return a string representation of the Timer + */ + @Override + public String toString() { + return "Timer [millisecondCounter=" + + millisecondCounter + + ", lastFrameCount=" + + lastFrameCount + + ", fps=" + + fps + + ", lastTime=" + + lastTime + + ", time=" + + time + + ", totalTime=" + + totalTime + + ", timeScale=" + + timeScale + + ", frameCount=" + + frameCount + + "]"; + } }