diff --git a/src/main/java/engine/Timer.java b/src/main/java/engine/Timer.java new file mode 100644 index 00000000..d3572e22 --- /dev/null +++ b/src/main/java/engine/Timer.java @@ -0,0 +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. + * + *
Key features include: + * + *
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. + * + * @return the scaled total elapsed time in seconds + */ + public float getTotalTime() { + return totalTime / 1000.0f * timeScale; + } + + /** + * 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 scaled total time in the format HH:MM:SS. + * + * @return the formatted scaled total time + */ + public String getFormattedTotalTime() { + return formatTime(getTotalTime()); + } + + /** + * 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, 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 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 current time scaling factor. + * + * @return the time scale + */ + public float getTimeScale() { + return timeScale; + } + + /** + * 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. + * + *
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. + * + * @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, 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 + + "]"; + } +} diff --git a/src/main/java/engine/components/AbstractComponent.java b/src/main/java/engine/components/AbstractComponent.java new file mode 100644 index 00000000..669586e0 --- /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 diff --git a/src/main/java/engine/components/CinematicBlackBarsRenderer.java b/src/main/java/engine/components/CinematicBlackBarsRenderer.java new file mode 100644 index 00000000..7a397239 --- /dev/null +++ b/src/main/java/engine/components/CinematicBlackBarsRenderer.java @@ -0,0 +1,203 @@ +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 RenderableComponent { + + /** 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); + } + + @Override + public void onAttach() { + } + + @Override + 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 new file mode 100644 index 00000000..4e4aa193 --- /dev/null +++ b/src/main/java/engine/components/CircularAnimationComponent.java @@ -0,0 +1,110 @@ +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: + *
+ * 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); + } + + @Override + public void onAttach() { + } + + @Override + public void onDetach() { + } + +} \ No newline at end of file diff --git a/src/main/java/engine/components/Component.java b/src/main/java/engine/components/Component.java new file mode 100644 index 00000000..9cd5fc4c --- /dev/null +++ b/src/main/java/engine/components/Component.java @@ -0,0 +1,65 @@ +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 onAttach()}, + * {@code update()}, and {@code onDetach()} 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); + + /** + * Called when the component is attached to a {@link SceneNode}. + *+ * This allows the component to set up necessary resources, state, or perform + * other preparatory work specific to being added to a node. + *
+ */ + void onAttach(); + + /** + * 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 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 new file mode 100644 index 00000000..87b2d42e --- /dev/null +++ b/src/main/java/engine/components/ControlWASD.java @@ -0,0 +1,163 @@ +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; + } + + /** + * 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; + } + + /** + * 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; + } + + @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 new file mode 100644 index 00000000..eb658fc6 --- /dev/null +++ b/src/main/java/engine/components/Geometry.java @@ -0,0 +1,164 @@ +package engine.components; + +import engine.render.Material; +import math.Bounds; +import math.Color; +import mesh.Mesh3D; +import mesh.util.MeshBoundsCalculator; +import workspace.ui.Graphics; + +/** + * 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. + * + * 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 mesh representing the geometry of the object. */ + private Mesh3D mesh; + + /** The material applied to the mesh for rendering. */ + private Material material; + + /** + * 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 {@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 {@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 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) { + throw new IllegalArgumentException("Mesh cannot be null."); + } + if (material == null) { + throw new IllegalArgumentException("Material cannot be null."); + } + } + + /** + * 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 render(Graphics g) { + material.apply(g); + g.fillFaces(mesh); + material.release(g); + debugRenderBounds(g); + } + + /** + * 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. + * + *+ * 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 {@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 update(float tpf) { + // Placeholder for potential mesh state updates + } + + @Override + public void onAttach() { + } + + @Override + public void onDetach() { + } + +} \ No newline at end of file diff --git a/src/main/java/engine/components/RenderableComponent.java b/src/main/java/engine/components/RenderableComponent.java new file mode 100644 index 00000000..b4a66416 --- /dev/null +++ b/src/main/java/engine/components/RenderableComponent.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 RenderableComponent 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 diff --git a/src/main/java/engine/components/RotationComponent.java b/src/main/java/engine/components/RotationComponent.java new file mode 100644 index 00000000..dbdab16f --- /dev/null +++ b/src/main/java/engine/components/RotationComponent.java @@ -0,0 +1,122 @@ +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: + *
+ * 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 onAttach() { + } + + @Override + public void onDetach() { + } + +} \ No newline at end of file diff --git a/src/main/java/engine/components/Transform.java b/src/main/java/engine/components/Transform.java new file mode 100644 index 00000000..dd4a0d84 --- /dev/null +++ b/src/main/java/engine/components/Transform.java @@ -0,0 +1,245 @@ +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: + *
+ * 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) { + } + + @Override + public void onAttach() { + } + + @Override + public void onDetach() { + } + +} \ No newline at end of file diff --git a/src/main/java/engine/render/Material.java b/src/main/java/engine/render/Material.java new file mode 100644 index 00000000..af8206f7 --- /dev/null +++ b/src/main/java/engine/render/Material.java @@ -0,0 +1,288 @@ +package engine.render; + +import math.Color; +import workspace.ui.Graphics; + +/** + * Represents a material with lighting and shader properties for 3D rendering. + *+ * 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 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 { + + /** + * 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 = MaterialFactory + .createMetallicSilver(); + + /** + * Metallic gold material with a shiny gold appearance. + */ + public static final Material GOLDEN_METALLIC_MATERIAL = MaterialFactory + .createGoldenMetallic(); + + /** + * Clear glass material with a transparent appearance. + */ + public static final Material GLASS_CLEAR_MATERIAL = MaterialFactory + .createGlassClear(); + + /** + * Stone grey material with a matte grey appearance. + */ + public static final Material STONE_GREY_MATERIAL = MaterialFactory + .createStoneGrey(); + + /** + * Water material with a reflective blue appearance. + */ + public static final Material WATER_MATERIAL = MaterialFactory.createWater(); + + private boolean useLighting; + + /** + * Base color for the material. + */ + private final Color color; + + /** + * Ambient light coefficient (R, G, B). + */ + private final float[] ambient; + + /** + * Diffuse light coefficient (R, G, B). + */ + private final float[] diffuse; + + /** + * Specular light coefficient (R, G, B). + */ + private final float[] specular; + + /** + * Shininess factor for specular highlights. + */ + private final float shininess; + + /** + * Constructor to set the base color of the material. + * + * @param color The base color of the material. + */ + public Material(Color color) { + this(new Builder().setColor(color)); + } + + private Material(Builder builder) { + this.useLighting = builder.useLighting; + this.color = builder.color; + this.ambient = builder.ambient; + this.diffuse = builder.diffuse; + this.specular = builder.specular; + this.shininess = builder.shininess; + } + + /** + * Builder class to facilitate the creation of custom materials with specific + * lighting and shader properties. + */ + 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 }; + + 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; + } + + public Builder setUseLighting(boolean useLighting) { + this.useLighting = useLighting; + 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 provided rendering context. + * + * @param g The {@link Graphics} instance to apply this material to. + */ + public void apply(Graphics g) { + g.setMaterial(this); + } + + /** + * 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 this material will be + * unbound. + */ + 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. + * + * @return The {@link Color} representing the base color of the material. + */ + public Color getColor() { + return color; + } + + /** + * Retrieves the ambient light coefficient of the material. + * + * @return An array representing the ambient light coefficient (R, G, B). + */ + public float[] getAmbient() { + return ambient; + } + + /** + * Retrieves the diffuse light coefficient of the material. + * + * @return An array representing the diffuse light coefficient (R, G, B). + */ + public float[] getDiffuse() { + return diffuse; + } + + /** + * Retrieves the specular light coefficient of the material. + * + * @return An array representing the specular light coefficient (R, G, B). + */ + public float[] getSpecular() { + return specular; + } + + /** + * Retrieves the shininess factor of the material. + * + * @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(); + } + +} 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; + } + +} 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..991d164a --- /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.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 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}. + *
+ *+ * 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 RenderableComponent { + + 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; + } + +} 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: + *
+ *+ * 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 diff --git a/src/main/java/engine/scene/SceneNode.java b/src/main/java/engine/scene/SceneNode.java new file mode 100644 index 00000000..5a5a6550 --- /dev/null +++ b/src/main/java/engine/scene/SceneNode.java @@ -0,0 +1,375 @@ +package engine.scene; + +import java.util.ArrayList; +import java.util.List; + +import engine.components.Component; +import engine.components.RenderableComponent; +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 RenderableComponent}. + *
+ *+ * Example use cases include: + *
+ * 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 RenderableComponent} 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 (RenderableComponent 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); + component.initialize(); + } + } + + /** + * 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+ * Enables querying for specific types of behavior or functionality attached + * to a node. + *
+ * + * @param componentClass The class type of the component to retrieve. + * @param+ * 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 { + + Vector3f getTarget(); + + /** + * 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 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/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 65%
rename from src/main/java/scene/light/LightRenderer.java
rename to src/main/java/engine/scene/light/LightRenderer.java
index 3490d213..4e519fef 100644
--- a/src/main/java/scene/light/LightRenderer.java
+++ b/src/main/java/engine/scene/light/LightRenderer.java
@@ -1,4 +1,6 @@
-package scene.light;
+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/scene/light/LightType.java b/src/main/java/engine/scene/light/LightType.java similarity index 55% rename from src/main/java/scene/light/LightType.java rename to src/main/java/engine/scene/light/LightType.java index 051300a9..8d641565 100644 --- a/src/main/java/scene/light/LightType.java +++ b/src/main/java/engine/scene/light/LightType.java @@ -1,9 +1,9 @@ -package scene.light; +package engine.scene.light; /** * 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 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; diff --git a/src/main/java/math/Bounds.java b/src/main/java/math/Bounds.java new file mode 100644 index 00000000..57f62990 --- /dev/null +++ b/src/main/java/math/Bounds.java @@ -0,0 +1,229 @@ +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 p the point to check + * @return {@code true} if the point is inside the bounding box, {@code false} + * otherwise + */ + 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 p the point to include in the bounding box + */ + 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)); + } + + /** + * 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) { + float halfAmount = amount / 2; + min.subtractLocal(halfAmount, halfAmount, halfAmount); + max.addLocal(halfAmount, halfAmount, halfAmount); + } + + /** + * 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 {@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 intersectsRay(Ray3f ray) { + if (ray.getDirection().isZero()) { + return false; // A ray with zero direction cannot intersect anything + } + + if (min.equals(max)) { + return ray.getOrigin().equals(min); + } + + 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 true; + } + + /** + * 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 + * @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); + } + + /** + * 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 diff --git a/src/main/java/math/Color.java b/src/main/java/math/Color.java index cc1613bf..8af38db0 100644 --- a/src/main/java/math/Color.java +++ b/src/main/java/math/Color.java @@ -1,564 +1,512 @@ package math; -/** - * Representation of RGBA colors. The components (r,g,b) define a color in RGB - * color space. - * - * @author Simon - * @version 0.3, 11 December 2017 - * - */ +/** 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 subtract(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 bya 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;
- }
-
- /**
- * 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 + "]";
+ }
}
diff --git a/src/main/java/math/Matrix4f.java b/src/main/java/math/Matrix4f.java
index 00a282fe..f148193e 100644
--- a/src/main/java/math/Matrix4f.java
+++ b/src/main/java/math/Matrix4f.java
@@ -4,389 +4,188 @@
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);
+ }
+
+ /**
+ * 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; + } + + /** + * 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: + *
+ * 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.+ * 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.
+ *
+ * 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.+ * 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; + + /** + * 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. + *+ * 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(); + 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. + */ + public Vector3f getOrigin() { + return origin; + } + + /** + * 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. + */ + 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}. + *+ * The formula for the point is: + * + *
+ * {@code
+ * point = origin + t * direction
+ * }
+ *
+ *
+ * where {@code t} is a scalar representing the distance along the ray from
+ * the origin. Positive values of {@code t} will give points in the direction
+ * the ray is pointing, while negative values will give points in the opposite
+ * direction.
+ *
+ *
+ * @param t The parameter along the ray (can be negative, zero, or positive).
+ * @return The point at parameter {@code t}.
+ */
+ public Vector3f getPointAt(float t) {
+ return origin.add(direction.mult(t));
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/math/Vector3f.java b/src/main/java/math/Vector3f.java
index 7d756a56..935b0f5f 100644
--- a/src/main/java/math/Vector3f.java
+++ b/src/main/java/math/Vector3f.java
@@ -2,552 +2,569 @@
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;
+ }
+
+ 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;
+ 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 + "]";
+ }
}
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- * 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(); - ListThe 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
+ * 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).
+ *
+ * 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.
+ *
+ * @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
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
+ * 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.
+ *
+ * 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.
+ *
@@ -60,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}.
+ *
+ * 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.
+ *
- * 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.
- *
- * 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.
- *
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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ *