diff --git a/src/main/java/engine/application/Application.java b/src/main/java/engine/application/Application.java new file mode 100644 index 00000000..23957ac0 --- /dev/null +++ b/src/main/java/engine/application/Application.java @@ -0,0 +1,17 @@ +package engine.application; + +import engine.input.Input; +import workspace.ui.Graphics; + +public interface Application { + + void initialize(); + + void update(); + + void render(Graphics g); + + void cleanup(); + + void setInput(Input input); +} diff --git a/src/main/java/engine/application/ApplicationContainer.java b/src/main/java/engine/application/ApplicationContainer.java new file mode 100644 index 00000000..6cf1ef82 --- /dev/null +++ b/src/main/java/engine/application/ApplicationContainer.java @@ -0,0 +1,63 @@ +package engine.application; + +import engine.input.Input; +import workspace.ui.Graphics; + +public class ApplicationContainer { + + private boolean isInitialized = false; + + private Graphics graphics; + + private Application application; + + public ApplicationContainer(Application application) { + if (application == null) { + throw new IllegalArgumentException("Application cannot be null."); + } + this.application = application; + } + + public void initialize() { + if (isInitialized) { + return; + } + application.initialize(); + isInitialized = true; + } + + public void update() { + application.update(); + } + + public void render() { + checkInitialization(); + if (graphics == null) { + throw new IllegalStateException( + "Graphics context is not initialized. Call setGraphics() first."); + } + application.render(graphics); + } + + public void cleanup() { + application.cleanup(); + } + + private void checkInitialization() { + if (!isInitialized) { + throw new IllegalStateException( + "ApplicationContainer is not initialized. Call initialize() first."); + } + } + + public void setInput(Input input) { + application.setInput(input); + } + + public void setGraphics(Graphics g) { + if (g == null) { + throw new IllegalArgumentException("Graphics cannot be null."); + } + this.graphics = g; + } +} diff --git a/src/main/java/engine/application/ApplicationSettings.java b/src/main/java/engine/application/ApplicationSettings.java new file mode 100644 index 00000000..c8c42499 --- /dev/null +++ b/src/main/java/engine/application/ApplicationSettings.java @@ -0,0 +1,135 @@ +package engine.application; + +/** + * Encapsulates configuration settings for an application, including dimensions, fullscreen mode, + * and title. Provides default values and validation for each setting. + */ +public class ApplicationSettings { + + private static final String DEFAULT_TITLE = "Untitled-Application"; + + private int width; + + private int height; + + private boolean fullscreen; + + private String title; + + /** + * Constructs an ApplicationSettings instance with default values: + * + * + */ + public ApplicationSettings() { + this.width = 1024; + this.height = 768; + this.fullscreen = false; + this.title = DEFAULT_TITLE; + } + + /** + * Creates and returns an ApplicationSettings instance with default values. + * + * + * + * @return A new {@link ApplicationSettings} instance with default values. + */ + public static ApplicationSettings defaultSettings() { + return new ApplicationSettings(); + } + + /** + * Gets the width of the application window. + * + * @return The width in pixels. + */ + public int getWidth() { + return width; + } + + /** + * Sets the width of the application window. + * + * @param width The width in pixels. Must be greater than 0. + * @throws IllegalArgumentException if the width is less than or equal to 0. + */ + public void setWidth(int width) { + if (width <= 0) { + throw new IllegalArgumentException("Width must be greater than 0."); + } + this.width = width; + } + + /** + * Gets the height of the application window. + * + * @return The height in pixels. + */ + public int getHeight() { + return height; + } + + /** + * Sets the height of the application window. + * + * @param height The height in pixels. Must be greater than 0. + * @throws IllegalArgumentException if the height is less than or equal to 0. + */ + public void setHeight(int height) { + if (height <= 0) { + throw new IllegalArgumentException("Height must be greater than 0."); + } + this.height = height; + } + + /** + * Checks if the application is set to fullscreen mode. + * + * @return {@code true} if fullscreen mode is enabled, {@code false} otherwise. + */ + public boolean isFullscreen() { + return fullscreen; + } + + /** + * Sets whether the application should run in fullscreen mode. + * + * @param fullscreen {@code true} to enable fullscreen mode, {@code false} to disable it. + */ + public void setFullscreen(boolean fullscreen) { + this.fullscreen = fullscreen; + } + + /** + * Gets the title of the application window. + * + * @return The title as a {@link String}. + */ + public String getTitle() { + return title; + } + + /** + * Sets the title of the application window. + * + * @param title The title of the application window. Cannot be {@code null} or empty. + * @throws IllegalArgumentException if the title is {@code null} or empty. + */ + public void setTitle(String title) { + if (title == null || title.isEmpty()) { + throw new IllegalArgumentException("Title cannot be null or empty."); + } + this.title = title; + } +} diff --git a/src/main/java/engine/application/BasicApplication.java b/src/main/java/engine/application/BasicApplication.java new file mode 100644 index 00000000..4a5c14f0 --- /dev/null +++ b/src/main/java/engine/application/BasicApplication.java @@ -0,0 +1,173 @@ +package engine.application; + +import engine.Timer; +import engine.debug.DebugInfoUpdater; +import engine.debug.DebugOverlay; +import engine.debug.FpsGraph; +import engine.debug.FpsHistory; +import engine.input.Input; +import engine.input.Key; +import engine.processing.ProcessingApplication; +import engine.scene.Scene; +import engine.scene.SceneNode; +import workspace.ui.Graphics; + +public abstract class BasicApplication implements Application { + + private boolean launched; + + private boolean displayInfoText = true; + + private boolean isPaused = false; + + private Timer timer; + + protected Input input; + + protected Scene activeScene; + + protected SceneNode rootUI; + + protected DebugOverlay debugOverlay; + + protected DebugInfoUpdater debugInfoUpdater; + + protected FpsGraph fpsGraph; + + public BasicApplication() { + this.timer = new Timer(); + } + + public abstract void onInitialize(); + + public abstract void onUpdate(float tpf); + + public abstract void onRender(Graphics g); + + public abstract void onCleanup(); + + private boolean lastZ; + + public void launch(ApplicationSettings settings) { + if (launched) { + throw new IllegalStateException("Application already launched."); + } + launched = true; + ApplicationContainer container = new ApplicationContainer(this); + ProcessingApplication.launchApplication(container, settings); + Runtime.getRuntime() + .addShutdownHook( + new Thread( + () -> { + System.out.println("Cleanup application."); + cleanup(); + })); + } + + public void launch() { + launch(ApplicationSettings.defaultSettings()); + } + + @Override + public void initialize() { + rootUI = new SceneNode(); + initializeDebugOverlay(); + fpsGraph = new FpsGraph(new FpsHistory()); + onInitialize(); + } + + private void initializeDebugOverlay() { + debugOverlay = new DebugOverlay(); + debugInfoUpdater = new DebugInfoUpdater(debugOverlay); + } + + @Override + public void update() { + if (activeScene != null) { + + if (input.isKeyPressed(Key.Z) && !lastZ) { + activeScene.setWireframeMode(!activeScene.isWireframeMode()); + } + + lastZ = input.isKeyPressed(Key.Z); + } + + timer.update(); + fpsGraph.update(timer); + debugInfoUpdater.update(timer, activeScene, input); + + float tpf = timer.getTimePerFrame(); + if (input != null) { + input.update(); + } + if (!isPaused) { + if (activeScene != null) { + activeScene.update(tpf); + } + } + rootUI.update(tpf); + onUpdate(tpf); + } + + @Override + public void render(Graphics g) { + if (activeScene != null) { + activeScene.render(g); + } + + onRender(g); + + g.disableDepthTest(); + g.lightsOff(); + g.camera(); + + g.strokeWeight(1); + renderUi(g); + renderDebugUi(g); + fpsGraph.render(g); + + g.enableDepthTest(); + } + + private void renderUi(Graphics g) { + rootUI.render(g); + } + + private void renderDebugUi(Graphics g) { + if (!displayInfoText) return; + debugOverlay.render(g); + } + + @Override + public void cleanup() { + if (activeScene != null) { + activeScene.cleanup(); + } + rootUI.cleanup(); + onCleanup(); + } + + public void pause() { + isPaused = true; + } + + public void resume() { + isPaused = false; + } + + public void setInput(Input input) { + this.input = input; + } + + public Input getInput() { + return input; + } + + public Scene getActiveScene() { + return activeScene; + } + + public void setActiveScene(Scene activeScene) { + this.activeScene = activeScene; + } +} diff --git a/src/main/java/engine/components/AbstractComponent.java b/src/main/java/engine/components/AbstractComponent.java index 669586e0..cdf4231b 100644 --- a/src/main/java/engine/components/AbstractComponent.java +++ b/src/main/java/engine/components/AbstractComponent.java @@ -4,38 +4,34 @@ /** * 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. - *

+ * + *

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; + /** 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; - } + /** + * 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 + /** + * Retrieves the owning node for convenience. + * + * @return The owning SceneNode instance. + */ + public SceneNode getOwner() { + return owner; + } +} diff --git a/src/main/java/engine/components/CinematicBlackBarsRenderer.java b/src/main/java/engine/components/CinematicBlackBarsRenderer.java index 7a397239..76bab0da 100644 --- a/src/main/java/engine/components/CinematicBlackBarsRenderer.java +++ b/src/main/java/engine/components/CinematicBlackBarsRenderer.java @@ -4,200 +4,178 @@ 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). - *

+ * 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 +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() {} +} diff --git a/src/main/java/engine/components/CircularAnimationComponent.java b/src/main/java/engine/components/CircularAnimationComponent.java index 4e4aa193..2c3117b1 100644 --- a/src/main/java/engine/components/CircularAnimationComponent.java +++ b/src/main/java/engine/components/CircularAnimationComponent.java @@ -4,107 +4,89 @@ 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: + * 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. - *

- * + * + *

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; + /** 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; + /** 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; + /** 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; - } + /** + * 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; - } + /** + * 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; + /** + * 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); + // 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(); + // 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); - } + // Set the new position while maintaining the current Y-coordinate + transform.setPosition(x, transform.getPosition().y, z); + } - @Override - public void onAttach() { - } + @Override + public void onAttach() {} - @Override - public void onDetach() { - } - -} \ No newline at end of file + @Override + public void onDetach() {} +} diff --git a/src/main/java/engine/components/Component.java b/src/main/java/engine/components/Component.java index 9cd5fc4c..e9a27e8e 100644 --- a/src/main/java/engine/components/Component.java +++ b/src/main/java/engine/components/Component.java @@ -4,62 +4,50 @@ /** * 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. - *

+ * + *

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); + /** + * 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); + /** + * 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 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 + /** + * 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(); +} diff --git a/src/main/java/engine/components/ControlWASD.java b/src/main/java/engine/components/ControlWASD.java index 87b2d42e..7b1c49c4 100644 --- a/src/main/java/engine/components/ControlWASD.java +++ b/src/main/java/engine/components/ControlWASD.java @@ -6,158 +6,131 @@ 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. - *

+ * 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 + /** 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() {} +} diff --git a/src/main/java/engine/components/Geometry.java b/src/main/java/engine/components/Geometry.java index eb658fc6..823e599a 100644 --- a/src/main/java/engine/components/Geometry.java +++ b/src/main/java/engine/components/Geometry.java @@ -8,16 +8,14 @@ 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. - * + * 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 @@ -25,140 +23,128 @@ */ 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 + /** 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() {} +} diff --git a/src/main/java/engine/components/RenderableComponent.java b/src/main/java/engine/components/RenderableComponent.java index b4a66416..4fad8d71 100644 --- a/src/main/java/engine/components/RenderableComponent.java +++ b/src/main/java/engine/components/RenderableComponent.java @@ -5,41 +5,33 @@ /** * 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. - *

- * + * + *

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 + /** + * 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); +} diff --git a/src/main/java/engine/components/RotationComponent.java b/src/main/java/engine/components/RotationComponent.java index dbdab16f..f5b889f3 100644 --- a/src/main/java/engine/components/RotationComponent.java +++ b/src/main/java/engine/components/RotationComponent.java @@ -4,119 +4,110 @@ import math.Vector3f; /** - * A simple rotation component that rotates the owning SceneNode around a - * specified axis at a constant angular speed. - * - *

- * Properties: + * 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. - *

+ * + *

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 + /** 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() {} +} diff --git a/src/main/java/engine/components/Transform.java b/src/main/java/engine/components/Transform.java index dd4a0d84..834ba661 100644 --- a/src/main/java/engine/components/Transform.java +++ b/src/main/java/engine/components/Transform.java @@ -4,242 +4,216 @@ 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: + * 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. - *

- * + * + *

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 + /** 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() {} +} diff --git a/src/main/java/engine/debug/DebugInfoUpdater.java b/src/main/java/engine/debug/DebugInfoUpdater.java new file mode 100644 index 00000000..a6c890c1 --- /dev/null +++ b/src/main/java/engine/debug/DebugInfoUpdater.java @@ -0,0 +1,128 @@ +package engine.debug; + +import java.lang.management.ManagementFactory; +import java.util.Collection; + +import com.sun.management.OperatingSystemMXBean; + +import engine.Timer; +import engine.input.Input; +import engine.input.Key; +import engine.scene.Scene; + +/** + * The {@code DebugInfoUpdater} class is responsible for updating debug information displayed by a + * {@code DebugOverlay}. It collects information from various sources such as the {@code Timer}, + * {@code Scene}, and {@code Input}. + */ +public class DebugInfoUpdater { + + private static final String CATEGORY_TIME = "Time"; + + private static final String CATEGORY_INPUT = "Input"; + + private static final String CATEGORY_SCENE = "Scene"; + + private static final String CATEGORY_SYSTEM = "System"; + + private static final String CATEGORY_OS = "OS"; + + private final DebugOverlay debugOverlay; + + private final PerformanceMetrics performanceMetrics = new PerformanceMetrics(); + + private final MemoryMetrics memoryMetrics = new MemoryMetrics(); + + private final DrawCallCounter drawCallCounter = new DrawCallCounter(); + + private final OperatingSystemMXBean osBean = + (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean(); + + /** + * Constructs a new {@code DebugInfoUpdater} with the given {@code DebugOverlay}. + * + * @param debugOverlay the overlay where debug information will be displayed. + * @throws IllegalArgumentException if {@code debugOverlay} is {@code null}. + */ + public DebugInfoUpdater(DebugOverlay debugOverlay) { + if (debugOverlay == null) { + throw new IllegalArgumentException("DebugOverlay cannot be null."); + } + this.debugOverlay = debugOverlay; + } + + private void setInfo(String category, String key, String value) { + debugOverlay.setDebugItem(category, key, value); + } + + private void setInfo(String category, String key, float value) { + setInfo(category, key, String.valueOf(value)); + } + + private void setInfo(String category, String key, boolean value) { + setInfo(category, key, String.valueOf(value)); + } + + private void setInfo(String category, String key, int value) { + setInfo(category, key, String.valueOf(value)); + } + + /** + * Updates debug information by collecting data from the given sources. + * + * @param timer the {@code Timer} providing time-related debug information. + * @param activeScene the active {@code Scene}, or {@code null} if no scene is active. + * @param input the {@code Input} providing input-related debug information. + */ + public void update(Timer timer, Scene activeScene, Input input) { + updateTimeMetrics(timer); + updatePerformanceMetrics(); + updateInputMetrics(input); + if (activeScene != null) { + updateSceneMetrics(activeScene); + } + updateOsMetrics(); + } + + private String keysToString(Collection keys) { + String pressedKeys = ""; + for (Key key : keys) { + pressedKeys += key + " "; + } + return pressedKeys; + } + + private void updateOsMetrics() { + setInfo(CATEGORY_OS, "Name", osBean.getName()); + setInfo(CATEGORY_OS, "Arch", osBean.getArch()); + setInfo(CATEGORY_OS, "Processors", osBean.getAvailableProcessors()); + setInfo(CATEGORY_OS, "Version", osBean.getVersion()); + } + + private void updateInputMetrics(Input input) { + setInfo(CATEGORY_INPUT, "Mouse", "x=" + input.getMouseX() + ", y=" + input.getMouseY()); + setInfo(CATEGORY_INPUT, "Keys pressed", keysToString(input.getPressedKeys())); + } + + private void updateSceneMetrics(Scene activeScene) { + setInfo(CATEGORY_SCENE, "Scene", activeScene.getName()); + setInfo(CATEGORY_SCENE, "Root count", activeScene.getRootCount()); + setInfo(CATEGORY_SCENE, "Lights count", activeScene.getLightCount()); + setInfo(CATEGORY_SCENE, "Wireframe mode", activeScene.isWireframeMode()); + } + + private void updateTimeMetrics(Timer timer) { + setInfo(CATEGORY_TIME, "Time per frame (tpf)", timer.getTimePerFrame()); + setInfo(CATEGORY_TIME, "Frames per second (fps)", timer.getFrameRate()); + setInfo(CATEGORY_TIME, "Frames rendered", timer.getFrameCount()); + setInfo(CATEGORY_TIME, "Total time (hh:mm:ss)", timer.getFormattedTotalTime()); + setInfo(CATEGORY_TIME, "Time scale", timer.getTimeScale()); + } + + private void updatePerformanceMetrics() { + setInfo(CATEGORY_SYSTEM, "CPU Usage", performanceMetrics.getCpuUsage() + "%"); + setInfo(CATEGORY_SYSTEM, "Draw Calls", drawCallCounter.getCount()); + setInfo(CATEGORY_SYSTEM, "Memory", memoryMetrics.getMemoryInfo()); + setInfo(CATEGORY_SYSTEM, "Memory Used", memoryMetrics.getUsedMemory() / (1024 * 1024) + " MB"); + } +} diff --git a/src/main/java/engine/debug/DebugOverlay.java b/src/main/java/engine/debug/DebugOverlay.java new file mode 100644 index 00000000..9417d0a5 --- /dev/null +++ b/src/main/java/engine/debug/DebugOverlay.java @@ -0,0 +1,186 @@ +package engine.debug; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +import math.Color; +import workspace.ui.Graphics; + +/** + * The {@code DebugOverlay} class is responsible for displaying a customizable debug overlay on the + * screen. It provides functionality for managing debug items, rendering them with optional + * alignment, and customizing appearance such as line spacing and visibility of categories. + */ +public class DebugOverlay { + + private final Map> debugItems = new LinkedHashMap<>(); + + private final Map categoryVisibility = new HashMap<>(); + + /** Toggle for overall visibility. */ + private boolean visible = true; + + /** Toggle for aligning values in columns. */ + private boolean alignValues = true; + + /** Spacing between individual lines in pixels (20px by default). */ + private int lineSpacing = 20; + + /** Spacing between categories in pixels (40px by default). */ + private int categorySpacing = 40; + + /** + * Sets the visibility of the debug overlay. + * + * @param visible {@code true} to make the overlay visible, {@code false} otherwise. + */ + public void setVisible(boolean visible) { + this.visible = visible; + } + + /** + * Checks if the debug overlay is visible. + * + * @return {@code true} if the overlay is visible, {@code false} otherwise. + */ + public boolean isVisible() { + return visible; + } + + /** + * Toggles the alignment of debug item values. + * + * @param alignValues {@code true} to align values, {@code false} for no alignment. + */ + public void setAlignValues(boolean alignValues) { + this.alignValues = alignValues; + } + + /** + * Sets the spacing between individual lines in the overlay. + * + * @param spacing the line spacing in pixels. + */ + public void setLineSpacing(int spacing) { + this.lineSpacing = spacing; + } + + /** + * Sets the spacing between categories in the overlay. + * + * @param spacing the category spacing in pixels. + */ + public void setCategorySpacing(int spacing) { + this.categorySpacing = spacing; + } + + /** + * Sets the visibility of a specific debug category. + * + * @param category the name of the category. + * @param visible {@code true} to make the category visible, {@code false} otherwise. + */ + public void setCategoryVisible(String category, boolean visible) { + categoryVisibility.put(category, visible); + } + + /** + * Checks if a specific debug category is visible. + * + * @param category the name of the category. + * @return {@code true} if the category is visible, {@code false} otherwise. + */ + public boolean isCategoryVisible(String category) { + return categoryVisibility.getOrDefault(category, true); + } + + /** + * Adds or updates a debug item. If the specified category or key does not exist, it will be + * created. + * + * @param category the category under which the item belongs. + * @param key the key of the debug item. + * @param value the value of the debug item. + */ + public void setDebugItem(String category, String key, String value) { + debugItems.computeIfAbsent(category, k -> new LinkedHashMap<>()).put(key, value); + } + + /** + * Adds or updates a debug item with a float value. Internally, the float is converted to a + * string. + * + * @param category the category under which the item belongs. + * @param key the key of the debug item. + * @param value the float value of the debug item. + */ + public void setDebugItem(String category, String key, float value) { + setDebugItem(category, key, String.valueOf(value)); + } + + /** + * Renders the debug overlay. The items are displayed grouped by categories, with optional + * alignment for values. + * + * @param g the {@code Graphics} context used for rendering. + */ + public void render(Graphics g) { + if (!visible) { + return; + } + + g.pushMatrix(); + g.translate(10, 20); + g.setColor(Color.WHITE); + + int yOffset = 0; + int columnWidth = 200; // Width for aligning values + + for (Map.Entry> category : debugItems.entrySet()) { + if (!isCategoryVisible(category.getKey())) { + continue; + } + + // Render category header + g.text(category.getKey() + ":", 0, yOffset); + yOffset += lineSpacing; + g.text("===========================", 0, yOffset); + yOffset += lineSpacing; + + // Render items + for (Map.Entry item : category.getValue().entrySet()) { + if (alignValues) { + renderAlignedText(g, item.getKey(), item.getValue(), 0, yOffset, columnWidth); + } else { + g.text(item.getKey() + ": " + item.getValue(), 0, yOffset); + } + yOffset += lineSpacing; + } + + // Add spacing between categories + yOffset += categorySpacing - lineSpacing; + } + + g.popMatrix(); + } + + /** + * Helper method to render aligned text. Aligns the value column based on a specified width. + * + * @param g the {@code Graphics} context used for rendering. + * @param key the key text. + * @param value the value text. + * @param x the x-coordinate to start rendering. + * @param y the y-coordinate to render the text. + * @param columnWidth the width reserved for the key and value columns. + */ + private void renderAlignedText( + Graphics g, String key, String value, int x, int y, int columnWidth) { + g.text(key + ": ", x, y); // Draw the key + int valueX = x + columnWidth - (int) g.textWidth(value); // Calculate + // x-position for + // value + g.text(value, valueX, y); // Draw the value aligned to the right + } +} diff --git a/src/main/java/engine/debug/DrawCallCounter.java b/src/main/java/engine/debug/DrawCallCounter.java new file mode 100644 index 00000000..81ada292 --- /dev/null +++ b/src/main/java/engine/debug/DrawCallCounter.java @@ -0,0 +1,18 @@ +package engine.debug; + +public class DrawCallCounter { + + private int drawCalls = 0; + + public void increment() { + drawCalls++; + } + + public int getCount() { + return drawCalls; + } + + public void reset() { + drawCalls = 0; + } +} diff --git a/src/main/java/engine/debug/FpsGraph.java b/src/main/java/engine/debug/FpsGraph.java new file mode 100644 index 00000000..8bebfc8c --- /dev/null +++ b/src/main/java/engine/debug/FpsGraph.java @@ -0,0 +1,60 @@ +package engine.debug; + +import java.util.Queue; + +import engine.Timer; +import math.Color; +import workspace.ui.Graphics; + +public class FpsGraph { + + private final FpsHistory fpsHistory; + + public FpsGraph(FpsHistory fpsHistory) { + this.fpsHistory = fpsHistory; + } + + public void render(Graphics g) { + renderFpsGraph(g, 300, 100); + } + + public void update(Timer timer) { + fpsHistory.addSample(timer.getFrameRate()); + } + + private void renderFpsGraph(Graphics g, int width, int height) { + int x = g.getWidth() - width - 20; + int y = 30; + + Queue fpsValues = fpsHistory.getHistory(); + + float maxFps = fpsHistory.getMaxFps(); + + // Draw background + g.setColor(new Color(0.5f, 0.5f, 0.5f, 0.3f)); + g.fillRect(x, y, width, height); + + // Draw FPS values as a line graph + g.setColor(Color.RED); + int i = 0; + int prevX = x, prevY = y + height; + float step = (float) width / (float) fpsValues.size(); + + for (float fps : fpsValues) { + int barHeight = (int) ((fps / maxFps) * height); + int currentX = x + (int) (i * step); + int currentY = y + height - barHeight; + + g.drawLine(prevX, prevY, currentX, currentY); + + prevX = currentX; + prevY = currentY; + i++; + } + + // Draw axis labels + g.setColor(Color.WHITE); + g.text("0 FPS", x, y + height + 15); // Min FPS label + g.text((int) maxFps + " FPS", x, y - 5); // Max FPS label + } +} diff --git a/src/main/java/engine/debug/FpsHistory.java b/src/main/java/engine/debug/FpsHistory.java new file mode 100644 index 00000000..1e02a09f --- /dev/null +++ b/src/main/java/engine/debug/FpsHistory.java @@ -0,0 +1,30 @@ +package engine.debug; + +import java.util.LinkedList; +import java.util.Queue; + +public class FpsHistory { + + private final Queue fpsHistory = new LinkedList<>(); + + private final int maxSamples = 300; // Max samples to display in the graph + + public void addSample(float fps) { + if (fpsHistory.size() >= maxSamples) { + fpsHistory.poll(); // Remove the oldest FPS value + } + fpsHistory.add(fps); + } + + public Queue getHistory() { + return fpsHistory; + } + + public float getMaxFps() { + return fpsHistory.stream().max(Float::compare).orElse(60.0f); + } + + public int getMaxSamples() { + return maxSamples; + } +} diff --git a/src/main/java/engine/debug/MemoryMetrics.java b/src/main/java/engine/debug/MemoryMetrics.java new file mode 100644 index 00000000..a5204232 --- /dev/null +++ b/src/main/java/engine/debug/MemoryMetrics.java @@ -0,0 +1,34 @@ +package engine.debug; + +public class MemoryMetrics { + + private final Runtime runtime = Runtime.getRuntime(); + + public long getUsedMemory() { + return runtime.totalMemory() - runtime.freeMemory(); + } + + public long getTotalMemory() { + return runtime.totalMemory(); + } + + public long getMaxMemory() { + return runtime.maxMemory(); + } + + public long getFreeMemory() { + return runtime.freeMemory(); + } + + public String getMemoryInfo() { + long usedMB = (getTotalMemory() - getFreeMemory()) / 1024 / 1024; + long totalMemory = getTotalMemory() / 1024 / 1024; + + int percentage = 0; + if (totalMemory > 0) { + percentage = (int) (usedMB * 100 / totalMemory); + } + + return percentage + "% (" + usedMB + ") of " + totalMemory + "MB"; + } +} diff --git a/src/main/java/engine/debug/MetricHistory.java b/src/main/java/engine/debug/MetricHistory.java new file mode 100644 index 00000000..e9670534 --- /dev/null +++ b/src/main/java/engine/debug/MetricHistory.java @@ -0,0 +1,22 @@ +package engine.debug; + +import java.util.LinkedList; +import java.util.Queue; + +public class MetricHistory { + + private final Queue memoryHistory = new LinkedList<>(); + + private final int maxSamples = 300; + + public void addSample(long memoryUsage) { + if (memoryHistory.size() >= maxSamples) { + memoryHistory.poll(); // Remove the oldest sample + } + memoryHistory.add(memoryUsage); + } + + public Queue getHistory() { + return memoryHistory; + } +} diff --git a/src/main/java/engine/debug/PerformanceMetrics.java b/src/main/java/engine/debug/PerformanceMetrics.java new file mode 100644 index 00000000..155cfd3a --- /dev/null +++ b/src/main/java/engine/debug/PerformanceMetrics.java @@ -0,0 +1,17 @@ +package engine.debug; + +import com.sun.management.OperatingSystemMXBean; +import java.lang.management.ManagementFactory; + +public class PerformanceMetrics { + + private final OperatingSystemMXBean osBean; + + public PerformanceMetrics() { + osBean = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean(); + } + + public double getCpuUsage() { + return Math.round(osBean.getCpuLoad() * 100); + } +} diff --git a/src/main/java/engine/debug/PerformanceMonitor.java b/src/main/java/engine/debug/PerformanceMonitor.java new file mode 100644 index 00000000..a7a3935d --- /dev/null +++ b/src/main/java/engine/debug/PerformanceMonitor.java @@ -0,0 +1,54 @@ +package engine.debug; + +public class PerformanceMonitor { + + private static PerformanceMonitor instance; + + private int sceneNodeCount = 0; + + private int meshCount = 0; + + private int lightCount = 0; + + private PerformanceMonitor() {} + + public static PerformanceMonitor getInstance() { + if (instance == null) { + instance = new PerformanceMonitor(); + } + return instance; + } + + public void incrementSceneNodeCount() { + sceneNodeCount++; + } + + public void decrementSceneNodeCount() { + sceneNodeCount--; + } + + public void incrementMeshCount() { + meshCount++; + } + + public void decrementMeshCount() { + meshCount--; + } + + public void incrementLightCount() { + lightCount++; + } + + public void decrementLightCount() { + lightCount--; + } + + public int getSceneNodeCount() { + return sceneNodeCount; + } + + public String getStatistics() { + return String.format( + "Scene Nodes: %d, Meshes: %d, Lights: %d", sceneNodeCount, meshCount, lightCount); + } +} diff --git a/src/main/java/engine/input/Input.java b/src/main/java/engine/input/Input.java new file mode 100644 index 00000000..ab4c7c62 --- /dev/null +++ b/src/main/java/engine/input/Input.java @@ -0,0 +1,6 @@ +package engine.input; + +public interface Input extends KeyInput, MouseInput { + + void update(); // Calls both `updateKeyState` and `updateMouseState` +} diff --git a/src/main/java/engine/input/Key.java b/src/main/java/engine/input/Key.java new file mode 100644 index 00000000..ad414c77 --- /dev/null +++ b/src/main/java/engine/input/Key.java @@ -0,0 +1,98 @@ +package engine.input; + +public enum Key { + SPACE, + SHIFT, + CTRL, + ARROW_UP, + ARROW_DOWN, + ARROW_LEFT, + ARROW_RIGHT, + + ESC, + ENTER, + TAB, + BACKSPACE, + + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T, + U, + V, + W, + X, + Y, + Z, + + F1, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + + HOME, + END, + PAGE_UP, + PAGE_DOWN, + INSERT, + DELETE, + + NUM_0, + NUM_1, + NUM_2, + NUM_3, + NUM_4, + NUM_5, + NUM_6, + NUM_7, + NUM_8, + NUM_9, + + NUM_PAD_0, + NUM_PAD_1, + NUM_PAD_2, + NUM_PAD_3, + + NUM_PAD_4, + NUM_PAD_5, + NUM_PAD_6, + NUM_PAD_7, + + NUM_PAD_8, + NUM_PAD_9, + + CAPS_LOCK, + NUM_LOCK, + SCROLL_LOCK, + + PAUSE, + PRINT_SCREEN, + ALT, + ALT_GR, + + UNKNOWN +} diff --git a/src/main/java/engine/input/KeyCharacterMapper.java b/src/main/java/engine/input/KeyCharacterMapper.java new file mode 100644 index 00000000..98eb91db --- /dev/null +++ b/src/main/java/engine/input/KeyCharacterMapper.java @@ -0,0 +1,83 @@ +package engine.input; + +import java.util.HashMap; + +public class KeyCharacterMapper { + + private static HashMap keyMap; + + static { + keyMap = new HashMap(); + + keyMap.put('a', Key.A); + keyMap.put('b', Key.B); + keyMap.put('c', Key.C); + keyMap.put('d', Key.D); + keyMap.put('e', Key.E); + keyMap.put('f', Key.F); + keyMap.put('g', Key.G); + keyMap.put('h', Key.H); + keyMap.put('i', Key.I); + keyMap.put('j', Key.J); + keyMap.put('k', Key.K); + keyMap.put('l', Key.L); + keyMap.put('m', Key.M); + keyMap.put('n', Key.N); + keyMap.put('o', Key.O); + keyMap.put('p', Key.P); + keyMap.put('q', Key.Q); + keyMap.put('r', Key.R); + keyMap.put('s', Key.S); + keyMap.put('t', Key.T); + keyMap.put('u', Key.U); + keyMap.put('v', Key.V); + keyMap.put('w', Key.W); + keyMap.put('x', Key.X); + keyMap.put('y', Key.Y); + keyMap.put('z', Key.Z); + + keyMap.put('A', Key.A); + keyMap.put('B', Key.B); + keyMap.put('C', Key.C); + keyMap.put('D', Key.D); + keyMap.put('E', Key.E); + keyMap.put('F', Key.F); + keyMap.put('G', Key.G); + keyMap.put('H', Key.H); + keyMap.put('I', Key.I); + keyMap.put('J', Key.J); + keyMap.put('K', Key.K); + keyMap.put('L', Key.L); + keyMap.put('M', Key.M); + keyMap.put('N', Key.N); + keyMap.put('O', Key.O); + keyMap.put('P', Key.P); + keyMap.put('Q', Key.Q); + keyMap.put('R', Key.R); + keyMap.put('S', Key.S); + keyMap.put('T', Key.T); + keyMap.put('U', Key.U); + keyMap.put('V', Key.V); + keyMap.put('W', Key.W); + keyMap.put('X', Key.X); + keyMap.put('Y', Key.Y); + keyMap.put('Z', Key.Z); + + keyMap.put('0', Key.NUM_0); + keyMap.put('1', Key.NUM_1); + keyMap.put('2', Key.NUM_2); + keyMap.put('3', Key.NUM_3); + keyMap.put('4', Key.NUM_4); + keyMap.put('5', Key.NUM_5); + keyMap.put('6', Key.NUM_6); + keyMap.put('7', Key.NUM_7); + keyMap.put('8', Key.NUM_8); + keyMap.put('9', Key.NUM_9); + + keyMap.put(' ', Key.SPACE); + } + + public static Key getMappedKey(char character) { + return keyMap.getOrDefault(character, Key.UNKNOWN); + } +} diff --git a/src/main/java/engine/input/KeyInput.java b/src/main/java/engine/input/KeyInput.java new file mode 100644 index 00000000..15d494e1 --- /dev/null +++ b/src/main/java/engine/input/KeyInput.java @@ -0,0 +1,12 @@ +package engine.input; + +import java.util.Collection; + +public interface KeyInput { + + void updateKeyState(); // To track key-specific states + + boolean isKeyPressed(Key key); + + Collection getPressedKeys(); +} diff --git a/src/main/java/engine/input/MouseInput.java b/src/main/java/engine/input/MouseInput.java new file mode 100644 index 00000000..435874dd --- /dev/null +++ b/src/main/java/engine/input/MouseInput.java @@ -0,0 +1,28 @@ +package engine.input; + +public interface MouseInput { + + float getScreenWidth(); + + float getScreenHeight(); + + boolean isMousePressed(int button); + + boolean isMouseReleased(int button); + + float getMouseX(); + + float getMouseY(); + + float getLastMouseX(); + + float getLastMouseY(); + + float getMouseWheelDelta(); + + float getMouseDeltaX(); + + float getMouseDeltaY(); + + void updateMouseState(); // To track mouse-specific states +} diff --git a/src/main/java/engine/processing/LightGizmoRenderer.java b/src/main/java/engine/processing/LightGizmoRenderer.java new file mode 100644 index 00000000..8cc15f10 --- /dev/null +++ b/src/main/java/engine/processing/LightGizmoRenderer.java @@ -0,0 +1,125 @@ +package engine.processing; + +import engine.scene.light.AmbientLight; +import engine.scene.light.DirectionalLight; +import engine.scene.light.Light; +import engine.scene.light.LightRenderer; +import engine.scene.light.LightType; +import engine.scene.light.PointLight; +import engine.scene.light.SpotLight; +import math.Mathf; +import math.Vector3f; +import modular.Laf; +import processing.core.PApplet; +import workspace.ui.Graphics; + +public class LightGizmoRenderer implements LightRenderer { + + private float radius = 3f; + + private float size = 0.1f; + + private PApplet p; + + private Graphics g; + + public LightGizmoRenderer(PApplet p) { + this.p = p; + } + + @Override + public void setGraphics(Graphics g) { + this.g = g; + } + + @Override + public void render(Light light) { + switch (light.getType()) { + case SPOT: + render((SpotLight) light); + break; + case POINT: + render((PointLight) light); + break; + case DIRECTIONAL: + render((DirectionalLight) light); + break; + case AMBIENT: + render((AmbientLight) light); + break; + default: + logUnexpectedLightType(light.getType()); + } + } + + @Override + public void render(AmbientLight light) { + // TODO Auto-generated method stub + + } + + @Override + public void render(SpotLight light) { + render(light.getPosition()); + } + + @Override + public void render(PointLight light) { + render(light.getPosition()); + } + + private void render(Vector3f position) { + p.pushStyle(); + p.pushMatrix(); + p.translate(position.x, position.y, position.z); + + p.fill(128); + p.noStroke(); + p.box(size); + + p.noFill(); + + p.stroke(Laf.LIGHT_GIZMO_X.getRGBA()); + p.ellipse(0, 0, radius, radius); + + p.rotateX(Mathf.HALF_PI); + p.stroke(Laf.LIGHT_GIZMO_Y.getRGBA()); + p.ellipse(0, 0, radius, radius); + + p.rotateY(Mathf.HALF_PI); + p.stroke(Laf.LIGHT_GIZMO_Z.getRGBA()); + p.ellipse(0, 0, radius, radius); + + p.popMatrix(); + p.popStyle(); + } + + @Override + public void render(DirectionalLight light) { + p.pushStyle(); + p.pushMatrix(); + + // Set up the starting point (you can choose the origin or a fixed spot to render from) + p.translate(0, 0, 0); // Visualize the light emanating from the origin + + // Visualize the direction as a line extending in the light's direction vector + Vector3f direction = light.getDirection(); + + // Normalize the direction to ensure it renders consistently + float length = radius; // Set the visualization length + float dirX = direction.x * length; + float dirY = direction.y * length; + float dirZ = direction.z * length; + + p.stroke(Laf.LIGHT_GIZMO_DIRECTIONAL.getRGBA()); + p.line(0, 0, 0, dirX, dirY, dirZ); + + p.popMatrix(); + p.popStyle(); + } + + private void logUnexpectedLightType(LightType lightType) { + System.err.println("Unexpected LightType: " + lightType); + throw new IllegalArgumentException("Unexpected value: " + lightType); + } +} diff --git a/src/main/java/engine/processing/LightRendererImpl.java b/src/main/java/engine/processing/LightRendererImpl.java new file mode 100644 index 00000000..c7f7f636 --- /dev/null +++ b/src/main/java/engine/processing/LightRendererImpl.java @@ -0,0 +1,114 @@ +package engine.processing; + +import engine.scene.light.AmbientLight; +import engine.scene.light.DirectionalLight; +import engine.scene.light.Light; +import engine.scene.light.LightRenderer; +import engine.scene.light.LightType; +import engine.scene.light.PointLight; +import engine.scene.light.SpotLight; +import math.Color; +import processing.core.PApplet; +import workspace.ui.Graphics; + +public class LightRendererImpl implements LightRenderer { + + private boolean debug; + + private PApplet p; + + private Graphics g; + + public LightRendererImpl(PApplet p) { + this.p = p; + } + + @Override + public void setGraphics(Graphics g) { + this.g = g; + } + + @Override + public void render(Light light) { + switch (light.getType()) { + case SPOT: + render((SpotLight) light); + break; + case POINT: + render((PointLight) light); + break; + case DIRECTIONAL: + render((DirectionalLight) light); + break; + case AMBIENT: + render((AmbientLight) light); + break; + default: + logUnexpectedLightType(light.getType()); + } + } + + public void render(SpotLight light) { + renderCommon(light.getColor(), light.getConcentration()); + p.spotLight( + light.getColor().getRedInt(), + light.getColor().getGreenInt(), + light.getColor().getBlueInt(), + light.getPosition().getX(), + light.getPosition().getY(), + light.getPosition().getZ(), + light.getDirection().getX(), + light.getDirection().getY(), + light.getDirection().getZ(), + light.getAngle(), + light.getConcentration()); + } + + public void render(PointLight light) { + renderCommon(light.getColor(), light.getIntensity()); + p.pointLight( + light.getColor().getRedInt(), + light.getColor().getGreenInt(), + light.getColor().getBlueInt(), + light.getPosition().getX(), + light.getPosition().getY(), + light.getPosition().getZ()); + } + + public void render(DirectionalLight light) { + renderCommon(light.getColor(), light.getIntensity()); + p.directionalLight( + light.getColor().getRedInt(), + light.getColor().getGreenInt(), + light.getColor().getBlueInt(), + light.getDirection().getX(), + light.getDirection().getY(), + light.getDirection().getZ()); + } + + @Override + public void render(AmbientLight light) { + renderCommon(light.getColor(), 1); + g.setAmbientColor(light.getColor()); + } + + private void renderCommon(Color color, float intensity) { + if (!debug) return; + float scaledRed = color.getRedInt() * intensity; + float scaledGreen = color.getGreenInt() * intensity; + float scaledBlue = color.getBlueInt() * intensity; + + System.out.println( + "Rendering light with values - R: " + + scaledRed + + ", G: " + + scaledGreen + + ", B: " + + scaledBlue); + } + + private void logUnexpectedLightType(LightType lightType) { + System.err.println("Unexpected LightType: " + lightType); + throw new IllegalArgumentException("Unexpected value: " + lightType); + } +} diff --git a/src/main/java/engine/processing/NewtKeyMapper.java b/src/main/java/engine/processing/NewtKeyMapper.java new file mode 100644 index 00000000..26c267ef --- /dev/null +++ b/src/main/java/engine/processing/NewtKeyMapper.java @@ -0,0 +1,60 @@ +package engine.processing; + +import java.util.HashMap; + +import com.jogamp.newt.event.KeyEvent; + +import engine.input.Key; + +public class NewtKeyMapper { + + private static HashMap keyMap; + + static { + keyMap = new HashMap(); + + keyMap.put(KeyEvent.VK_A, Key.A); + keyMap.put(KeyEvent.VK_B, Key.B); + keyMap.put(KeyEvent.VK_C, Key.C); + keyMap.put(KeyEvent.VK_D, Key.D); + keyMap.put(KeyEvent.VK_E, Key.E); + keyMap.put(KeyEvent.VK_F, Key.F); + keyMap.put(KeyEvent.VK_G, Key.G); + keyMap.put(KeyEvent.VK_H, Key.H); + keyMap.put(KeyEvent.VK_I, Key.I); + keyMap.put(KeyEvent.VK_J, Key.J); + keyMap.put(KeyEvent.VK_K, Key.K); + keyMap.put(KeyEvent.VK_L, Key.L); + keyMap.put(KeyEvent.VK_M, Key.M); + keyMap.put(KeyEvent.VK_N, Key.N); + keyMap.put(KeyEvent.VK_O, Key.O); + keyMap.put(KeyEvent.VK_P, Key.P); + keyMap.put(KeyEvent.VK_Q, Key.Q); + keyMap.put(KeyEvent.VK_R, Key.R); + keyMap.put(KeyEvent.VK_S, Key.S); + keyMap.put(KeyEvent.VK_T, Key.T); + keyMap.put(KeyEvent.VK_U, Key.U); + keyMap.put(KeyEvent.VK_V, Key.V); + keyMap.put(KeyEvent.VK_W, Key.W); + keyMap.put(KeyEvent.VK_X, Key.X); + keyMap.put(KeyEvent.VK_Y, Key.Y); + keyMap.put(KeyEvent.VK_Z, Key.Z); + + keyMap.put(KeyEvent.VK_F1, Key.F1); + keyMap.put(KeyEvent.VK_F1, Key.F1); + keyMap.put(KeyEvent.VK_F1, Key.F1); + keyMap.put(KeyEvent.VK_F1, Key.F1); + keyMap.put(KeyEvent.VK_F1, Key.F1); + keyMap.put(KeyEvent.VK_F1, Key.F1); + keyMap.put(KeyEvent.VK_F1, Key.F1); + keyMap.put(KeyEvent.VK_F1, Key.F1); + keyMap.put(KeyEvent.VK_F1, Key.F1); + keyMap.put(KeyEvent.VK_F1, Key.F1); + keyMap.put(KeyEvent.VK_F1, Key.F1); + keyMap.put(KeyEvent.VK_F1, Key.F1); + } + + public static Key mapKeyCode(int keyCode) { + return keyMap.getOrDefault(keyCode, Key.UNKNOWN); + } +} diff --git a/src/main/java/engine/processing/ProcessingApplication.java b/src/main/java/engine/processing/ProcessingApplication.java new file mode 100644 index 00000000..cc25e518 --- /dev/null +++ b/src/main/java/engine/processing/ProcessingApplication.java @@ -0,0 +1,86 @@ +package engine.processing; + +import engine.application.ApplicationContainer; +import engine.application.ApplicationSettings; +import engine.input.Input; +import engine.input.KeyInput; +import engine.input.MouseInput; +import processing.core.PApplet; +import workspace.GraphicsPImpl; +import workspace.Workspace; +import workspace.ui.Graphics; + +public class ProcessingApplication extends PApplet { + + private static boolean launched = false; + + private static ApplicationContainer container; + + private static ApplicationSettings settings; + + private Workspace workspace; + + @Override + public void settings() { + size(settings.getWidth(), settings.getHeight(), P3D); + smooth(8); + if (settings.isFullscreen()) { + fullScreen(); + } + } + + @Override + public void setup() { + Graphics g = new GraphicsPImpl(this); + container.setGraphics(g); + getSurface().setTitle(settings.getTitle()); + setupInput(); + // workspace = new Workspace(this); + // workspace.setLoop(true); + // workspace.setGridVisible(false); + // workspace.setUiVisible(false); + container.initialize(); + noCursor(); + } + + private void setupInput() { + KeyInput keyInput = new ProcessingKeyInput(this); + MouseInput mouseInput = new ProcessingMouseInput(this); + Input input = new ProcessingInput(keyInput, mouseInput); + container.setInput(input); + } + + @Override + public void draw() { + colorMode(RGB); + background(0); + scale(100); + strokeWeight(0.01f); + noLights(); + container.update(); + container.render(); + } + + public static void launchApplication( + ApplicationContainer appContainer, ApplicationSettings appSettings) { + if (launched) { + throw new IllegalStateException("Application already launched."); + } + if (appContainer == null) { + throw new IllegalArgumentException("ApplicationContainer cannot be null."); + } + if (appSettings == null) { + throw new IllegalArgumentException("ApplicationSettings cannot be null"); + } + launched = true; + settings = appSettings; + container = appContainer; + PApplet.main(ProcessingApplication.class); + } + + @Override + public void exit() { + container.cleanup(); + super.exit(); + } +} diff --git a/src/main/java/engine/processing/ProcessingInput.java b/src/main/java/engine/processing/ProcessingInput.java new file mode 100644 index 00000000..4133e63a --- /dev/null +++ b/src/main/java/engine/processing/ProcessingInput.java @@ -0,0 +1,101 @@ +package engine.processing; + +import java.util.Collection; + +import engine.input.Input; +import engine.input.Key; +import engine.input.KeyInput; +import engine.input.MouseInput; + +public class ProcessingInput implements Input { + + private final KeyInput keyInput; + + private final MouseInput mouseInput; + + public ProcessingInput(KeyInput keyInput, MouseInput mouseInput) { + this.keyInput = keyInput; + this.mouseInput = mouseInput; + } + + @Override + public Collection getPressedKeys() { + return keyInput.getPressedKeys(); + } + + @Override + public boolean isKeyPressed(Key key) { + return keyInput.isKeyPressed(key); + } + + @Override + public void updateKeyState() { + keyInput.updateKeyState(); + } + + @Override + public boolean isMousePressed(int button) { + return mouseInput.isMousePressed(button); + } + + @Override + public boolean isMouseReleased(int button) { + return mouseInput.isMouseReleased(button); + } + + @Override + public float getScreenWidth() { + return mouseInput.getScreenWidth(); + } + + @Override + public float getScreenHeight() { + return mouseInput.getScreenHeight(); + } + + @Override + public float getMouseX() { + return mouseInput.getMouseX(); + } + + @Override + public float getMouseY() { + return mouseInput.getMouseY(); + } + + @Override + public float getLastMouseX() { + return mouseInput.getLastMouseX(); + } + + @Override + public float getLastMouseY() { + return mouseInput.getLastMouseY(); + } + + @Override + public float getMouseDeltaX() { + return mouseInput.getMouseDeltaX(); + } + + @Override + public float getMouseDeltaY() { + return mouseInput.getMouseDeltaY(); + } + + @Override + public float getMouseWheelDelta() { + return mouseInput.getMouseWheelDelta(); + } + + @Override + public void updateMouseState() { + mouseInput.updateMouseState(); + } + + @Override + public void update() { + updateKeyState(); + updateMouseState(); + } +} diff --git a/src/main/java/engine/processing/ProcessingKeyInput.java b/src/main/java/engine/processing/ProcessingKeyInput.java new file mode 100644 index 00000000..dd33ed0d --- /dev/null +++ b/src/main/java/engine/processing/ProcessingKeyInput.java @@ -0,0 +1,70 @@ +package engine.processing; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; + +import engine.input.Key; +import engine.input.KeyCharacterMapper; +import engine.input.KeyInput; +import processing.core.PApplet; +import processing.event.KeyEvent; + +public class ProcessingKeyInput implements KeyInput { + + private final PApplet applet; + + private HashSet keysDown; + + public ProcessingKeyInput(PApplet applet) { + this.applet = applet; + applet.registerMethod("keyEvent", this); + keysDown = new HashSet(); + } + + @Override + public boolean isKeyPressed(Key key) { + return keysDown.contains(key); + } + + @Override + public Collection getPressedKeys() { + synchronized (keysDown) { + return new ArrayList(keysDown); + } + } + + public void keyReleased() {} + + @Override + public void updateKeyState() { + // Handle frame-specific key state updates if necessary + } + + private Key getCodedKey(int keyCode) { + if (keyCode == PApplet.SHIFT) return Key.SHIFT; + if (keyCode == PApplet.ALT) return Key.ALT; + if (keyCode == PApplet.CONTROL) return Key.CTRL; + return Key.UNKNOWN; + } + + private void map(KeyEvent e, Key key) { + if (key == Key.UNKNOWN) return; + if (e.getAction() == KeyEvent.PRESS) { + keysDown.add(key); + } + if (e.getAction() == KeyEvent.RELEASE) { + keysDown.remove(key); + } + } + + public void keyEvent(KeyEvent e) { + if (e.getKey() == PApplet.CODED) { + Key key = getCodedKey(e.getKeyCode()); + map(e, key); + } else { + Key key = KeyCharacterMapper.getMappedKey(e.getKey()); + map(e, key); + } + } +} diff --git a/src/main/java/engine/processing/ProcessingMouseInput.java b/src/main/java/engine/processing/ProcessingMouseInput.java new file mode 100644 index 00000000..f7e1659a --- /dev/null +++ b/src/main/java/engine/processing/ProcessingMouseInput.java @@ -0,0 +1,85 @@ +package engine.processing; + +import engine.input.MouseInput; +import processing.core.PApplet; + +public class ProcessingMouseInput implements MouseInput { + + private final PApplet applet; + + private float mouseWheelDelta = 0; + + public ProcessingMouseInput(PApplet applet) { + this.applet = applet; + applet.registerMethod("mouseEvent", this); + } + + public void mouseEvent(processing.event.MouseEvent event) { + if (event.getAction() == processing.event.MouseEvent.WHEEL) { + mouseWheelDelta = event.getCount(); + } + } + + @Override + public boolean isMousePressed(int button) { + return applet.mousePressed && applet.mouseButton == button; + } + + @Override + public boolean isMouseReleased(int button) { + // Custom state tracking needed + return false; + } + + @Override + public float getScreenWidth() { + return applet.width; + } + + @Override + public float getScreenHeight() { + return applet.height; + } + + @Override + public float getMouseX() { + return applet.mouseX; + } + + @Override + public float getMouseY() { + return applet.mouseY; + } + + @Override + public float getLastMouseX() { + return applet.pmouseX; + } + + @Override + public float getLastMouseY() { + return applet.pmouseY; + } + + @Override + public float getMouseDeltaX() { + return applet.mouseX - applet.pmouseX; + } + + @Override + public float getMouseDeltaY() { + return applet.mouseY - applet.pmouseY; + } + + @Override + public float getMouseWheelDelta() { + float delta = mouseWheelDelta; + mouseWheelDelta = 0; // Reset after read + return delta; + } + + @Override + public void updateMouseState() { + // Handle frame-specific mouse state updates if necessary + } +} diff --git a/src/main/java/engine/render/Material.java b/src/main/java/engine/render/Material.java index af8206f7..3793d9d1 100644 --- a/src/main/java/engine/render/Material.java +++ b/src/main/java/engine/render/Material.java @@ -5,284 +5,239 @@ /** * 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. - *

+ * + *

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 + /** 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; + } +} diff --git a/src/main/java/engine/render/MaterialFactory.java b/src/main/java/engine/render/MaterialFactory.java index 0360b0a0..24dd29ee 100644 --- a/src/main/java/engine/render/MaterialFactory.java +++ b/src/main/java/engine/render/MaterialFactory.java @@ -3,119 +3,107 @@ 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. - *

+ * 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 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 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 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 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(); - } - + /** + * 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 index 7e413908..53bc0bbb 100644 --- a/src/main/java/engine/render/effects/Particle.java +++ b/src/main/java/engine/render/effects/Particle.java @@ -3,166 +3,153 @@ 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. - *

- * + * 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; - } - + /** 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 index 991d164a..e5012955 100644 --- a/src/main/java/engine/render/effects/ParticleComponent.java +++ b/src/main/java/engine/render/effects/ParticleComponent.java @@ -5,115 +5,102 @@ 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. - *

- * + * 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 { +public class ParticleComponent extends AbstractComponent implements RenderableComponent { - private ParticleEmitter emitter; + private ParticleEmitter emitter; - private ParticleRenderer renderer; + 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; - } + /** + * 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(); - } + /** Initializes the renderer resources necessary for drawing particles. */ + @Override + public void onAttach() { + 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); - } + /** + * 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()); - } + /** + * 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(); - } + /** Cleans up any resources used by the particle renderer. */ + @Override + public void onDetach() { + 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; - } + /** + * 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; - } + /** + * 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; - } + /** + * 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 index b6aaf710..8d1999f4 100644 --- a/src/main/java/engine/render/effects/ParticleEmitter.java +++ b/src/main/java/engine/render/effects/ParticleEmitter.java @@ -5,206 +5,194 @@ 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: - *

+ * 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: + * *

- * + * * @author Simon Dietz */ public class ParticleEmitter { - /** The world-space origin of the particle emitter. */ - private Vector3f position; - - /** The range for randomizing initial particle velocities. */ - private Vector3f velocityRange; - - /** The range for randomizing initial particle accelerations. */ - private Vector3f accelerationRange; - - /** The range of possible particle lifetimes. */ - private float lifetimeRange; - - /** The rate at which particles are emitted (particles per second). */ - private int particlesPerSecond; - - /** Whether the emitter is currently configured for burst emission mode. */ - private boolean burstMode; - - /** Number of particles to emit during each burst. */ - private int burstCount; - - /** - * Tracks elapsed time to determine when particles should be emitted during - * continuous mode. - */ - private float timeSinceLastEmission = 0f; - - /** A thread-safe queue storing active particles. */ - private ConcurrentLinkedQueue particles; - - /** - * Constructs a new ParticleEmitter with a specified position and emission - * rate. - * - * @param position The initial world-space position of the emitter. - * @param particlesPerSecond The rate at which particles are emitted in - * continuous mode (particles per second). - */ - public ParticleEmitter(Vector3f position, int particlesPerSecond) { - this.position = position; - this.particlesPerSecond = particlesPerSecond; - this.velocityRange = new Vector3f(1f, 1f, 1f); - this.accelerationRange = new Vector3f(0f, 0f, 0f); - this.lifetimeRange = 5f; // Default particle lifetime of 5 seconds - this.particles = new ConcurrentLinkedQueue<>(); - this.burstMode = false; // Default mode is continuous particle emission - this.burstCount = 0; - } - - /** - * Updates particles and performs emission logic based on elapsed time. - * Handles both continuous emission and burst emission logic. Cleans up - * expired particles from the particle queue. - * - * @param deltaTime Time elapsed since the last frame, in seconds. - */ - public void update(float deltaTime) { - if (burstMode) { - emitBurst(); - } else { - timeSinceLastEmission += deltaTime; - float emissionInterval = 1f / particlesPerSecond; - - // Emit particles continuously based on elapsed time - while (timeSinceLastEmission >= emissionInterval) { - emitParticle(); - timeSinceLastEmission -= emissionInterval; - } - } - - // Update and clean expired particles - for (Particle particle : particles) { - particle.update(deltaTime); - if (!particle.isAlive()) { - particles.remove(particle); - } - } - } - - /** - * Emits a single particle with randomized properties (velocity, acceleration, - * and lifetime) within their configured ranges. - */ - private void emitParticle() { - Vector3f initialPosition = new Vector3f(position); - Vector3f initialVelocity = randomizeVector(velocityRange); - Vector3f initialAcceleration = randomizeVector(accelerationRange); - float lifetime = randomizeFloat(lifetimeRange); - - Particle particle = new Particle(initialPosition, initialVelocity, - initialAcceleration, lifetime); - particles.add(particle); - } - - /** - * Emits a burst of particles, the number of which is defined by the - * `burstCount`. After completing a burst, burst mode is disabled - * automatically. - */ - private void emitBurst() { - for (int i = 0; i < burstCount; i++) { - emitParticle(); - } - burstMode = false; // Disable burst mode after the burst is emitted. - } - - /** - * Randomizes a vector's x, y, z components within their respective ranges. - * - * @param range The range to randomize values within. - * @return A new randomized vector. - */ - private Vector3f randomizeVector(Vector3f range) { - return new Vector3f((float) (Math.random() * range.x * 2 - range.x), - (float) (Math.random() * range.y * 2 - range.y), - (float) (Math.random() * range.z * 2 - range.z)); - } - - /** - * Randomizes a float value within a range [0, range). - * - * @param range The range to randomize values within. - * @return A randomized float value. - */ - private float randomizeFloat(float range) { - return (float) (Math.random() * range); - } - - /** - * Configures the emitter to use burst mode with a specified number of - * particles to emit. After enabling, particles will only emit in bursts until - * reset. - * - * @param burstCount Number of particles to emit during each burst. - */ - public void setBurstMode(int burstCount) { - this.burstMode = true; - this.burstCount = burstCount; - } - - /** - * Updates the range for randomizing initial particle velocities. - * - * @param velocityRange The new velocity range. - */ - public void setVelocityRange(Vector3f velocityRange) { - this.velocityRange = velocityRange; - } - - /** - * Updates the range for randomizing initial particle accelerations. - * - * @param accelerationRange The new acceleration range. - */ - public void setAccelerationRange(Vector3f accelerationRange) { - this.accelerationRange = accelerationRange; - } - - /** - * Updates the range of particle lifetimes. - * - * @param lifetimeRange The new lifetime range. - */ - public void setLifetimeRange(float lifetimeRange) { - this.lifetimeRange = lifetimeRange; - } - - /** - * Retrieves all currently active particles managed by this emitter. - * - * @return A concurrent queue containing the currently active particles. - */ - public ConcurrentLinkedQueue getParticles() { - return particles; - } - -} \ No newline at end of file + /** The world-space origin of the particle emitter. */ + private Vector3f position; + + /** The range for randomizing initial particle velocities. */ + private Vector3f velocityRange; + + /** The range for randomizing initial particle accelerations. */ + private Vector3f accelerationRange; + + /** The range of possible particle lifetimes. */ + private float lifetimeRange; + + /** The rate at which particles are emitted (particles per second). */ + private int particlesPerSecond; + + /** Whether the emitter is currently configured for burst emission mode. */ + private boolean burstMode; + + /** Number of particles to emit during each burst. */ + private int burstCount; + + /** Tracks elapsed time to determine when particles should be emitted during continuous mode. */ + private float timeSinceLastEmission = 0f; + + /** A thread-safe queue storing active particles. */ + private ConcurrentLinkedQueue particles; + + /** + * Constructs a new ParticleEmitter with a specified position and emission rate. + * + * @param position The initial world-space position of the emitter. + * @param particlesPerSecond The rate at which particles are emitted in continuous mode (particles + * per second). + */ + public ParticleEmitter(Vector3f position, int particlesPerSecond) { + this.position = position; + this.particlesPerSecond = particlesPerSecond; + this.velocityRange = new Vector3f(1f, 1f, 1f); + this.accelerationRange = new Vector3f(0f, 0f, 0f); + this.lifetimeRange = 5f; // Default particle lifetime of 5 seconds + this.particles = new ConcurrentLinkedQueue<>(); + this.burstMode = false; // Default mode is continuous particle emission + this.burstCount = 0; + } + + /** + * Updates particles and performs emission logic based on elapsed time. Handles both continuous + * emission and burst emission logic. Cleans up expired particles from the particle queue. + * + * @param deltaTime Time elapsed since the last frame, in seconds. + */ + public void update(float deltaTime) { + if (burstMode) { + emitBurst(); + } else { + timeSinceLastEmission += deltaTime; + float emissionInterval = 1f / particlesPerSecond; + + // Emit particles continuously based on elapsed time + while (timeSinceLastEmission >= emissionInterval) { + emitParticle(); + timeSinceLastEmission -= emissionInterval; + } + } + + // Update and clean expired particles + for (Particle particle : particles) { + particle.update(deltaTime); + if (!particle.isAlive()) { + particles.remove(particle); + } + } + } + + /** + * Emits a single particle with randomized properties (velocity, acceleration, and lifetime) + * within their configured ranges. + */ + private void emitParticle() { + Vector3f initialPosition = new Vector3f(position); + Vector3f initialVelocity = randomizeVector(velocityRange); + Vector3f initialAcceleration = randomizeVector(accelerationRange); + float lifetime = randomizeFloat(lifetimeRange); + + Particle particle = + new Particle(initialPosition, initialVelocity, initialAcceleration, lifetime); + particles.add(particle); + } + + /** + * Emits a burst of particles, the number of which is defined by the `burstCount`. After + * completing a burst, burst mode is disabled automatically. + */ + private void emitBurst() { + for (int i = 0; i < burstCount; i++) { + emitParticle(); + } + burstMode = false; // Disable burst mode after the burst is emitted. + } + + /** + * Randomizes a vector's x, y, z components within their respective ranges. + * + * @param range The range to randomize values within. + * @return A new randomized vector. + */ + private Vector3f randomizeVector(Vector3f range) { + return new Vector3f( + (float) (Math.random() * range.x * 2 - range.x), + (float) (Math.random() * range.y * 2 - range.y), + (float) (Math.random() * range.z * 2 - range.z)); + } + + /** + * Randomizes a float value within a range [0, range). + * + * @param range The range to randomize values within. + * @return A randomized float value. + */ + private float randomizeFloat(float range) { + return (float) (Math.random() * range); + } + + /** + * Configures the emitter to use burst mode with a specified number of particles to emit. After + * enabling, particles will only emit in bursts until reset. + * + * @param burstCount Number of particles to emit during each burst. + */ + public void setBurstMode(int burstCount) { + this.burstMode = true; + this.burstCount = burstCount; + } + + /** + * Updates the range for randomizing initial particle velocities. + * + * @param velocityRange The new velocity range. + */ + public void setVelocityRange(Vector3f velocityRange) { + this.velocityRange = velocityRange; + } + + /** + * Updates the range for randomizing initial particle accelerations. + * + * @param accelerationRange The new acceleration range. + */ + public void setAccelerationRange(Vector3f accelerationRange) { + this.accelerationRange = accelerationRange; + } + + /** + * Updates the range of particle lifetimes. + * + * @param lifetimeRange The new lifetime range. + */ + public void setLifetimeRange(float lifetimeRange) { + this.lifetimeRange = lifetimeRange; + } + + /** + * Retrieves all currently active particles managed by this emitter. + * + * @return A concurrent queue containing the currently active particles. + */ + public ConcurrentLinkedQueue getParticles() { + return particles; + } +} diff --git a/src/main/java/engine/render/effects/ParticleRenderer.java b/src/main/java/engine/render/effects/ParticleRenderer.java index 90b6cfea..ef2e7673 100644 --- a/src/main/java/engine/render/effects/ParticleRenderer.java +++ b/src/main/java/engine/render/effects/ParticleRenderer.java @@ -5,32 +5,31 @@ import workspace.ui.Graphics; /** - * Interface for rendering particles in a particle system. Implementations of - * this interface define how particles are visually represented, such as using - * sprites, points, or other rendering techniques. - * + * Interface for rendering particles in a particle system. Implementations of this interface define + * how particles are visually represented, such as using sprites, points, or other rendering + * techniques. + * * @author Simon Dietz */ public interface ParticleRenderer { - /** - * Renders a batch of particles using the provided graphics context. - * - * @param g The graphics context used for rendering. - * @param particles The collection of particles to render. - */ - void render(Graphics g, Collection particles); + /** + * Renders a batch of particles using the provided graphics context. + * + * @param g The graphics context used for rendering. + * @param particles The collection of particles to render. + */ + void render(Graphics g, Collection particles); - /** - * Initializes any resources or setup required for rendering particles. This - * could include shaders, textures, or other rendering assets. - */ - void initialize(); + /** + * Initializes any resources or setup required for rendering particles. This could include + * shaders, textures, or other rendering assets. + */ + void initialize(); - /** - * Cleans up resources used by the renderer when it is no longer needed. This - * ensures efficient memory and resource management. - */ - void cleanup(); - -} \ No newline at end of file + /** + * Cleans up resources used by the renderer when it is no longer needed. This ensures efficient + * memory and resource management. + */ + void cleanup(); +} diff --git a/src/main/java/engine/scene/Scene.java b/src/main/java/engine/scene/Scene.java index af0935e4..598e7d7e 100644 --- a/src/main/java/engine/scene/Scene.java +++ b/src/main/java/engine/scene/Scene.java @@ -1,8 +1,8 @@ package engine.scene; import java.util.ArrayList; -import java.util.Collections; import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -11,311 +11,285 @@ import workspace.ui.Graphics; /** - * The {@code Scene} class manages a hierarchy of {@code SceneNode}s for - * rendering and updating. It handles root-level scene nodes, lighting, and a - * thread pool for parallel updates, offering a high-level interface for - * managing and rendering complex 3D scenes. + * The {@code Scene} class manages a hierarchy of {@code SceneNode}s for rendering and updating. It + * handles root-level scene nodes, lighting, and a thread pool for parallel updates, offering a + * high-level interface for managing and rendering complex 3D scenes. */ public class Scene { - /** Default name assigned to a newly created scene if no name is provided. */ - private static final String DEFAULT_NAME = "Untitled-Scene"; - - /** - * List of root-level nodes in the scene hierarchy for rendering and updates. - */ - private final List rootNodes = new ArrayList<>(); - - /** List of lights in the scene that are used for lighting calculations. */ - private final List lights = new ArrayList<>(); - - /** Thread pool used to parallelize updates for performance optimization. */ - private final ExecutorService updateExecutor = Executors - .newFixedThreadPool(Runtime.getRuntime().availableProcessors()); - - /** Flag indicating whether the scene is rendered in wireframe mode. */ - private boolean wireframeMode; - - /** Name of the scene. Used for identification or debugging purposes. */ - private final String name; - - /** - * The currently active camera that determines the scene's view - * transformation. - */ - private Camera activeCamera; - - /** - * Constructs a {@code Scene} with a default name. - */ - public Scene() { - this(DEFAULT_NAME); - } - - /** - * Constructs a {@code Scene} with the specified name. - * - * @param name The name of the scene. - * @throws IllegalArgumentException if the name is {@code null}. - */ - public Scene(String name) { - if (name == null) { - throw new IllegalArgumentException("Name cannot be null."); - } - this.name = name; - } - - /** - * Adds a SceneNode to the root level of the scene graph. - * - * @param node The node to add to the root level. - */ - public void addNode(SceneNode node) { - if (node == null) { - throw new IllegalArgumentException("Node cannot be null."); - } - synchronized (rootNodes) { - rootNodes.add(node); - } - } - - /** - * Adds a light to the scene's list of lights for rendering and lighting - * calculations. Ensures thread-safe addition by synchronizing on the `lights` - * list. - * - * @param light The Light instance to be added to the scene. - * @throws IllegalArgumentException if the provided light is null. - */ - public void addLight(Light light) { - if (light == null) { - throw new IllegalArgumentException("Light cannot be null."); - } - synchronized (lights) { - lights.add(light); - } - } - - /** - * Removes a SceneNode from the root level. - * - * @param node The node to remove from the root level. - */ - public void removeNode(SceneNode node) { - if (node == null) - return; - synchronized (rootNodes) { - rootNodes.remove(node); - } - } - - /** - * Perform parallel updates on all nodes in the scene graph. - * - * @param deltaTime The time step for simulation logic updates. - */ - public void update(float deltaTime) { - synchronized (rootNodes) { - for (SceneNode node : Collections.unmodifiableList(rootNodes)) { - // Submit updates to worker threads - updateExecutor.submit(() -> node.update(deltaTime)); - } - } - } - - /** - * Render lights and nodes concurrently. However, rendering must still run on - * the main thread for compatibility with most rendering APIs. - */ - public void render(Graphics g) { - if (activeCamera != null) { - g.applyCamera(activeCamera); - } - - g.setWireframeMode(wireframeMode); - renderLights(g); - - synchronized (rootNodes) { - - for (SceneNode node : rootNodes) { - node.render(g); - } - } - - } - - /** - * Renders all lights in the scene safely by synchronizing access to the - * lights list. This ensures thread-safe iteration and rendering, especially - * when lights are added or removed concurrently. - * - * @param g The graphics context used for rendering the lights. - */ - private void renderLights(Graphics g) { - synchronized (lights) { - for (Light light : lights) { - g.render(light); - } - } - } - - /** - * Cleans up resources and shuts down the executor safely. - */ - public void cleanup() { - // Shutdown thread pool properly - updateExecutor.shutdown(); - synchronized (rootNodes) { - for (SceneNode node : rootNodes) { - node.cleanup(); - } - rootNodes.clear(); - } - } - - /** - * Retrieves the number of root nodes in the scene. - * - * @return The count of root nodes currently present in the scene graph. - */ - public int getRootCount() { - synchronized (rootNodes) { - return rootNodes.size(); - } - } - - /** - * Performs a complete cleanup of all lights and nodes in the scene, in - * addition to shutting down worker threads. This ensures no memory leaks or - * dangling references persist after the scene is no longer needed. - */ - public void cleanupAllResources() { - synchronized (lights) { - lights.clear(); - } - synchronized (rootNodes) { - for (SceneNode node : rootNodes) { - node.cleanup(); - } - rootNodes.clear(); - } - updateExecutor.shutdown(); - } - - /** - * Retrieves all lights that are currently active in the scene. This allows - * querying of lights for dynamic light management features. - * - * @return A thread-safe copy of the current lights list. - */ - public List getAllLights() { - synchronized (lights) { - return new ArrayList<>(lights); - } - } - - /** - * Fetches all the nodes at the root level in a thread-safe way. Useful for - * debugging, visualization, or debugging purposes to monitor scene nodes in - * real-time. - * - * @return A list of root SceneNodes currently in the scene graph. - */ - public List getRootNodes() { - synchronized (rootNodes) { - return new ArrayList<>(rootNodes); - } - } - - /** - * Finds and removes all SceneNodes matching a particular condition. For - * example, it can remove nodes based on type or other predicates. - * - * @param predicate The condition to test nodes against. - * @return The number of nodes removed. - */ - public int removeNodesIf(java.util.function.Predicate predicate) { - int count = 0; - synchronized (rootNodes) { - for (SceneNode node : new ArrayList<>(rootNodes)) { - if (predicate.test(node)) { - node.cleanup(); - rootNodes.remove(node); - count++; - } - } - } - return count; - } - - /** - * Sets the currently active camera for the scene. The active camera - * determines the view and projection matrices used during rendering. If no - * active camera is set, rendering will proceed without camera - * transformations. - * - * @param camera The camera to set as the active camera. May be null to - * disable camera-based rendering logic. - */ - public void setActiveCamera(Camera camera) { - this.activeCamera = camera; - } - - /** - * Retrieves the currently active camera used for rendering the scene. The - * active camera's view and projection matrices define the perspective and - * viewport used during rendering. - * - * @return The currently active camera, or {@code null} if no active camera - * has been set. - */ - public Camera getActiveCamera() { - return this.activeCamera; - } - - /** - * Retrieves the number of lights currently managed by the scene. - *

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

- * - * @return The total number of lights currently in the scene. - */ - public int getLightCount() { - synchronized (lights) { - return lights.size(); - } - } - - /** - * Retrieves the name of the scene. - * - * @return The name of the scene. - */ - public String getName() { - return name; - } - - /** - * Checks if the scene is in wireframe rendering mode. - * - * @return {@code true} if wireframe mode is enabled, {@code false} otherwise. - */ - public boolean isWireframeMode() { - return wireframeMode; - } - - /** - * Enables or disables wireframe rendering mode for the scene. - * - * @param wireframeMode {@code true} to enable wireframe mode, {@code false} - * to disable it. - */ - public void setWireframeMode(boolean wireframeMode) { - this.wireframeMode = wireframeMode; - } - -} \ No newline at end of file + /** Default name assigned to a newly created scene if no name is provided. */ + private static final String DEFAULT_NAME = "Untitled-Scene"; + + /** List of root-level nodes in the scene hierarchy for rendering and updates. */ + private final ConcurrentLinkedQueue rootNodes = new ConcurrentLinkedQueue<>(); + + /** List of lights in the scene that are used for lighting calculations. */ + private final List lights = new ArrayList<>(); + + /** Thread pool used to parallelize updates for performance optimization. */ + private final ExecutorService updateExecutor = + Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + + /** Flag indicating whether the scene is rendered in wireframe mode. */ + private boolean wireframeMode; + + /** Name of the scene. Used for identification or debugging purposes. */ + private final String name; + + /** The currently active camera that determines the scene's view transformation. */ + private Camera activeCamera; + + /** Constructs a {@code Scene} with a default name. */ + public Scene() { + this(DEFAULT_NAME); + } + + /** + * Constructs a {@code Scene} with the specified name. + * + * @param name The name of the scene. + * @throws IllegalArgumentException if the name is {@code null}. + */ + public Scene(String name) { + if (name == null) { + throw new IllegalArgumentException("Name cannot be null."); + } + this.name = name; + } + + /** + * Adds a SceneNode to the root level of the scene graph. + * + * @param node The node to add to the root level. + */ + public void addNode(SceneNode node) { + if (node == null) { + throw new IllegalArgumentException("Node cannot be null."); + } + synchronized (rootNodes) { + rootNodes.add(node); + } + } + + /** + * Adds a light to the scene's list of lights for rendering and lighting calculations. Ensures + * thread-safe addition by synchronizing on the `lights` list. + * + * @param light The Light instance to be added to the scene. + * @throws IllegalArgumentException if the provided light is null. + */ + public void addLight(Light light) { + if (light == null) { + throw new IllegalArgumentException("Light cannot be null."); + } + synchronized (lights) { + lights.add(light); + } + } + + /** + * Removes a SceneNode from the root level. + * + * @param node The node to remove from the root level. + */ + public void removeNode(SceneNode node) { + if (node == null) return; + synchronized (rootNodes) { + rootNodes.remove(node); + } + } + + /** + * Perform parallel updates on all nodes in the scene graph. + * + * @param deltaTime The time step for simulation logic updates. + */ + public void update(float deltaTime) { + for (SceneNode node : rootNodes) { + updateExecutor.submit(() -> node.update(deltaTime)); + } + } + + /** + * Render lights and nodes concurrently. However, rendering must still run on the main thread for + * compatibility with most rendering APIs. + */ + public void render(Graphics g) { + if (activeCamera != null) { + g.applyCamera(activeCamera); + } + + g.setWireframeMode(wireframeMode); + renderLights(g); + + synchronized (rootNodes) { + for (SceneNode node : rootNodes) { + node.render(g); + } + } + } + + /** + * Renders all lights in the scene safely by synchronizing access to the lights list. This ensures + * thread-safe iteration and rendering, especially when lights are added or removed concurrently. + * + * @param g The graphics context used for rendering the lights. + */ + private void renderLights(Graphics g) { + synchronized (lights) { + for (Light light : lights) { + g.render(light); + } + } + } + + /** Cleans up resources and shuts down the executor safely. */ + public void cleanup() { + // Shutdown thread pool properly + updateExecutor.shutdown(); + synchronized (rootNodes) { + for (SceneNode node : rootNodes) { + node.cleanup(); + } + rootNodes.clear(); + } + } + + /** + * Retrieves the number of root nodes in the scene. + * + * @return The count of root nodes currently present in the scene graph. + */ + public int getRootCount() { + synchronized (rootNodes) { + return rootNodes.size(); + } + } + + /** + * Performs a complete cleanup of all lights and nodes in the scene, in addition to shutting down + * worker threads. This ensures no memory leaks or dangling references persist after the scene is + * no longer needed. + */ + public void cleanupAllResources() { + synchronized (lights) { + lights.clear(); + } + synchronized (rootNodes) { + for (SceneNode node : rootNodes) { + node.cleanup(); + } + rootNodes.clear(); + } + updateExecutor.shutdown(); + } + + /** + * Retrieves all lights that are currently active in the scene. This allows querying of lights for + * dynamic light management features. + * + * @return A thread-safe copy of the current lights list. + */ + public List getAllLights() { + synchronized (lights) { + return new ArrayList<>(lights); + } + } + + /** + * Fetches all the nodes at the root level in a thread-safe way. Useful for debugging, + * visualization, or debugging purposes to monitor scene nodes in real-time. + * + * @return A list of root SceneNodes currently in the scene graph. + */ + public List getRootNodes() { + synchronized (rootNodes) { + return new ArrayList<>(rootNodes); + } + } + + /** + * Finds and removes all SceneNodes matching a particular condition. For example, it can remove + * nodes based on type or other predicates. + * + * @param predicate The condition to test nodes against. + * @return The number of nodes removed. + */ + public int removeNodesIf(java.util.function.Predicate predicate) { + int count = 0; + synchronized (rootNodes) { + for (SceneNode node : new ArrayList<>(rootNodes)) { + if (predicate.test(node)) { + node.cleanup(); + rootNodes.remove(node); + count++; + } + } + } + return count; + } + + /** + * Sets the currently active camera for the scene. The active camera determines the view and + * projection matrices used during rendering. If no active camera is set, rendering will proceed + * without camera transformations. + * + * @param camera The camera to set as the active camera. May be null to disable camera-based + * rendering logic. + */ + public void setActiveCamera(Camera camera) { + this.activeCamera = camera; + } + + /** + * Retrieves the currently active camera used for rendering the scene. The active camera's view + * and projection matrices define the perspective and viewport used during rendering. + * + * @return The currently active camera, or {@code null} if no active camera has been set. + */ + public Camera getActiveCamera() { + return this.activeCamera; + } + + /** + * Retrieves the number of lights currently managed by the scene. + * + *

This method provides the total count of lights that are part of the scene's lighting system. + * It operates in a thread-safe manner by synchronizing access to the shared lights list to ensure + * accurate and safe read operations, even when lights are being added or removed concurrently. + * + * @return The total number of lights currently in the scene. + */ + public int getLightCount() { + synchronized (lights) { + return lights.size(); + } + } + + /** + * Retrieves the name of the scene. + * + * @return The name of the scene. + */ + public String getName() { + return name; + } + + /** + * Checks if the scene is in wireframe rendering mode. + * + * @return {@code true} if wireframe mode is enabled, {@code false} otherwise. + */ + public boolean isWireframeMode() { + return wireframeMode; + } + + /** + * Enables or disables wireframe rendering mode for the scene. + * + * @param wireframeMode {@code true} to enable wireframe mode, {@code false} to disable it. + */ + public void setWireframeMode(boolean wireframeMode) { + this.wireframeMode = wireframeMode; + } +} diff --git a/src/main/java/engine/scene/SceneNode.java b/src/main/java/engine/scene/SceneNode.java index 5a5a6550..64cc3808 100644 --- a/src/main/java/engine/scene/SceneNode.java +++ b/src/main/java/engine/scene/SceneNode.java @@ -10,366 +10,343 @@ /** * 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: + * + *

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: + * *

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

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

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

- * - * @param g The graphics context used for rendering this node and its - * children. - */ - public void render(Graphics g) { - g.pushMatrix(); - - applyLocalTransform(g); - renderComponents(g); - - for (SceneNode child : children) { - child.render(g); - } - - g.popMatrix(); - } - - /** - * Applies the local transformation to the graphics context. - */ - private void applyLocalTransform(Graphics g) { - getTransform().apply(g); - } - - /** - * Renders all associated {@link 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 The type of the component. - * @return The first matching component, or {@code null} if none exists. - */ - public T getComponent(Class componentClass) { - for (Component component : components) { - if (componentClass.isInstance(component)) { - return componentClass.cast(component); - } - } - return null; - } - - /** - * Retrieves a list of components of a specific type attached to this node. - *

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

- * - * @param componentClass The class type of the component to retrieve. - * @param The type of component to search for. - * @return A list of components matching the specified type. - */ - public List getComponents(Class componentClass) { - List result = new ArrayList<>(); - for (Component component : components) { - if (componentClass.isInstance(component)) { - result.add(componentClass.cast(component)); - } - } - return result; - } - - /** - * Retrieves all render components for this node. - * - * @return A list of {@link RenderableComponent} instances associated with this - * node. - */ - public List getRenderComponents() { - return getComponents(RenderableComponent.class); - } - - /** - * Retrieves the root node in the scene graph hierarchy. - * - * @return The root {@code SceneNode} in the hierarchy. - */ - public SceneNode getRoot() { - if (parent == null) { - return this; - } - return parent.getRoot(); - } - - /** - * Checks whether this node is the root node in the hierarchy. - * - * @return {@code true} if this node is the root; {@code false} otherwise. - */ - public boolean isRoot() { - return parent == null; - } - - /** - * Checks whether this node is a leaf node (has no children). - * - * @return {@code true} if this node has no children; {@code false} otherwise. - */ - public boolean isLeaf() { - return children.isEmpty(); - } - - /** Retrieves the Transform component associated with this node. */ - public Transform getTransform() { - return getComponents(Transform.class).stream().findFirst().orElseThrow( - () -> new IllegalStateException("Transform component is missing.")); - } - - /** - * Retrieves the name of this {@code SceneNode}. - * - * @return The name of the node. - */ - public String getName() { - return name; - } - - /** - * Sets the name of this {@code SceneNode}. - * - * @param name The new name to assign to the node. - * @throws IllegalArgumentException if the name is {@code null}. - */ - public void setName(String name) { - if (name == null) { - throw new IllegalArgumentException("Name cannot be null."); - } - this.name = name; - } - -} \ No newline at end of file + /** The default name assigned to a scene node if no name is provided. */ + private static final String DEFAULT_NAME = "Untitled-Node"; + + /** The name of this node, primarily intended for debugging and identification purposes. */ + private String name; + + /** The parent node in the scene graph hierarchy. */ + private SceneNode parent; + + /** List of child nodes attached to this node. */ + private List children; + + /** List of components (logic/rendering behavior) attached to this node. */ + private List components; + + /** + * Creates a new {@code SceneNode} with the specified name. + * + * @param name The name to assign to the scene node. + * @throws IllegalArgumentException if the name is {@code null}. + */ + public SceneNode(String name) { + if (name == null) { + throw new IllegalArgumentException("Name cannot be null."); + } + this.name = name; + this.children = new ArrayList(); + this.components = new ArrayList(); + // Add a default Transform component + this.components.add(new Transform()); + } + + /** + * Constructs a new, empty {@code SceneNode} with default transformations, an empty list of + * children, and an empty list of components. + */ + public SceneNode() { + this(DEFAULT_NAME); + } + + /** + * Renders this node and all its children recursively. + * + *

This method applies the local transformation, renders components, and traverses through all + * child nodes to render them as well. This ensures the entire subtree rooted at this node is + * rendered properly. + * + * @param g The graphics context used for rendering this node and its children. + */ + public void render(Graphics g) { + g.pushMatrix(); + + applyLocalTransform(g); + renderComponents(g); + + for (SceneNode child : children) { + child.render(g); + } + + g.popMatrix(); + } + + /** Applies the local transformation to the graphics context. */ + private void applyLocalTransform(Graphics g) { + getTransform().apply(g); + } + + /** + * Renders all associated {@link 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.onDetach(); + component.setOwner(null); + } 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.onAttach(); + } + } + + /** + * 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.onDetach(); + component.setOwner(null); + } + } + + /** + * Retrieves the first component of the specified type attached to this node. + * + * @param componentClass The class type of the component to retrieve. + * @param The type of the component. + * @return The first matching component, or {@code null} if none exists. + */ + public T getComponent(Class componentClass) { + for (Component component : components) { + if (componentClass.isInstance(component)) { + return componentClass.cast(component); + } + } + return null; + } + + /** + * Retrieves a list of components of a specific type attached to this node. + * + *

Enables querying for specific types of behavior or functionality attached to a node. + * + * @param componentClass The class type of the component to retrieve. + * @param The type of component to search for. + * @return A list of components matching the specified type. + */ + public List getComponents(Class componentClass) { + List result = new ArrayList<>(); + for (Component component : components) { + if (componentClass.isInstance(component)) { + result.add(componentClass.cast(component)); + } + } + return result; + } + + /** + * Retrieves all render components for this node. + * + * @return A list of {@link RenderableComponent} instances associated with this node. + */ + public List getRenderComponents() { + return getComponents(RenderableComponent.class); + } + + /** + * Retrieves the root node in the scene graph hierarchy. + * + * @return The root {@code SceneNode} in the hierarchy. + */ + public SceneNode getRoot() { + if (parent == null) { + return this; + } + return parent.getRoot(); + } + + /** + * Checks whether this node is the root node in the hierarchy. + * + * @return {@code true} if this node is the root; {@code false} otherwise. + */ + public boolean isRoot() { + return parent == null; + } + + /** + * Checks whether this node is a leaf node (has no children). + * + * @return {@code true} if this node has no children; {@code false} otherwise. + */ + public boolean isLeaf() { + return children.isEmpty(); + } + + /** Retrieves the Transform component associated with this node. */ + public Transform getTransform() { + return getComponents(Transform.class) + .stream() + .findFirst() + .orElseThrow(() -> new IllegalStateException("Transform component is missing.")); + } + + /** + * Retrieves the name of this {@code SceneNode}. + * + * @return The name of the node. + */ + public String getName() { + return name; + } + + /** + * Sets the name of this {@code SceneNode}. + * + * @param name The new name to assign to the node. + * @throws IllegalArgumentException if the name is {@code null}. + */ + public void setName(String name) { + if (name == null) { + throw new IllegalArgumentException("Name cannot be null."); + } + this.name = name; + } +} diff --git a/src/main/java/engine/scene/camera/Camera.java b/src/main/java/engine/scene/camera/Camera.java index 2c1e14a8..2ba4ebb6 100644 --- a/src/main/java/engine/scene/camera/Camera.java +++ b/src/main/java/engine/scene/camera/Camera.java @@ -6,197 +6,168 @@ import math.Vector3f; /** - * Represents a generic camera within a 3D scene. A camera defines the view and - * projection settings necessary for rendering a 3D scene from a specific - * perspective. This interface abstracts common properties and functionalities - * that all camera types share, including transformation, view matrices, - * projection matrices, and interaction with screen-space coordinates. - *

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

+ * Represents a generic camera within a 3D scene. A camera defines the view and projection settings + * necessary for rendering a 3D scene from a specific perspective. This interface abstracts common + * properties and functionalities that all camera types share, including transformation, view + * matrices, projection matrices, and interaction with screen-space coordinates. + * + *

Cameras are responsible for defining how 3D scenes are projected onto a 2D screen and can be + * configured for various projection modes, such as perspective and orthographic projections. + * Implementations should define their specific logic for projection and view matrix handling, field + * of view adjustments, clipping plane configuration, and conversion between screen and world + * coordinates. */ public interface Camera { - 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 + 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); +} diff --git a/src/main/java/engine/scene/light/AmbientLight.java b/src/main/java/engine/scene/light/AmbientLight.java index b6e52e9b..3d15772c 100644 --- a/src/main/java/engine/scene/light/AmbientLight.java +++ b/src/main/java/engine/scene/light/AmbientLight.java @@ -4,17 +4,13 @@ /** * 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. - *

- * + * + *

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},
@@ -24,76 +20,70 @@
  * - 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}. + * + * 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; + /** 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 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); - } + /** + * 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; - } + /** + * 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); - } + /** + * 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; - } + /** + * 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 + /** + * 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; + } +} diff --git a/src/main/java/engine/scene/light/DirectionalLight.java b/src/main/java/engine/scene/light/DirectionalLight.java index ccfd8661..b1399d92 100644 --- a/src/main/java/engine/scene/light/DirectionalLight.java +++ b/src/main/java/engine/scene/light/DirectionalLight.java @@ -5,196 +5,176 @@ /** * Represents a directional light source in a 3D scene. - * - *

- * A directional light simulates light emitted from a distant source, such as - * the sun or moon. Unlike point lights or spotlights, directional lights have - * no specific position, and their light rays travel in a uniform direction - * throughout the scene. This makes them ideal for creating consistent lighting - * over large areas. - *

- * + * + *

A directional light simulates light emitted from a distant source, such as the sun or moon. + * Unlike point lights or spotlights, directional lights have no specific position, and their light + * rays travel in a uniform direction throughout the scene. This makes them ideal for creating + * consistent lighting over large areas. + * *

  * Key characteristics of a directional light:
  * - The light's direction is defined by a normalized vector.
- * - It emits light uniformly in the specified direction, without attenuation 
+ * - It emits light uniformly in the specified direction, without attenuation
  *   (intensity does not decrease with distance).
  * - It is commonly used to simulate natural light sources like sunlight during
  *   the day or moonlight at night.
  * 
- * - * This class provides methods to configure the light's direction, color, and - * intensity, as well as integration with rendering systems via the - * {@link LightRenderer}. + * + * This class provides methods to configure the light's direction, color, and intensity, as well as + * integration with rendering systems via the {@link LightRenderer}. */ public class DirectionalLight implements Light { - /** - * The color of the light emitted by the directional light source. - */ - private Color color; - - /** - * The direction of the light source. - */ - private Vector3f direction; - - /** - * The intensity of the light emitted by the directional light source. - */ - private float intensity; - - /** - * Creates a new DirectionalLight instance with default settings. - * - *

- * This constructor initializes the light with the following defaults: - - * Color: White light. RGB(255, 255, 255) - Direction: A downward-facing - * vector (0, 1, 0), simulating overhead light. - Intensity: 1.0 (full - * strength). - *

- */ - public DirectionalLight() { - this(Color.WHITE, new Vector3f(0, 1, 0), 1.0f); - } - - /** - * Creates a new DirectionalLight instance. - * - * @param color The color of the light emitted by the directional light - * source. Represents the RGB values of the light's color. - * This parameter cannot be null. - * @param direction The direction of the light source. This vector determines - * the direction in which the light rays travel. The provided - * vector is automatically normalized during construction, - * ensuring the direction's magnitude is always 1. This - * parameter cannot be null. - * @param intensity The intensity of the light emitted by the directional - * light source. This value must be non-negative. - * - * @throws IllegalArgumentException if the direction or color is null, or if - * the intensity is negative. - */ - public DirectionalLight(Color color, Vector3f direction, float intensity) { - setColor(color); - setDirection(direction); - setIntensity(intensity); - } - - /** - * Gets the direction of the light source. - * - * @return The direction of the light source. - * @see #setDirection(Vector3f) - */ - public Vector3f getDirection() { - return direction; - } - - /** - * Sets the direction of the directional light source. - * - * The provided vector is normalized to ensure the light's direction always - * has a magnitude of 1, which maintains consistent light behavior. This - * method validates that the input is not null to avoid runtime errors. - * - * @param direction The new direction vector for the light source. This vector - * defines the direction in which the light rays travel. - * - * @throws IllegalArgumentException if the provided direction vector is null. - */ - public void setDirection(Vector3f direction) { - if (direction == null) - throw new IllegalArgumentException("Direction cannot be null."); - this.direction = direction.normalize(); - } - - /** - * Gets the color of the light emitted by the directional light source. - * - * @return The color of the light. - * @see #setColor(Color) - */ - @Override - public Color getColor() { - return color; - } - - /** - * Sets the color of the directional light source. - * - * This method updates the light's emitted color. It validates that the - * provided color is not null to ensure the light's color is always valid. - * - * @param color The new color of the light to set. Represents the RGB values - * of the light's color. - * - * @throws IllegalArgumentException if the provided color is null. - */ - public void setColor(Color color) { - if (color == null) - throw new IllegalArgumentException("Color cannot be null."); - this.color = color; - } - - /** - * Gets the intensity of the light emitted by the directional light source. - * - * @return The intensity of the light. - * @see #setIntensity(float) - */ - public float getIntensity() { - return intensity; - } - - /** - * Sets the intensity of the light emitted by the directional light source. - * - * The intensity value determines how bright the light appears in the scene. - * This method ensures that the value is non-negative, as negative intensity - * does not make logical sense in this context. - * - * @param intensity The new intensity value to set for the light source. Must - * be non-negative to represent valid light brightness. - * - * @throws IllegalArgumentException if the provided intensity is negative. - */ - public void setIntensity(float intensity) { - if (intensity < 0) - throw new IllegalArgumentException("Intensity must be non-negative."); - this.intensity = intensity; - } - - /** - * Gets the type of the light source. - * - * @return The type of the light source, which is `LightType.DIRECTIONAL`. - */ - @Override - public LightType getType() { - return LightType.DIRECTIONAL; - } - - /** - * Renders the directional light source using the provided renderer. - * - * @param renderer The renderer to use for rendering the light source. - */ - @Override - public void render(LightRenderer renderer) { - renderer.render(this); - } - - /** - * Provides a string representation of this directional light instance for - * debugging. - * - * @return String describing the current state of the directional light. - */ - @Override - public String toString() { - return "DirectionalLight [color=" + color + ", direction=" + direction - + ", intensity=" + intensity + "]"; - } - -} \ No newline at end of file + /** The color of the light emitted by the directional light source. */ + private Color color; + + /** The direction of the light source. */ + private Vector3f direction; + + /** The intensity of the light emitted by the directional light source. */ + private float intensity; + + /** + * Creates a new DirectionalLight instance with default settings. + * + *

This constructor initializes the light with the following defaults: - Color: White light. + * RGB(255, 255, 255) - Direction: A downward-facing vector (0, 1, 0), simulating overhead light. + * - Intensity: 1.0 (full strength). + */ + public DirectionalLight() { + this(Color.WHITE, new Vector3f(0, 1, 0), 1.0f); + } + + /** + * Creates a new DirectionalLight instance. + * + * @param color The color of the light emitted by the directional light source. Represents the RGB + * values of the light's color. This parameter cannot be null. + * @param direction The direction of the light source. This vector determines the direction in + * which the light rays travel. The provided vector is automatically normalized during + * construction, ensuring the direction's magnitude is always 1. This parameter cannot be + * null. + * @param intensity The intensity of the light emitted by the directional light source. This value + * must be non-negative. + * @throws IllegalArgumentException if the direction or color is null, or if the intensity is + * negative. + */ + public DirectionalLight(Color color, Vector3f direction, float intensity) { + setColor(color); + setDirection(direction); + setIntensity(intensity); + } + + /** + * Gets the direction of the light source. + * + * @return The direction of the light source. + * @see #setDirection(Vector3f) + */ + public Vector3f getDirection() { + return direction; + } + + /** + * Sets the direction of the directional light source. + * + *

The provided vector is normalized to ensure the light's direction always has a magnitude of + * 1, which maintains consistent light behavior. This method validates that the input is not null + * to avoid runtime errors. + * + * @param direction The new direction vector for the light source. This vector defines the + * direction in which the light rays travel. + * @throws IllegalArgumentException if the provided direction vector is null. + */ + public void setDirection(Vector3f direction) { + if (direction == null) throw new IllegalArgumentException("Direction cannot be null."); + this.direction = direction.normalize(); + } + + /** + * Gets the color of the light emitted by the directional light source. + * + * @return The color of the light. + * @see #setColor(Color) + */ + @Override + public Color getColor() { + return color; + } + + /** + * Sets the color of the directional light source. + * + *

This method updates the light's emitted color. It validates that the provided color is not + * null to ensure the light's color is always valid. + * + * @param color The new color of the light to set. Represents the RGB values of the light's color. + * @throws IllegalArgumentException if the provided color is null. + */ + public void setColor(Color color) { + if (color == null) throw new IllegalArgumentException("Color cannot be null."); + this.color = color; + } + + /** + * Gets the intensity of the light emitted by the directional light source. + * + * @return The intensity of the light. + * @see #setIntensity(float) + */ + public float getIntensity() { + return intensity; + } + + /** + * Sets the intensity of the light emitted by the directional light source. + * + *

The intensity value determines how bright the light appears in the scene. This method + * ensures that the value is non-negative, as negative intensity does not make logical sense in + * this context. + * + * @param intensity The new intensity value to set for the light source. Must be non-negative to + * represent valid light brightness. + * @throws IllegalArgumentException if the provided intensity is negative. + */ + public void setIntensity(float intensity) { + if (intensity < 0) throw new IllegalArgumentException("Intensity must be non-negative."); + this.intensity = intensity; + } + + /** + * Gets the type of the light source. + * + * @return The type of the light source, which is `LightType.DIRECTIONAL`. + */ + @Override + public LightType getType() { + return LightType.DIRECTIONAL; + } + + /** + * Renders the directional light source using the provided renderer. + * + * @param renderer The renderer to use for rendering the light source. + */ + @Override + public void render(LightRenderer renderer) { + renderer.render(this); + } + + /** + * Provides a string representation of this directional light instance for debugging. + * + * @return String describing the current state of the directional light. + */ + @Override + public String toString() { + return "DirectionalLight [color=" + + color + + ", direction=" + + direction + + ", intensity=" + + intensity + + "]"; + } +} diff --git a/src/main/java/engine/scene/light/Light.java b/src/main/java/engine/scene/light/Light.java index 18765910..d14ec7c6 100644 --- a/src/main/java/engine/scene/light/Light.java +++ b/src/main/java/engine/scene/light/Light.java @@ -4,46 +4,39 @@ /** * Interface for defining light sources within a 3D scene. - * - *

- * This interface serves as a contract for all light types (e.g., PointLight, - * DirectionalLight, SpotLight) by defining essential behaviors and properties - * that any light source should possess. It provides mechanisms to query a - * light's type, retrieve its color, and delegate rendering logic to a given - * renderer. - *

+ * + *

This interface serves as a contract for all light types (e.g., PointLight, DirectionalLight, + * SpotLight) by defining essential behaviors and properties that any light source should possess. + * It provides mechanisms to query a light's type, retrieve its color, and delegate rendering logic + * to a given renderer. */ public interface Light { - /** - * Gets the color of the light emitted by the light source. - * - * @return The {@link Color} object representing the light's color. The color - * should define the RGB components that determine the light's hue and - * saturation. - */ - Color getColor(); + /** + * Gets the color of the light emitted by the light source. + * + * @return The {@link Color} object representing the light's color. The color should define the + * RGB components that determine the light's hue and saturation. + */ + Color getColor(); - /** - * Gets the type of the light source. - * - * @return The {@link LightType} that identifies the specific type of light - * (e.g., POINT, DIRECTIONAL, or SPOT) this instance represents. - */ - LightType getType(); + /** + * Gets the type of the light source. + * + * @return The {@link LightType} that identifies the specific type of light (e.g., POINT, + * DIRECTIONAL, or SPOT) this instance represents. + */ + LightType getType(); - /** - * Gets the light source using the provided renderer to draw the light's - * effects. - * - * This method allows the implementation to delegate rendering logic to the - * given {@link LightRenderer}. The rendering logic could involve adding - * effects like shadows, light rays, or other visual representations specific - * to the light's type. - * - * @param renderer The {@link LightRenderer} implementation responsible for - * rendering this light's effects in the scene. - */ - void render(LightRenderer renderer); - -} \ No newline at end of file + /** + * Gets the light source using the provided renderer to draw the light's effects. + * + *

This method allows the implementation to delegate rendering logic to the given {@link + * LightRenderer}. The rendering logic could involve adding effects like shadows, light rays, or + * other visual representations specific to the light's type. + * + * @param renderer The {@link LightRenderer} implementation responsible for rendering this light's + * effects in the scene. + */ + void render(LightRenderer renderer); +} diff --git a/src/main/java/engine/scene/light/LightRenderer.java b/src/main/java/engine/scene/light/LightRenderer.java index 4e519fef..638d9c2b 100644 --- a/src/main/java/engine/scene/light/LightRenderer.java +++ b/src/main/java/engine/scene/light/LightRenderer.java @@ -4,94 +4,78 @@ /** * Interface for rendering various light sources in a 3D scene. - *

- * This interface establishes a contract for rendering different types of light - * sources in a 3D environment. It provides specific rendering methods for each - * type of light, such as {@link PointLight}, {@link DirectionalLight}, and - * {@link SpotLight}. Implementations of this interface handle the actual - * rendering logic for these light types within a 3D graphics or game engine. - *

+ * + *

This interface establishes a contract for rendering different types of light sources in a 3D + * environment. It provides specific rendering methods for each type of light, such as {@link + * PointLight}, {@link DirectionalLight}, and {@link SpotLight}. Implementations of this interface + * handle the actual rendering logic for these light types within a 3D graphics or game engine. */ 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); + /** + * 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. - *

- * This method is a catch-all for rendering any light source that implements - * the {@link Light} interface. Specific rendering logic for the light type - * may be determined by the implementation. - *

- * - * @param light The light source to render. Must not be null. - */ - void render(Light light); + /** + * Renders a generic light source. + * + *

This method is a catch-all for rendering any light source that implements the {@link Light} + * interface. Specific rendering logic for the light type may be determined by the implementation. + * + * @param light The light source to render. Must not be null. + */ + void render(Light light); - /** - * Renders a spotlight. - *

- * This method is responsible for rendering a spotlight with specific - * directionality, cone angles, and attenuation effects. Spotlights are used - * to simulate focused beams of light, such as those from flashlights, lamps, - * or theater lighting. - *

- * - * @param light The spotlight to render. Must not be null. - */ - void render(SpotLight light); + /** + * Renders a spotlight. + * + *

This method is responsible for rendering a spotlight with specific directionality, cone + * angles, and attenuation effects. Spotlights are used to simulate focused beams of light, such + * as those from flashlights, lamps, or theater lighting. + * + * @param light The spotlight to render. Must not be null. + */ + void render(SpotLight light); - /** - * Renders a point light source. - *

- * This method handles the rendering of a point light, which emits light - * uniformly in all directions from a single point in 3D space. Point lights - * are commonly used to simulate small localized light sources such as light - * bulbs or torches. - *

- * - * @param light The point light source to render. Must not be null. - */ - void render(PointLight light); + /** + * Renders a point light source. + * + *

This method handles the rendering of a point light, which emits light uniformly in all + * directions from a single point in 3D space. Point lights are commonly used to simulate small + * localized light sources such as light bulbs or torches. + * + * @param light The point light source to render. Must not be null. + */ + void render(PointLight light); - /** - * Renders a directional light source. - *

- * This method handles rendering for a directional light, which simulates - * light coming from a distant, uniform direction (e.g., sunlight or - * moonlight). Directional lights are ideal for simulating natural light - * sources that do not have an attenuation effect based on distance. - *

- * - * @param light The directional light source to render. Must not be null. - */ - void render(DirectionalLight light); + /** + * Renders a directional light source. + * + *

This method handles rendering for a directional light, which simulates light coming from a + * distant, uniform direction (e.g., sunlight or moonlight). Directional lights are ideal for + * simulating natural light sources that do not have an attenuation effect based on distance. + * + * @param light The directional light source to render. Must not be null. + */ + 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 + /** + * 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); +} diff --git a/src/main/java/engine/scene/light/LightType.java b/src/main/java/engine/scene/light/LightType.java index 8d641565..86ac016e 100644 --- a/src/main/java/engine/scene/light/LightType.java +++ b/src/main/java/engine/scene/light/LightType.java @@ -3,22 +3,22 @@ /** * Enum representing different types of lights. * - * This enum defines the four primary types of lights commonly used in 3D - * graphics: + *

This enum defines the four primary types of lights commonly used in 3D graphics: * *

  * - POINT: A point light emits light uniformly in all directions.
- * - DIRECTIONAL: A directional light emits light in parallel rays from 
+ * - DIRECTIONAL: A directional light emits light in parallel rays from
  *   a specific direction.
- * - SPOT: A spotlight emits light in a cone shape, with a specific 
+ * - 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 
+ * - 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, AMBIENT - -} \ No newline at end of file + POINT, + DIRECTIONAL, + SPOT, + AMBIENT +} diff --git a/src/main/java/engine/scene/light/PointLight.java b/src/main/java/engine/scene/light/PointLight.java index 6262f402..9a71633d 100644 --- a/src/main/java/engine/scene/light/PointLight.java +++ b/src/main/java/engine/scene/light/PointLight.java @@ -5,18 +5,15 @@ /** * Represents a point light source in a 3D scene. - * - *

- * A point light simulates a light-emitting point in space, radiating light - * uniformly in all directions. It is characterized by its position, color, - * intensity, and range. This class is ideal for simulating localized light - * sources such as lightbulbs, torches, or other small light emitters in a 3D - * environment. - *

- * + * + *

A point light simulates a light-emitting point in space, radiating light uniformly in all + * directions. It is characterized by its position, color, intensity, and range. This class is ideal + * for simulating localized light sources such as lightbulbs, torches, or other small light emitters + * in a 3D environment. + * *

  * Key Characteristics of a point light:
- * Position: A 3D vector representing the spatial location of the light in 
+ * Position: A 3D vector representing the spatial location of the light in
  * the scene.
  * Color: The color of light the point light emits. Represented by an instance
  * of {@link math.Color}.
@@ -25,186 +22,176 @@
  * Range: The maximum distance at which the light's effect is visible, beyond
  * which the light has no influence.
  * 
- * - * Usage: This class provides methods to dynamically configure light properties, - * such as changing the light's intensity, range, or color at runtime. - * Integration with rendering systems can be accomplished via the - * {@link LightRenderer}. + * + * Usage: This class provides methods to dynamically configure light properties, such as changing + * the light's intensity, range, or color at runtime. Integration with rendering systems can be + * accomplished via the {@link LightRenderer}. */ public class PointLight implements Light { - /** - * The color of the light emitted by the point light source. - */ - private Color color; - - /** - * The 3D position of the point light source within the scene. - */ - private Vector3f position; - - /** - * The intensity of the light emitted by the point light source. - */ - private float intensity; - - /** - * The maximum distance at which the light's effect can influence objects. - */ - private float range; - - /** - * Creates a new PointLight instance with default settings. - *

- * This constructor initializes the point light with the following default - * values: - Color: White (RGB(255, 255, 255)). - Position: (0, 0, 0). - - * Intensity: 1.0. - Range: 10.0. - *

- */ - public PointLight() { - this(Color.WHITE, new Vector3f(0, 0, 0), 1.0f, 10.0f); - } - - /** - * Creates a new PointLight instance with specified parameters. - * - * @param color The color of the light. Must not be null. - * @param position The 3D position of the light source in the scene. Must not - * be null. - * @param intensity The intensity of the light. Must be a non-negative value. - * @param range The maximum distance of the light's effect. Must be - * non-negative. - * - * @throws IllegalArgumentException if any argument is invalid (e.g., null - * values or negative numbers). - */ - public PointLight(Color color, Vector3f position, float intensity, - float range) { - setColor(color); - setPosition(position); - setIntensity(intensity); - setRange(range); - } - - /** - * Gets the maximum range at which the light's effect is felt. - * - * @return The range of the light's effect in world units. - */ - public float getRange() { - return range; - } - - /** - * Sets the maximum range of the light's influence in the scene. - * - * @param range The new range value. Must be non-negative. - * @throws IllegalArgumentException if the provided range is less than 0. - */ - public void setRange(float range) { - if (range < 0) { - throw new IllegalArgumentException("Range must be non-negative."); - } - this.range = range; - } - - /** - * Gets the current intensity of the light. - * - * @return The intensity value, a non-negative float. - */ - public float getIntensity() { - return intensity; - } - - /** - * Sets the intensity of the light source. - * - * @param intensity The new intensity value to apply. Must be non-negative. - * @throws IllegalArgumentException if intensity is less than 0. - */ - public void setIntensity(float intensity) { - if (intensity < 0) { - throw new IllegalArgumentException("Intensity must be non-negative."); - } - this.intensity = intensity; - } - - /** - * Gets the color of the light emitted by the point light source. - * - * @return The current {@link math.Color} of the point light. - */ - @Override - public Color getColor() { - return color; - } - - /** - * Sets the color of the light source. - * - * @param color The new color value for the light source. Must not be null. - * @throws IllegalArgumentException if color is null. - */ - public void setColor(Color color) { - if (color == null) { - throw new IllegalArgumentException("Color cannot be null."); - } - this.color = color; - } - - /** - * Gets the 3D position of the light source. - * - * @return The current position of the light as a {@link math.Vector3f}. - */ - public Vector3f getPosition() { - return position; - } - - /** - * Sets the 3D position of the light source within the 3D scene. - * - * @param position The new position value to set. Must not be null. - * @throws IllegalArgumentException if position is null. - */ - public void setPosition(Vector3f position) { - if (position == null) { - throw new IllegalArgumentException("Position cannot be null."); - } - this.position = position; - } - - /** - * Gets the type of light. - * - * @return The type of the light, represented as `LightType.POINT`. - */ - @Override - public LightType getType() { - return LightType.POINT; - } - - /** - * Renders this point light source using the provided renderer. - * - * @param renderer The renderer responsible for rendering the light in the 3D - * scene. - */ - @Override - public void render(LightRenderer renderer) { - renderer.render(this); - } - - /** - * Generates a string representation of this {@link PointLight}. - * - * @return A string describing the current state of this point light. - */ - @Override - public String toString() { - return "PointLight [color=" + color + ", position=" + position - + ", intensity=" + intensity + ", range=" + range + "]"; - } - -} \ No newline at end of file + /** The color of the light emitted by the point light source. */ + private Color color; + + /** The 3D position of the point light source within the scene. */ + private Vector3f position; + + /** The intensity of the light emitted by the point light source. */ + private float intensity; + + /** The maximum distance at which the light's effect can influence objects. */ + private float range; + + /** + * Creates a new PointLight instance with default settings. + * + *

This constructor initializes the point light with the following default values: - Color: + * White (RGB(255, 255, 255)). - Position: (0, 0, 0). - Intensity: 1.0. - Range: 10.0. + */ + public PointLight() { + this(Color.WHITE, new Vector3f(0, 0, 0), 1.0f, 10.0f); + } + + /** + * Creates a new PointLight instance with specified parameters. + * + * @param color The color of the light. Must not be null. + * @param position The 3D position of the light source in the scene. Must not be null. + * @param intensity The intensity of the light. Must be a non-negative value. + * @param range The maximum distance of the light's effect. Must be non-negative. + * @throws IllegalArgumentException if any argument is invalid (e.g., null values or negative + * numbers). + */ + public PointLight(Color color, Vector3f position, float intensity, float range) { + setColor(color); + setPosition(position); + setIntensity(intensity); + setRange(range); + } + + /** + * Gets the maximum range at which the light's effect is felt. + * + * @return The range of the light's effect in world units. + */ + public float getRange() { + return range; + } + + /** + * Sets the maximum range of the light's influence in the scene. + * + * @param range The new range value. Must be non-negative. + * @throws IllegalArgumentException if the provided range is less than 0. + */ + public void setRange(float range) { + if (range < 0) { + throw new IllegalArgumentException("Range must be non-negative."); + } + this.range = range; + } + + /** + * Gets the current intensity of the light. + * + * @return The intensity value, a non-negative float. + */ + public float getIntensity() { + return intensity; + } + + /** + * Sets the intensity of the light source. + * + * @param intensity The new intensity value to apply. Must be non-negative. + * @throws IllegalArgumentException if intensity is less than 0. + */ + public void setIntensity(float intensity) { + if (intensity < 0) { + throw new IllegalArgumentException("Intensity must be non-negative."); + } + this.intensity = intensity; + } + + /** + * Gets the color of the light emitted by the point light source. + * + * @return The current {@link math.Color} of the point light. + */ + @Override + public Color getColor() { + return color; + } + + /** + * Sets the color of the light source. + * + * @param color The new color value for the light source. Must not be null. + * @throws IllegalArgumentException if color is null. + */ + public void setColor(Color color) { + if (color == null) { + throw new IllegalArgumentException("Color cannot be null."); + } + this.color = color; + } + + /** + * Gets the 3D position of the light source. + * + * @return The current position of the light as a {@link math.Vector3f}. + */ + public Vector3f getPosition() { + return position; + } + + /** + * Sets the 3D position of the light source within the 3D scene. + * + * @param position The new position value to set. Must not be null. + * @throws IllegalArgumentException if position is null. + */ + public void setPosition(Vector3f position) { + if (position == null) { + throw new IllegalArgumentException("Position cannot be null."); + } + this.position = position; + } + + /** + * Gets the type of light. + * + * @return The type of the light, represented as `LightType.POINT`. + */ + @Override + public LightType getType() { + return LightType.POINT; + } + + /** + * Renders this point light source using the provided renderer. + * + * @param renderer The renderer responsible for rendering the light in the 3D scene. + */ + @Override + public void render(LightRenderer renderer) { + renderer.render(this); + } + + /** + * Generates a string representation of this {@link PointLight}. + * + * @return A string describing the current state of this point light. + */ + @Override + public String toString() { + return "PointLight [color=" + + color + + ", position=" + + position + + ", intensity=" + + intensity + + ", range=" + + range + + "]"; + } +} diff --git a/src/main/java/engine/scene/light/SpotLight.java b/src/main/java/engine/scene/light/SpotLight.java index 72bc0cf1..b789a840 100644 --- a/src/main/java/engine/scene/light/SpotLight.java +++ b/src/main/java/engine/scene/light/SpotLight.java @@ -6,10 +6,9 @@ /** * Represents a spotlight in a 3D scene. * - * A spotlight emits light in a cone shape, with a defined position, direction, - * cone angle, and concentration (center bias). This class models the essential - * properties of a spotlight, allowing users to specify its behavior and - * appearance in a 3D environment. + *

A spotlight emits light in a cone shape, with a defined position, direction, cone angle, and + * concentration (center bias). This class models the essential properties of a spotlight, allowing + * users to specify its behavior and appearance in a 3D environment. * *

  * Key properties include:
@@ -20,249 +19,236 @@
  * - Concentration: The exponent controlling how focused the spotlight
  *   is on its center.
  * 
- * - * This class supports both a default spotlight configuration and customizable - * initialization via its constructors. Input values are validated to ensure - * realistic and meaningful spotlight behavior. + * + * This class supports both a default spotlight configuration and customizable initialization via + * its constructors. Input values are validated to ensure realistic and meaningful spotlight + * behavior. */ public class SpotLight implements Light { - /** - * 45° in radians, the default cone angle for a standard spotlight. - */ - private static final float DEFAULT_ANGLE = (float) Math.PI / 4; - - /** - * Default center bias value for the spotlight's cone. - */ - private static final float DEFAULT_CONCENTRATION = 10.0f; - - /** - * The default position of the spotlight, located at the origin. - */ - private static final Vector3f DEFAULT_POSITION = new Vector3f(0, 0, 0); - - /** - * The default direction for the spotlight, pointing along the negative - * Z-axis. - */ - private static final Vector3f DEFAULT_DIRECTION = new Vector3f(0, 0, -1); - - /** - * The default color of the spotlight's emitted light (white light). - */ - private static final Color DEFAULT_COLOR = Color.WHITE; - - /** The angle of the spotlight's cone in radians. */ - private float angle; - - /** Determines the spotlight's intensity concentration toward its center. */ - private float concentration; - - /** The position of the spotlight in 3D space. */ - private Vector3f position; - - /** The direction vector indicating the spotlight's orientation. */ - private Vector3f direction; - - /** The color of the emitted spotlight's light. */ - private Color color; - - /** - * Default constructor initializes the spotlight with pre-defined defaults. - * - *
-	 * The defaults include:
-	 * - Position at (0,0,0).
-	 * - Direction pointing along the negative Z-axis.
-	 * - White color.
-	 * - A cone angle of 45° (π/4 radians).
-	 * - A concentration value of 10.0 (focused light)
-	 * 
- */ - public SpotLight() { - this(DEFAULT_POSITION, DEFAULT_DIRECTION, DEFAULT_COLOR, - DEFAULT_CONCENTRATION, DEFAULT_ANGLE); - } - - /** - * Constructs a new SpotLight instance with the specified properties. - * - *

- * Initializes the spotlight with the provided position, direction, color, - * concentration, and cone angle values. Each input is validated to ensure it - * adheres to acceptable ranges or requirements. - *

- * - * @param position The 3D position of the spotlight. Must not be null. - * @param direction The direction the spotlight points towards. Must not - * be null. - * @param color The emitted light's color. Must not be null. - * @param concentration The center bias (intensity focus) of the spotlight - * cone. Must be non-negative. - * @param angle The cone angle in radians. Must be greater than 0 and - * less than or equal to π radians. - * @throws IllegalArgumentException if any of the following conditions are - * met: - `position` is null. - `direction` - * is null. - `color` is null. - - * `concentration` is negative. - `angle` is - * less than or equal to 0, or greater than π - * radians. - */ - public SpotLight(Vector3f position, Vector3f direction, Color color, - float concentration, float angle) { - setPosition(position); - setDirection(direction); - setColor(color); - setConcentration(concentration); - setAngle(angle); - } - - /** - * Gets the angle of the spotlight cone. - * - * @return The cone's angle in radians. - */ - public float getAngle() { - return angle; - } - - /** - * Sets the cone angle, ensuring it is within valid physical limits. - * - * @param angle The new angle of the spotlight cone. - * @throws IllegalArgumentException if the value is less than or equal to 0 or - * exceeds π radians. - */ - public void setAngle(float angle) { - if (angle <= 0 || angle > Math.PI) { - throw new IllegalArgumentException( - "Angle must be between 0 and PI radians."); - } - this.angle = angle; - } - - /** - * Gets the concentration (center bias) of the spotlight's cone. - * - * @return The concentration value of the spotlight. - */ - public float getConcentration() { - return concentration; - } - - /** - * Sets the concentration value for the spotlight cone's focus. - * - * @param concentration The new concentration value. - * @throws IllegalArgumentException if the value is negative. - */ - public void setConcentration(float concentration) { - if (concentration < 0) { - throw new IllegalArgumentException("Concentration must be non-negative."); - } - this.concentration = concentration; - } - - /** - * Retrieves the direction vector of the spotlight. - * - * @return The current direction vector. - */ - public Vector3f getDirection() { - return direction; - } - - /** - * Sets the direction vector of the spotlight. - * - * @param direction The new direction vector. - * @throws IllegalArgumentException if the provided vector is null. - */ - public void setDirection(Vector3f direction) { - if (direction == null) { - throw new IllegalArgumentException("Direction cannot be null."); - } - this.direction = direction; - } - - /** - * Retrieves the position of the spotlight. - * - * @return The position vector. - */ - public Vector3f getPosition() { - return position; - } - - /** - * Sets the position of the spotlight in 3D space. - * - * @param position The new position vector. - * @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 = position; - } - - /** - * Retrieves the color of the spotlight's light. - * - * @return The spotlight's color. - */ - @Override - public Color getColor() { - return color; - } - - /** - * Sets the color of the spotlight's emitted light. - * - * @param color The new color value. - * @throws IllegalArgumentException if the provided color is null. - */ - public void setColor(Color color) { - if (color == null) { - throw new IllegalArgumentException("Color cannot be null."); - } - this.color = color; - } - - /** - * Determines the type of light, specifically `LightType.SPOT`. - * - * @return The type of light. - */ - @Override - public LightType getType() { - return LightType.SPOT; - } - - /** - * Renders the spotlight using the provided rendering system. - * - * Delegates rendering logic to the specified {@link LightRenderer}. - * - * @param renderer The renderer responsible for spotlight rendering. - */ - @Override - public void render(LightRenderer renderer) { - renderer.render(this); - } - - /** - * Provides a string representation of this spotlight instance for debugging. - * - * @return String describing the current state of the spotlight. - */ - @Override - public String toString() { - return "SpotLight [angle=" + angle + ", concentration=" + concentration - + ", position=" + position + ", direction=" + direction + ", color=" - + color + "]"; - } - -} \ No newline at end of file + /** 45° in radians, the default cone angle for a standard spotlight. */ + private static final float DEFAULT_ANGLE = (float) Math.PI / 4; + + /** Default center bias value for the spotlight's cone. */ + private static final float DEFAULT_CONCENTRATION = 10.0f; + + /** The default position of the spotlight, located at the origin. */ + private static final Vector3f DEFAULT_POSITION = new Vector3f(0, 0, 0); + + /** The default direction for the spotlight, pointing along the negative Z-axis. */ + private static final Vector3f DEFAULT_DIRECTION = new Vector3f(0, 0, -1); + + /** The default color of the spotlight's emitted light (white light). */ + private static final Color DEFAULT_COLOR = Color.WHITE; + + /** The angle of the spotlight's cone in radians. */ + private float angle; + + /** Determines the spotlight's intensity concentration toward its center. */ + private float concentration; + + /** The position of the spotlight in 3D space. */ + private Vector3f position; + + /** The direction vector indicating the spotlight's orientation. */ + private Vector3f direction; + + /** The color of the emitted spotlight's light. */ + private Color color; + + /** + * Default constructor initializes the spotlight with pre-defined defaults. + * + *
+   * The defaults include:
+   * - Position at (0,0,0).
+   * - Direction pointing along the negative Z-axis.
+   * - White color.
+   * - A cone angle of 45° (π/4 radians).
+   * - A concentration value of 10.0 (focused light)
+   * 
+ */ + public SpotLight() { + this(DEFAULT_POSITION, DEFAULT_DIRECTION, DEFAULT_COLOR, DEFAULT_CONCENTRATION, DEFAULT_ANGLE); + } + + /** + * Constructs a new SpotLight instance with the specified properties. + * + *

Initializes the spotlight with the provided position, direction, color, concentration, and + * cone angle values. Each input is validated to ensure it adheres to acceptable ranges or + * requirements. + * + * @param position The 3D position of the spotlight. Must not be null. + * @param direction The direction the spotlight points towards. Must not be null. + * @param color The emitted light's color. Must not be null. + * @param concentration The center bias (intensity focus) of the spotlight cone. Must be + * non-negative. + * @param angle The cone angle in radians. Must be greater than 0 and less than or equal to π + * radians. + * @throws IllegalArgumentException if any of the following conditions are met: - `position` is + * null. - `direction` is null. - `color` is null. - `concentration` is negative. - `angle` is + * less than or equal to 0, or greater than π radians. + */ + public SpotLight( + Vector3f position, Vector3f direction, Color color, float concentration, float angle) { + setPosition(position); + setDirection(direction); + setColor(color); + setConcentration(concentration); + setAngle(angle); + } + + /** + * Gets the angle of the spotlight cone. + * + * @return The cone's angle in radians. + */ + public float getAngle() { + return angle; + } + + /** + * Sets the cone angle, ensuring it is within valid physical limits. + * + * @param angle The new angle of the spotlight cone. + * @throws IllegalArgumentException if the value is less than or equal to 0 or exceeds π radians. + */ + public void setAngle(float angle) { + if (angle <= 0 || angle > Math.PI) { + throw new IllegalArgumentException("Angle must be between 0 and PI radians."); + } + this.angle = angle; + } + + /** + * Gets the concentration (center bias) of the spotlight's cone. + * + * @return The concentration value of the spotlight. + */ + public float getConcentration() { + return concentration; + } + + /** + * Sets the concentration value for the spotlight cone's focus. + * + * @param concentration The new concentration value. + * @throws IllegalArgumentException if the value is negative. + */ + public void setConcentration(float concentration) { + if (concentration < 0) { + throw new IllegalArgumentException("Concentration must be non-negative."); + } + this.concentration = concentration; + } + + /** + * Retrieves the direction vector of the spotlight. + * + * @return The current direction vector. + */ + public Vector3f getDirection() { + return direction; + } + + /** + * Sets the direction vector of the spotlight. + * + * @param direction The new direction vector. + * @throws IllegalArgumentException if the provided vector is null. + */ + public void setDirection(Vector3f direction) { + if (direction == null) { + throw new IllegalArgumentException("Direction cannot be null."); + } + this.direction = direction; + } + + /** + * Retrieves the position of the spotlight. + * + * @return The position vector. + */ + public Vector3f getPosition() { + return position; + } + + /** + * Sets the position of the spotlight in 3D space. + * + * @param position The new position vector. + * @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 = position; + } + + /** + * Retrieves the color of the spotlight's light. + * + * @return The spotlight's color. + */ + @Override + public Color getColor() { + return color; + } + + /** + * Sets the color of the spotlight's emitted light. + * + * @param color The new color value. + * @throws IllegalArgumentException if the provided color is null. + */ + public void setColor(Color color) { + if (color == null) { + throw new IllegalArgumentException("Color cannot be null."); + } + this.color = color; + } + + /** + * Determines the type of light, specifically `LightType.SPOT`. + * + * @return The type of light. + */ + @Override + public LightType getType() { + return LightType.SPOT; + } + + /** + * Renders the spotlight using the provided rendering system. + * + *

Delegates rendering logic to the specified {@link LightRenderer}. + * + * @param renderer The renderer responsible for spotlight rendering. + */ + @Override + public void render(LightRenderer renderer) { + renderer.render(this); + } + + /** + * Provides a string representation of this spotlight instance for debugging. + * + * @return String describing the current state of the spotlight. + */ + @Override + public String toString() { + return "SpotLight [angle=" + + angle + + ", concentration=" + + concentration + + ", position=" + + position + + ", direction=" + + direction + + ", color=" + + color + + "]"; + } +} diff --git a/src/main/java/math/Bounds.java b/src/main/java/math/Bounds.java index 57f62990..b21f770b 100644 --- a/src/main/java/math/Bounds.java +++ b/src/main/java/math/Bounds.java @@ -1,229 +1,225 @@ 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. - *

+ * 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 + /** 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; + } + + /** + * Returns a string representation of the Bounds object. The string includes the minimum and + * maximum points of the bounds. + * + * @return a string representation of this Bounds object in the format: "Bounds [min=, + * max=]". + */ + @Override + public String toString() { + return "Bounds [min=" + min + ", max=" + max + "]"; + } +} diff --git a/src/main/java/math/GeometryUtil.java b/src/main/java/math/GeometryUtil.java index cdc656f6..9c04cfe8 100644 --- a/src/main/java/math/GeometryUtil.java +++ b/src/main/java/math/GeometryUtil.java @@ -2,156 +2,158 @@ public class GeometryUtil { - /** - * Calculates the centroid (center of mass) of a triangle defined by three - * points. - * - * The centroid is computed as the average of the x, y, and z coordinates of - * the three vertices. - * - * @param a The first vertex of the triangle. - * @param b The second vertex of the triangle. - * @param c The third vertex of the triangle. - * @return The centroid of the triangle as a new Vector3f. - */ - public static Vector3f calculateCentroid(Vector3f a, Vector3f b, - Vector3f c) { - float x = (a.x + b.x + c.x) / 3f; - float y = (a.y + b.y + c.y) / 3f; - float z = (a.z + b.z + c.z) / 3f; - return new Vector3f(x, y, z); - } + /** + * Calculates the centroid (center of mass) of a triangle defined by three points. + * + *

The centroid is computed as the average of the x, y, and z coordinates of the three + * vertices. + * + * @param a The first vertex of the triangle. + * @param b The second vertex of the triangle. + * @param c The third vertex of the triangle. + * @return The centroid of the triangle as a new Vector3f. + */ + public static Vector3f calculateCentroid(Vector3f a, Vector3f b, Vector3f c) { + float x = (a.x + b.x + c.x) / 3f; + float y = (a.y + b.y + c.y) / 3f; + float z = (a.z + b.z + c.z) / 3f; + return new Vector3f(x, y, z); + } - /** - * Calculates the centroid (or center of mass) of a triangle defined by - * three 2D points. - * - * @param a The first point of the triangle. - * @param b The second point of the triangle. - * @param c The third point of the triangle. - * @return The centroid of the triangle. - */ - public static Vector2f getMainEmphasis(Vector2f a, Vector2f b, Vector2f c) { - Vector2f m = a.add(b).add(c).mult(Mathf.ONE_THIRD); - return m; - } + /** + * Calculates the centroid (or center of mass) of a triangle defined by three 2D points. + * + * @param a The first point of the triangle. + * @param b The second point of the triangle. + * @param c The third point of the triangle. + * @return The centroid of the triangle. + */ + public static Vector2f getMainEmphasis(Vector2f a, Vector2f b, Vector2f c) { + Vector2f m = a.add(b).add(c).mult(Mathf.ONE_THIRD); + return m; + } - /** - * Calculates the center point of a quadrilateral defined by four 2D points. - * - * @param a The first point of the quadrilateral. - * @param b The second point of the quadrilateral. - * @param c The third point of the quadrilateral. - * @param d The fourth point of the quadrilateral. - * @return The center point of the quadrilateral. - */ - public static Vector2f calculateCenter(Vector2f a, Vector2f b, Vector2f c, - Vector2f d) { - float x = (a.x + b.x + c.x + d.x) * 0.25f; - float y = (a.y + b.y + c.y + d.y) * 0.25f; - return new Vector2f(x, y); - } + /** + * Calculates the center point of a quadrilateral defined by four 2D points. + * + * @param a The first point of the quadrilateral. + * @param b The second point of the quadrilateral. + * @param c The third point of the quadrilateral. + * @param d The fourth point of the quadrilateral. + * @return The center point of the quadrilateral. + */ + public static Vector2f calculateCenter(Vector2f a, Vector2f b, Vector2f c, Vector2f d) { + float x = (a.x + b.x + c.x + d.x) * 0.25f; + float y = (a.y + b.y + c.y + d.y) * 0.25f; + return new Vector2f(x, y); + } - /** - * Calculates the center point of a quadrilateral defined by four 3D points. - * - * @param a The first point of the quadrilateral. - * @param b The second point of the quadrilateral. - * @param c The third point of the quadrilateral. - * @param d The fourth point of the quadrilateral. - * @return The center point of the quadrilateral. - */ - public static Vector3f calculateCenter(Vector3f a, Vector3f b, Vector3f c, - Vector3f d) { - float x = (a.x + b.x + c.x + d.x) * 0.25f; - float y = (a.y + b.y + c.y + d.y) * 0.25f; - float z = (a.z + b.z + c.z + d.z) * 0.25f; - return new Vector3f(x, y, z); - } + /** + * Calculates the center point of a quadrilateral defined by four 3D points. + * + * @param a The first point of the quadrilateral. + * @param b The second point of the quadrilateral. + * @param c The third point of the quadrilateral. + * @param d The fourth point of the quadrilateral. + * @return The center point of the quadrilateral. + */ + public static Vector3f calculateCenter(Vector3f a, Vector3f b, Vector3f c, Vector3f d) { + float x = (a.x + b.x + c.x + d.x) * 0.25f; + float y = (a.y + b.y + c.y + d.y) * 0.25f; + float z = (a.z + b.z + c.z + d.z) * 0.25f; + return new Vector3f(x, y, z); + } - /** - * Calculates the midpoint between two 3D points. - * - * @param start The starting point. - * @param end The ending point. - * @return The midpoint between the two points. - */ - public static Vector3f getMidpoint(Vector3f start, Vector3f end) { - return start.add(end).mult(0.5f); - } + /** + * Calculates the midpoint between two 3D points. + * + * @param start The starting point. + * @param end The ending point. + * @return The midpoint between the two points. + */ + public static Vector3f getMidpoint(Vector3f start, Vector3f end) { + return start.add(end).mult(0.5f); + } - /** - * Calculates the midpoint between two 2D points. - * - * @param start The starting point. - * @param end The ending point. - * @return The midpoint between the two points. - */ - public static Vector2f getMidpoint(Vector2f start, Vector2f end) { - return start.add(end).mult(0.5f); - } + /** + * Calculates the midpoint between two 2D points. + * + * @param start The starting point. + * @param end The ending point. + * @return The midpoint between the two points. + */ + public static Vector2f getMidpoint(Vector2f start, Vector2f end) { + return start.add(end).mult(0.5f); + } - /** - * Calculates the angle in radians between two 3D vectors. - * - * @param v1 The first vector. - * @param v2 The second vector. - * @return The angle between the two vectors in radians. - * @throws IllegalArgumentException if either vector has zero length. - */ - public static double angleBetweenVectors(Vector3f v1, Vector3f v2) { - double dotProduct = v1.dot(v2); - double magnitude1 = v1.length(); - double magnitude2 = v2.length(); - - if (magnitude1 == 0 || magnitude2 == 0) { - throw new IllegalArgumentException( - "Vectors cannot have zero length"); - } - - // Handle floating-point precision issues - double cosTheta = Math.max(-1.0, - Math.min(1.0, dotProduct / (magnitude1 * magnitude2))); - - double angle = Math.acos(cosTheta); - - return angle; - } + /** + * Calculates the angle in radians between two 3D vectors. + * + * @param v1 The first vector. + * @param v2 The second vector. + * @return The angle between the two vectors in radians. + * @throws IllegalArgumentException if either vector has zero length. + */ + public static double angleBetweenVectors(Vector3f v1, Vector3f v2) { + double dotProduct = v1.dot(v2); + double magnitude1 = v1.length(); + double magnitude2 = v2.length(); - /** - * Calculates a point along the line segment between two 2D points. - * - * @param start The starting point of the line segment. - * @param end The ending point of the line segment. - * @param lambda A value between 0 and 1 that determines the position of the - * point along the line segment. A value of 0 will return the - * start point, a value of 1 will return the end point, and - * values between 0 and 1 will return points in between. - * @return The point along the line segment. - */ - public static Vector2f getDistributionPoint(Vector2f start, Vector2f end, - float lambda) { - float scalar = 1f / (1f + lambda); - return start.add(end.mult(lambda)).mult(scalar); + if (magnitude1 == 0 || magnitude2 == 0) { + throw new IllegalArgumentException("Vectors cannot have zero length"); } - /** - * Calculates a point on a circle given a center point, radius, angle, and - * direction. - * - * @param center The center point of the circle. - * @param radius The radius of the circle. - * @param angle The angle in radians. - * @param cw If true, the angle is measured clockwise from the positive - * x-axis. If false, the angle is measured counterclockwise. - * @return A point on the circle. - */ - public static Vector2f pointOnCircle(Vector2f center, float radius, - float angle, boolean cw) { - angle = cw ? angle : -angle; - float x = (float) (center.x + radius * Math.cos(angle)); - float y = (float) (center.y + radius * Math.sin(angle)); - return new Vector2f(x, y); - } + // Handle floating-point precision issues + double cosTheta = Math.max(-1.0, Math.min(1.0, dotProduct / (magnitude1 * magnitude2))); + + double angle = Math.acos(cosTheta); + + return angle; + } + + /** + * Calculates a point along the line segment between two 2D points. + * + * @param start The starting point of the line segment. + * @param end The ending point of the line segment. + * @param lambda A value between 0 and 1 that determines the position of the point along the line + * segment. A value of 0 will return the start point, a value of 1 will return the end point, + * and values between 0 and 1 will return points in between. + * @return The point along the line segment. + */ + public static Vector2f getDistributionPoint(Vector2f start, Vector2f end, float lambda) { + float scalar = 1f / (1f + lambda); + return start.add(end.mult(lambda)).mult(scalar); + } + + /** + * Calculates a point on a circle given a center point, radius, angle, and direction. + * + * @param center The center point of the circle. + * @param radius The radius of the circle. + * @param angle The angle in radians. + * @param cw If true, the angle is measured clockwise from the positive x-axis. If false, the + * angle is measured counterclockwise. + * @return A point on the circle. + */ + public static Vector2f pointOnCircle(Vector2f center, float radius, float angle, boolean cw) { + angle = cw ? angle : -angle; + float x = (float) (center.x + radius * Math.cos(angle)); + float y = (float) (center.y + radius * Math.sin(angle)); + return new Vector2f(x, y); + } + + /** + * Computes an arbitrary vector orthogonal to the given vector. + * + * @param vector The input vector to find an orthogonal vector to. + * @return A vector orthogonal to the provided vector. + */ + public static Vector3f getOrthogonalVector(Vector3f vector) { + // Choose an arbitrary vector that isn't parallel to the input vector + Vector3f arbitrary = Math.abs(vector.x) < 0.9 ? new Vector3f(1, 0, 0) : new Vector3f(0, 1, 0); + // Compute a cross product to ensure orthogonality + return vector.cross(arbitrary).normalize(); + } } diff --git a/src/main/java/math/Mathf.java b/src/main/java/math/Mathf.java index 70c06d16..2099fb22 100644 --- a/src/main/java/math/Mathf.java +++ b/src/main/java/math/Mathf.java @@ -3,981 +3,891 @@ import java.util.Random; /** - * This class provides a collection of mathematical utility functions that are - * commonly used in various numerical computations and game development. It - * offers a range of functionalities, including trigonometric functions, - * logarithmic functions, rounding, clamping, and random number generation. + * This class provides a collection of mathematical utility functions that are commonly used in + * various numerical computations and game development. It offers a range of functionalities, + * including trigonometric functions, logarithmic functions, rounding, clamping, and random number + * generation. */ public class Mathf { - /** - * A random number generator used to generate random values. - */ - private static Random random = new Random(); - - /** - * A float representation of the golden ratio, approximately 1.618. - */ - public static final float GOLDEN_RATIO = (1 + sqrt(5)) / 2.0f; - - /** - * A float representation of the reciprocal of the golden ratio, , which is - * exactly 1 less than the golden ratio itself; approximately 0.618. - */ - public static final float GOLDEN_RATIO_RECIPROCAL = 2 / (1 + sqrt(5)); - - /** - * Euler's number, the base of the natural logarithm, approximately 2.718. - */ - public static final float E = (float) Math.E; - - /** - * A representation of negative infinity. - */ - public static final float NEGATIVE_INFINITY = Float.NEGATIVE_INFINITY; - - /** - * A representation of positive infinity. - */ - public static final float POSITIVE_INFINITY = Float.POSITIVE_INFINITY; - - /** - * The smallest positive nonzero value representable as a float. - */ - public static final float MIN_VALUE = Float.MIN_VALUE; - - /** - * The largest finite value representable as a float. - */ - public static final float MAX_VALUE = Float.MAX_VALUE; - - /** - * A small value used for floating-point comparisons, approximately 2.22E-16. - */ - public static final double DBL_EPSILON = 2.220446049250313E-16d; - - /** - * A small value used for floating-point comparisons, approximately 1.19E-7. - */ - public static final float FLT_EPSILON = 1.1920928955078125E-7f; - - /** - * A small tolerance value for comparing floating-point numbers. - */ - public static final float ZERO_TOLERANCE = 0.0001f; - - /** - * A float representation of one-third, approximately 0.33333334. - */ - public static final float ONE_THIRD = 1f / 3f; - - /** - * The value of Pi, approximately 3.14159. - */ - public static final float PI = (float) Math.PI; - - /** - * Twice the value of Pi, approximately 6.283185. - */ - public static final float TWO_PI = 2.0f * PI; - - /** - * Half the value of Pi, approximately 1.570796. - */ - public static final float HALF_PI = 0.5f * PI; - - /** - * A quarter of the value of Pi, approximately 0.785398. - */ - public static final float QUARTER_PI = 0.25f * PI; - - /** - * The reciprocal of Pi, approximately 0.3183099. - */ - public static final float INV_PI = 1.0f / PI; - - /** - * The reciprocal of two times Pi, approximately 0.1591549. - */ - public static final float INV_TWO_PI = 1.0f / TWO_PI; - - /** - * A factor to convert degrees to radians, approximately 0.0174533. - */ - public static final float DEG_TO_RAD = PI / 180.0f; - - /** - * A factor to convert radians to degrees, approximately 57.29578. - */ - public static final float RAD_TO_DEG = 180.0f / PI; - - /** - * The Tribonacci constant, often denoted as t, is the real root of the cubic - * equation x³ - x² - x - 1 = 0. It is approximately equal to - * 1.83928675521416. - */ - public static final float TRIBONACCI_CONSTANT = 1.83928675521416f; - - /** - * Converts a 2D index (row, column) into a 1D index for a matrix or array. - * - *

- * This method is useful when working with matrices or arrays that are stored - * in a 1D array. It calculates the 1D index corresponding to the specified - * row and column in a matrix with the given number of columns. - * - * @param rowIndex The zero-based index of the row. - * @param colIndex The zero-based index of the column. - * @param numberOfColumns The total number of columns in the matrix. - * @return The 1D index corresponding to the given row and column. - * - * @throws IllegalArgumentException if `rowIndex` or `colIndex` is negative, - * or if `numberOfColumns` is less than or - * equal to zero. - */ - public static int toOneDimensionalIndex(int rowIndex, int colIndex, - int numberOfColumns) { - if (rowIndex < 0 || colIndex < 0) - throw new IllegalArgumentException(); - - if (numberOfColumns <= 0) - throw new IllegalArgumentException(); - - return rowIndex * numberOfColumns + colIndex; - } - - /** - * Returns the smaller of two int values. That is, the result is the argument - * closer to {@link Integer#MIN_VALUE}. If the arguments have the same value, - * the result is that same value. - * - * @param a The first integer. - * @param b The second integer. - * @return The smaller of `a` and `b`. - */ - public static int min(int a, int b) { - return Math.min(a, b); - } - - /** - * Returns the larger of two int values. That is, the result is the argument - * closer to {@link Integer#MAX_VALUE}. If the arguments have the same value, - * the result is that same value. - * - * @param a The first integer. - * @param b The second integer. - * @return The larger of `a` and `b`. - */ - public static int max(int a, int b) { - return Math.max(a, b); - } - - /** - * Returns the minimum value in the given array. - * - * @param values The array of integers. - * @return The minimum value in the array, or 0 if the array is empty. - */ - public static int min(int[] values) { - if (values.length == 0) - return 0; - - int min = values[0]; - for (int i = 1; i < values.length; i++) - min = Math.min(min, values[i]); - return min; - } - - /** - * Returns the maximum value in the given array. - * - * @param values The array of integers. - * @return The maximum value in the array, or 0 if the array is empty. - */ - public static int max(int[] values) { - if (values.length == 0) - return 0; - - int max = values[0]; - for (int i = 1; i < values.length; i++) - max = Math.max(max, values[i]); - return max; - } - - /** - * Returns the larger of the two given float values. - * - * @param a The first float value. - * @param b The second float value. - * @return The larger of `a` and `b`. - */ - public static float max(float a, float b) { - return Math.max(a, b); - } - - /** - * Returns the smaller of the two given float values. - * - * @param a The first float value. - * @param b The second float value. - * @return The smaller of `a` and `b`. - */ - public static float min(float a, float b) { - return Math.min(a, b); - } - - /** - * Returns the maximum float value in the given array. - * - * @param values The array of float values. - * @return The maximum value in the array, or {@link Float#NaN} if the array - * is empty. - */ - public static float max(float... values) { - if (values.length == 0) - return Float.NaN; - - float max = values[0]; - for (int i = 1; i < values.length; i++) - max = Math.max(max, values[i]); - return max; - } - - /** - * Returns the minimum float value in the given array. - * - * @param values The array of float values. - * @return The minimum value in the array, or {@link Float#NaN} if the array - * is empty. - */ - public static float min(float... values) { - if (values.length == 0) - return Float.NaN; - - float min = values[0]; - for (int i = 1; i < values.length; i++) - min = Math.min(min, values[i]); - return min; - } - - /** - * Rounds a float value to the nearest integer, rounding ties towards positive - * infinity. - * - * @param a The float value to be rounded. - * @return The rounded integer value. - */ - public static int roundToInt(float a) { - return Math.round(a); - } - - /** - * Rounds a float value to the nearest integer. - * - *

- * This method rounds the given float value to the nearest integer. If the - * fractional part is 0.5 or greater, the value is rounded up. Otherwise, it - * is rounded down. - * - * @param a The float value to be rounded. - * @return The rounded float value. - */ - public static float round(float a) { - return Math.round(a); - } - - /** - * Clamps a value between a minimum and maximum value. - * - * @param a The value to clamp. - * @param min The minimum value. - * @param max The maximum value- - * @return The clamped value. - */ - public static float clamp(float a, float min, float max) { - return Math.max(min, Math.min(max, a)); - } - - /** - * Clamps a between min and max and returns the clamped value. - * - * @param a The value to clamp - * @param min The minimum for a. - * @param max The maximum for a. - * @return The clamped value. - */ - public static int clampInt(int a, int min, int max) { - return a < min ? min : (a > max ? max : a); - } - - /** - * Clamps the given float value to be between 0 and 1. This method is - * equivalent to {@link #saturate(float)}. - * - * @param a The value to clamp. - * @return A clamped value between 0 and 1- - * @see #saturate(float) - */ - public static float clamp01(float a) { - return clamp(a, 0f, 1f); - } - - /** - * Converts an angle measured in degrees to an approximately equivalent angle - * measured in radians. The conversion from degrees to radians is generally - * inexact; users should not expect cos(toRadians(90.0)) to exactly equal 0.0. - * - * @param angdeg The angle, in degrees. - * @return The angle in radians. - */ - public static float toRadians(float angdeg) { - return (float) Math.toRadians((double) angdeg); - } - - /** - * Converts an angle measured in radians to an approximately equivalent angle - * measured in degrees. The conversion from radians to degreees is generally - * inexact; users should not expect cos(toRadians(90.0)) to exactlyequal 0.0. - * - * @param angrad The angle, in radians. - * @return The angle in degrees. - */ - public static float toDegrees(float angrad) { - return (float) Math.toDegrees((double) angrad); - } - - /** - * Returns a hash code for a float value; compatible with Float.hashCode(). - * - * @param value The value to hash. - * @return A hash code value for a float value. - */ - public static int hashCode(float value) { - return Float.hashCode(value); - } - - /** - * Returns the absolute value of a. - * - * @param a The argument whose absolute value is to be determined. - * @return The absolute value of the argument. - */ - public static float abs(float a) { - return Math.abs(a); - } - - /** - * Returns the trigonometric tangent of an angle. Special cases: - *

    - *
  • If the argument is NaN or an infinity, then the result is NaN.
  • - *
  • If the argument is zero, then the result is a zero with the same sign - * as the argument.
  • The computed result must be within 1 ulp of the exact - * result. Results must be semi-monotonic. - * - * @param a An angle, in radians. - * @return The tangent of the argument. - */ - public static float tan(float a) { - return (float) Math.tan((double) a); - } - - /** - * Returns the trigonometric cosine of an angle. - *
      - *
    • Special cases: If the argument is NaN or an infinity, then the result - * is NaN.
    • - *
    - * - * @param a An angle, in radians. - * @return The cosine of the argument. - */ - public static float cos(float a) { - return (float) Math.cos((double) a); - } - - /** - * Returns the trigonometric sine of an angle. Special cases: - *
      - *
    • If the argument is NaN or an infinity, then the result is NaN.
    • - *
    • If the argument is zero, then the result is a zero with the same sign - * as the argument.
    • - *
    - * The computed result must be within 1 ulp of the exact result. Results must - * be semi-monotonic. - * - * @param a An angle, in radians. - * @return The sine of the argument. - */ - public static float sin(float a) { - return (float) Math.sin((double) a); - } - - /** - * Determines whether or not the given value a is in range of min and max. - * - * @param a The value to check. - * @param min The minimum value for a. - * @param max The maximum value for a. - * @return true if the value is in range of [min,max], false otherwise. - */ - public static boolean isInRange(float a, int min, int max) { - return a >= min && a <= max; - } - - /** - * Returns the signum function of the argument; zero if the argument is zero, - * 1.0f if the argument is greater than zero, -1.0f if the argument is less - * than zero. Special Cases: - *
      - *
    • If the argument is NaN, then the result is NaN.
    • - *
    • If the argument is positive zero or negative zero, then the result is - * the same as the argument.
    • - *
    - * - * @param a The floating-point value whose signum is to be returned. - * @return The signum function of the argument. - */ - public static float sign(float a) { - return Math.signum(a); - } - - /** - * Returns the correctly rounded positive square root of a float value. - * Special cases: - *
      - *
    • If the argument is NaN or less than zero, then the result is NaN.
    • - *
    • If the argument is positive infinity, then the result is positive - * infinity.
    • - *
    • If the argument is positive zero or negative zero, then the result is - * the same as the argument.

    • - * Otherwise, the result is the double value closest to the true mathematical - * square root of the argument value. - *
    - * - * @param a A value. - * @return The positive square root of a. If the argument is NaN or less than - * zero, the result is NaN. - */ - public static float sqrt(float a) { - return (float) Math.sqrt((double) a); - } - - /** - * Returns the largest (closest to positive infinity) float value that is less - * than or equal to the argument and is equal to a mathematical integer. - * Special cases: - *
      - *
    • If the argument value is already equal to a mathematical integer, then - * the result is the same as the argument.
    • - *
    • If the argument is NaN or an infinity or positive zero or negative - * zero, then the result is the same as the argument.
    • - *
    - * - * @param a A value. - * @return The largest (closest to positive infinity) floating-point value - * that less than or equal to the argument and is equal to a - * mathematical integer. - */ - public static float floor(float a) { - return (float) Math.floor((double) a); - } - - /** - * Returns Euler's number e raised to the power of a float value. Special - * cases: - *
      - *
    • If the argument is NaN, the result is NaN.
    • - *
    • If the argument is positive infinity, then the result is positive - * infinity.
    • - *
    • If the argument is negative infinity, then the result is positive - * zero.
    • - *
    - * The computed result must be within 1 ulp of the exact result. Results must - * be semi-monotonic. - * - * @param a The exponent to raise e to. - * @return The value ea, where e is the base of the natural - * logarithms. - */ - public static float exp(float a) { - return (float) Math.exp((double) a); - } - - /** - * Returns true if the argument is a finite floating-point value; returns - * false otherwise (for NaN and infinity arguments). - * - * @param f The float value to be tested. - * @return true if the argument is a finite floating-point value, false - * otherwise. - */ - public static boolean isFinite(float f) { - return Float.isFinite(f); - } - - /** - * Returns true if the specified number is infinitely large in magnitude, - * false otherwise. - * - * @param v The value to be tested. - * @return true if the argument is positive infinity or negative infinity; - * false otherwise. - */ - public static boolean isInfinite(float v) { - return Float.isInfinite(v); - } - - /** - * Returns true if the specified number is a Not-a-Number (NaN) value, false - * otherwise. - * - * @param v The value to be tested. - * @return true if the argument is NaN; false otherwise. - */ - public static boolean isNaN(float v) { - return Float.isNaN(v); - } - - /** - * Returns the arc cosine of a (the angle in radians whose cosine is a). - * - * @param a The value whose arc cosine is to be returned. - * @return The arc cosine of a. - */ - public static float acos(float a) { - return (float) Math.acos((double) a); - } - - /** - * Returns the arc-sine of a - the angle in radians whose sine is a. - * - * @param a The value whose arc sine is to be returned. - * @return The arc sine of the argument. - */ - public static float asin(float a) { - return (float) Math.asin((double) a); - } - - /** - * Returns the arc tangent of a (the angle in radians whose tangent is a). - * - * @param a The value whose arc tangent is to be returned. - * @return The arc tangent of the argument. - */ - public static float atan(float a) { - return (float) Math.atan((double) a); - } - - /** - * Returns the angle in radians whose Tan is y/x. - * - * Return value is the angle between the x-axis and a 2D vector starting at - * zero and terminating at (x,y). - * - * @param y The ordinate coordinate. - * @param x The abscissa coordinate. - * @return The theta component of the point (r, theta) in polar coordinates - * that corresponds to the point (x, y) in Cartesian coordinates. - */ - public static float atan2(float y, float x) { - return (float) Math.atan2((double) y, (double) x); - } - - /** - * Returns the smallest mathematical integer greater to or equal to a. - * - * @param a value - * @return The smallest (closest to negative infinity) floating-point value - * that is greater than or equal to the argument and is equal to a - * mathematical integer. - */ - public static float ceil(float a) { - return (float) Math.ceil((double) a); - } - - /** - * Returns the value of the first argument raised to the power of the second - * argument. - * - * @param a The base. - * @param b The exponent. - * @return The base raised to the power of b. - * @see Math#pow(double, double) - */ - public static float pow(float a, float b) { - return (float) Math.pow((double) a, (double) b); - } - - /** - * Returns the base 10 logarithm of a double value. Special cases: - *
      - *
    • If the argument is NaN or less than zero, then the result is NaN.
    • - *
    • If the argument is positive infinity, then the result is positive - * infinity.
    • - *
    • If the argument is positive zero or negative zero, then the result is - * negative infinity. - *
    - * - * @param a A value. - * @return The base 10 logarithm of a. - */ - public static float log10(float a) { - return (float) Math.log10((double) a); - } - - /** - * Returns the natural logarithm (base e) of a float value. Special cases: - *
      - *
    • If the argument is NaN or less than zero, then the result is NaN.
    • - *
    • If the argument is positive infinity, then the result is positive - * infinity.
    • - *
    • If the argument is positive zero or negative zero, then the result is - * negative infinity.
    • - *
    - * The computed result must be within 1 ulp of the exact result. Results must - * be semi-monotonic. - * - * @param a A value. - * @return The value ln a, the natural logarithm of a. - */ - public static float log(float a) { - return (float) Math.log((double) a); - } - - /** - * Returns the largest (closest to positive infinity) integer value that is - * less than or equal to the argument. - *
      - *
    • If the argument value is already equal to a mathematical integer, then - * the result is the same as the argument.
    • - *
    • If the argument is NaN or an infinity or positive zero or negative - * zero, then the result is the same as the argument.
    • - *
    - * - * @param a A value. - * @return The largest (closest to positive infinity) integer value that is - * less than or equal to the argument. - */ - public static int floorToInt(float a) { - return (int) Math.floor((double) a); - } - - /** - * Returns the smallest mathematical integer greater to or equal to a. - * - * @param a The value to ceil. - * @return The smallest (closest to negative infinity) integer value that is - * greater than or equal to the argument and is equal to a - * mathematical integer. - */ - public static int ceilToInt(float a) { - return (int) Math.ceil((double) a); - } - - /** - * Linearly interpolates between a and b by t. The parameter t is not clamped - * and values outside the range [0, 1] will result in a return value outside - * the range [a, /b/]. - * - *
    -	 * When t = 0 returns a.
    -	 * When t = 1 returns b.
    -	 * When t = 0.5 returns the midpoint of a and b.
    -	 * 
    - * - * @param a The value to interpolate from. - * @param b The value to interpolate to. - * @param t The value to interpolate by. - * @return The resultant interpolated value. - */ - public static float lerpUnclamped(float a, float b, float t) { - return a + (b - a) * t; - } - - /** - * Linearly interpolates between from and to by t. The parameter t is clamped - * to the range [0, 1]. - * - *
    -	 * When t = 0 returns a. 
    -	 * When t = 1 return b. 
    -	 * When t = 0.5 returns the midpoint of a and b.
    -	 * 
    - * - * @param from The value to interpolate from. - * @param to The value to interpolate to. - * @param t The value to interpolate by. - * @return The resultant interpolated value. - */ - public static float lerp(float from, float to, float t) { - return from + (to - from) * clamp01(t); - } - - /** - * Returns the next power of two greater than or equal to the given value. - * - *

    - * For example: - *

      - *
    • nextPowerOfTwo(1) returns 2
    • - *
    • nextPowerOfTwo(2) returns 2
    • - *
    • nextPowerOfTwo(3) returns 4
    • - *
    • nextPowerOfTwo(16) returns 16
    • - *
    • nextPowerOfTwo(17) returns 32
    • - *
    - * - * @param value the input value - * @return the next power of two greater than or equal to the input value - */ - public static int nextPowerOfTwo(int value) { - return value > 0 ? Integer.highestOneBit(value - 1) << 1 : 1; - } - - /** - * Smoothly interpolates between two values. This function provides a smoother - * transition between the two values compared to linear interpolation. It uses - * a cubic Hermite spline to achieve a smooth curve. - * - * @param from The starting value. - * @param to The ending value. - * @param t The interpolation factor, clamped to the range [0, 1]. - * @return The interpolated value. - */ - public static float smoothStep(float from, float to, float t) { - t = clamp01(t); - t = -2f * t * t * t + 3f * t * t; - return to * t + from * (1f - t); - } - - /** - * Returns a random float value between the specified minimum and maximum - * values, inclusive. - * - * @param min The minimum value. - * @param max The maximum value. - * @return A random float value between min and max, inclusive. - */ - public static float random(float min, float max) { - return random.nextFloat() * (max - min) + min; - } - - /** - * Returns a random integer value between the specified minimum and maximum - * values, inclusive. - * - * @param min The minimum value. - * @param max The maximum value. - * @return A random integer value between min and max, inclusive. - */ - public static int random(int min, int max) { - return random.nextInt(max - min + 1) + min; - } - - /** - * Sets the seed for the random number generator. This allows for reproducible - * random sequences. - * - * @param seed The seed value. - */ - public static void setSeed(long seed) { - random.setSeed(seed); - } - - /** - * Returns a random float value between 0.0 (inclusive) and 1.0 (exclusive). - * - * This method uses a random number generator with a specified seed to ensure - * reproducibility. - * - * @return A random float value between 0.0 (inclusive) and 1.0 (exclusive). - */ - public static float randomFloat() { - return random.nextFloat(); - } - - /** - * Calculates a smooth, oscillating value between 0 and `length` over time - * `t`. - * - * This function is commonly used in game development to create various - * effects, such as character movement, object animations, camera effects, and - * particle systems. - * - * The function works by repeating the input time `t` over an interval of - * `length * 2`, and then calculating the distance between the repeated time - * and the midpoint `length`. This distance is then subtracted from `length` - * to produce the final oscillating value. - * - * @param t The input time. - * @param length The desired range of oscillation. - * @return The calculated oscillating value. - */ - public static float pingPong(float t, float length) { - t = repeat(t, length * 2f); - return length - abs(t - length); - } - - /** - * Normalizes an angle to a specific range centered around a given center - * angle. - * - * This method ensures that the returned angle is within a specific range, - * typically between -π and π or 0 and 2π. - * - * @param a The angle to be normalized. - * @param center The center angle of the desired range. - * @return The normalized angle. - */ - public static float normalizeAngle(float a, float center) { - return a - TWO_PI * floor((a + PI - center) / TWO_PI); - } - - /** - * Wraps a value cyclically within a specified range. - * - * This method takes a value `t` and maps it to a value within the interval - * [0, length). The value is repeatedly decreased by `length` until it becomes - * less than `length`. This creates a cyclic effect, where the value - * continuously cycles from 0 to `length` and then back to 0. - * - * **Example:** For `t = 12` and `length = 5`, the result is: - `floor(12 / 5) - * = 2` (number of full cycles) - `2 * 5 = 10` (value exceeding the range) - - * `12 - 10 = 2` (the returned value) - * - * @param t The value to be wrapped. - * @param length The length of the interval within which the value is wrapped. - * @return The wrapped value within the interval [0, length). - */ - public static float repeat(float t, float length) { - return t - floor(t / length) * length; - } - - /** - * Determines if two floating-point numbers are approximately equal. - * - * This method compares two floating-point numbers, `a` and `b`, considering - * the limited precision of floating-point numbers. It accounts for both - * relative and absolute tolerances to provide a robust comparison method. - * - * **How it works:** 1. **Calculates absolute difference:** The absolute - * difference between `a` and `b` is calculated. 2. **Determines relative - * tolerance:** The larger of the two absolute values of `a` and `b` is - * multiplied by a small factor (e.g., 1e-6) to obtain a relative tolerance. - * 3. **Determines absolute tolerance:** A small fixed value (e.g., - * `FLT_EPSILON * 8`) is set as the absolute tolerance. 4. **Comparison:** The - * absolute difference is compared with the larger of the two tolerances. If - * the difference is smaller, the numbers are considered approximately equal. - * - * **Why such a method is necessary:** Due to the limited precision of - * floating-point numbers, small rounding errors can occur, causing two - * mathematically equal values to not be represented exactly equal in a - * computer. This method allows ignoring such small differences. - * - * @param a The first floating-point number. - * @param b The second floating-point number. - * @return `true` if `a` and `b` are approximately equal, otherwise `false`. - */ - public static boolean approximately(float a, float b) { - return abs(b - a) < max(1E-06f * max(abs(a), abs(b)), FLT_EPSILON * 8f); - } - - /** - * Clamps the given float value to be between 0 and 1. This method is - * equivalent to {@link #clamp01(float)}. - * - * @param a The value to clamp. - * @return A clamped between 0 and 1. - * @see #clamp01(float) - */ - public static float saturate(float a) { - return clamp(a, 0f, 1f); - } - - /** - * Returns the next power of two of the given value. - * - * E.g. for a value of 100, this returns 128. Returns 1 for all numbers <= 1. - * - * @param value The number to obtain the power of two for. - * @return The closest power of two. - */ - public static int closestPowerOfTwo(int value) { - value--; - value |= value >> 1; - value |= value >> 2; - value |= value >> 4; - value |= value >> 8; - value |= value >> 16; - value++; - value += (value == 0) ? 1 : 0; - return value; - } - - /** - * Maps a value from one range to another using linear interpolation. - * - * @param value The value to be mapped. - * @param from0 The lower bound of the input range. - * @param to0 The upper bound of the input range. - * @param from1 The lower bound of the output range. - * @param to1 The upper bound of the output range. - * @return The mapped value. - * - * @throws IllegalArgumentException if `from0 == to0` or `from1 == to1`. - */ - public static float map(float value, float from0, float to0, float from1, - float to1) { - if (from0 == to0 || from1 == to1) { - throw new IllegalArgumentException("Invalid input ranges"); - } - - float result = from1 + (to1 - from1) * ((value - from0) / (to0 - from0)); - - if (Float.isNaN(result)) { - throw new IllegalArgumentException("Result is NaN"); - } else if (Float.isInfinite(result)) { - throw new IllegalArgumentException("Result is infinite"); - } - - return result; - } - - /** - * Calculates the floating-point remainder of dividing two values. This method - * works similarly to the fmod function in other programming languages. - * - * @param a the dividend - * @param b the divisor - * @return the remainder when a is divided by b - */ - public static float fmod(float a, float b) { - return (float) (a - b * Math.floor(a / b)); - } - - /** - * Normalizes the input angle to the range [0, 2π] in radians. - * - * Small values close to zero (less than 1e-6) are snapped to zero to handle - * floating-point precision issues. - * - * @param angle The input angle in radians. - * @return The normalized angle in the range [0, 2π]. - */ - public static float normalizeAngle(float angle) { - float smallAngleThreshold = 1e-6f; - - angle = angle % (2 * Mathf.PI); - - if (Mathf.abs(angle) < smallAngleThreshold) { - angle = 0; - } - - if (angle < 0) { - angle += 2 * Mathf.PI; - } - - return angle; - } + /** A random number generator used to generate random values. */ + private static Random random = new Random(); + /** A float representation of the golden ratio, approximately 1.618. */ + public static final float GOLDEN_RATIO = (1 + sqrt(5)) / 2.0f; + + /** + * A float representation of the reciprocal of the golden ratio, , which is exactly 1 less than + * the golden ratio itself; approximately 0.618. + */ + public static final float GOLDEN_RATIO_RECIPROCAL = 2 / (1 + sqrt(5)); + + /** Euler's number, the base of the natural logarithm, approximately 2.718. */ + public static final float E = (float) Math.E; + + /** A representation of negative infinity. */ + public static final float NEGATIVE_INFINITY = Float.NEGATIVE_INFINITY; + + /** A representation of positive infinity. */ + public static final float POSITIVE_INFINITY = Float.POSITIVE_INFINITY; + + /** The smallest positive nonzero value representable as a float. */ + public static final float MIN_VALUE = Float.MIN_VALUE; + + /** The largest finite value representable as a float. */ + public static final float MAX_VALUE = Float.MAX_VALUE; + + /** A small value used for floating-point comparisons, approximately 2.22E-16. */ + public static final double DBL_EPSILON = 2.220446049250313E-16d; + + /** A small value used for floating-point comparisons, approximately 1.19E-7. */ + public static final float FLT_EPSILON = 1.1920928955078125E-7f; + + /** A small tolerance value for comparing floating-point numbers. */ + public static final float ZERO_TOLERANCE = 0.0001f; + + /** A float representation of one-third, approximately 0.33333334. */ + public static final float ONE_THIRD = 1f / 3f; + + /** The value of Pi, approximately 3.14159. */ + public static final float PI = (float) Math.PI; + + /** Twice the value of Pi, approximately 6.283185. */ + public static final float TWO_PI = 2.0f * PI; + + /** Half the value of Pi, approximately 1.570796. */ + public static final float HALF_PI = 0.5f * PI; + + /** A quarter of the value of Pi, approximately 0.785398. */ + public static final float QUARTER_PI = 0.25f * PI; + + /** The reciprocal of Pi, approximately 0.3183099. */ + public static final float INV_PI = 1.0f / PI; + + /** The reciprocal of two times Pi, approximately 0.1591549. */ + public static final float INV_TWO_PI = 1.0f / TWO_PI; + + /** A factor to convert degrees to radians, approximately 0.0174533. */ + public static final float DEG_TO_RAD = PI / 180.0f; + + /** A factor to convert radians to degrees, approximately 57.29578. */ + public static final float RAD_TO_DEG = 180.0f / PI; + + /** + * The Tribonacci constant, often denoted as t, is the real root of the cubic equation x³ - x² - x + * - 1 = 0. It is approximately equal to 1.83928675521416. + */ + public static final float TRIBONACCI_CONSTANT = 1.83928675521416f; + + /** + * Converts a 2D index (row, column) into a 1D index for a matrix or array. + * + *

    This method is useful when working with matrices or arrays that are stored in a 1D array. It + * calculates the 1D index corresponding to the specified row and column in a matrix with the + * given number of columns. + * + * @param rowIndex The zero-based index of the row. + * @param colIndex The zero-based index of the column. + * @param numberOfColumns The total number of columns in the matrix. + * @return The 1D index corresponding to the given row and column. + * @throws IllegalArgumentException if `rowIndex` or `colIndex` is negative, or if + * `numberOfColumns` is less than or equal to zero. + */ + public static int toOneDimensionalIndex(int rowIndex, int colIndex, int numberOfColumns) { + if (rowIndex < 0 || colIndex < 0) throw new IllegalArgumentException(); + + if (numberOfColumns <= 0) throw new IllegalArgumentException(); + + return rowIndex * numberOfColumns + colIndex; + } + + /** + * Returns the smaller of two int values. That is, the result is the argument closer to {@link + * Integer#MIN_VALUE}. If the arguments have the same value, the result is that same value. + * + * @param a The first integer. + * @param b The second integer. + * @return The smaller of `a` and `b`. + */ + public static int min(int a, int b) { + return Math.min(a, b); + } + + /** + * Returns the larger of two int values. That is, the result is the argument closer to {@link + * Integer#MAX_VALUE}. If the arguments have the same value, the result is that same value. + * + * @param a The first integer. + * @param b The second integer. + * @return The larger of `a` and `b`. + */ + public static int max(int a, int b) { + return Math.max(a, b); + } + + /** + * Returns the minimum value in the given array. + * + * @param values The array of integers. + * @return The minimum value in the array, or 0 if the array is empty. + */ + public static int min(int[] values) { + if (values.length == 0) return 0; + + int min = values[0]; + for (int i = 1; i < values.length; i++) min = Math.min(min, values[i]); + return min; + } + + /** + * Returns the maximum value in the given array. + * + * @param values The array of integers. + * @return The maximum value in the array, or 0 if the array is empty. + */ + public static int max(int[] values) { + if (values.length == 0) return 0; + + int max = values[0]; + for (int i = 1; i < values.length; i++) max = Math.max(max, values[i]); + return max; + } + + /** + * Returns the larger of the two given float values. + * + * @param a The first float value. + * @param b The second float value. + * @return The larger of `a` and `b`. + */ + public static float max(float a, float b) { + return Math.max(a, b); + } + + /** + * Returns the smaller of the two given float values. + * + * @param a The first float value. + * @param b The second float value. + * @return The smaller of `a` and `b`. + */ + public static float min(float a, float b) { + return Math.min(a, b); + } + + /** + * Returns the maximum float value in the given array. + * + * @param values The array of float values. + * @return The maximum value in the array, or {@link Float#NaN} if the array is empty. + */ + public static float max(float... values) { + if (values.length == 0) return Float.NaN; + + float max = values[0]; + for (int i = 1; i < values.length; i++) max = Math.max(max, values[i]); + return max; + } + + /** + * Returns the minimum float value in the given array. + * + * @param values The array of float values. + * @return The minimum value in the array, or {@link Float#NaN} if the array is empty. + */ + public static float min(float... values) { + if (values.length == 0) return Float.NaN; + + float min = values[0]; + for (int i = 1; i < values.length; i++) min = Math.min(min, values[i]); + return min; + } + + /** + * Rounds a float value to the nearest integer, rounding ties towards positive infinity. + * + * @param a The float value to be rounded. + * @return The rounded integer value. + */ + public static int roundToInt(float a) { + return Math.round(a); + } + + /** + * Rounds a float value to the nearest integer. + * + *

    This method rounds the given float value to the nearest integer. If the fractional part is + * 0.5 or greater, the value is rounded up. Otherwise, it is rounded down. + * + * @param a The float value to be rounded. + * @return The rounded float value. + */ + public static float round(float a) { + return Math.round(a); + } + + /** + * Clamps a value between a minimum and maximum value. + * + * @param a The value to clamp. + * @param min The minimum value. + * @param max The maximum value- + * @return The clamped value. + */ + public static float clamp(float a, float min, float max) { + return Math.max(min, Math.min(max, a)); + } + + /** + * Clamps a between min and max and returns the clamped value. + * + * @param a The value to clamp + * @param min The minimum for a. + * @param max The maximum for a. + * @return The clamped value. + */ + public static int clampInt(int a, int min, int max) { + return a < min ? min : (a > max ? max : a); + } + + /** + * Clamps the given float value to be between 0 and 1. This method is equivalent to {@link + * #saturate(float)}. + * + * @param a The value to clamp. + * @return A clamped value between 0 and 1- + * @see #saturate(float) + */ + public static float clamp01(float a) { + return clamp(a, 0f, 1f); + } + + /** + * Converts an angle measured in degrees to an approximately equivalent angle measured in radians. + * The conversion from degrees to radians is generally inexact; users should not expect + * cos(toRadians(90.0)) to exactly equal 0.0. + * + * @param angdeg The angle, in degrees. + * @return The angle in radians. + */ + public static float toRadians(float angdeg) { + return (float) Math.toRadians((double) angdeg); + } + + /** + * Converts an angle measured in radians to an approximately equivalent angle measured in degrees. + * The conversion from radians to degreees is generally inexact; users should not expect + * cos(toRadians(90.0)) to exactlyequal 0.0. + * + * @param angrad The angle, in radians. + * @return The angle in degrees. + */ + public static float toDegrees(float angrad) { + return (float) Math.toDegrees((double) angrad); + } + + /** + * Returns a hash code for a float value; compatible with Float.hashCode(). + * + * @param value The value to hash. + * @return A hash code value for a float value. + */ + public static int hashCode(float value) { + return Float.hashCode(value); + } + + /** + * Returns the absolute value of a. + * + * @param a The argument whose absolute value is to be determined. + * @return The absolute value of the argument. + */ + public static float abs(float a) { + return Math.abs(a); + } + + /** + * Returns the trigonometric tangent of an angle. Special cases: + * + *

      + *
    • If the argument is NaN or an infinity, then the result is NaN. + *
    • If the argument is zero, then the result is a zero with the same sign as the argument. + * The computed result must be within 1 ulp of the exact result. Results must be + * semi-monotonic. + * + * @param a An angle, in radians. + * @return The tangent of the argument. + */ + public static float tan(float a) { + return (float) Math.tan((double) a); + } + + /** + * Returns the trigonometric cosine of an angle. + * + *
        + *
      • Special cases: If the argument is NaN or an infinity, then the result is NaN. + *
      + * + * @param a An angle, in radians. + * @return The cosine of the argument. + */ + public static float cos(float a) { + return (float) Math.cos((double) a); + } + + /** + * Returns the trigonometric sine of an angle. Special cases: + * + *
        + *
      • If the argument is NaN or an infinity, then the result is NaN. + *
      • If the argument is zero, then the result is a zero with the same sign as the argument. + *
      + * + * The computed result must be within 1 ulp of the exact result. Results must be semi-monotonic. + * + * @param a An angle, in radians. + * @return The sine of the argument. + */ + public static float sin(float a) { + return (float) Math.sin((double) a); + } + + /** + * Determines whether or not the given value a is in range of min and max. + * + * @param a The value to check. + * @param min The minimum value for a. + * @param max The maximum value for a. + * @return true if the value is in range of [min,max], false otherwise. + */ + public static boolean isInRange(float a, int min, int max) { + return a >= min && a <= max; + } + + /** + * Returns the signum function of the argument; zero if the argument is zero, 1.0f if the argument + * is greater than zero, -1.0f if the argument is less than zero. Special Cases: + * + *
        + *
      • If the argument is NaN, then the result is NaN. + *
      • If the argument is positive zero or negative zero, then the result is the same as the + * argument. + *
      + * + * @param a The floating-point value whose signum is to be returned. + * @return The signum function of the argument. + */ + public static float sign(float a) { + return Math.signum(a); + } + + /** + * Returns the correctly rounded positive square root of a float value. Special cases: + * + *
        + *
      • If the argument is NaN or less than zero, then the result is NaN. + *
      • If the argument is positive infinity, then the result is positive infinity. + *
      • If the argument is positive zero or negative zero, then the result is the same as the + * argument.
        + * Otherwise, the result is the double value closest to the true mathematical square root of + * the argument value. + *
      + * + * @param a A value. + * @return The positive square root of a. If the argument is NaN or less than zero, the result is + * NaN. + */ + public static float sqrt(float a) { + return (float) Math.sqrt((double) a); + } + + /** + * Returns the largest (closest to positive infinity) float value that is less than or equal to + * the argument and is equal to a mathematical integer. Special cases: + * + *
        + *
      • If the argument value is already equal to a mathematical integer, then the result is the + * same as the argument. + *
      • If the argument is NaN or an infinity or positive zero or negative zero, then the result + * is the same as the argument. + *
      + * + * @param a A value. + * @return The largest (closest to positive infinity) floating-point value that less than or equal + * to the argument and is equal to a mathematical integer. + */ + public static float floor(float a) { + return (float) Math.floor((double) a); + } + + /** + * Returns Euler's number e raised to the power of a float value. Special cases: + * + *
        + *
      • If the argument is NaN, the result is NaN. + *
      • If the argument is positive infinity, then the result is positive infinity. + *
      • If the argument is negative infinity, then the result is positive zero. + *
      + * + * The computed result must be within 1 ulp of the exact result. Results must be semi-monotonic. + * + * @param a The exponent to raise e to. + * @return The value ea, where e is the base of the natural logarithms. + */ + public static float exp(float a) { + return (float) Math.exp((double) a); + } + + /** + * Returns true if the argument is a finite floating-point value; returns false otherwise (for NaN + * and infinity arguments). + * + * @param f The float value to be tested. + * @return true if the argument is a finite floating-point value, false otherwise. + */ + public static boolean isFinite(float f) { + return Float.isFinite(f); + } + + /** + * Returns true if the specified number is infinitely large in magnitude, false otherwise. + * + * @param v The value to be tested. + * @return true if the argument is positive infinity or negative infinity; false otherwise. + */ + public static boolean isInfinite(float v) { + return Float.isInfinite(v); + } + + /** + * Returns true if the specified number is a Not-a-Number (NaN) value, false otherwise. + * + * @param v The value to be tested. + * @return true if the argument is NaN; false otherwise. + */ + public static boolean isNaN(float v) { + return Float.isNaN(v); + } + + /** + * Returns the arc cosine of a (the angle in radians whose cosine is a). + * + * @param a The value whose arc cosine is to be returned. + * @return The arc cosine of a. + */ + public static float acos(float a) { + return (float) Math.acos((double) a); + } + + /** + * Returns the arc-sine of a - the angle in radians whose sine is a. + * + * @param a The value whose arc sine is to be returned. + * @return The arc sine of the argument. + */ + public static float asin(float a) { + return (float) Math.asin((double) a); + } + + /** + * Returns the arc tangent of a (the angle in radians whose tangent is a). + * + * @param a The value whose arc tangent is to be returned. + * @return The arc tangent of the argument. + */ + public static float atan(float a) { + return (float) Math.atan((double) a); + } + + /** + * Returns the angle in radians whose Tan is y/x. + * + *

      Return value is the angle between the x-axis and a 2D vector starting at zero and + * terminating at (x,y). + * + * @param y The ordinate coordinate. + * @param x The abscissa coordinate. + * @return The theta component of the point (r, theta) in polar coordinates that corresponds to + * the point (x, y) in Cartesian coordinates. + */ + public static float atan2(float y, float x) { + return (float) Math.atan2((double) y, (double) x); + } + + /** + * Returns the smallest mathematical integer greater to or equal to a. + * + * @param a value + * @return The smallest (closest to negative infinity) floating-point value that is greater than + * or equal to the argument and is equal to a mathematical integer. + */ + public static float ceil(float a) { + return (float) Math.ceil((double) a); + } + + /** + * Returns the value of the first argument raised to the power of the second argument. + * + * @param a The base. + * @param b The exponent. + * @return The base raised to the power of b. + * @see Math#pow(double, double) + */ + public static float pow(float a, float b) { + return (float) Math.pow((double) a, (double) b); + } + + /** + * Returns the base 10 logarithm of a double value. Special cases: + * + *

        + *
      • If the argument is NaN or less than zero, then the result is NaN. + *
      • If the argument is positive infinity, then the result is positive infinity. + *
      • If the argument is positive zero or negative zero, then the result is negative infinity. + *
      + * + * @param a A value. + * @return The base 10 logarithm of a. + */ + public static float log10(float a) { + return (float) Math.log10((double) a); + } + + /** + * Returns the natural logarithm (base e) of a float value. Special cases: + * + *
        + *
      • If the argument is NaN or less than zero, then the result is NaN. + *
      • If the argument is positive infinity, then the result is positive infinity. + *
      • If the argument is positive zero or negative zero, then the result is negative infinity. + *
      + * + * The computed result must be within 1 ulp of the exact result. Results must be semi-monotonic. + * + * @param a A value. + * @return The value ln a, the natural logarithm of a. + */ + public static float log(float a) { + return (float) Math.log((double) a); + } + + /** + * Returns the largest (closest to positive infinity) integer value that is less than or equal to + * the argument. + * + *
        + *
      • If the argument value is already equal to a mathematical integer, then the result is the + * same as the argument. + *
      • If the argument is NaN or an infinity or positive zero or negative zero, then the result + * is the same as the argument. + *
      + * + * @param a A value. + * @return The largest (closest to positive infinity) integer value that is less than or equal to + * the argument. + */ + public static int floorToInt(float a) { + return (int) Math.floor((double) a); + } + + /** + * Returns the smallest mathematical integer greater to or equal to a. + * + * @param a The value to ceil. + * @return The smallest (closest to negative infinity) integer value that is greater than or equal + * to the argument and is equal to a mathematical integer. + */ + public static int ceilToInt(float a) { + return (int) Math.ceil((double) a); + } + + /** + * Linearly interpolates between a and b by t. The parameter t is not clamped and values outside + * the range [0, 1] will result in a return value outside the range [a, /b/]. + * + *
      +   * When t = 0 returns a.
      +   * When t = 1 returns b.
      +   * When t = 0.5 returns the midpoint of a and b.
      +   * 
      + * + * @param a The value to interpolate from. + * @param b The value to interpolate to. + * @param t The value to interpolate by. + * @return The resultant interpolated value. + */ + public static float lerpUnclamped(float a, float b, float t) { + return a + (b - a) * t; + } + + /** + * Linearly interpolates between from and to by t. The parameter t is clamped to the range [0, 1]. + * + *
      +   * When t = 0 returns a.
      +   * When t = 1 return b.
      +   * When t = 0.5 returns the midpoint of a and b.
      +   * 
      + * + * @param from The value to interpolate from. + * @param to The value to interpolate to. + * @param t The value to interpolate by. + * @return The resultant interpolated value. + */ + public static float lerp(float from, float to, float t) { + return from + (to - from) * clamp01(t); + } + + /** + * Returns the next power of two greater than or equal to the given value. + * + *

      For example: + * + *

        + *
      • nextPowerOfTwo(1) returns 2 + *
      • nextPowerOfTwo(2) returns 2 + *
      • nextPowerOfTwo(3) returns 4 + *
      • nextPowerOfTwo(16) returns 16 + *
      • nextPowerOfTwo(17) returns 32 + *
      + * + * @param value the input value + * @return the next power of two greater than or equal to the input value + */ + public static int nextPowerOfTwo(int value) { + return value > 0 ? Integer.highestOneBit(value - 1) << 1 : 1; + } + + /** + * Smoothly interpolates between two values. This function provides a smoother transition between + * the two values compared to linear interpolation. It uses a cubic Hermite spline to achieve a + * smooth curve. + * + * @param from The starting value. + * @param to The ending value. + * @param t The interpolation factor, clamped to the range [0, 1]. + * @return The interpolated value. + */ + public static float smoothStep(float from, float to, float t) { + t = clamp01(t); + t = -2f * t * t * t + 3f * t * t; + return to * t + from * (1f - t); + } + + /** + * Returns a random float value between the specified minimum and maximum values, inclusive. + * + * @param min The minimum value. + * @param max The maximum value. + * @return A random float value between min and max, inclusive. + */ + public static float random(float min, float max) { + return random.nextFloat() * (max - min) + min; + } + + /** + * Returns a random integer value between the specified minimum and maximum values, inclusive. + * + * @param min The minimum value. + * @param max The maximum value. + * @return A random integer value between min and max, inclusive. + */ + public static int random(int min, int max) { + return random.nextInt(max - min + 1) + min; + } + + /** + * Sets the seed for the random number generator. This allows for reproducible random sequences. + * + * @param seed The seed value. + */ + public static void setSeed(long seed) { + random.setSeed(seed); + } + + /** + * Returns a random float value between 0.0 (inclusive) and 1.0 (exclusive). + * + *

      This method uses a random number generator with a specified seed to ensure reproducibility. + * + * @return A random float value between 0.0 (inclusive) and 1.0 (exclusive). + */ + public static float randomFloat() { + return random.nextFloat(); + } + + /** + * Calculates a smooth, oscillating value between 0 and `length` over time `t`. + * + *

      This function is commonly used in game development to create various effects, such as + * character movement, object animations, camera effects, and particle systems. + * + *

      The function works by repeating the input time `t` over an interval of `length * 2`, and + * then calculating the distance between the repeated time and the midpoint `length`. This + * distance is then subtracted from `length` to produce the final oscillating value. + * + * @param t The input time. + * @param length The desired range of oscillation. + * @return The calculated oscillating value. + */ + public static float pingPong(float t, float length) { + t = repeat(t, length * 2f); + return length - abs(t - length); + } + + /** + * Normalizes an angle to a specific range centered around a given center angle. + * + *

      This method ensures that the returned angle is within a specific range, typically between -π + * and π or 0 and 2π. + * + * @param a The angle to be normalized. + * @param center The center angle of the desired range. + * @return The normalized angle. + */ + public static float normalizeAngle(float a, float center) { + return a - TWO_PI * floor((a + PI - center) / TWO_PI); + } + + /** + * Wraps a value cyclically within a specified range. + * + *

      This method takes a value `t` and maps it to a value within the interval [0, length). The + * value is repeatedly decreased by `length` until it becomes less than `length`. This creates a + * cyclic effect, where the value continuously cycles from 0 to `length` and then back to 0. + * + *

      **Example:** For `t = 12` and `length = 5`, the result is: - `floor(12 / 5) = 2` (number of + * full cycles) - `2 * 5 = 10` (value exceeding the range) - `12 - 10 = 2` (the returned value) + * + * @param t The value to be wrapped. + * @param length The length of the interval within which the value is wrapped. + * @return The wrapped value within the interval [0, length). + */ + public static float repeat(float t, float length) { + return t - floor(t / length) * length; + } + + /** + * Determines if two floating-point numbers are approximately equal. + * + *

      This method compares two floating-point numbers, `a` and `b`, considering the limited + * precision of floating-point numbers. It accounts for both relative and absolute tolerances to + * provide a robust comparison method. + * + *

      **How it works:** 1. **Calculates absolute difference:** The absolute difference between `a` + * and `b` is calculated. 2. **Determines relative tolerance:** The larger of the two absolute + * values of `a` and `b` is multiplied by a small factor (e.g., 1e-6) to obtain a relative + * tolerance. 3. **Determines absolute tolerance:** A small fixed value (e.g., `FLT_EPSILON * 8`) + * is set as the absolute tolerance. 4. **Comparison:** The absolute difference is compared with + * the larger of the two tolerances. If the difference is smaller, the numbers are considered + * approximately equal. + * + *

      **Why such a method is necessary:** Due to the limited precision of floating-point numbers, + * small rounding errors can occur, causing two mathematically equal values to not be represented + * exactly equal in a computer. This method allows ignoring such small differences. + * + * @param a The first floating-point number. + * @param b The second floating-point number. + * @return `true` if `a` and `b` are approximately equal, otherwise `false`. + */ + public static boolean approximately(float a, float b) { + return abs(b - a) < max(1E-06f * max(abs(a), abs(b)), FLT_EPSILON * 8f); + } + + /** + * Clamps the given float value to be between 0 and 1. This method is equivalent to {@link + * #clamp01(float)}. + * + * @param a The value to clamp. + * @return A clamped between 0 and 1. + * @see #clamp01(float) + */ + public static float saturate(float a) { + return clamp(a, 0f, 1f); + } + + /** + * Returns the next power of two of the given value. + * + *

      E.g. for a value of 100, this returns 128. Returns 1 for all numbers <= 1. + * + * @param value The number to obtain the power of two for. + * @return The closest power of two. + */ + public static int closestPowerOfTwo(int value) { + value--; + value |= value >> 1; + value |= value >> 2; + value |= value >> 4; + value |= value >> 8; + value |= value >> 16; + value++; + value += (value == 0) ? 1 : 0; + return value; + } + + /** + * Maps a value from one range to another using linear interpolation. + * + * @param value The value to be mapped. + * @param from0 The lower bound of the input range. + * @param to0 The upper bound of the input range. + * @param from1 The lower bound of the output range. + * @param to1 The upper bound of the output range. + * @return The mapped value. + * @throws IllegalArgumentException if `from0 == to0` or `from1 == to1`. + */ + public static float map(float value, float from0, float to0, float from1, float to1) { + if (from0 == to0 || from1 == to1) { + throw new IllegalArgumentException("Invalid input ranges"); + } + + float result = from1 + (to1 - from1) * ((value - from0) / (to0 - from0)); + + if (Float.isNaN(result)) { + throw new IllegalArgumentException("Result is NaN"); + } else if (Float.isInfinite(result)) { + throw new IllegalArgumentException("Result is infinite"); + } + + return result; + } + + /** + * Calculates the floating-point remainder of dividing two values. This method works similarly to + * the fmod function in other programming languages. + * + * @param a the dividend + * @param b the divisor + * @return the remainder when a is divided by b + */ + public static float fmod(float a, float b) { + return (float) (a - b * Math.floor(a / b)); + } + + /** + * Normalizes the input angle to the range [0, 2Ï€] in radians. + * + *

      Small values close to zero (less than 1e-6) are snapped to zero to handle floating-point + * precision issues. + * + * @param angle The input angle in radians. + * @return The normalized angle in the range [0, 2Ï€]. + */ + public static float normalizeAngle(float angle) { + float smallAngleThreshold = 1e-6f; + + angle = angle % (2 * Mathf.PI); + + if (Mathf.abs(angle) < smallAngleThreshold) { + angle = 0; + } + + if (angle < 0) { + angle += 2 * Mathf.PI; + } + + return angle; + } } diff --git a/src/main/java/math/Matrix4f.java b/src/main/java/math/Matrix4f.java index f148193e..2494300d 100644 --- a/src/main/java/math/Matrix4f.java +++ b/src/main/java/math/Matrix4f.java @@ -4,188 +4,378 @@ public class Matrix4f { - 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: - *

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

      - * - * @param eye the position of the camera in world space, represented as a - * {@link Vector3f}. - * @param pitch the pitch angle (rotation around the X-axis), in radians. A - * positive value tilts the camera upward. - * @param yaw the yaw angle (rotation around the Y-axis), in radians. A - * positive value rotates the camera to the right. - * @return a {@link Matrix4f} representing the view matrix for the specified - * position and orientation. - * - * @see - * Understanding the View Matrix - */ - public static Matrix4f fpsViewRH(Vector3f eye, float pitch, float yaw) { - float cosPitch = Mathf.cos(pitch); - float sinPitch = Mathf.sin(pitch); - float cosYaw = Mathf.cos(yaw); - float sinYaw = Mathf.sin(yaw); - - Vector3f right = new Vector3f(cosYaw, 0, -sinYaw); - Vector3f up = new Vector3f(sinYaw * sinPitch, cosPitch, cosYaw * sinPitch); - Vector3f forward = new Vector3f(sinYaw * cosPitch, -sinPitch, - cosPitch * cosYaw); - - Matrix4f viewMatrix = new Matrix4f(right.x, up.x, forward.x, 0, right.y, - up.y, forward.y, 0, right.z, up.z, forward.z, 0, -right.dot(eye), - -up.dot(eye), -forward.dot(eye), 1); - - return viewMatrix; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder("Matrix4f:\n"); - for (int row = 0; row < 4; row++) { - sb.append("[ "); - for (int col = 0; col < 4; col++) { - sb.append(get(row, col)).append(" "); - } - sb.append("]\n"); - } - return sb.toString(); - } - -} \ No newline at end of file + 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); + } + + public Matrix4f invert() { + float[] inv = new float[16]; + float det; + + // Calculate the determinant of the 4x4 matrix and compute its inverse + inv[0] = + values[5] * values[10] * values[15] + - values[5] * values[11] * values[14] + - values[9] * values[6] * values[15] + + values[9] * values[7] * values[14] + + values[13] * values[6] * values[11] + - values[13] * values[7] * values[10]; + + if (Math.abs(inv[0]) < 1e-6) { + throw new IllegalStateException("Matrix is singular and cannot be inverted."); + } + + det = 1.0f / inv[0]; + + inv[4] = + -(values[4] * values[10] * values[15] + - values[4] * values[11] * values[14] + - values[8] * values[6] * values[15] + + values[8] * values[7] * values[14] + + values[12] * values[6] * values[11] + - values[12] * values[7] * values[10]); + + inv[8] = + values[0] * values[11] * values[14] + - values[0] * values[10] * values[15] + - values[2] * values[7] * values[15] + + values[2] * values[5] * values[15] + + values[3] * values[6] * values[11] + - values[3] * values[5] * values[14]; + + inv[12] = + -(values[0] * values[9] * values[14] + - values[0] * values[11] * values[13] + - values[2] * values[8] * values[15] + + values[2] * values[12] * values[13] + + values[3] * values[8] * values[13] + - values[3] * values[9] * values[12]); + + // Add the remaining matrix inversion logic similarly + + return new Matrix4f(inv); + } + + /** + * Constructs a view matrix based on the camera's position, look direction, and an up vector. + * + *

      This method assumes a right-handed coordinate system where: + * + *

        + *
      • +X points to the right + *
      • +Y points up + *
      • +Z points forward (out of the screen) + *
      + * + * The look direction vector should be a normalized vector representing the direction the camera + * is facing. The up vector defines the vertical direction for the camera. + * + * @param eye The position of the camera in world space, represented as a {@link Vector3f}. + * @param look The normalized direction vector of the camera, represented as a {@link Vector3f}. + * @param up The up vector defining the camera's vertical direction, represented as a {@link + * Vector3f}. This vector should be orthogonal to the look direction vector. + * @return A {@link Matrix4f} representing the view matrix for the specified camera configuration. + */ + // public Matrix4f lookAt(Vector3f eye, Vector3f look, Vector3f up) { + // // Calculate the right vector (orthogonal to look and up) + // Vector3f right = look.cross(up).normalizeLocal(); + // // Calculate the orthogonal up vector (orthogonal to look and right) + // up = right.cross(look).normalizeLocal(); + // + // float[][] view = new float[4][4]; + // view[0][0] = right.x; + // view[0][1] = up.x; + // view[0][2] = look.x; + // view[0][3] = -eye.dot(right); + // view[1][0] = right.y; + // view[1][1] = up.y; + // view[1][2] = look.y; + // view[1][3] = -eye.dot(up); + // view[2][0] = right.z; + // view[2][1] = up.z; + // view[2][2] = look.z; + // view[2][3] = -eye.dot(look); + // view[3][0] = 0.0f; + // view[3][1] = 0.0f; + // view[3][2] = 0.0f; + // view[3][3] = 1.0f; + // + // // Convert view matrix to a Matrix4f object + // for (int row = 0; row < 4; row++) { + // for (int col = 0; col < 4; col++) { + // this.values[row * 4 + col] = view[row][col]; + // } + // } + // return this; + // } + + public static Matrix4f lookAt(Vector3f eye, Vector3f target, Vector3f up) { + Vector3f forward = target.subtract(eye).normalize(); + Vector3f right = forward.cross(up).normalize(); + Vector3f cameraUp = right.cross(forward); + + Matrix4f result = new Matrix4f(); + result.set(0, 0, right.x); + result.set(0, 1, right.y); + result.set(0, 2, right.z); + result.set(0, 3, -right.dot(eye)); + + result.set(1, 0, cameraUp.x); + result.set(1, 1, cameraUp.y); + result.set(1, 2, cameraUp.z); + result.set(1, 3, -cameraUp.dot(eye)); + + result.set(2, 0, -forward.x); + result.set(2, 1, -forward.y); + result.set(2, 2, -forward.z); + result.set(2, 3, forward.dot(eye)); + + result.set(3, 0, 0); + result.set(3, 1, 0); + result.set(3, 2, 0); + result.set(3, 3, 1); + + return result; + } + + /** + * 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 with -Y as up. + // *

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

        + // *
      • +X points to the right
      • + // *
      • -Y points up
      • + // *
      • +Z points backward (into the screen)
      • + // *
      + // *

      + // * + // * @param eye the position of the camera in world space, represented as a + // * {@link Vector3f}. + // * @param pitch the pitch angle (rotation around the X-axis), in radians. A + // * positive value tilts the camera downward (due to -Y up). + // * @param yaw the yaw angle (rotation around the Y-axis), in radians. A + // * positive value rotates the camera to the right. + // * @return a {@link Matrix4f} representing the view matrix for the specified + // * position and orientation. + // */ + // public static Matrix4f fpsViewRH(Vector3f eye, float pitch, float yaw) { + // float cosPitch = Mathf.cos(pitch); + // float sinPitch = Mathf.sin(pitch); + // float cosYaw = Mathf.cos(yaw); + // float sinYaw = Mathf.sin(yaw); + // + // // Adjusted vectors for -Y up + // Vector3f right = new Vector3f(cosYaw, 0, -sinYaw); + // Vector3f up = new Vector3f(-sinYaw * sinPitch, -cosPitch, -cosYaw * sinPitch); + // Vector3f forward = new Vector3f(sinYaw * cosPitch, sinPitch, cosPitch * cosYaw); + // + // // Build the view matrix + // Matrix4f viewMatrix = new Matrix4f( + // right.x, up.x, forward.x, 0, + // right.y, up.y, forward.y, 0, + // right.z, up.z, forward.z, 0, + // -right.dot(eye), -up.dot(eye), -forward.dot(eye), 1 + // ); + // + // return viewMatrix; + // } + + /** + * Constructs a right-handed view matrix for an FPS (First-Person Shooter) style camera. + * + *

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

        + *
      • +X points to the right + *
      • +Y points up + *
      • +Z points backward (into the screen) + *
      + * + * This method is particularly useful for creating a camera that can move and rotate freely in a + * 3D scene, such as in games or visualization applications. + * + * @param eye the position of the camera in world space, represented as a {@link Vector3f}. + * @param pitch the pitch angle (rotation around the X-axis), in radians. A positive value tilts + * the camera upward. + * @param yaw the yaw angle (rotation around the Y-axis), in radians. A positive value rotates the + * camera to the right. + * @return a {@link Matrix4f} representing the view matrix for the specified position and + * orientation. + * @see Understanding the View + * Matrix + */ + public static Matrix4f fpsViewRH(Vector3f eye, float pitch, float yaw) { + float cosPitch = Mathf.cos(pitch); + float sinPitch = Mathf.sin(pitch); + float cosYaw = Mathf.cos(yaw); + float sinYaw = Mathf.sin(yaw); + + Vector3f right = new Vector3f(cosYaw, 0, -sinYaw); + Vector3f up = new Vector3f(sinYaw * sinPitch, cosPitch, cosYaw * sinPitch); + Vector3f forward = new Vector3f(sinYaw * cosPitch, -sinPitch, cosPitch * cosYaw); + + Matrix4f viewMatrix = + new Matrix4f( + right.x, + up.x, + forward.x, + 0, + right.y, + up.y, + forward.y, + 0, + right.z, + up.z, + forward.z, + 0, + -right.dot(eye), + -up.dot(eye), + -forward.dot(eye), + 1); + + return viewMatrix; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("Matrix4f:\n"); + for (int row = 0; row < 4; row++) { + sb.append("[ "); + for (int col = 0; col < 4; col++) { + sb.append(get(row, col)).append(" "); + } + sb.append("]\n"); + } + return sb.toString(); + } +} diff --git a/src/main/java/math/Plane.java b/src/main/java/math/Plane.java index 31000126..3dba9b2a 100644 --- a/src/main/java/math/Plane.java +++ b/src/main/java/math/Plane.java @@ -1,95 +1,89 @@ package math; /** - * Represents a geometric plane in 3D space defined by a normal vector and a - * distance from the origin. - *

      - * The equation of the plane is represented as:
      + * Represents a geometric plane in 3D space defined by a normal vector and a distance from the + * origin. + * + *

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

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

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

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

      - */ - public Plane() { - this.normal = new Vector3f(); - this.distance = 0; - } + /** + * Constructs a plane with a default normal vector (0, 0, 0) and a distance of 0. + * + *

      The resulting plane is uninitialized and must be configured using the {@link #set(float, + * float, float, float)} method. + */ + public Plane() { + this.normal = new Vector3f(); + this.distance = 0; + } - /** - * Sets the plane parameters using its coefficients. - *

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

      - * - * @param a the x-component of the normal vector. - * @param b the y-component of the normal vector. - * @param c the z-component of the normal vector. - * @param d the distance from the origin along the plane's normal vector. - */ - public void set(float a, float b, float c, float d) { - this.normal.set(a, b, c); - normal.normalizeLocal(); - this.distance = d; - } + /** + * Sets the plane parameters using its coefficients. + * + *

      The coefficients (A, B, C, D) define the plane equation Ax + By + Cz + D = 0. + * The normal vector is automatically normalized during this operation. + * + * @param a the x-component of the normal vector. + * @param b the y-component of the normal vector. + * @param c the z-component of the normal vector. + * @param d the distance from the origin along the plane's normal vector. + */ + public void set(float a, float b, float c, float d) { + this.normal.set(a, b, c); + normal.normalizeLocal(); + this.distance = d; + } - /** - * Calculates the signed distance from a given point to the plane. - *

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

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

      - * - * @param point the point to calculate the distance from. - * @return the signed distance from the point to the plane. A positive value - * indicates the point is in the direction of the normal vector, and a - * negative value indicates it is on the opposite side. - */ - public float distanceToPoint(Vector3f point) { - return normal.dot(point) + distance; - } + /** + * Calculates the signed distance from a given point to the plane. + * + *

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

        + *
      • normal is the plane's normal vector. + *
      • point is the 3D point to test. + *
      • D is the distance parameter of the plane. + *
      + * + * @param point the point to calculate the distance from. + * @return the signed distance from the point to the plane. A positive value indicates the point + * is in the direction of the normal vector, and a negative value indicates it is on the + * opposite side. + */ + public float distanceToPoint(Vector3f point) { + return normal.dot(point) + distance; + } - /** - * Gets the normal vector of the plane. - * - * @return the normal vector of the plane. - */ - public Vector3f getNormal() { - return normal; - } + /** + * Gets the normal vector of the plane. + * + * @return the normal vector of the plane. + */ + public Vector3f getNormal() { + return normal; + } - /** - * Gets the distance of the plane from the origin along its normal vector. - * - * @return the distance of the plane from the origin. - */ - public float getDistance() { - return distance; - } - -} \ No newline at end of file + /** + * Gets the distance of the plane from the origin along its normal vector. + * + * @return the distance of the plane from the origin. + */ + public float getDistance() { + return distance; + } +} diff --git a/src/main/java/math/Ray3f.java b/src/main/java/math/Ray3f.java index 1dd351dc..d3eaf9b5 100644 --- a/src/main/java/math/Ray3f.java +++ b/src/main/java/math/Ray3f.java @@ -1,121 +1,99 @@ package math; /** - * Represents a ray in 3D space, defined by an origin point and a direction - * vector. - *

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

      + * Represents a ray in 3D space, defined by an origin point and a direction vector. + * + *

      A ray is a mathematical abstraction used in 3D graphics, physics simulations, and + * computational geometry. It extends infinitely from its origin in the specified direction. The + * direction vector is automatically normalized during initialization to ensure consistent + * calculations. */ public class Ray3f { - /** - * The starting point of the ray. - */ - private final Vector3f origin; + /** The starting point of the ray. */ + private final Vector3f origin; - /** - * The normalized direction vector of the ray. - */ - private final Vector3f direction; + /** 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; + /** 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(); - } + /** + * 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 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 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; - } + /** + * 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 + /** + * 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)); + } +} diff --git a/src/main/java/math/Vector2f.java b/src/main/java/math/Vector2f.java index 0f413c3d..409eec4f 100644 --- a/src/main/java/math/Vector2f.java +++ b/src/main/java/math/Vector2f.java @@ -2,1026 +2,952 @@ /** * Representation of 2D vectors and points. - * + * * @author Simon * @version 0.3, 11 June 2016 - * */ public class Vector2f { - /** - * Shorthand for writing Vector2f(0, -1). - */ - public static final Vector2f DOWN = new Vector2f(0, -1); - - /** - * Shorthand for writing Vector2f(-1, 0). - */ - public static final Vector2f LEFT = new Vector2f(-1, 0); - - /** - * Shorthand for writing Vector2f(1, 0). - */ - public static final Vector2f RIGHT = new Vector2f(1, 0); - - /** - * Shorthand for writing Vector2f(0, 1). - */ - public static final Vector2f UP = new Vector2f(0, 1); - - /** - * Shorthand for writing Vector2f(1, 1). - */ - public static final Vector2f ONE = new Vector2f(1, 1); - - /** - * Shorthand for writing Vector2f(0, 0). - */ - public static final Vector2f ZERO = new Vector2f(0, 0); - - /** - * Shorthand for writing Vector2f(Float.MIN_VALUE, Float.MIN_VALUE). - */ - public static final Vector2f MIN = new Vector2f(Float.MIN_VALUE, - Float.MIN_VALUE); - - /** - * Shorthand for writing Vector2f(Float.MAX_VALUE, Float.MAX_VALUE). - */ - public static final Vector2f MAX = new Vector2f(Float.MAX_VALUE, - Float.MAX_VALUE); - - /** - * Shorthand for writing Vector2f(Float.POSITIVE_INFINITY, - * Float.POSITIVE_INFINITY). - */ - public static final Vector2f POSITIVE_INFINITY = new Vector2f( - Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY); - - /** - * Shorthand for writing Vector2f(Float.NEGATIVE_INFINITY, - * Float.NEGATIVE_INFINITY). - */ - public static final Vector2f NEGATIVE_INFINITY = new Vector2f( - Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY); - - /** - * The x component of the vector. - */ - public float x; - - /** - * The y component of the vector. - */ - public float y; - - /** - * Constructs a new instance of this {@link Vector2f} with x and y set to 0. - * Equivalent to Vector2f(0, 0). - */ - public Vector2f() { - x = y = 0; - } - - /** - * Constructs a new instance of the {@link Vector2f} with the given initial - * x and y values. - * - * @param x the x value of this {@link Vector2f} - * @param y the y value of this {@link Vector2f} - */ - public Vector2f(float x, float y) { - this.x = x; - this.y = y; - } - - /** - * Constructs a new instance of this {@link Vector2f} that contains the - * passed vector's information. - * - * @param v the vector to copy - */ - public Vector2f(Vector2f v) { - this.x = v.x; - this.y = v.y; - } - - /** - * Returns a new {@link Vector2f} containing the absolute x,y values of this - * vector. The values of this vector remain untouched. - * - * @return the newly created vector - */ - public Vector2f abs() { - return new Vector2f(Mathf.abs(x), Mathf.abs(y)); - } - - /** - * Sets the x,y components of this vector to their absolute values - * internally. - * - * @return this - */ - public Vector2f absLocal() { - this.x = Mathf.abs(x); - this.y = Mathf.abs(y); - return this; - } - - /** - * Returns a resultant vector that is made from the smallest components of - * this vector and the provided vector v. If the provided vector is null, - * null is returned. The values of this vector remain untouched. - * - * @param v the provided vector v - * @return the resultant vector or null if the provided vector is null - */ - public Vector2f min(Vector2f v) { - if (v == null) { - return null; - } - return new Vector2f(Mathf.min(x, v.x), Mathf.min(y, v.y)); - } - - /** - * Returns a resultant vector that is made from the largest components of - * this vector and the provided vector v. If the provided vector is null, - * null is returned. The values of this vector remain untouched. - * - * @param v the provided vector v - * @return the resultant vector or null if the provided vector is null - */ - public Vector2f max(Vector2f v) { - if (v == null) { - return null; - } - return new Vector2f(Mathf.max(x, v.x), Mathf.max(y, v.y)); - } - - /** - * Clamps the x,y components of this vector between 0 and 1 creating a - * resultant vector which is returned. The values of this vector remain - * untouched. - * - * @return the resultant vector - */ - public Vector2f clamp01() { - return new Vector2f(Mathf.clamp01(x), Mathf.clamp01(y)); - } - - /** - * Clamps the x,y components of this vector between 0 and 1 internally, and - * returns a handle to this vector for easy chaining of calls. - * - * @return this - */ - public Vector2f clamp01Local() { - x = Mathf.clamp01(x); - y = Mathf.clamp01(y); - return this; - } - - /** - * Clamps the x,y components of this vector between min and max creating a - * resultant vector which is returned. The values of this vector remain - * untouched. - * - * @param min the minimum value for x and y - * @param max the maximum value for x and y - * @return the resultant vector - */ - public Vector2f clamp(float min, float max) { - return new Vector2f(Mathf.clamp(x, min, max), Mathf.clamp(y, min, max)); - } - - /** - * Clamps the x,y components of this vector between min and max internally, - * and returns a handle to this vector for easy chaining of calls. - * - * @param min the minimum value for x and y - * @param max the maximum value for x and y - * @return this - */ - public Vector2f clampLocal(float min, float max) { - x = Mathf.clamp(x, min, max); - y = Mathf.clamp(y, min, max); - return this; - } - - /** - * Clamps the x,y components of this vector between the given values - * creating a resultant vector which is returned. The values of this vector - * remain untouched. - * - * @param minX the minimum value for x - * @param minY the minimum value for y - * @param maxX the maximum value for x - * @param maxY the maximum value for y - * @return the resultant vector - */ - public Vector2f clamp(float minX, float minY, float maxX, float maxY) { - return new Vector2f(Mathf.clamp(x, minX, maxX), - Mathf.clamp(y, minY, maxY)); - } - - /** - * Clamps the x,y components of this vector between the given values - * internally, and returns a handle to this vector for easy chaining of - * calls. - * - * @param minX the minimum value for x - * @param minY the minimum value for y - * @param maxX the maximum value for x - * @param maxY the maximum value for y - * @return this - */ - public Vector2f clampLocal(float minX, float minY, float maxX, float maxY) { - x = Mathf.clamp(x, minX, maxX); - y = Mathf.clamp(y, minY, maxY); - return this; - } - - /** - * Converts the given angle (in radians) to this vector. - * - * @param angrad the angle to convert this vector into - * @return this - */ - public Vector2f setToAngleRadLocal(float angrad) { - this.x = Mathf.cos(angrad); - this.y = Mathf.sin(angrad); - return this; - } - - /** - * Converts the given angle (in degrees) to this vector. - * - * @param angdeg the angle to convert this vector into - * @return this - */ - public Vector2f setToAngleDegLocal(float angdeg) { - this.x = Mathf.cos(Mathf.toRadians(angdeg)); - this.y = Mathf.sin(Mathf.toRadians(angdeg)); - return this; - } - - /** - * Set the components of this vector to the values provided by the float - * array (values[0] = x, values[1] = y). - * - * @param values the values to copy from - * @return this - * @throws IndexOutOfBoundsException if values.length < 0 || values.length < - * 2 - */ - public Vector2f set(float[] values) { - return set(values[0], values[1]); - } - - /** - * Sets the x,y components of this vector to the given values. - * - * @param x the x component of the vector - * @param y the y component of the vector - * @return this - */ - public Vector2f set(float x, float y) { - this.x = x; - this.y = y; - return this; - } - - /** - * Sets the x,y components of this vector to the values provided by the - * given vector v. - * - * @param v the vector to copy from - * @return this - */ - public Vector2f set(Vector2f v) { - this.x = v.x; - this.y = v.y; - return this; - } - - /** - * Adds the provided vector v to this vector creating a resultant vector - * which is returned. The values of this vector remain untouched. If the - * provided vector is null, null is returned. - * - * @param v the vector to add to this vector - * @return the resultant vector - */ - public Vector2f add(Vector2f v) { - if (null == v) { - return null; - } - return new Vector2f(x + v.x, y + v.y); - } - - /** - * Adds the provided vector v to this vector internally, and returns a - * handle to this vector for easy chaining of calls. If the provided vector - * is null, null is returned. - * - * @param v the vector to add to this vector - * @return this - */ - public Vector2f addLocal(Vector2f v) { - if (null == v) { - return null; - } - x += v.x; - y += v.y; - return this; - } - - /** - * Adds the provided values to this vector internally, and returns a handle - * to this vector for easy chaining of calls. - * - * @param x value to add to x - * @param y value to add to y - * @return this - */ - public Vector2f addLocal(float x, float y) { - this.x += x; - this.y += y; - return this; - } - - /** - * Adds this vector to the given vector v and stores the result in the - * provided result vector. The values of this vector remain untouched. If - * the provided vector v is null, null is returned. If the provided result - * vector is null a new vector is created to store the result in. - * - * @param v the vector to add - * @param result the vector to store the result in - * @return the result vector, after adding - */ - public Vector2f add(Vector2f v, Vector2f result) { - if (null == v) { - return null; - } - if (result == null) - result = new Vector2f(); - result.x = x + v.x; - result.y = y + v.y; - return result; - } - - public Vector2f add(float x, float y) { - return new Vector2f(this.x + x, this.y + y); - } - - /** - * Calculates the dot product of this vector and the provided vector v. If - * the provided vector is null, 0 is returned. - * - * @param v the vector to dot with this vector - * @return the resultant dot product of this vector and a given vector - */ - public float dot(Vector2f v) { - if (null == v) { - return 0; - } - return x * v.x + y * v.y; - } - - /** - * Calculates the cross product of this vector with a parameter vector v. - * - * @param v the vector to take the cross product of with this - * @return the cross product vector. - */ - public Vector3f cross(Vector2f v) { - return new Vector3f(0, 0, determinant(v)); - } - - /** - * Calculates the determinant of this vector and the given vector v. - * - * @param v the given vector v - * @return the resulting determinant - */ - public float determinant(Vector2f v) { - return (x * v.y) - (y * v.x); - } - - /** - * Sets this vector to the interpolation by changeAmnt from this to the - * finalVec this=(1-changeAmnt)*this + changeAmnt * finalVec - * - * @param finalVec the final vector to interpolate towards - * @param changeAmnt an amount between 0.0 - 1.0 representing a percentage - * change from this towards finalVec - */ - public Vector2f interpolateLocal(Vector2f finalVec, float changeAmnt) { - this.x = (1 - changeAmnt) * this.x + changeAmnt * finalVec.x; - this.y = (1 - changeAmnt) * this.y + changeAmnt * finalVec.y; - return this; - } - - /** - * Sets this vector to the interpolation by changeAmnt from beginVec to - * finalVec this=(1-changeAmnt)*beginVec + changeAmnt * finalVec - * - * @param beginVec the beginning vector (delta=0) - * @param finalVec the final vector to interpolate towards (delta=1) - * @param changeAmnt an amount between 0.0 - 1.0 representing a percentage - * change from beginVec towards finalVec - */ - public Vector2f interpolateLocal(Vector2f beginVec, Vector2f finalVec, - float changeAmnt) { - this.x = (1 - changeAmnt) * beginVec.x + changeAmnt * finalVec.x; - this.y = (1 - changeAmnt) * beginVec.y + changeAmnt * finalVec.y; - return this; - } - - /** - * Checks if the given vector v is valid. A vector is not valid if it is - * null or its floats are NaN or infinite, return false. Else return true. - * - * @param v the vector to check - * @return true or false as stated above - */ - public static boolean isValidVector(Vector2f v) { - if (v == null) - return false; - if (Float.isNaN(v.x) || Float.isNaN(v.y)) - return false; - if (Float.isInfinite(v.x) || Float.isInfinite(v.y)) - return false; - return true; - } - - /** - * Calculates the magnitude of this vector. - * - * @return the length (magnitude) of this vector - */ - public float length() { - return Mathf.sqrt(lengthSquared()); - } - - /** - * Calculates the squared value of the magnitude of this vector. - * - * @return the magnitude squared of this vector - */ - public float lengthSquared() { - return x * x + y * y; - } - - /** - * Performs a linear interpolation between this vector and another. - * - * Linear interpolation calculates a new vector that is a portion of the way - * between two given vectors. The parameter `t` determines how far along - * this line the new vector will be. - * - * Mathematically, the interpolation is calculated as follows: result = this - * + t * (other - this) - * - * Where: - `this` is the current vector - `other` is the target vector - - * `t` is the interpolation factor (between 0 and 1) - * - * @param other The vector to interpolate towards. - * @param t The interpolation factor. A value of 0 returns this vector, - * a value of 1 returns the other vector, and values between 0 - * and 1 return a vector between the two. - * @return A new vector representing the interpolated result. - */ - public Vector2f lerp(Vector2f other, float t) { - return new Vector2f(this.x + t * (other.x - this.x), - this.y + t * (other.y - this.y)); - } - - /** - * Calculates the distance squared between this vector and the given vector - * v. - * - * @param v the second vector to determine the distance squared - * @return the distance squared between the two vectors - */ - public float distanceSquared(Vector2f v) { - float dx = x - v.x; - float dy = y - v.y; - return dx * dx + dy * dy; - } - - /** - * Calculates the distance squared between this vector and the given vector - * v. - * - * @param x the x coordinate of the v vector - * @param y the y coordinate of the v vector - * @return the distance squared between the two vectors - */ - public float distanceSquared(float x, float y) { - float dx = this.x - x; - float dy = this.y - y; - return dx * dx + dy * dy; - } - - /** - * Calculates the distance between this vector and vector v. - * - * @param v the second vector to determine the distance - * @return the distance between the two vectors - */ - public float distance(Vector2f v) { - return Mathf.sqrt(distanceSquared(v)); - } - - /** - * Multiplies this vector by a scalar and resultant vector is returned. The - * values of this vector remain untouched. - * - * @param scalar the value to multiply this vector by - * @return the resultant vector - */ - public Vector2f mult(float scalar) { - return new Vector2f(x * scalar, y * scalar); - } - - /** - * Project this vector onto the given vector b. If the provided vector v is - * null, null is returned. If the provided result vector is null a new - * vector is created to store the result in. - * - * @param v the vector to project onto - * @param result the projected vector - * @param result - */ - public Vector2f projectOntoUnit(Vector2f v, Vector2f result) { - if (v == null) { - return null; - } - if (result == null) - result = new Vector2f(); - float dot = v.dot(this); - result.x = dot * v.getX(); - result.y = dot * v.getY(); - return result; - } - - /** - * Multiplies this vector by a scalar internally, and returns a handle to - * this vector for easy chaining of calls. - * - * @param scalar the value to multiply this vector by - * @return this - */ - public Vector2f multLocal(float scalar) { - x *= scalar; - y *= scalar; - return this; - } - - /** - * Multiplies a provided vector to this vector internally, and returns a - * handle to this vector for easy chaining of calls. If the provided vector - * is null, null is returned. - * - * @param v the vector to multiply with this vector - * @return this - */ - public Vector2f multLocal(Vector2f v) { - if (null == v) { - return null; - } - x *= v.x; - y *= v.y; - return this; - } - - public Vector2f mult(Vector2f v) { - if (null == v) { - return null; - } - return new Vector2f(x * v.x, y * v.y); - } - - /** - * Multiplies the x and y of this vector by the scalar and stores the result - * in product. The values of this vector remain untouched. The result is - * returned for chaining. - * - * @param scalar the scalar to multiply by - * @param product the vector to store the result in - * @return product, after multiplication. - */ - public Vector2f mult(float scalar, Vector2f product) { - if (null == product) { - product = new Vector2f(); - } - product.x = x * scalar; - product.y = y * scalar; - return product; - } - - /** - * Divides the values of this vector by a scalar and returns the result. The - * values of this vector remain untouched. Dividing by zero will result in - * an exception. - * - * @param scalar the value to divide this vectors attributes by - * @return the result vector - */ - public Vector2f divide(float scalar) { - return new Vector2f(x / scalar, y / scalar); - } - - /** - * Divides this vector by a scalar internally, and returns a handle to this - * vector for easy chaining of calls. Dividing by zero will result in an - * exception. - * - * @param scalar the value to divide this vector by - * @return this - */ - public Vector2f divideLocal(float scalar) { - x /= scalar; - y /= scalar; - return this; - } - - /** - * Divides each component of this vector by the corresponding component of - * the given vector v internally. - * - * @param v the vector providing the two divisors - * @return this - */ - public Vector2f divideLocal(Vector2f v) { - x /= v.x; - y /= v.y; - return this; - } - - /** - * Returns the negative of this vector. All values are negated and set to a - * new vector. The values of this vector remain untouched. - * - * @return the negated vector - */ - public Vector2f negate() { - return new Vector2f(-x, -y); - } - - /** - * Negates the values of this vector internally. - * - * @return this - */ - public Vector2f negateLocal() { - x = -x; - y = -y; - return this; - } - - /** - * Subtracts the values of a given vector from those of this vector creating - * a new vector object. If the provided vector is null, an exception is - * thrown. - * - * @param v the vector to subtract from this vector - * @return the resultant vector - */ - public Vector2f subtract(Vector2f v) { - return subtract(v, null); - } - - /** - * Subtracts the values of a given vector from those of this vector storing - * the result in the given vector. If the provided vector v is null, an - * exception is thrown. If the provided vector result is null, a new vector - * is created. - * - * @param v the vector to subtract from this vector - * @param result the vector to store the result in - * @return the resultant vector - */ - public Vector2f subtract(Vector2f v, Vector2f result) { - if (result == null) - result = new Vector2f(); - result.x = x - v.x; - result.y = y - v.y; - return result; - } - - /** - * Subtracts the given x,y values from those of this vector creating a new - * vector object. - * - * @param x value to subtract from x - * @param y value to subtract from y - * @return the resultant vector - */ - public Vector2f subtract(float x, float y) { - return new Vector2f(this.x - x, this.y - y); - } - - /** - * Subtracts a provided vector to this vector internally, and returns a - * handle to this vector for easy chaining of calls. If the provided vector - * is null, null is returned. - * - * @param v the vector to subtract - * @return this - */ - public Vector2f subtractLocal(Vector2f v) { - if (null == v) { - return null; - } - x -= v.x; - y -= v.y; - return this; - } - - /** - * Subtracts the provided values from this vector internally, and returns a - * handle to this vector for easy chaining of calls. - * - * @param x value to subtract from x - * @param y value to subtract from y - * @return this - */ - public Vector2f subtractLocal(float x, float y) { - this.x -= x; - this.y -= y; - return this; - } - - /** - * Returns the unit vector of this vector. The values of this vector remain - * untouched. - * - * @return the newly created unit vector of this vector - */ - public Vector2f normalize() { - float length = length(); - if (length != 0) { - return divide(length); - } - return divide(1); - } - - /** - * Makes this vector into a unit vector of itself internally. - * - * @return this - */ - public Vector2f normalizeLocal() { - float length = length(); - if (length != 0) { - return divideLocal(length); - } - return divideLocal(1); - } - - /** - * Returns the minimum angle (in radians) between two vectors. It is assumed - * that both this vector and the given vector are unit vectors (iow, - * normalized). - * - * @param otherVector a unit vector to find the angle against - * @return the angle in radians - */ - public float smallestAngleBetween(Vector2f otherVector) { - float dotProduct = dot(otherVector); - float angle = Mathf.acos(dotProduct); - return angle; - } - - /** - * Sets the closest int to the x and y components locally, with ties - * rounding to positive infinity. - * - * @return this - */ - public Vector2f roundToIntLocal() { - x = Mathf.roundToInt(x); - y = Mathf.roundToInt(y); - return this; - } - - public Vector2f rotate(float[][] rotationMatrix) { - float x0 = x * rotationMatrix[0][0] + y * rotationMatrix[0][1]; - float y0 = x * rotationMatrix[1][0] + y * rotationMatrix[1][1]; - this.x = x0; - this.y = y0; - return this; - } - - public Vector2f rotate(float angle) { - float cos = Mathf.cos(angle); - float sin = Mathf.sin(angle); - return new Vector2f(x * cos - y * sin, x * sin + y * cos); - } - - /** - * Returns (in radians) the angle required to rotate a ray represented by - * this vector to lie colinear to a ray described by the given vector. It is - * assumed that both this vector and the given vector are unit vectors (iow, - * normalized). - * - * @param otherVector the "destination" unit vector - * @return the angle in radians - */ - public float angleBetween(Vector2f otherVector) { - float angle = Mathf.atan2(otherVector.y, otherVector.x) - - Mathf.atan2(y, x); - return angle; - } - -// /** -// * Computes the angle (in radians) between the vector represented -// * by this vector and the specified vector. -// * @param x the X magnitude of the other vector -// * @param y the Y magnitude of the other vector -// * @return the angle between the two vectors measured in radians -// */ -// public float angle(float x, float y) { -// final float ax = getX(); -// final float ay = getY(); -// -// final float delta = (ax * x + ay * y) / Mathf.sqrt( -// (ax * ax + ay * ay) * (x * x + y * y)); -// -// if (delta > 1.0) { -// return 0.0f; -// } -// if (delta < -1.0) { -// return Mathf.PI; -// } -// -// return Mathf.acos(delta); -// } - - public Vector2f frac() { - // TODO Check if this is the correct way - return new Vector2f(x - (int) x, y - (int) y); - } - - /** - * Returns the x component of this vector. - * - * @return the x component of this vector - */ - public float getX() { - return x; - } - - /** - * Sets the x component of this vector to the specified new value, and - * returns a handle to this vector for easy chaining of calls. - * - * @param x the specified new x component - * @return this - */ - public Vector2f setX(float x) { - this.x = x; - return this; - } - - /** - * Returns the y component of this vector. - * - * @return the y component of this vector - */ - public float getY() { - return y; - } - - /** - * Sets the y component of this vector to the specified new value, and - * returns a handle to this vector for easy chaining of calls. - * - * @param y the specified new y component - * @return this - */ - public Vector2f setY(float y) { - this.y = y; - return this; - } - - /** - * Returns the angle (in radians) represented by this vector as expressed by - * a conversion from rectangular coordinates ( x,  - * y) to polar coordinates (r,  theta). - * - * @return the angle in radians. [-pi, pi) - */ - public float getAngle() { - return Mathf.atan2(y, x); - } - - /** - * Resets this vector's data to zero internally, and returns a handle to - * this vector for easy chaining of calls. - */ - public Vector2f zero() { - x = y = 0; - return this; - } - - /** - * Returns the perpendicular vector to this vector. - * - * @return the newly created perpendicular to this vector - */ - public Vector2f getPerpendicular() { - return new Vector2f(-y, x); - } - - /** - * Stores this vector into the given array. - * - * @param floats the array to store this vector in, if null, a new float - * array with the size of two is created - * @return the array, with x, y float values in that order - */ - public float[] toArray(float[] floats) { - if (floats == null) { - floats = new float[2]; - } - floats[0] = x; - floats[1] = y; - return floats; - } - - /** - * Stores this vector into a newly created float array with a size of two. - * - * @return the array that stores the vector with x,y component in that order - */ - public float[] toArray() { - return toArray(null); - } - - /** - * Rotates this vector around the origin. - * - * @param angle the angle to rotate (in radians) - * @param cw true to rotate clockwise, false to rotate counterclockwise - */ - public Vector2f rotate(float angle, boolean cw) { - if (cw) - angle = -angle; - float newX = Mathf.cos(angle) * x - Mathf.sin(angle) * y; - float newY = Mathf.sin(angle) * x + Mathf.cos(angle) * y; - return new Vector2f(newX, newY); - } - - /** - * Returns a unique hash code for this vector object based on it's values. - * If two vectors are logically equivalent, they will return the same hash - * code value. - * - * @return the hash code value of this vector - */ - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + Float.floatToIntBits(x); - result = prime * result + Float.floatToIntBits(y); - return result; - } - - /** - * Determines if this vector 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; - Vector2f other = (Vector2f) obj; - if (Float.floatToIntBits(x) != Float.floatToIntBits(other.x)) - return false; - if (Float.floatToIntBits(y) != Float.floatToIntBits(other.y)) - return false; - return true; - } - - /** - * Returns a string representation of this {@link Vector2f}. - * - * @return a string representation of this {@link Vector2f} - */ - @Override - public String toString() { - return "Vector2f [x=" + x + ", y=" + y + "]"; - } - -} \ No newline at end of file + /** Shorthand for writing Vector2f(0, -1). */ + public static final Vector2f DOWN = new Vector2f(0, -1); + + /** Shorthand for writing Vector2f(-1, 0). */ + public static final Vector2f LEFT = new Vector2f(-1, 0); + + /** Shorthand for writing Vector2f(1, 0). */ + public static final Vector2f RIGHT = new Vector2f(1, 0); + + /** Shorthand for writing Vector2f(0, 1). */ + public static final Vector2f UP = new Vector2f(0, 1); + + /** Shorthand for writing Vector2f(1, 1). */ + public static final Vector2f ONE = new Vector2f(1, 1); + + /** Shorthand for writing Vector2f(0, 0). */ + public static final Vector2f ZERO = new Vector2f(0, 0); + + /** Shorthand for writing Vector2f(Float.MIN_VALUE, Float.MIN_VALUE). */ + public static final Vector2f MIN = new Vector2f(Float.MIN_VALUE, Float.MIN_VALUE); + + /** Shorthand for writing Vector2f(Float.MAX_VALUE, Float.MAX_VALUE). */ + public static final Vector2f MAX = new Vector2f(Float.MAX_VALUE, Float.MAX_VALUE); + + /** Shorthand for writing Vector2f(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY). */ + public static final Vector2f POSITIVE_INFINITY = + new Vector2f(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY); + + /** Shorthand for writing Vector2f(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY). */ + public static final Vector2f NEGATIVE_INFINITY = + new Vector2f(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY); + + /** The x component of the vector. */ + public float x; + + /** The y component of the vector. */ + public float y; + + /** + * Constructs a new instance of this {@link Vector2f} with x and y set to 0. Equivalent to + * Vector2f(0, 0). + */ + public Vector2f() { + x = y = 0; + } + + /** + * Constructs a new instance of the {@link Vector2f} with the given initial x and y values. + * + * @param x the x value of this {@link Vector2f} + * @param y the y value of this {@link Vector2f} + */ + public Vector2f(float x, float y) { + this.x = x; + this.y = y; + } + + /** + * Constructs a new instance of this {@link Vector2f} that contains the passed vector's + * information. + * + * @param v the vector to copy + */ + public Vector2f(Vector2f v) { + this.x = v.x; + this.y = v.y; + } + + /** + * Returns a new {@link Vector2f} containing the absolute x,y values of this vector. The values of + * this vector remain untouched. + * + * @return the newly created vector + */ + public Vector2f abs() { + return new Vector2f(Mathf.abs(x), Mathf.abs(y)); + } + + /** + * Sets the x,y components of this vector to their absolute values internally. + * + * @return this + */ + public Vector2f absLocal() { + this.x = Mathf.abs(x); + this.y = Mathf.abs(y); + return this; + } + + /** + * Returns a resultant vector that is made from the smallest components of this vector and the + * provided vector v. If the provided vector is null, null is returned. The values of this vector + * remain untouched. + * + * @param v the provided vector v + * @return the resultant vector or null if the provided vector is null + */ + public Vector2f min(Vector2f v) { + if (v == null) { + return null; + } + return new Vector2f(Mathf.min(x, v.x), Mathf.min(y, v.y)); + } + + /** + * Returns a resultant vector that is made from the largest components of this vector and the + * provided vector v. If the provided vector is null, null is returned. The values of this vector + * remain untouched. + * + * @param v the provided vector v + * @return the resultant vector or null if the provided vector is null + */ + public Vector2f max(Vector2f v) { + if (v == null) { + return null; + } + return new Vector2f(Mathf.max(x, v.x), Mathf.max(y, v.y)); + } + + /** + * Clamps the x,y components of this vector between 0 and 1 creating a resultant vector which is + * returned. The values of this vector remain untouched. + * + * @return the resultant vector + */ + public Vector2f clamp01() { + return new Vector2f(Mathf.clamp01(x), Mathf.clamp01(y)); + } + + /** + * Clamps the x,y components of this vector between 0 and 1 internally, and returns a handle to + * this vector for easy chaining of calls. + * + * @return this + */ + public Vector2f clamp01Local() { + x = Mathf.clamp01(x); + y = Mathf.clamp01(y); + return this; + } + + /** + * Clamps the x,y components of this vector between min and max creating a resultant vector which + * is returned. The values of this vector remain untouched. + * + * @param min the minimum value for x and y + * @param max the maximum value for x and y + * @return the resultant vector + */ + public Vector2f clamp(float min, float max) { + return new Vector2f(Mathf.clamp(x, min, max), Mathf.clamp(y, min, max)); + } + + /** + * Clamps the x,y components of this vector between min and max internally, and returns a handle + * to this vector for easy chaining of calls. + * + * @param min the minimum value for x and y + * @param max the maximum value for x and y + * @return this + */ + public Vector2f clampLocal(float min, float max) { + x = Mathf.clamp(x, min, max); + y = Mathf.clamp(y, min, max); + return this; + } + + /** + * Clamps the x,y components of this vector between the given values creating a resultant vector + * which is returned. The values of this vector remain untouched. + * + * @param minX the minimum value for x + * @param minY the minimum value for y + * @param maxX the maximum value for x + * @param maxY the maximum value for y + * @return the resultant vector + */ + public Vector2f clamp(float minX, float minY, float maxX, float maxY) { + return new Vector2f(Mathf.clamp(x, minX, maxX), Mathf.clamp(y, minY, maxY)); + } + + /** + * Clamps the x,y components of this vector between the given values internally, and returns a + * handle to this vector for easy chaining of calls. + * + * @param minX the minimum value for x + * @param minY the minimum value for y + * @param maxX the maximum value for x + * @param maxY the maximum value for y + * @return this + */ + public Vector2f clampLocal(float minX, float minY, float maxX, float maxY) { + x = Mathf.clamp(x, minX, maxX); + y = Mathf.clamp(y, minY, maxY); + return this; + } + + /** + * Converts the given angle (in radians) to this vector. + * + * @param angrad the angle to convert this vector into + * @return this + */ + public Vector2f setToAngleRadLocal(float angrad) { + this.x = Mathf.cos(angrad); + this.y = Mathf.sin(angrad); + return this; + } + + /** + * Converts the given angle (in degrees) to this vector. + * + * @param angdeg the angle to convert this vector into + * @return this + */ + public Vector2f setToAngleDegLocal(float angdeg) { + this.x = Mathf.cos(Mathf.toRadians(angdeg)); + this.y = Mathf.sin(Mathf.toRadians(angdeg)); + return this; + } + + /** + * Set the components of this vector to the values provided by the float array (values[0] = x, + * values[1] = y). + * + * @param values the values to copy from + * @return this + * @throws IndexOutOfBoundsException if values.length < 0 || values.length < 2 + */ + public Vector2f set(float[] values) { + return set(values[0], values[1]); + } + + /** + * Sets the x,y components of this vector to the given values. + * + * @param x the x component of the vector + * @param y the y component of the vector + * @return this + */ + public Vector2f set(float x, float y) { + this.x = x; + this.y = y; + return this; + } + + /** + * Sets the x,y components of this vector to the values provided by the given vector v. + * + * @param v the vector to copy from + * @return this + */ + public Vector2f set(Vector2f v) { + this.x = v.x; + this.y = v.y; + return this; + } + + /** + * Adds the provided vector v to this vector creating a resultant vector which is returned. The + * values of this vector remain untouched. If the provided vector is null, null is returned. + * + * @param v the vector to add to this vector + * @return the resultant vector + */ + public Vector2f add(Vector2f v) { + if (null == v) { + return null; + } + return new Vector2f(x + v.x, y + v.y); + } + + /** + * Adds the provided vector v to this vector internally, and returns a handle to this vector for + * easy chaining of calls. If the provided vector is null, null is returned. + * + * @param v the vector to add to this vector + * @return this + */ + public Vector2f addLocal(Vector2f v) { + if (null == v) { + return null; + } + x += v.x; + y += v.y; + return this; + } + + /** + * Adds the provided values to this vector internally, and returns a handle to this vector for + * easy chaining of calls. + * + * @param x value to add to x + * @param y value to add to y + * @return this + */ + public Vector2f addLocal(float x, float y) { + this.x += x; + this.y += y; + return this; + } + + /** + * Adds this vector to the given vector v and stores the result in the provided result vector. The + * values of this vector remain untouched. If the provided vector v is null, null is returned. If + * the provided result vector is null a new vector is created to store the result in. + * + * @param v the vector to add + * @param result the vector to store the result in + * @return the result vector, after adding + */ + public Vector2f add(Vector2f v, Vector2f result) { + if (null == v) { + return null; + } + if (result == null) result = new Vector2f(); + result.x = x + v.x; + result.y = y + v.y; + return result; + } + + public Vector2f add(float x, float y) { + return new Vector2f(this.x + x, this.y + y); + } + + /** + * Calculates the dot product of this vector and the provided vector v. If the provided vector is + * null, 0 is returned. + * + * @param v the vector to dot with this vector + * @return the resultant dot product of this vector and a given vector + */ + public float dot(Vector2f v) { + if (null == v) { + return 0; + } + return x * v.x + y * v.y; + } + + /** + * Calculates the cross product of this vector with a parameter vector v. + * + * @param v the vector to take the cross product of with this + * @return the cross product vector. + */ + public Vector3f cross(Vector2f v) { + return new Vector3f(0, 0, determinant(v)); + } + + /** + * Calculates the determinant of this vector and the given vector v. + * + * @param v the given vector v + * @return the resulting determinant + */ + public float determinant(Vector2f v) { + return (x * v.y) - (y * v.x); + } + + /** + * Sets this vector to the interpolation by changeAmnt from this to the finalVec + * this=(1-changeAmnt)*this + changeAmnt * finalVec + * + * @param finalVec the final vector to interpolate towards + * @param changeAmnt an amount between 0.0 - 1.0 representing a percentage change from this + * towards finalVec + */ + public Vector2f interpolateLocal(Vector2f finalVec, float changeAmnt) { + this.x = (1 - changeAmnt) * this.x + changeAmnt * finalVec.x; + this.y = (1 - changeAmnt) * this.y + changeAmnt * finalVec.y; + return this; + } + + /** + * Sets this vector to the interpolation by changeAmnt from beginVec to finalVec + * this=(1-changeAmnt)*beginVec + changeAmnt * finalVec + * + * @param beginVec the beginning vector (delta=0) + * @param finalVec the final vector to interpolate towards (delta=1) + * @param changeAmnt an amount between 0.0 - 1.0 representing a percentage change from beginVec + * towards finalVec + */ + public Vector2f interpolateLocal(Vector2f beginVec, Vector2f finalVec, float changeAmnt) { + this.x = (1 - changeAmnt) * beginVec.x + changeAmnt * finalVec.x; + this.y = (1 - changeAmnt) * beginVec.y + changeAmnt * finalVec.y; + return this; + } + + /** + * Checks if the given vector v is valid. A vector is not valid if it is null or its floats are + * NaN or infinite, return false. Else return true. + * + * @param v the vector to check + * @return true or false as stated above + */ + public static boolean isValidVector(Vector2f v) { + if (v == null) return false; + if (Float.isNaN(v.x) || Float.isNaN(v.y)) return false; + if (Float.isInfinite(v.x) || Float.isInfinite(v.y)) return false; + return true; + } + + /** + * Calculates the magnitude of this vector. + * + * @return the length (magnitude) of this vector + */ + public float length() { + return Mathf.sqrt(lengthSquared()); + } + + /** + * Calculates the squared value of the magnitude of this vector. + * + * @return the magnitude squared of this vector + */ + public float lengthSquared() { + return x * x + y * y; + } + + /** + * Performs a linear interpolation between this vector and another. + * + *

      Linear interpolation calculates a new vector that is a portion of the way between two given + * vectors. The parameter `t` determines how far along this line the new vector will be. + * + *

      Mathematically, the interpolation is calculated as follows: result = this + t * (other - + * this) + * + *

      Where: - `this` is the current vector - `other` is the target vector - `t` is the + * interpolation factor (between 0 and 1) + * + * @param other The vector to interpolate towards. + * @param t The interpolation factor. A value of 0 returns this vector, a value of 1 returns the + * other vector, and values between 0 and 1 return a vector between the two. + * @return A new vector representing the interpolated result. + */ + public Vector2f lerp(Vector2f other, float t) { + return new Vector2f(this.x + t * (other.x - this.x), this.y + t * (other.y - this.y)); + } + + /** + * Calculates the distance squared between this vector and the given vector v. + * + * @param v the second vector to determine the distance squared + * @return the distance squared between the two vectors + */ + public float distanceSquared(Vector2f v) { + float dx = x - v.x; + float dy = y - v.y; + return dx * dx + dy * dy; + } + + /** + * Calculates the distance squared between this vector and the given vector v. + * + * @param x the x coordinate of the v vector + * @param y the y coordinate of the v vector + * @return the distance squared between the two vectors + */ + public float distanceSquared(float x, float y) { + float dx = this.x - x; + float dy = this.y - y; + return dx * dx + dy * dy; + } + + /** + * Calculates the distance between this vector and vector v. + * + * @param v the second vector to determine the distance + * @return the distance between the two vectors + */ + public float distance(Vector2f v) { + return Mathf.sqrt(distanceSquared(v)); + } + + /** + * Multiplies this vector by a scalar and resultant vector is returned. The values of this vector + * remain untouched. + * + * @param scalar the value to multiply this vector by + * @return the resultant vector + */ + public Vector2f mult(float scalar) { + return new Vector2f(x * scalar, y * scalar); + } + + /** + * Project this vector onto the given vector b. If the provided vector v is null, null is + * returned. If the provided result vector is null a new vector is created to store the result in. + * + * @param v the vector to project onto + * @param result the projected vector + * @param result + */ + public Vector2f projectOntoUnit(Vector2f v, Vector2f result) { + if (v == null) { + return null; + } + if (result == null) result = new Vector2f(); + float dot = v.dot(this); + result.x = dot * v.getX(); + result.y = dot * v.getY(); + return result; + } + + /** + * Multiplies this vector by a scalar internally, and returns a handle to this vector for easy + * chaining of calls. + * + * @param scalar the value to multiply this vector by + * @return this + */ + public Vector2f multLocal(float scalar) { + x *= scalar; + y *= scalar; + return this; + } + + /** + * Multiplies a provided vector to this vector internally, and returns a handle to this vector for + * easy chaining of calls. If the provided vector is null, null is returned. + * + * @param v the vector to multiply with this vector + * @return this + */ + public Vector2f multLocal(Vector2f v) { + if (null == v) { + return null; + } + x *= v.x; + y *= v.y; + return this; + } + + public Vector2f mult(Vector2f v) { + if (null == v) { + return null; + } + return new Vector2f(x * v.x, y * v.y); + } + + /** + * Multiplies the x and y of this vector by the scalar and stores the result in product. The + * values of this vector remain untouched. The result is returned for chaining. + * + * @param scalar the scalar to multiply by + * @param product the vector to store the result in + * @return product, after multiplication. + */ + public Vector2f mult(float scalar, Vector2f product) { + if (null == product) { + product = new Vector2f(); + } + product.x = x * scalar; + product.y = y * scalar; + return product; + } + + /** + * Divides the values of this vector by a scalar and returns the result. The values of this vector + * remain untouched. Dividing by zero will result in an exception. + * + * @param scalar the value to divide this vectors attributes by + * @return the result vector + */ + public Vector2f divide(float scalar) { + return new Vector2f(x / scalar, y / scalar); + } + + /** + * Divides this vector by a scalar internally, and returns a handle to this vector for easy + * chaining of calls. Dividing by zero will result in an exception. + * + * @param scalar the value to divide this vector by + * @return this + */ + public Vector2f divideLocal(float scalar) { + x /= scalar; + y /= scalar; + return this; + } + + /** + * Divides each component of this vector by the corresponding component of the given vector v + * internally. + * + * @param v the vector providing the two divisors + * @return this + */ + public Vector2f divideLocal(Vector2f v) { + x /= v.x; + y /= v.y; + return this; + } + + /** + * Returns the negative of this vector. All values are negated and set to a new vector. The values + * of this vector remain untouched. + * + * @return the negated vector + */ + public Vector2f negate() { + return new Vector2f(-x, -y); + } + + /** + * Negates the values of this vector internally. + * + * @return this + */ + public Vector2f negateLocal() { + x = -x; + y = -y; + return this; + } + + /** + * Subtracts the values of a given vector from those of this vector creating a new vector object. + * If the provided vector is null, an exception is thrown. + * + * @param v the vector to subtract from this vector + * @return the resultant vector + */ + public Vector2f subtract(Vector2f v) { + return subtract(v, null); + } + + /** + * Subtracts the values of a given vector from those of this vector storing the result in the + * given vector. If the provided vector v is null, an exception is thrown. If the provided vector + * result is null, a new vector is created. + * + * @param v the vector to subtract from this vector + * @param result the vector to store the result in + * @return the resultant vector + */ + public Vector2f subtract(Vector2f v, Vector2f result) { + if (result == null) result = new Vector2f(); + result.x = x - v.x; + result.y = y - v.y; + return result; + } + + /** + * Subtracts the given x,y values from those of this vector creating a new vector object. + * + * @param x value to subtract from x + * @param y value to subtract from y + * @return the resultant vector + */ + public Vector2f subtract(float x, float y) { + return new Vector2f(this.x - x, this.y - y); + } + + /** + * Subtracts a provided vector to this vector internally, and returns a handle to this vector for + * easy chaining of calls. If the provided vector is null, null is returned. + * + * @param v the vector to subtract + * @return this + */ + public Vector2f subtractLocal(Vector2f v) { + if (null == v) { + return null; + } + x -= v.x; + y -= v.y; + return this; + } + + /** + * Subtracts the provided values from this vector internally, and returns a handle to this vector + * for easy chaining of calls. + * + * @param x value to subtract from x + * @param y value to subtract from y + * @return this + */ + public Vector2f subtractLocal(float x, float y) { + this.x -= x; + this.y -= y; + return this; + } + + /** + * Returns the unit vector of this vector. The values of this vector remain untouched. + * + * @return the newly created unit vector of this vector + */ + public Vector2f normalize() { + float length = length(); + if (length != 0) { + return divide(length); + } + return divide(1); + } + + /** + * Makes this vector into a unit vector of itself internally. + * + * @return this + */ + public Vector2f normalizeLocal() { + float length = length(); + if (length != 0) { + return divideLocal(length); + } + return divideLocal(1); + } + + /** + * Returns the minimum angle (in radians) between two vectors. It is assumed that both this vector + * and the given vector are unit vectors (iow, normalized). + * + * @param otherVector a unit vector to find the angle against + * @return the angle in radians + */ + public float smallestAngleBetween(Vector2f otherVector) { + float dotProduct = dot(otherVector); + float angle = Mathf.acos(dotProduct); + return angle; + } + + /** + * Sets the closest int to the x and y components locally, with ties rounding to positive + * infinity. + * + * @return this + */ + public Vector2f roundToIntLocal() { + x = Mathf.roundToInt(x); + y = Mathf.roundToInt(y); + return this; + } + + public Vector2f rotate(float[][] rotationMatrix) { + float x0 = x * rotationMatrix[0][0] + y * rotationMatrix[0][1]; + float y0 = x * rotationMatrix[1][0] + y * rotationMatrix[1][1]; + this.x = x0; + this.y = y0; + return this; + } + + public Vector2f rotate(float angle) { + float cos = Mathf.cos(angle); + float sin = Mathf.sin(angle); + return new Vector2f(x * cos - y * sin, x * sin + y * cos); + } + + /** + * Returns (in radians) the angle required to rotate a ray represented by this vector to lie + * colinear to a ray described by the given vector. It is assumed that both this vector and the + * given vector are unit vectors (iow, normalized). + * + * @param otherVector the "destination" unit vector + * @return the angle in radians + */ + public float angleBetween(Vector2f otherVector) { + float angle = Mathf.atan2(otherVector.y, otherVector.x) - Mathf.atan2(y, x); + return angle; + } + + // /** + // * Computes the angle (in radians) between the vector represented + // * by this vector and the specified vector. + // * @param x the X magnitude of the other vector + // * @param y the Y magnitude of the other vector + // * @return the angle between the two vectors measured in radians + // */ + // public float angle(float x, float y) { + // final float ax = getX(); + // final float ay = getY(); + // + // final float delta = (ax * x + ay * y) / Mathf.sqrt( + // (ax * ax + ay * ay) * (x * x + y * y)); + // + // if (delta > 1.0) { + // return 0.0f; + // } + // if (delta < -1.0) { + // return Mathf.PI; + // } + // + // return Mathf.acos(delta); + // } + + public Vector2f frac() { + // TODO Check if this is the correct way + return new Vector2f(x - (int) x, y - (int) y); + } + + /** + * Returns the x component of this vector. + * + * @return the x component of this vector + */ + public float getX() { + return x; + } + + /** + * Sets the x component of this vector to the specified new value, and returns a handle to this + * vector for easy chaining of calls. + * + * @param x the specified new x component + * @return this + */ + public Vector2f setX(float x) { + this.x = x; + return this; + } + + /** + * Returns the y component of this vector. + * + * @return the y component of this vector + */ + public float getY() { + return y; + } + + /** + * Sets the y component of this vector to the specified new value, and returns a handle to this + * vector for easy chaining of calls. + * + * @param y the specified new y component + * @return this + */ + public Vector2f setY(float y) { + this.y = y; + return this; + } + + /** + * Returns the angle (in radians) represented by this vector as expressed by a conversion from + * rectangular coordinates ( xy) to polar coordinates (r,  + * theta). + * + * @return the angle in radians. [-pi, pi) + */ + public float getAngle() { + return Mathf.atan2(y, x); + } + + /** + * Resets this vector's data to zero internally, and returns a handle to this vector for easy + * chaining of calls. + */ + public Vector2f zero() { + x = y = 0; + return this; + } + + /** + * Returns the perpendicular vector to this vector. + * + * @return the newly created perpendicular to this vector + */ + public Vector2f getPerpendicular() { + return new Vector2f(-y, x); + } + + /** + * Stores this vector into the given array. + * + * @param floats the array to store this vector in, if null, a new float array with the size of + * two is created + * @return the array, with x, y float values in that order + */ + public float[] toArray(float[] floats) { + if (floats == null) { + floats = new float[2]; + } + floats[0] = x; + floats[1] = y; + return floats; + } + + /** + * Stores this vector into a newly created float array with a size of two. + * + * @return the array that stores the vector with x,y component in that order + */ + public float[] toArray() { + return toArray(null); + } + + /** + * Rotates this vector around the origin. + * + * @param angle the angle to rotate (in radians) + * @param cw true to rotate clockwise, false to rotate counterclockwise + */ + public Vector2f rotate(float angle, boolean cw) { + if (cw) angle = -angle; + float newX = Mathf.cos(angle) * x - Mathf.sin(angle) * y; + float newY = Mathf.sin(angle) * x + Mathf.cos(angle) * y; + return new Vector2f(newX, newY); + } + + /** + * Returns a unique hash code for this vector object based on it's values. If two vectors are + * logically equivalent, they will return the same hash code value. + * + * @return the hash code value of this vector + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Float.floatToIntBits(x); + result = prime * result + Float.floatToIntBits(y); + return result; + } + + /** + * Determines if this vector 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; + Vector2f other = (Vector2f) obj; + if (Float.floatToIntBits(x) != Float.floatToIntBits(other.x)) return false; + if (Float.floatToIntBits(y) != Float.floatToIntBits(other.y)) return false; + return true; + } + + /** + * Returns a string representation of this {@link Vector2f}. + * + * @return a string representation of this {@link Vector2f} + */ + @Override + public String toString() { + return "Vector2f [x=" + x + ", y=" + y + "]"; + } +} diff --git a/src/main/java/math/Vector3f.java b/src/main/java/math/Vector3f.java index 935b0f5f..b75ad8a2 100644 --- a/src/main/java/math/Vector3f.java +++ b/src/main/java/math/Vector3f.java @@ -2,569 +2,553 @@ 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(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 + "]"; - } + 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 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 boolean isZero() { + float threshold = 1e-6f; + return Math.abs(x) < threshold && Math.abs(y) < threshold && Math.abs(z) < threshold; + } + + 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/workspace/FirstPersonView.java b/src/main/java/workspace/FirstPersonView.java index d6b71a7e..9480aad7 100644 --- a/src/main/java/workspace/FirstPersonView.java +++ b/src/main/java/workspace/FirstPersonView.java @@ -1,5 +1,6 @@ package workspace; +import engine.world.PlayerMock; import math.Mathf; import math.Matrix3f; import math.Matrix4f; @@ -10,145 +11,130 @@ public class FirstPersonView { - private boolean enabled; + private boolean enabled; - private boolean left; + private boolean left; - private boolean right; + private boolean right; - private boolean forward; + private boolean forward; - private boolean back; + private boolean back; - private boolean up; + private boolean up; - private boolean down; + private boolean down; - private float pitch = Mathf.PI; + private float pitch = Mathf.PI; - private float yaw = 0; + private float yaw = 0; - private Vector3f eye = new Vector3f(-1000, 0, 1000); + private Vector3f eye = new Vector3f(-1000, 0, 1000); - private float speed = 10; + private float speed = 10; - private PApplet context; + private PApplet context; - public FirstPersonView(PApplet context) { - this.context = context; - context.registerMethod("pre", this); - context.registerMethod("keyEvent", this); - } + public FirstPersonView(PApplet context) { + this.context = context; + context.registerMethod("pre", this); + context.registerMethod("keyEvent", this); + } - public void pre() { - if (!enabled) - return; - yaw = Mathf.map(context.mouseX, 0, context.width, Mathf.PI, -Mathf.PI); - pitch = Mathf.map(context.mouseY, 0, context.height, -Mathf.PI, Mathf.PI); + public void pre() { + if (!enabled) return; + yaw = Mathf.map(context.mouseX, 0, context.width, Mathf.PI, -Mathf.PI); + pitch = Mathf.map(context.mouseY, 0, context.height, -Mathf.PI, Mathf.PI); -// if (pitch > 89) -// pitch = 89; -// if (pitch < -89) -// pitch = -89; + // if (pitch > 89) + // pitch = 89; + // if (pitch < -89) + // pitch = -89; - Vector3f front = new Vector3f(); - float x = Mathf.cos(Mathf.toRadians(yaw)) - * Mathf.cos(Mathf.toRadians(pitch)); - float y = Mathf.sin(Mathf.toRadians(pitch)); - float z = Mathf.cos(Mathf.toRadians(yaw)) - * Mathf.cos(Mathf.toRadians(pitch)); - front.set(x, y, z); + Vector3f front = new Vector3f(); + float x = Mathf.cos(Mathf.toRadians(yaw)) * Mathf.cos(Mathf.toRadians(pitch)); + float y = Mathf.sin(Mathf.toRadians(pitch)); + float z = Mathf.cos(Mathf.toRadians(yaw)) * Mathf.cos(Mathf.toRadians(pitch)); + front.set(x, y, z); - Vector3f velocity = new Vector3f(); + Vector3f velocity = new Vector3f(); - if (left) { - velocity.addLocal(-1, 0, 0); - } + if (left) { + velocity.addLocal(-1, 0, 0); + } - if (right) { - velocity.addLocal(1, 0, 0); - } + if (right) { + velocity.addLocal(1, 0, 0); + } - if (back) { - velocity.addLocal(0, 0, 1); - } + if (back) { + velocity.addLocal(0, 0, 1); + } - if (forward) { - velocity.addLocal(0, 0, -1); - } + if (forward) { + velocity.addLocal(0, 0, -1); + } - velocity.multLocal(getRotationMatrix(yaw)); + velocity.multLocal(getRotationMatrix(yaw)); - eye.addLocal(velocity.mult(speed)); - eye.setY(-300); - } + eye.addLocal(velocity.mult(speed)); + eye.setY(-300); - public void apply() { - Matrix4f m = Matrix4f.fpsViewRH(eye, pitch, yaw).transpose(); - PMatrix matrix = context.getMatrix(); - matrix.set(m.getValues()); - context.setMatrix(matrix); - } + PlayerMock.playerPosition.addLocal(velocity.mult(0.1f)); + } - public void keyEvent(KeyEvent key) { - if (key.getAction() == KeyEvent.PRESS) - onKeyPressed(key.getKey()); - if (key.getAction() == KeyEvent.RELEASE) - onKeyReleased(key.getKey()); - } + public void apply() { + Matrix4f m = Matrix4f.fpsViewRH(eye, pitch, yaw).transpose(); + PMatrix matrix = context.getMatrix(); + matrix.set(m.getValues()); + context.setMatrix(matrix); + } - public void onKeyPressed(char key) { - if (key == 'w' || key == 'W') - forward = true; + public void keyEvent(KeyEvent key) { + if (key.getAction() == KeyEvent.PRESS) onKeyPressed(key.getKey()); + if (key.getAction() == KeyEvent.RELEASE) onKeyReleased(key.getKey()); + } - if (key == 's' || key == 'S') - back = true; + public void onKeyPressed(char key) { + if (key == 'w' || key == 'W') forward = true; - if (key == 'a' || key == 'A') - left = true; + if (key == 's' || key == 'S') back = true; - if (key == 'd' || key == 'D') - right = true; + if (key == 'a' || key == 'A') left = true; - if (key == ' ') - up = true; + if (key == 'd' || key == 'D') right = true; - if (key == 'c' || key == 'C') - down = true; - } + if (key == ' ') up = true; - public void onKeyReleased(char key) { - if (key == 'w' || key == 'W') - forward = false; + if (key == 'c' || key == 'C') down = true; + } - if (key == 's' || key == 'S') - back = false; + public void onKeyReleased(char key) { + if (key == 'w' || key == 'W') forward = false; - if (key == 'a' || key == 'A') - left = false; + if (key == 's' || key == 'S') back = false; - if (key == 'd' || key == 'D') - right = false; + if (key == 'a' || key == 'A') left = false; - if (key == ' ') - up = false; + if (key == 'd' || key == 'D') right = false; - if (key == 'c' || key == 'C') - down = false; - } + if (key == ' ') up = false; - public Matrix3f getRotationMatrix(float angle) { - Matrix3f m = new Matrix3f(Mathf.cos(angle), 0, Mathf.sin(angle), 0, 1, 0, - -Mathf.sin(angle), 0, Mathf.cos(angle)); - return m; - } + if (key == 'c' || key == 'C') down = false; + } - public boolean isEnabled() { - return enabled; - } + public Matrix3f getRotationMatrix(float angle) { + Matrix3f m = + new Matrix3f( + Mathf.cos(angle), 0, Mathf.sin(angle), 0, 1, 0, -Mathf.sin(angle), 0, Mathf.cos(angle)); + return m; + } - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } + public boolean isEnabled() { + return enabled; + } + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } } diff --git a/src/main/java/workspace/GraphicsPImpl.java b/src/main/java/workspace/GraphicsPImpl.java index 161358d5..4a22e96c 100644 --- a/src/main/java/workspace/GraphicsPImpl.java +++ b/src/main/java/workspace/GraphicsPImpl.java @@ -1,215 +1,592 @@ package workspace; +import java.util.List; + +import engine.processing.LightGizmoRenderer; +import engine.processing.LightRendererImpl; +import engine.render.Material; +import engine.scene.camera.Camera; +import engine.scene.light.Light; +import engine.scene.light.LightRenderer; +import math.Matrix4f; +import math.Vector3f; +import mesh.Face3D; import mesh.Mesh3D; import processing.core.PApplet; import processing.core.PGraphics; +import processing.opengl.PShader; import workspace.render.Mesh3DRenderer; import workspace.ui.Color; import workspace.ui.Graphics; public class GraphicsPImpl implements Graphics { - private Color color; - - private PGraphics g; - - private Mesh3DRenderer renderer; - - public GraphicsPImpl(PApplet p) { - this.g = p.g; - renderer = new Mesh3DRenderer(p); - color = Color.BLACK; - } - - @Override - public void fillFaces(Mesh3D mesh) { - g.noStroke(); - fill(); - renderer.drawFaces(mesh); - } - - @Override - public int getWidth() { - return g.width; - } - - @Override - public int getHeight() { - return g.height; - } - - private void stroke() { - g.stroke(color.getRed(), color.getGreen(), color.getBlue(), - color.getAlpha()); - } - - private void fill() { - g.fill(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()); - } - - @Override - public void pushMatrix() { - g.pushMatrix(); - } - - @Override - public void popMatrix() { - g.popMatrix(); - } - - @Override - public void scale(float sx, float sy, float sz) { - g.scale(sx, sy, sz); - } - - @Override - public void translate(float x, float y) { - g.translate(x, y); - } - - @Override - public void translate(float x, float y, float z) { - g.translate(x, y, z); - } - - @Override - public void strokeWeight(float weight) { - g.strokeWeight(weight); - } - - @Override - public void setColor(Color color) { - this.color = color; - } - - @Override - public void setColor(int red, int green, int blue) { - color = new Color(red, green, blue); - } - - @Override - public void setColor(math.Color color) { - setColor(color.getRedInt(), color.getGreenInt(), color.getBlueInt()); - } - - @Override - public void drawRect(float x, float y, float width, float height) { - g.pushStyle(); - g.noFill(); - stroke(); - g.rectMode(PApplet.CORNER); - g.rect(x, y, width, height); - g.popStyle(); - } - - @Override - public void drawLine(float x1, float y1, float x2, float y2) { - g.pushStyle(); - g.noFill(); - stroke(); - g.line(x1, y1, x2, y2); - g.popStyle(); - } - - @Override - public void drawLine(float x1, float y1, float z1, float x2, float y2, - float z2) { - g.pushStyle(); - g.noFill(); - stroke(); - g.line(x1, y1, z1, x2, y2, z2); - g.popStyle(); - } - - @Override - public void fillRect(float x, float y, float width, float height) { - g.pushStyle(); - g.noStroke(); - fill(); - g.rectMode(PApplet.CORNER); - g.rect(x, y, width, height); - g.popStyle(); - } - - @Override - public void drawOval(float x, float y, float width, float height) { - g.pushStyle(); - g.noFill(); - stroke(); - g.ellipseMode(PApplet.CORNER); - g.ellipse(x, y, height, width); - g.popStyle(); - } - - @Override - public void fillOval(float x, float y, float width, float height) { - g.pushStyle(); - g.noStroke(); - fill(); - g.ellipseMode(PApplet.CORNER); - g.ellipse(x, y, height, width); - g.popStyle(); - } - - @Override - public void textSize(float size) { - g.textSize(size); - } - - @Override - public float getTextSize() { - return g.textSize; - } - - @Override - public float textWidth(String text) { - return g.textWidth(text); - } - - @Override - public float textAscent() { - return g.textAscent(); - } - - @Override - public float textDescent() { - return g.textDescent(); - } - - @Override - public void text(String text, float x, float y) { - fill(); - g.text(text, x, y); - } - - @Override - public void enableDepthTest() { - g.hint(PApplet.ENABLE_DEPTH_TEST); - } - - @Override - public void disableDepthTest() { - g.hint(PApplet.DISABLE_DEPTH_TEST); - } - - @Override - public void rotate(float angle) { - g.rotate(angle); - } - - @Override - public void rotateX(float angle) { - g.rotateX(angle); - } - - @Override - public void rotateY(float angle) { - g.rotateY(angle); - } - - @Override - public void rotateZ(float angle) { - g.rotate(angle); - } + private boolean wireframeMode; + + private Color color; + + private math.Color ambientColor; + + private PGraphics g; + + private Mesh3DRenderer renderer; + + private LightRenderer lightRenderer; + + private LightGizmoRenderer lightGizmoRenderer; + + @Override + public void setAmbientColor(math.Color ambientColor) { + this.ambientColor = ambientColor; + } + + @Override + public math.Color getAmbientColor() { + return ambientColor; + } + + @Override + public void setWireframeMode(boolean wireframeMode) { + this.wireframeMode = wireframeMode; + } + + public GraphicsPImpl(PApplet p) { + this.g = p.g; + renderer = new Mesh3DRenderer(p); + + lightRenderer = new LightRendererImpl(p); + lightRenderer.setGraphics(this); + + lightGizmoRenderer = new LightGizmoRenderer(p); + lightGizmoRenderer.setGraphics(this); + + color = Color.BLACK; + ambientColor = math.Color.WHITE; + } + + @Override + public void fillFaces(Mesh3D mesh) { + if (wireframeMode) { + g.noFill(); + stroke(); + renderer.drawFaces(mesh); + } else { + g.noStroke(); + fill(); + renderer.drawFaces(mesh); + } + } + + @Override + public void renderInstances(Mesh3D mesh, List instanceTransforms) { + if (mesh.getFaces().isEmpty() || mesh.getVertices().isEmpty()) { + return; + } + + setColor(Color.WHITE); + + for (Matrix4f transform : instanceTransforms) { + g.pushMatrix(); + applyTransform(transform); + drawMeshFaces(mesh); + g.popMatrix(); + } + } + + private void applyTransform(Matrix4f transform) { + float[] matrix = transform.getValues(); + + g.applyMatrix( + matrix[0], + matrix[1], + matrix[2], + matrix[3], + matrix[4], + matrix[5], + matrix[6], + matrix[7], + matrix[8], + matrix[9], + matrix[10], + matrix[11], + matrix[12], + matrix[13], + matrix[14], + matrix[15]); + } + + private void drawMeshFaces(Mesh3D mesh) { + // g.beginShape(PApplet.TRIANGLES); + // for (Face3D f : mesh.getFaces()) { + // for (int index : f.indices) { + // Vector3f v = mesh.vertices.get(index); + // g.vertex(v.getX(), v.getY(), v.getZ()); + // } + // } + // g.endShape(); + for (Face3D f : mesh.getFaces()) { + if (f.indices.length == 3) { + g.beginShape(PApplet.TRIANGLES); + } else if (f.indices.length == 4) { + g.beginShape(PApplet.QUADS); + } else { + g.beginShape(PApplet.POLYGON); + } + for (int index : f.indices) { + Vector3f v = mesh.vertices.get(index); + g.vertex(v.getX(), v.getY(), v.getZ()); + } + g.endShape(PApplet.CLOSE); + } + } + + @Override + public int getWidth() { + return g.width; + } + + @Override + public int getHeight() { + return g.height; + } + + private void stroke() { + g.stroke(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()); + } + + private void fill() { + g.fill(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()); + } + + @Override + public void pushMatrix() { + g.pushMatrix(); + } + + @Override + public void popMatrix() { + g.popMatrix(); + } + + @Override + public void scale(float sx, float sy, float sz) { + g.scale(sx, sy, sz); + } + + @Override + public void scale(float sx, float sy) { + g.scale(sx, sy); + } + + @Override + public void translate(float x, float y) { + g.translate(x, y, 0); + } + + @Override + public void translate(float x, float y, float z) { + g.translate(x, y, z); + } + + @Override + public void strokeWeight(float weight) { + g.strokeWeight(weight); + } + + @Override + public void setColor(Color color) { + this.color = color; + } + + @Override + public void setColor(int red, int green, int blue) { + color = new Color(red, green, blue); + } + + @Override + public void setColor(math.Color color) { + this.color = + new Color(color.getRedInt(), color.getGreenInt(), color.getBlueInt(), color.getAlphaInt()); + } + + @Override + public void drawRect(float x, float y, float width, float height) { + g.pushStyle(); + g.noFill(); + stroke(); + g.rectMode(PApplet.CORNER); + g.rect(x, y, width, height); + g.popStyle(); + } + + @Override + public void fillRect(float x, float y, float width, float height) { + g.pushStyle(); + g.noStroke(); + fill(); + g.rectMode(PApplet.CORNER); + g.rect(x, y, width, height); + g.popStyle(); + } + + @Override + public void drawRoundRect(float x, float y, float width, float height, float radii) { + g.pushStyle(); + g.noFill(); + stroke(); + g.rectMode(PApplet.CORNER); + g.rect(x, y, width, height, radii); + g.popStyle(); + } + + @Override + public void fillRoundRect(float x, float y, float width, float height, float radii) { + g.pushStyle(); + g.noStroke(); + fill(); + g.rectMode(PApplet.CORNER); + g.rect(x, y, width, height, radii); + g.popStyle(); + } + + @Override + public void drawLine(float x1, float y1, float x2, float y2) { + g.pushStyle(); + g.noFill(); + stroke(); + g.line(x1, y1, x2, y2); + g.popStyle(); + } + + @Override + public void drawLine(float x1, float y1, float z1, float x2, float y2, float z2) { + g.pushStyle(); + g.noFill(); + stroke(); + g.line(x1, y1, z1, x2, y2, z2); + g.popStyle(); + } + + @Override + public void drawOval(float x, float y, float width, float height) { + g.pushStyle(); + g.noFill(); + stroke(); + g.ellipseMode(PApplet.CORNER); + g.ellipse(x, y, height, width); + g.popStyle(); + } + + @Override + public void fillOval(float x, float y, float width, float height) { + g.pushStyle(); + g.noStroke(); + fill(); + g.ellipseMode(PApplet.CORNER); + g.ellipse(x, y, height, width); + g.popStyle(); + } + + @Override + public void textSize(float size) { + g.textSize(size); + } + + @Override + public float getTextSize() { + return g.textSize; + } + + @Override + public float textWidth(String text) { + return g.textWidth(text); + } + + @Override + public float textAscent() { + return g.textAscent(); + } + + @Override + public float textDescent() { + return g.textDescent(); + } + + @Override + public void text(String text, float x, float y) { + fill(); + g.text(text, x, y); + } + + @Override + public void enableDepthTest() { + g.hint(PApplet.ENABLE_DEPTH_TEST); + } + + @Override + public void disableDepthTest() { + g.hint(PApplet.DISABLE_DEPTH_TEST); + } + + @Override + public void rotate(float angle) { + g.rotate(angle); + } + + @Override + public void rotateX(float angle) { + g.rotateX(angle); + } + + @Override + public void rotateY(float angle) { + g.rotateY(angle); + } + + @Override + public void rotateZ(float angle) { + g.rotate(angle); + } + + public void camera() { + g.camera(); + // // Push the current transformation state + // pushMatrix(); + // + // // Set up orthographic projection for 2D UI rendering + // // Adjust these parameters depending on your screen size or UI layout + // float left = 0; + // float right = getWidth(); + // float bottom = getHeight(); + // float top = 0; + // float near = -1; + // float far = 1; + // + // Matrix4f orthoProjection = new Matrix4f().setOrtho(left, right, bottom, top, + // near, far); + // setProjectionMatrix(orthoProjection); + // + // // Disable depth testing to ensure 2D UI renders on top + // disableDepthTest(); + } + + // @Override + // public void setMaterial(Material material) { + // if (material == null) { + // System.err.println("Warning: Null material passed to setMaterial()."); + // return; + // } + // + // // Extract material properties + // math.Color color = material.getColor(); + // float[] ambient = material.getAmbient(); + // float[] diffuse = material.getDiffuse(); // NEW + // float[] specular = material.getSpecular(); + // float shininess = material.getShininess(); + // + // // Ensure ambient, diffuse, and specular arrays are valid + // if (ambient == null || ambient.length < 3) { + // ambient = new float[] { 0.2f, 0.2f, 0.2f }; // Default ambient + // System.err + // .println("Warning: Material ambient property is null or incomplete."); + // } + // if (diffuse == null || diffuse.length < 3) { + // diffuse = new float[] { 1.0f, 1.0f, 1.0f }; // Default diffuse + // System.err + // .println("Warning: Material diffuse property is null or incomplete."); + // } + // if (specular == null || specular.length < 3) { + // specular = new float[] { 1.0f, 1.0f, 1.0f }; // Default specular + // System.err.println( + // "Warning: Material specular property is null or incomplete."); + // } + // + // // Apply material properties + // setColor(color != null ? color : math.Color.WHITE); // Default to white + // + // math.Color ambientColor = new math.Color(this.ambientColor); + // ambientColor.multLocal(ambient[0], ambient[1], ambient[2], 1); + // ambientColor.clampLocal(); + // + // g.ambient(ambientColor.getRedInt(), ambientColor.getGreenInt(), + // ambientColor.getBlueInt()); + // // Set diffuse indirectly using fill() for now + // // FIXME + // // g.fill(diffuse[0] * 255, diffuse[1] * 255, diffuse[2] * 255); + // g.specular(specular[0], specular[1], specular[2]); + // g.shininess(shininess); + // } + + @Override + public void setMaterial(Material material) { + if (material == null) { + System.err.println("Warning: Null material passed to setMaterial()."); + return; + } + + // Extract material properties + math.Color color = material.getColor(); + float[] ambient = material.getAmbient(); + float[] diffuse = material.getDiffuse(); + float[] specular = material.getSpecular(); + float shininess = material.getShininess(); + + // Validate and set defaults for ambient, diffuse, and specular arrays + if (ambient == null || ambient.length < 3) { + ambient = new float[] {0.2f, 0.2f, 0.2f}; // Default ambient + System.err.println( + "Warning: Material ambient property is null or incomplete. Using default."); + } + if (diffuse == null || diffuse.length < 3) { + diffuse = new float[] {1.0f, 1.0f, 1.0f}; // Default diffuse + System.err.println( + "Warning: Material diffuse property is null or incomplete. Using default."); + } + if (specular == null || specular.length < 3) { + specular = new float[] {1.0f, 1.0f, 1.0f}; // Default specular + System.err.println( + "Warning: Material specular property is null or incomplete. Using default."); + } + + // Apply material properties + setColor(color != null ? color : math.Color.WHITE); // Default to white + + // Calculate and apply ambient color + math.Color ambientColor = new math.Color(this.ambientColor); + ambientColor.multLocal(ambient[0], ambient[1], ambient[2], 1.0f); + ambientColor.clampLocal(); + g.ambient(ambientColor.getRedInt(), ambientColor.getGreenInt(), ambientColor.getBlueInt()); + + // Set diffuse color + math.Color diffuseColor = new math.Color(color != null ? color : math.Color.WHITE); + diffuseColor.multLocal(diffuse[0], diffuse[1], diffuse[2], 1.0f); + diffuseColor.clampLocal(); + g.fill(diffuseColor.getRedInt(), diffuseColor.getGreenInt(), diffuseColor.getBlueInt()); + + // Set specular and shininess properties + g.specular(specular[0] * 255, specular[1] * 255, specular[2] * 255); + g.shininess(shininess); + } + + @Override + public void setShader(String vertexShaderName, String fragmentShaderName) { + try { + // Correctly load the shader using Processing's loadShader + PShader shader = g.loadShader("shaders/" + vertexShaderName, "shaders/" + fragmentShaderName); + + if (shader == null) { + System.err.println( + "Failed to load shader: " + vertexShaderName + ", " + fragmentShaderName); + } else { + g.shader(shader); // Apply shader to PGraphics + System.out.println("Shader applied successfully."); + } + } catch (Exception e) { + System.err.println("Error while loading shader: " + e.getMessage()); + e.printStackTrace(); + } + } + + @Override + public void lightsOff() { + g.noLights(); + } + + @Override + public void render(Light light) { + light.render(lightRenderer); + light.render(lightGizmoRenderer); + } + + @Override + public void lookAt(Vector3f eye, Vector3f target, Vector3f up) { + // g.camera(eye.x, eye.y, eye.z, target.x, target.y, target.z, up.x, up.y, + // up.z); + } + + @Override + public void applyMatrix(Matrix4f matrix) { + if (matrix == null) return; + float[] values = matrix.getValues(); + g.applyMatrix( + values[0], + values[1], + values[2], + values[3], + values[4], + values[5], + values[6], + values[7], + values[8], + values[9], + values[10], + values[11], + values[12], + values[13], + values[14], + values[15]); + } + + @Override + public void applyCamera(Camera camera) { + if (camera == null) { + throw new IllegalArgumentException("Camera instance cannot be null."); + } + applyMatrix(camera.getViewMatrix()); + + // // Get camera parameters + // Vector3f position = camera.getPosition(); + // float pitch = camera.getPitch(); + // float yaw = camera.getYaw(); + // Matrix4f viewMatrix = camera.getViewMatrix(); + Matrix4f projectionMatrix = camera.getProjectionMatrix(); + // + // // Set the view matrix + // g.camera( + // position.x, position.y, position.z, // Camera position + // position.x + (float) Math.cos(yaw), // Look at position (X) + // position.y + (float) Math.sin(pitch), // Look at position (Y) + // position.z + (float) Math.sin(yaw), // Look at position (Z) + // 0, 1, 0 // Up vector + // ); + // + // Set the projection matrix (if applicable) + if (projectionMatrix != null) { + // applyProjectionMatrix(projectionMatrix); + } + } + + private void applyProjectionMatrix(Matrix4f projectionMatrix) { + float near = projectionMatrix.get(2, 3) / (projectionMatrix.get(2, 2) - 1); + float far = projectionMatrix.get(3, 2) / (projectionMatrix.get(2, 2) + 1); + + float left = near * (projectionMatrix.get(2, 0) - 1) / projectionMatrix.get(0, 0); + float right = near * (projectionMatrix.get(2, 0) + 1) / projectionMatrix.get(0, 0); + float bottom = near * (projectionMatrix.get(2, 1) - 1) / projectionMatrix.get(1, 1); + float top = near * (projectionMatrix.get(2, 1) + 1) / projectionMatrix.get(1, 1); + + g.frustum(left, right, bottom, top, near, far); + } + + // @Override + // public void setViewMatrix(Matrix4f viewMatrix) { + //// float[] viewValues = viewMatrix.getValues(); + //// g.applyMatrix(viewValues[0], viewValues[1], viewValues[2], viewValues[3], + //// viewValues[4], viewValues[5], viewValues[6], viewValues[7], + //// viewValues[8], viewValues[9], viewValues[10], viewValues[11], + //// viewValues[12], viewValues[13], viewValues[14], viewValues[15]); + ////// g.camera(0, 0, 100, 0, 0, 0, 0, 1, 0); + // } + // + // @Override + // public void setProjectionMatrix(Matrix4f projectionMatrix) { + //// float[] projectionValues = projectionMatrix.getValues(); + //// g.applyMatrix(projectionValues[0], projectionValues[1], projectionValues[2], + //// projectionValues[3], projectionValues[4], projectionValues[5], + //// projectionValues[6], projectionValues[7], projectionValues[8], + //// projectionValues[9], projectionValues[10], projectionValues[11], + //// projectionValues[12], projectionValues[13], projectionValues[14], + //// projectionValues[15]); + // } } diff --git a/src/main/java/workspace/Workspace.java b/src/main/java/workspace/Workspace.java index 266a8333..ac1cfe41 100644 --- a/src/main/java/workspace/Workspace.java +++ b/src/main/java/workspace/Workspace.java @@ -17,479 +17,466 @@ public class Workspace extends Editor implements ModelListener { - int vertices; - - int faces; - - private PApplet p; - - private Mesh3DRenderer renderer; - - private FirstPersonView firstPersonView; - - private ObjectSelectionRender selectionRender; - - private SceneObject selectedObject; - - private boolean select; - - private Grid3D grid; - - private Axis3D axis; - - private GraphicsPImpl gImpl; - - public Workspace(PApplet p) { - grid = new Grid3D(32, 32, 1); - axis = new Axis3D(1); - this.p = p; - registerMethods(); - firstPersonView = new FirstPersonView(p); - renderer = new Mesh3DRenderer(p); - selectionRender = new ObjectSelectionRender(p); - refreshLoopPreference(); - model.addListener(this); - gImpl = new GraphicsPImpl(p); - } - - private void registerMethods() { - p.registerMethod("pre", this); - p.registerMethod("draw", this); - p.registerMethod("post", this); - p.registerMethod("mouseEvent", this); - p.registerMethod("keyEvent", this); - } - - @Override - public void onModelChanged() { - super.onModelChanged(); - refreshLoopPreference(); - gizmo.setRotation(new Vector3f(model.getRotationX(), model.getRotationY(), - model.getRotationZ())); - if (!isLoop()) - p.redraw(); - } - - protected void refreshLoopPreference() { - if (!isLoop()) { - p.noLoop(); - } else { - p.loop(); - } - } - - public void applyTransformations() { - if (firstPersonView.isEnabled()) { - firstPersonView.apply(); - p.scale(getScale()); - } else { - p.translate(p.width / 2, p.height / 2); - p.translate(getPanningX(), getPanningY()); - p.scale(getScale()); - p.rotateX(getRotationX()); - p.rotateY(getRotationY()); - p.rotateZ(getRotationZ()); - } - } - - public void applyCamera() { - firstPersonView.apply(); - } - - public void drawGrid() { - grid.setVisible(model.isGridVisible()); - grid.render(gImpl); - } - - protected void drawAxis(float size) { - axis.setSize(size); - axis.setXAxisVisible(isxAxisVisible()); - axis.setYAxisVisible(isyAxisVisible()); - axis.setZAxisVisible(iszAxisVisible()); - axis.render(gImpl, getScale()); - } - - public void pre() { - resize(0, 0, p.width, p.height); - vertices = 0; - faces = 0; - p.background(getBackground().getRGBA()); - p.lights(); - applyTransformations(); - p.strokeWeight(1 / getScale()); - drawGrid(); - drawAxis(2000); - } - - protected void disableDepthTestFor2dDrawing() { - p.hint(PApplet.DISABLE_DEPTH_TEST); - } - - protected void enableDepthTestFor3dDrawing() { - p.hint(PApplet.ENABLE_DEPTH_TEST); - } - - protected void drawUI() { - disableDepthTestFor2dDrawing(); - p.camera(); - p.noLights(); - rootUi.render(gImpl); - enableDepthTestFor3dDrawing(); - } - - public void draw() { - drawSelection(); - drawSceneObjects(); - - if (selectedObject != null) { - p.fill(255); - renderer.drawFaces(selectedObject.getMesh()); - } - - drawUI(); - - menu.setText(getInformationString()); - - // Debug code -// p.pushMatrix(); -// p.camera(); -// p.hint(PApplet.DISABLE_DEPTH_TEST); -// selectionRender.drawColorBuffer(); -// p.hint(PApplet.ENABLE_DEPTH_TEST); -// p.popMatrix(); - } - - private void drawSelection() { - selectionRender.draw(sceneObjects); - } - - public void drawSceneObjects() { - for (SceneObject sceneObject : sceneObjects) { - draw(sceneObject.getMesh(), sceneObject.getFillColor()); - } - } - - public void draw(Mesh3D mesh, Color color) { - p.pushStyle(); - vertices = mesh.vertices.size(); - faces = mesh.faces.size(); - - if (!isWireframe()) { - if (isEdgesVisible()) { - p.stroke(0); - } else { - p.noStroke(); - } - p.fill(color.getRed(), color.getGreen(), color.getBlue(), - color.getAlpha()); - renderer.drawFaces(mesh, mesh.faces, getShading()); - } else { - p.stroke( - UiValues.getColor(UiConstants.KEY_EDITOR_WIREFRAME_COLOR).getRGBA()); - renderer.drawEdges(mesh); - } - -// if (isEdgesVisible()) { -// p.noFill(); -// renderer.drawEdges(mesh); -// } - - if (isFaceNormalsVisible()) { - p.stroke(255); - renderer.drawFaceNormals(mesh); - } - - if (isVertexNormalsVisible()) { - p.stroke(35, 97, 221); - VertexNormals normals = new VertexNormals(mesh); - renderer.drawVertexNormals(mesh, normals.getVertexNormals()); - } - p.popStyle(); - } - - public void drawVertices(Mesh3D mesh) { - p.pushStyle(); - p.stroke(255); - p.fill(255); - p.strokeWeight(0.08f); - renderer.drawVertices(mesh); - p.popStyle(); - } - - public void draw(Mesh3D mesh) { - draw(mesh, new Color(220, 220, 220)); - } - - public void post() { -// p.saveFrame("output/workspace/workspace_demo####.png"); - } - - protected void onMouseDragged() { - if (p.mouseButton != 3) - return; - float rx = getRotationX() + (p.pmouseY - p.mouseY) * PApplet.TWO_PI / 1000; - float ry = getRotationY() - (p.pmouseX - p.mouseX) * PApplet.TWO_PI / 1000; - setRotation(rx, ry, 0); - } - - protected void onShiftMouseDragged() { - if (p.mouseButton != 3) - return; - float panningX = getPanningX() - ((p.pmouseX - p.mouseX) * 2); - float panningY = getPanningY() - ((p.pmouseY - p.mouseY) * 2); - setPanningX(panningX); - setPanningY(panningY); - } - - private void handleSelection(int x, int y) { - SceneObject sceneObject = null; - String sceneObjectName = selectionRender.getObject(x, y); - - if (sceneObjectName != null) { - for (SceneObject o : sceneObjects) { - if (o.getName().equals(sceneObjectName)) { - sceneObject = o; - break; - } - } - } - selectedObject = sceneObject; - } - - /** - * - * @param e - */ - public void mouseEvent(MouseEvent e) { - int action = e.getAction(); - - switch (action) { - case MouseEvent.CLICK: - select = true; - handleMouseClicked(e.getX(), e.getY()); - break; - case MouseEvent.DRAG: - handleMouseDragged(e.getX(), e.getY()); - if (e.isShiftDown()) { - onShiftMouseDragged(); - break; - } - onMouseDragged(); - break; - case MouseEvent.WHEEL: - handleMouseWheel(e.getCount()); - break; - case MouseEvent.RELEASE: - handleMouseReleased(e.getX(), e.getY()); - break; - case MouseEvent.PRESS: - handleMousePressed(e.getX(), e.getY()); - break; - } - // Model? - if (!isLoop()) - p.redraw(); - } - - public void keyEvent(KeyEvent e) { - if (!isUseKeyBindings()) - return; - - if (e.getAction() != KeyEvent.TYPE) - return; - - switch (e.getKey()) { - case '4': - if (!firstPersonView.isEnabled()) { - setLoop(true); - } else { - p.redraw(); - } - firstPersonView.setEnabled(!firstPersonView.isEnabled()); - commands.getCommand('s').setEnabled(!firstPersonView.isEnabled()); - break; - default: - commands.execute(e.getKey()); - break; - } - } - - protected String getInformationString() { - StringBuffer buffer = new StringBuffer(); - buffer.append("Verts:"); - buffer.append(vertices); - buffer.append(" | Faces:"); - buffer.append(faces); - buffer.append(" | FPS:"); - buffer.append(p.frameRate); - buffer.append(" | FrameCount:"); - buffer.append(p.frameCount); - return buffer.toString(); - } - - public Mesh3DRenderer getRenderer() { - return renderer; - } - - public SceneObject getSceneObject(int mouseX, int mouseY) { - String objectName = selectionRender.getObject(mouseX, mouseY); - - if (objectName == null) - return null; - for (SceneObject sceneObject : sceneObjects) { - if (sceneObject.getName().equals(objectName)) { - return sceneObject; - } - } - return null; - } - - public float getPanningX() { - return model.getPanningX(); - } - - public void setPanningX(float panningX) { - model.setPanningX(panningX); - } - - public float getPanningY() { - return model.getPanningY(); - } - - public void setPanningY(float panningY) { - model.setPanningY(panningY); - } - - public float getRotationX() { - return model.getRotationX(); - } - - public float getRotationY() { - return model.getRotationY(); - } - - public float getRotationZ() { - return model.getRotationZ(); - } - - public void setRotation(float rx, float ry, float rz) { - model.setRotation(rx, ry, rz); - } - - public float getScale() { - return model.getScale(); - } - - public void setScale(float scale) { - model.setScale(scale); - } - - public WorkspaceModel getModel() { - return model; - } - - public boolean isxAxisVisible() { - return model.isxAxisVisible(); - } - - public void setxAxisVisible(boolean xAxisVisible) { - model.setxAxisVisible(xAxisVisible); - } - - public boolean isyAxisVisible() { - return model.isyAxisVisible(); - } - - public void setyAxisVisible(boolean yAxisVisible) { - model.setyAxisVisible(yAxisVisible); - } - - public boolean iszAxisVisible() { - return model.iszAxisVisible(); - } - - public void setzAxisVisible(boolean zAxisVisible) { - model.setzAxisVisible(zAxisVisible); - } - - public boolean isGridVisible() { - return model.isGridVisible(); - } - - public void setGridVisible(boolean gridVisible) { - model.setGridVisible(gridVisible); - } - - public boolean isFaceNormalsVisible() { - return model.isFaceNormalsVisible(); - } - - public void setFaceNormalsVisible(boolean faceNormalsVisible) { - model.setFaceNormalsVisible(faceNormalsVisible); - } - - public boolean isVertexNormalsVisible() { - return model.isVertexNormalsVisible(); - } - - public void setVertexNormalsVisible(boolean vertexNormalsVisible) { - model.setVertexNormalsVisible(vertexNormalsVisible); - } - - public boolean isEdgesVisible() { - return model.isEdgesVisible(); - } + int vertices; + + int faces; + + private PApplet p; + + private Mesh3DRenderer renderer; + + private FirstPersonView firstPersonView; + + private ObjectSelectionRender selectionRender; + + private SceneObject selectedObject; + + private boolean select; + + private Grid3D grid; + + private Axis3D axis; + + private GraphicsPImpl gImpl; + + public Workspace(PApplet p) { + grid = new Grid3D(32, 32, 1); + axis = new Axis3D(1); + this.p = p; + registerMethods(); + firstPersonView = new FirstPersonView(p); + renderer = new Mesh3DRenderer(p); + selectionRender = new ObjectSelectionRender(p); + refreshLoopPreference(); + model.addListener(this); + gImpl = new GraphicsPImpl(p); + } + + private void registerMethods() { + p.registerMethod("pre", this); + p.registerMethod("draw", this); + p.registerMethod("post", this); + p.registerMethod("mouseEvent", this); + p.registerMethod("keyEvent", this); + } + + @Override + public void onModelChanged() { + super.onModelChanged(); + refreshLoopPreference(); + gizmo.setRotation( + new Vector3f(model.getRotationX(), model.getRotationY(), model.getRotationZ())); + if (!isLoop()) p.redraw(); + } + + protected void refreshLoopPreference() { + if (!isLoop()) { + p.noLoop(); + } else { + p.loop(); + } + } + + public void applyTransformations() { + if (firstPersonView.isEnabled()) { + firstPersonView.apply(); + p.scale(getScale()); + } else { + p.translate(p.width / 2, p.height / 2); + p.translate(getPanningX(), getPanningY()); + p.scale(getScale()); + p.rotateX(getRotationX()); + p.rotateY(getRotationY()); + p.rotateZ(getRotationZ()); + } + } + + public void applyCamera() { + firstPersonView.apply(); + } + + public void drawGrid() { + grid.setVisible(model.isGridVisible()); + grid.render(gImpl); + } + + protected void drawAxis(float size) { + axis.setSize(size); + axis.setXAxisVisible(isxAxisVisible()); + axis.setYAxisVisible(isyAxisVisible()); + axis.setZAxisVisible(iszAxisVisible()); + axis.render(gImpl, getScale()); + } + + public void pre() { + resize(0, 0, p.width, p.height); + vertices = 0; + faces = 0; + p.background(getBackground().getRGBA()); + p.lights(); + applyTransformations(); + p.strokeWeight(1 / getScale()); + drawGrid(); + drawAxis(2000); + } + + protected void disableDepthTestFor2dDrawing() { + p.hint(PApplet.DISABLE_DEPTH_TEST); + } + + protected void enableDepthTestFor3dDrawing() { + p.hint(PApplet.ENABLE_DEPTH_TEST); + } + + protected void drawUI() { + disableDepthTestFor2dDrawing(); + p.camera(); + p.noLights(); + rootUi.render(gImpl); + enableDepthTestFor3dDrawing(); + } + + public void draw() { + drawSelection(); + drawSceneObjects(); + + if (selectedObject != null) { + p.fill(255); + renderer.drawFaces(selectedObject.getMesh()); + } + + drawUI(); + + menu.setText(getInformationString()); + + // Debug code + // p.pushMatrix(); + // p.camera(); + // p.hint(PApplet.DISABLE_DEPTH_TEST); + // selectionRender.drawColorBuffer(); + // p.hint(PApplet.ENABLE_DEPTH_TEST); + // p.popMatrix(); + } + + private void drawSelection() { + selectionRender.draw(sceneObjects); + } + + public void drawSceneObjects() { + for (SceneObject sceneObject : sceneObjects) { + draw(sceneObject.getMesh(), sceneObject.getFillColor()); + } + } + + public void draw(Mesh3D mesh, Color color) { + p.pushStyle(); + vertices = mesh.vertices.size(); + faces = mesh.faces.size(); + + if (!isWireframe()) { + if (isEdgesVisible()) { + p.stroke(0); + } else { + p.noStroke(); + } + p.fill(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()); + renderer.drawFaces(mesh, mesh.faces, getShading()); + } else { + p.stroke(UiValues.getColor(UiConstants.KEY_EDITOR_WIREFRAME_COLOR).getRGBA()); + renderer.drawEdges(mesh); + } + + // if (isEdgesVisible()) { + // p.noFill(); + // renderer.drawEdges(mesh); + // } + + if (isFaceNormalsVisible()) { + p.stroke(255); + renderer.drawFaceNormals(mesh); + } + + if (isVertexNormalsVisible()) { + p.stroke(35, 97, 221); + VertexNormals normals = new VertexNormals(mesh); + renderer.drawVertexNormals(mesh, normals.getVertexNormals()); + } + p.popStyle(); + } + + public void drawVertices(Mesh3D mesh) { + p.pushStyle(); + p.stroke(255); + p.fill(255); + p.strokeWeight(0.08f); + renderer.drawVertices(mesh); + p.popStyle(); + } + + public void draw(Mesh3D mesh) { + draw(mesh, new Color(220, 220, 220)); + } + + public void post() { + // p.saveFrame("output/workspace/workspace_demo####.png"); + } + + protected void onMouseDragged() { + if (p.mouseButton != 3) return; + float rx = getRotationX() + (p.pmouseY - p.mouseY) * PApplet.TWO_PI / 1000; + float ry = getRotationY() - (p.pmouseX - p.mouseX) * PApplet.TWO_PI / 1000; + setRotation(rx, ry, 0); + } + + protected void onShiftMouseDragged() { + if (p.mouseButton != 3) return; + float panningX = getPanningX() - ((p.pmouseX - p.mouseX) * 2); + float panningY = getPanningY() - ((p.pmouseY - p.mouseY) * 2); + setPanningX(panningX); + setPanningY(panningY); + } + + private void handleSelection(int x, int y) { + SceneObject sceneObject = null; + String sceneObjectName = selectionRender.getObject(x, y); + + if (sceneObjectName != null) { + for (SceneObject o : sceneObjects) { + if (o.getName().equals(sceneObjectName)) { + sceneObject = o; + break; + } + } + } + selectedObject = sceneObject; + } + + /** @param e */ + public void mouseEvent(MouseEvent e) { + int action = e.getAction(); + + switch (action) { + case MouseEvent.CLICK: + select = true; + handleMouseClicked(e.getX(), e.getY()); + break; + case MouseEvent.DRAG: + handleMouseDragged(e.getX(), e.getY()); + if (e.isShiftDown()) { + onShiftMouseDragged(); + break; + } + onMouseDragged(); + break; + case MouseEvent.WHEEL: + handleMouseWheel(e.getCount()); + break; + case MouseEvent.RELEASE: + handleMouseReleased(e.getX(), e.getY()); + break; + case MouseEvent.PRESS: + handleMousePressed(e.getX(), e.getY()); + break; + } + // Model? + if (!isLoop()) p.redraw(); + } + + public void keyEvent(KeyEvent e) { + if (!isUseKeyBindings()) return; + + if (e.getAction() != KeyEvent.TYPE) return; + + switch (e.getKey()) { + case '4': + if (!firstPersonView.isEnabled()) { + setLoop(true); + } else { + p.redraw(); + } + firstPersonView.setEnabled(!firstPersonView.isEnabled()); + commands.getCommand('s').setEnabled(!firstPersonView.isEnabled()); + break; + default: + commands.execute(e.getKey()); + break; + } + } + + protected String getInformationString() { + StringBuffer buffer = new StringBuffer(); + buffer.append("Verts:"); + buffer.append(vertices); + buffer.append(" | Faces:"); + buffer.append(faces); + buffer.append(" | FPS:"); + buffer.append(p.frameRate); + buffer.append(" | FrameCount:"); + buffer.append(p.frameCount); + return buffer.toString(); + } + + public Mesh3DRenderer getRenderer() { + return renderer; + } + + public SceneObject getSceneObject(int mouseX, int mouseY) { + String objectName = selectionRender.getObject(mouseX, mouseY); + + if (objectName == null) return null; + for (SceneObject sceneObject : sceneObjects) { + if (sceneObject.getName().equals(objectName)) { + return sceneObject; + } + } + return null; + } + + public float getPanningX() { + return model.getPanningX(); + } + + public void setPanningX(float panningX) { + model.setPanningX(panningX); + } + + public float getPanningY() { + return model.getPanningY(); + } + + public void setPanningY(float panningY) { + model.setPanningY(panningY); + } + + public float getRotationX() { + return model.getRotationX(); + } + + public float getRotationY() { + return model.getRotationY(); + } + + public float getRotationZ() { + return model.getRotationZ(); + } + + public void setRotation(float rx, float ry, float rz) { + model.setRotation(rx, ry, rz); + } + + public float getScale() { + return model.getScale(); + } + + public void setScale(float scale) { + model.setScale(scale); + } + + public WorkspaceModel getModel() { + return model; + } + + public boolean isxAxisVisible() { + return model.isxAxisVisible(); + } + + public void setxAxisVisible(boolean xAxisVisible) { + model.setxAxisVisible(xAxisVisible); + } + + public boolean isyAxisVisible() { + return model.isyAxisVisible(); + } + + public void setyAxisVisible(boolean yAxisVisible) { + model.setyAxisVisible(yAxisVisible); + } + + public boolean iszAxisVisible() { + return model.iszAxisVisible(); + } + + public void setzAxisVisible(boolean zAxisVisible) { + model.setzAxisVisible(zAxisVisible); + } + + public boolean isGridVisible() { + return model.isGridVisible(); + } + + public void setGridVisible(boolean gridVisible) { + model.setGridVisible(gridVisible); + } + + public boolean isFaceNormalsVisible() { + return model.isFaceNormalsVisible(); + } + + public void setFaceNormalsVisible(boolean faceNormalsVisible) { + model.setFaceNormalsVisible(faceNormalsVisible); + } + + public boolean isVertexNormalsVisible() { + return model.isVertexNormalsVisible(); + } + + public void setVertexNormalsVisible(boolean vertexNormalsVisible) { + model.setVertexNormalsVisible(vertexNormalsVisible); + } + + public boolean isEdgesVisible() { + return model.isEdgesVisible(); + } + + public void setEdgesVisible(boolean edgesVisible) { + model.setEdgesVisible(edgesVisible); + } - public void setEdgesVisible(boolean edgesVisible) { - model.setEdgesVisible(edgesVisible); - } + public boolean isUiVisible() { + return model.isUiVisible(); + } - public boolean isUiVisible() { - return model.isUiVisible(); - } + public void setUiVisible(boolean uiVisible) { + model.setUiVisible(uiVisible); + } - public void setUiVisible(boolean uiVisible) { - model.setUiVisible(uiVisible); - } + public boolean isWireframe() { + return model.isWireframe(); + } - public boolean isWireframe() { - return model.isWireframe(); - } + public void setWireframe(boolean wireframe) { + model.setWireframe(wireframe); + } - public void setWireframe(boolean wireframe) { - model.setWireframe(wireframe); - } + public boolean isLoop() { + return model.isLoop(); + } - public boolean isLoop() { - return model.isLoop(); - } + public void setLoop(boolean loop) { + model.setLoop(loop); + } - public void setLoop(boolean loop) { - model.setLoop(loop); - } + public boolean isUseKeyBindings() { + return model.isUseKeyBindings(); + } - public boolean isUseKeyBindings() { - return model.isUseKeyBindings(); - } + public void setUseKeyBindings(boolean useKeyBindings) { + model.setUseKeyBindings(useKeyBindings); + } - public void setUseKeyBindings(boolean useKeyBindings) { - model.setUseKeyBindings(useKeyBindings); - } + public Color getBackground() { + return model.getBackground(); + } - public Color getBackground() { - return model.getBackground(); - } + public void setBackground(Color background) { + model.setBackground(background); + } - public void setBackground(Color background) { - model.setBackground(background); - } - - public Shading getShading() { - return model.getShading(); - } - - public void setShading(Shading shading) { - model.setShading(shading); - } + public Shading getShading() { + return model.getShading(); + } + public void setShading(Shading shading) { + model.setShading(shading); + } } diff --git a/src/main/java/workspace/ui/Graphics3D.java b/src/main/java/workspace/ui/Graphics3D.java index 138e6a18..7e9c4ba7 100644 --- a/src/main/java/workspace/ui/Graphics3D.java +++ b/src/main/java/workspace/ui/Graphics3D.java @@ -3,16 +3,18 @@ import java.util.List; import engine.render.Material; +import engine.scene.camera.Camera; import engine.scene.light.Light; import math.Matrix4f; +import math.Vector3f; 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); @@ -20,7 +22,7 @@ public interface Graphics3D extends Graphics2D { void rotateZ(float angle); void render(Light light); - + void fillFaces(Mesh3D mesh); void renderInstances(Mesh3D mesh, List instanceTransforms); @@ -39,22 +41,44 @@ public interface Graphics3D extends Graphics2D { void lightsOff(); - /** - * Sets the current view matrix for rendering. The view matrix transforms - * coordinates from world space to camera (view) space. - * - * @param viewMatrix The 4x4 view matrix to be applied for rendering. - */ - void setViewMatrix(Matrix4f viewMatrix); - - /** - * Sets the current projection matrix for rendering. The projection matrix - * defines how 3D coordinates are projected into the 2D viewport for rendering - * purposes. - * - * @param projectionMatrix The 4x4 projection matrix to be applied for - * rendering. - */ - void setProjectionMatrix(Matrix4f projectionMatrix); + void setWireframeMode(boolean wireframeMode); + + void lookAt(Vector3f eye, Vector3f target, Vector3f up); + + /** + * Sets the global ambient light color for the scene. + * + * @param color The color of the ambient light. Must not be null. + */ + void setAmbientColor(math.Color color); + + /** + * Gets the current global ambient light color. + * + * @return The current ambient light color. + */ + math.Color getAmbientColor(); + + void applyMatrix(Matrix4f matrix); + + void applyCamera(Camera camera); + +// /** +// * Sets the current view matrix for rendering. The view matrix transforms +// * coordinates from world space to camera (view) space. +// * +// * @param viewMatrix The 4x4 view matrix to be applied for rendering. +// */ +// void setViewMatrix(Matrix4f viewMatrix); +// +// /** +// * Sets the current projection matrix for rendering. The projection matrix +// * defines how 3D coordinates are projected into the 2D viewport for rendering +// * purposes. +// * +// * @param projectionMatrix The 4x4 projection matrix to be applied for +// * rendering. +// */ +// void setProjectionMatrix(Matrix4f projectionMatrix); }