diff --git a/src/main/java/engine/components/AbstractComponent.java b/src/main/java/engine/components/AbstractComponent.java index cdf4231b..a24afe86 100644 --- a/src/main/java/engine/components/AbstractComponent.java +++ b/src/main/java/engine/components/AbstractComponent.java @@ -6,20 +6,29 @@ * 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. + * reducing boilerplate code and centralizing shared logic for ease of maintenance. + * + *
Components extending this class inherit basic lifecycle management, including activation state + * handling and owner node reference management. The {@link #update(float)} method includes a check + * for the active state, ensuring that inactive components are automatically skipped during the + * update cycle. */ public abstract class AbstractComponent implements Component { - /** Reference to the owning SceneNode */ + /** Indicates whether the component is currently active. */ + protected boolean active = true; + + /** Reference to the owning {@link 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. + *
This method is called when the component is added to a {@link SceneNode}. It stores a + * reference to the owning node, which can be used for interactions with other components or scene + * graph operations. * - * @param owner The SceneNode that owns this component. + * @param owner The {@link SceneNode} that owns this component; must not be {@code null}. */ @Override public void setOwner(SceneNode owner) { @@ -27,11 +36,77 @@ public void setOwner(SceneNode owner) { } /** - * Retrieves the owning node for convenience. + * Retrieves the owning {@link SceneNode} of this component. * - * @return The owning SceneNode instance. + *
This method provides convenient access to the parent node, allowing components to interact + * with the scene graph or access shared properties such as transformations or child nodes. + * + * @return The {@link SceneNode} instance that owns this component, or {@code null} if not set. */ + @Override public SceneNode getOwner() { return owner; } + + /** + * Sets the active state of this component. + * + *
An active component participates in updates and other operations within the scene's + * lifecycle. Inactive components are effectively disabled and will not be processed during + * updates or rendering. + * + * @param active A boolean value where {@code true} sets the component as active, and {@code + * false} sets it as inactive. + */ + @Override + public void setActive(boolean active) { + this.active = active; + } + + /** + * Returns the active state of this component. + * + *
If the component is active, it will be included in updates and other scene lifecycle + * processes. Inactive components are skipped to optimize performance. + * + * @return {@code true} if the component is active, {@code false} otherwise. + */ + @Override + public boolean isActive() { + return active; + } + + /** + * Updates this component during the scene's update cycle. + * + *
This method checks if the component is active before invoking the {@link #onUpdate(float)} + * method. If the component is inactive, the update logic is skipped. + * + * @param tpf The time per frame in seconds (time delta) since the last update. + */ + @Override + public void update(float tpf) { + if (active) { + onUpdate(tpf); + } + } + + /** + * Abstract method to be implemented by subclasses to define component-specific update logic. + * + *
This method is only called if the component is active. Subclasses should implement their + * frame-specific logic here, such as animations, physics calculations, or interactions with other + * components. + * + *
Example use cases include: + * + *
Each component should manage its lifecycle, with {@code onAttach()}, {@code update()}, and - * {@code onDetach()} methods, allowing nodes to manage their behavior lifecycle cleanly. + *
Each component should manage its lifecycle with {@code onAttach()}, {@code update(float)}, and + * {@code onDetach()} methods, allowing nodes to manage component behavior efficiently. */ public interface Component { + /** + * Sets the active state of this component. + * + *
An active component will participate in updates, rendering, or other operations during the + * scene's lifecycle. Inactive components are skipped in these processes, allowing for optimized + * performance and dynamic behavior control. + * + * @param active A boolean value where {@code true} sets the component as active, and {@code + * false} sets it as inactive. + */ + void setActive(boolean active); + + /** + * Returns the active state of this component. + * + *
An active component participates in updates and other operations. If a component is + * inactive, it is effectively disabled and will not be updated or rendered by the owning {@link + * SceneNode}. + * + * @return {@code true} if the component is active, {@code false} otherwise. + */ + boolean isActive(); + + /** + * Returns the owning {@link SceneNode} of this component. + * + *
This method provides access to the {@link SceneNode} that owns this component, allowing the + * component to interact with its parent node and other components within the scene graph. + * + *
If the component has not been attached to a {@link SceneNode}, this method may return {@code + * null}. + * + * @return The {@link SceneNode} that owns this component, or {@code null} if not attached. + */ + SceneNode getOwner(); + /** * 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. + *
This method is called when the component is added to a {@link SceneNode}. The owning node + * provides context and access to the scene graph, allowing the component to interact with other + * components, transformations, and scene properties. * - * @param owner The {@link SceneNode} that owns this component; cannot be null. + * @param owner The {@link SceneNode} that owns this component; must not be {@code null}. */ void setOwner(SceneNode owner); /** - * Updates the component's logic every frame. + * Updates the component during the scene's update cycle. + * + *
This method is responsible for invoking the component's update logic if it is active. It + * checks the component's active state and then calls {@link #onUpdate(float)} to perform any + * custom behavior defined by the component. * - *
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. + *
The {@link AbstractComponent} base class typically implements this method to handle active + * state checks, ensuring that {@link #onUpdate(float)} is only called when the component is + * enabled. * - * @param tpf The time per frame in seconds (time delta) since the last update. + *
Subclasses should override {@link #onUpdate(float)} to define their specific update logic, + * rather than overriding this method directly. + * + * @param tpf The time per frame in seconds since the last update. */ void update(float tpf); + /** + * Called during the scene's update cycle to perform component-specific logic. + * + *
This method is intended to be overridden by components to define their unique behavior. + * Unlike {@code update(float)}, which is provided by the {@link AbstractComponent} base class, + * this method does not need to handle active state checks. It is only called if the component is + * active. + * + *
Implementations should ensure that the logic is efficient and avoids unnecessary + * computations to maintain performance. + * + * @param tpf The time per frame in seconds since the last update. + */ + void onUpdate(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. + *
This method provides an opportunity to perform any initialization or setup required when the + * component becomes part of a node. It can be used to register listeners, allocate resources, or + * initialize state. + * + *
Components should avoid performing heavy computations in this method to minimize performance + * impact during scene setup. */ 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. + *
This method allows the component to clean up any resources, references, or listeners that + * were established during its lifecycle. It is essential to release resources here to avoid + * memory leaks or lingering state that could impact performance or stability. + * + *
Common tasks include unregistering listeners, stopping threads, or nullifying references to + * other objects in the scene. */ void onDetach(); } diff --git a/src/main/java/engine/components/ControlWASD.java b/src/main/java/engine/components/ControlWASD.java index e8a2c67f..7986ec45 100644 --- a/src/main/java/engine/components/ControlWASD.java +++ b/src/main/java/engine/components/ControlWASD.java @@ -82,7 +82,7 @@ public ControlWASD(Input input, float speed) { * @param tpf Time per frame, used to ensure frame-rate-independent movement. */ @Override - public void update(float tpf) { + public void onUpdate(float tpf) { SceneNode node = getOwner(); Vector3f velocity = handleInput(); diff --git a/src/main/java/engine/components/FlyByCameraControl.java b/src/main/java/engine/components/FlyByCameraControl.java index e4ce9aa5..3013bb91 100644 --- a/src/main/java/engine/components/FlyByCameraControl.java +++ b/src/main/java/engine/components/FlyByCameraControl.java @@ -73,7 +73,7 @@ public FlyByCameraControl(Input input, PerspectiveCamera camera) { * @param tpf The time per frame (delta time) used for movement scaling. */ @Override - public void update(float tpf) { + public void onUpdate(float tpf) { float mouseX = input.getMouseDeltaX() * mouseSensitivity * tpf; float mouseY = input.getMouseDeltaY() * mouseSensitivity * tpf; diff --git a/src/main/java/engine/components/Geometry.java b/src/main/java/engine/components/Geometry.java index 823e599a..a500061e 100644 --- a/src/main/java/engine/components/Geometry.java +++ b/src/main/java/engine/components/Geometry.java @@ -114,21 +114,21 @@ public void debugRenderBounds(Graphics g) { 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); +// 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); } /** @@ -138,7 +138,7 @@ public void debugRenderBounds(Graphics g) { * @param tpf The time per frame used for the update (in seconds). */ @Override - public void update(float tpf) { + public void onUpdate(float tpf) { // Placeholder for potential mesh state updates } diff --git a/src/main/java/engine/components/RotationComponent.java b/src/main/java/engine/components/RotationComponent.java index f5b889f3..c73a0053 100644 --- a/src/main/java/engine/components/RotationComponent.java +++ b/src/main/java/engine/components/RotationComponent.java @@ -55,7 +55,7 @@ public RotationComponent(Vector3f axis, float angularSpeed) { * @param tpf Time per frame (in seconds since the last frame). */ @Override - public void update(float tpf) { + public void onUpdate(float tpf) { SceneNode node = getOwner(); if (node == null) return; diff --git a/src/main/java/engine/components/RoundReticle.java b/src/main/java/engine/components/RoundReticle.java index 0972da06..af7773cd 100644 --- a/src/main/java/engine/components/RoundReticle.java +++ b/src/main/java/engine/components/RoundReticle.java @@ -82,7 +82,7 @@ private void renderCenteredOval( * @param tpf time per frame, used for animations or updates. */ @Override - public void update(float tpf) {} + public void onUpdate(float tpf) {} /** Called when the component is attached to a {@link engine.SceneNode}. */ @Override diff --git a/src/main/java/engine/components/SmoothFlyByCameraControl.java b/src/main/java/engine/components/SmoothFlyByCameraControl.java index 5ff2d70e..4eb90438 100644 --- a/src/main/java/engine/components/SmoothFlyByCameraControl.java +++ b/src/main/java/engine/components/SmoothFlyByCameraControl.java @@ -72,7 +72,7 @@ public SmoothFlyByCameraControl(Input input, PerspectiveCamera camera) { * @param tpf Time per frame, used to adjust movement and smoothing. */ @Override - public void update(float tpf) { + public void onUpdate(float tpf) { float rawMouseX = input.getMouseDeltaX() * mouseSensitivity * tpf; float rawMouseY = input.getMouseDeltaY() * mouseSensitivity * tpf; diff --git a/src/main/java/engine/components/StaticGeometry.java b/src/main/java/engine/components/StaticGeometry.java index 96eb8d1a..c08a549c 100644 --- a/src/main/java/engine/components/StaticGeometry.java +++ b/src/main/java/engine/components/StaticGeometry.java @@ -75,7 +75,7 @@ public void render(Graphics g) { } @Override - public void update(float tpf) {} + public void onUpdate(float tpf) {} @Override public void onAttach() {} diff --git a/src/main/java/engine/components/Transform.java b/src/main/java/engine/components/Transform.java index 99287b3d..9dd56a98 100644 --- a/src/main/java/engine/components/Transform.java +++ b/src/main/java/engine/components/Transform.java @@ -251,8 +251,30 @@ public Vector3f getRight() { return new Vector3f(-sinY, 0, cosY).normalizeLocal(); } + /** + * Sets the forward direction of this transform. + * + *
This method calculates the rotation angles (pitch and yaw) needed to make the object face
+ * the given forward direction and updates the rotation vector accordingly.
+ *
+ * @param forward The desired forward direction as a normalized vector.
+ */
+ public void setForward(Vector3f forward) {
+ if (forward == null || forward.length() == 0) {
+ throw new IllegalArgumentException("Forward vector cannot be null or zero-length.");
+ }
+
+ // Calculate yaw (rotation around the Y-axis) and pitch (rotation around the X-axis)
+ float yaw = (float) Math.atan2(forward.z, forward.x);
+ float pitch =
+ (float) Math.atan2(forward.y, Math.sqrt(forward.x * forward.x + forward.z * forward.z));
+
+ // Update the rotation vector
+ this.rotation.set(pitch, yaw, 0);
+ }
+
@Override
- public void update(float tpf) {}
+ public void onUpdate(float tpf) {}
@Override
public void onAttach() {}
diff --git a/src/main/java/engine/demos/landmass/MapGenerator.java b/src/main/java/engine/demos/landmass/MapGenerator.java
index c04ea4c5..59566a66 100644
--- a/src/main/java/engine/demos/landmass/MapGenerator.java
+++ b/src/main/java/engine/demos/landmass/MapGenerator.java
@@ -13,10 +13,11 @@
*/
public class MapGenerator {
-// private int chunkSize = 481;
- private int chunkSize = 961;
- private int mapWidth = chunkSize;
- private int mapHeight = chunkSize;
+ private int chunkSize;
+ // private int chunkSize = 481;
+ // private int chunkSize = 961;
+ private int mapWidth;
+ private int mapHeight;
private int seed = 221;
private int octaves = 4;
private float scale = 50;
@@ -26,7 +27,10 @@ public class MapGenerator {
private TerrainType[] regions;
/** Constructs a new {@code MapGenerator} and initializes the height map and terrain regions. */
- public MapGenerator() {
+ public MapGenerator(int chunkSize) {
+ this.chunkSize = chunkSize + 1;
+ this.mapWidth = this.chunkSize;
+ this.mapHeight = this.chunkSize;
initializeRegions();
heightMap =
Noise.createHeightMap(mapWidth, mapHeight, seed, scale, octaves, persistance, lacunarity);
diff --git a/src/main/java/engine/demos/landmass/NoiseMapDisplay.java b/src/main/java/engine/demos/landmass/NoiseMapDisplay.java
index 6d5ab554..bf0b2a23 100644
--- a/src/main/java/engine/demos/landmass/NoiseMapDisplay.java
+++ b/src/main/java/engine/demos/landmass/NoiseMapDisplay.java
@@ -107,7 +107,7 @@ public void render(Graphics g) {
* @param tpf time per frame, in seconds
*/
@Override
- public void update(float tpf) {
+ public void onUpdate(float tpf) {
// No updates are required for this component
}
diff --git a/src/main/java/engine/demos/landmass/ProceduralLandmassDemo.java b/src/main/java/engine/demos/landmass/ProceduralLandmassDemo.java
index 751850c7..ee8fd3ac 100644
--- a/src/main/java/engine/demos/landmass/ProceduralLandmassDemo.java
+++ b/src/main/java/engine/demos/landmass/ProceduralLandmassDemo.java
@@ -2,9 +2,9 @@
import engine.application.ApplicationSettings;
import engine.application.BasicApplication;
-import engine.components.StaticGeometry;
import engine.components.RoundReticle;
import engine.components.SmoothFlyByCameraControl;
+import engine.components.StaticGeometry;
import engine.render.Material;
import engine.resources.Texture2D;
import engine.scene.Scene;
@@ -42,6 +42,8 @@ public enum DrawMode {
// Configuration fields
private int levelOfDetail = 0; // Level of detail for the terrain mesh (0 - 6)
+ private int chunkSize = 240; // TODO Note that the size has to fit LOD
+ private int chunkScale = 3;
private DrawMode drawMode = DrawMode.COLOR_MAP;
private Scene scene;
@@ -80,7 +82,7 @@ private void setupUI() {
/** Creates the terrain based on the selected draw mode and level of detail. */
private void createTerrain() {
- MapGenerator generator = new MapGenerator();
+ MapGenerator generator = new MapGenerator(chunkSize);
float[][] noiseMap = generator.getHeightMap();
// Create and display the noise map or color map based on the draw mode
@@ -100,13 +102,18 @@ private void createTerrain() {
// Generate the terrain mesh, apply transformations, and create a geometry node
Mesh3D terrainMesh = new TerrainMeshLOD(noiseMap, levelOfDetail).getMesh();
- terrainMesh.apply(new ScaleModifier(3));
+ terrainMesh.apply(new ScaleModifier(chunkScale));
terrainMesh.apply(new CenterAtModifier());
StaticGeometry terrainGeometry = new StaticGeometry(terrainMesh, mapMaterial);
SceneNode terrainNode = new SceneNode();
terrainNode.addComponent(terrainGeometry);
scene.addNode(terrainNode);
+
+ // Visualize chunk
+ SceneNode chunkDisplayNode = new SceneNode();
+ chunkDisplayNode.addComponent(new ChunkBoxDisplay(chunkSize * chunkScale));
+ scene.addNode(chunkDisplayNode);
}
/** Sets up the camera with smooth fly-by controls. */
diff --git a/src/main/java/engine/processing/VBOProcessing.java b/src/main/java/engine/processing/VBOProcessing.java
index 12e3b9fc..f2dcd86b 100644
--- a/src/main/java/engine/processing/VBOProcessing.java
+++ b/src/main/java/engine/processing/VBOProcessing.java
@@ -1,7 +1,6 @@
package engine.processing;
import engine.render.Material;
-import engine.resources.FilterMode;
import engine.resources.Texture;
import engine.vbo.VBO;
import math.Vector2f;
@@ -23,15 +22,16 @@ public VBOProcessing(PGraphics graphics) {
}
@Override
- public void create(Mesh3D mesh, Material material) { // TODO Auto-generated method stub
+ public void create(Mesh3D mesh, Material material) {
faceCount = mesh.getFaceCount();
vertexCount = mesh.getVertexCount();
shape = graphics.createShape();
shape.beginShape();
-
shape.noStroke();
+ // TODO Full material support
+ shape.fill(material.getColor().getRGBA());
for (Face3D f : mesh.getFaces()) {
if (f.indices.length == 3) {
@@ -41,9 +41,6 @@ public void create(Mesh3D mesh, Material material) { // TODO Auto-generated meth
} else {
shape.beginShape(PApplet.POLYGON);
}
-
- // applyTexture();
-
int[] indices = f.indices;
for (int i = 0; i < indices.length; i++) {
Vector3f v = mesh.vertices.get(f.indices[i]);
@@ -55,9 +52,7 @@ public void create(Mesh3D mesh, Material material) { // TODO Auto-generated meth
shape.vertex(v.getX(), v.getY(), v.getZ());
}
}
- // shape.endShape();
}
-
shape.endShape();
Texture texture = material.getDiffuseTexture();
@@ -68,35 +63,17 @@ public void create(Mesh3D mesh, Material material) { // TODO Auto-generated meth
}
}
- private int getSamplingMode(FilterMode filterMode) {
- switch (filterMode) {
- case POINT:
- return 2;
- case LINEAR:
- return 3;
- case BILINEAR:
- return 4;
- case TRILINEAR:
- return 5;
- default:
- System.err.println("Warning: Unexpected filter mode value: " + filterMode);
- return 4; // Default to BILINEAR
- }
- }
-
@Override
public void create(float[] vertices, int[] indices) {
+ // TODO Full implementation
// Create a PShape object for the mesh
- // shape = new PShape(PShape.POINTS, vertices.length / 3);
shape = new PShape();
-
// Load vertices into the PShape
shape.beginShape();
for (int i = 0; i < vertices.length; i += 3) {
shape.vertex(vertices[i], vertices[i + 1], vertices[i + 2]);
}
shape.endShape();
-
// Optional: store indices if using indexed rendering
vertexCount = vertices.length / 3; // Assuming vertices are in 3D (x, y, z)
}
diff --git a/src/main/java/engine/render/effects/ParticleComponent.java b/src/main/java/engine/render/effects/ParticleComponent.java
index e5012955..1cf15f25 100644
--- a/src/main/java/engine/render/effects/ParticleComponent.java
+++ b/src/main/java/engine/render/effects/ParticleComponent.java
@@ -47,7 +47,7 @@ public void onAttach() {
* time.
*/
@Override
- public void update(float tpf) {
+ public void onUpdate(float tpf) {
emitter.update(tpf);
}
diff --git a/src/main/java/engine/scene/Scene.java b/src/main/java/engine/scene/Scene.java
index 9348a9b7..b2877a44 100644
--- a/src/main/java/engine/scene/Scene.java
+++ b/src/main/java/engine/scene/Scene.java
@@ -6,9 +6,12 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import engine.scene.audio.AudioListener;
+import engine.scene.audio.AudioSystem;
import engine.scene.camera.Camera;
import engine.scene.light.Light;
import math.Color;
+import math.Vector3f;
import workspace.GraphicsPImpl;
import workspace.ui.Graphics;
@@ -44,9 +47,12 @@ public class Scene {
/** The currently active camera that determines the scene's view transformation. */
private Camera activeCamera;
+ private AudioSystem audioSystem;
+
/** Constructs a {@code Scene} with a default name. */
public Scene() {
this(DEFAULT_NAME);
+ audioSystem = new AudioSystem();
}
/**
@@ -112,7 +118,22 @@ public void removeNode(SceneNode node) {
*/
public void update(float deltaTime) {
for (SceneNode node : rootNodes) {
- updateExecutor.submit(() -> node.update(deltaTime));
+ // updateExecutor.submit(() -> node.update(deltaTime));
+ node.update(deltaTime);
+ }
+ updateAudio();
+ }
+
+ private void updateAudio() {
+ if (activeCamera == null) return;
+
+ AudioListener listener = new AudioListener();
+ listener.setPosition(activeCamera.getTransform().getPosition());
+ listener.setForward(activeCamera.getTransform().getForward());
+
+ audioSystem.setListener(listener);
+ for (SceneNode node : rootNodes) {
+ node.updateAudio(audioSystem);
}
}
@@ -121,12 +142,12 @@ public void update(float deltaTime) {
* compatibility with most rendering APIs.
*/
public void render(Graphics g) {
- g.clear(background);
-
if (activeCamera != null) {
g.applyCamera(activeCamera);
}
+ g.clear(background);
+
g.setWireframeMode(wireframeMode);
renderLights(g);
diff --git a/src/main/java/engine/scene/SceneNode.java b/src/main/java/engine/scene/SceneNode.java
index 64cc3808..182e65c2 100644
--- a/src/main/java/engine/scene/SceneNode.java
+++ b/src/main/java/engine/scene/SceneNode.java
@@ -6,6 +6,8 @@
import engine.components.Component;
import engine.components.RenderableComponent;
import engine.components.Transform;
+import engine.scene.audio.AudioSource;
+import engine.scene.audio.AudioSystem;
import workspace.ui.Graphics;
/**
@@ -37,12 +39,16 @@ public class SceneNode {
/** The default name assigned to a scene node if no name is provided. */
private static final String DEFAULT_NAME = "Untitled-Node";
+ private boolean active;
+
/** 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;
+ private Transform transform;
+
/** List of child nodes attached to this node. */
private List This method returns whether the node is active or not. An active node is typically involved
+ * in updates, rendering, and other game logic, whereas an inactive node may be ignored during
+ * these processes.
+ *
+ * @return {@code true} if the node is active, {@code false} if it is inactive.
+ */
+ public boolean isActive() {
+ return active;
+ }
+
+ /**
+ * Sets the active state of this node.
+ *
+ * This method changes the active state of the node. If the node's active state is already in
+ * the desired state, it does nothing to avoid unnecessary changes. When the active state is
+ * updated, it also propagates the change to the node's components and child nodes.
+ *
+ * @param active The desired active state for this node. {@code true} sets the node as active,
+ * {@code false} deactivates it.
+ */
+ public void setActive(boolean active) {
+ // Skip if already in the desired state
+ if (active == this.active) return;
+ this.active = active;
+ setComponentsActive();
+ setChildrenActive();
+ }
+
+ /**
+ * Sets the active state for all child nodes of this node.
+ *
+ * This method recursively sets the active state of each child node in the hierarchy. If the
+ * parent node is set to active, all of its child nodes will also be activated, and vice versa.
+ * This ensures that all child nodes are consistently updated with the parent node's active state.
+ */
+ protected void setChildrenActive() {
+ for (SceneNode child : children) {
+ child.setActive(active);
+ }
+ }
+
+ /**
+ * Sets the active state for all components attached to this node.
+ *
+ * This method updates the active state of each component attached to the node. This ensures
+ * that all components are either activated or deactivated in sync with the node itself, depending
+ * on the current active state of the node.
+ */
+ protected void setComponentsActive() {
+ for (Component component : components) {
+ component.setActive(active);
+ }
+ }
}
diff --git a/src/main/java/engine/scene/audio/AudioListener.java b/src/main/java/engine/scene/audio/AudioListener.java
new file mode 100644
index 00000000..b7f13989
--- /dev/null
+++ b/src/main/java/engine/scene/audio/AudioListener.java
@@ -0,0 +1,28 @@
+package engine.scene.audio;
+
+import math.Vector3f;
+
+public class AudioListener {
+ private Vector3f position;
+ private Vector3f forward;
+
+ public AudioListener() {
+ this.position = new Vector3f(0, 0, 0);
+ }
+
+ public Vector3f getPosition() {
+ return position;
+ }
+
+ public void setPosition(Vector3f position) {
+ this.position = position;
+ }
+
+ public Vector3f getForward() {
+ return forward;
+ }
+
+ public void setForward(Vector3f forward) {
+ this.forward = forward;
+ }
+}
diff --git a/src/main/java/engine/scene/audio/AudioSource.java b/src/main/java/engine/scene/audio/AudioSource.java
new file mode 100644
index 00000000..6728ebbb
--- /dev/null
+++ b/src/main/java/engine/scene/audio/AudioSource.java
@@ -0,0 +1,160 @@
+package engine.scene.audio;
+
+import engine.components.AbstractComponent;
+import math.Mathf;
+import math.Vector3f;
+
+/**
+ * Represents an audio source attached to a scene node. The position of the audio source is
+ * determined by the position of the node, and the volume is adjusted based on the listener's
+ * distance from the source.
+ */
+public class AudioSource extends AbstractComponent {
+
+ private Sound sound;
+ private float maxDistance; // Max distance for sound attenuation
+ private float volume; // Volume of the sound
+ private boolean loop; // Whether the sound should loop
+
+ /**
+ * Constructs an {@code AudioSource} with a sound and a maximum distance for attenuation.
+ *
+ * @param sound the sound to be played by this audio source.
+ * @param maxDistance the maximum distance at which the sound will be audible.
+ */
+ public AudioSource(Sound sound, float maxDistance) {
+ this.sound = sound;
+ this.maxDistance = maxDistance;
+ this.volume = 1.0f;
+ }
+
+ /** Plays the sound, either looping or once depending on the loop setting. */
+ public void play() {
+ if (sound == null) {
+ return;
+ }
+ if (loop) {
+ sound.loop();
+ } else {
+ sound.play();
+ }
+ }
+
+ /** Stops the currently playing sound. */
+ public void stop() {
+ if (sound != null) {
+ sound.stop();
+ }
+ }
+
+ /**
+ * Updates the volume of the sound based on the distance to the listener.
+ *
+ * @param audioListener The current listener (player, camera) in 3D space.
+ */
+ public void update(AudioListener audioListener) {
+ if (sound == null) return;
+
+ // Get the position of the audio source from the owner node's transform
+ Vector3f sourcePosition = getOwner().getTransform().getPosition();
+
+ // Calculate the distance between the audio source and the listener
+ float distance = audioListener.getPosition().distance(sourcePosition);
+
+ // // Update the volume based on the distance
+ float newVolume = calculateVolume(distance);
+ // Set the new volume
+ sound.setVolume(newVolume);
+
+ // Apply a basic distance attenuation (volume decreases as distance increases)
+ // float distanceFactor = Math.max(0, 1 - distance / 100.0f); // Example attenuation factor
+ // sound.setVolume(volume * distanceFactor);
+
+ // Apply directional effects based on the listener's orientation
+ Vector3f directionToSound = sourcePosition.subtract(audioListener.getPosition()).normalize();
+ float dotProduct = directionToSound.dot(audioListener.getForward()); // Directional panning
+ applyPanning(dotProduct);
+ }
+
+ private void applyPanning(float dotProduct) {
+ // Adjust panning based on the listener's orientation (dot product of listener's forward
+ // direction)
+ // Positive values (dot > 0) = sound in front of listener
+ // Negative values (dot < 0) = sound behind the listener
+
+ // Calculate the pan value: between -1 (left) and 1 (right)
+ // A simple approach is to map the dot product (-1 to 1) directly to pan
+ float pan = Mathf.clamp(dotProduct, -1.0f, 1.0f);
+
+ // Apply the pan to the sound object
+// sound.setPan(pan);
+ }
+
+ // TODO Other methods for controlling playback, volume, pitch, etc.
+
+ /**
+ * Calculates the volume of the sound based on the distance to the listener. The volume decreases
+ * with distance according to a linear attenuation model.
+ *
+ * @param distance the distance from the listener to the sound source.
+ * @return the new volume for the sound.
+ */
+ private float calculateVolume(float distance) {
+ if (distance > maxDistance) return 0.0f;
+ return 1.0f - (distance / maxDistance);
+ }
+
+ /**
+ * Sets whether the audio should loop when played.
+ *
+ * @param loop {@code true} if the sound should loop, {@code false} otherwise.
+ */
+ public void setLoop(boolean loop) {
+ this.loop = loop;
+ }
+
+ /**
+ * Sets the volume of the audio source.
+ *
+ * @param volume the new volume level (between 0.0f and 1.0f).
+ */
+ public void setVolume(float volume) {
+ this.volume = volume;
+ if (sound != null) {
+ sound.setVolume(volume);
+ }
+ }
+
+ /**
+ * Gets the current volume of the audio source.
+ *
+ * @return the volume level of the sound.
+ */
+ public float getVolume() {
+ return volume;
+ }
+
+ /**
+ * Gets the maximum distance for sound attenuation.
+ *
+ * @return the maximum distance for the sound.
+ */
+ public float getMaxDistance() {
+ return maxDistance;
+ }
+
+ @Override
+ public void onUpdate(float tpf) {
+ // Can be used for additional updates in the future.
+ }
+
+ @Override
+ public void onAttach() {
+ // Logic to handle attachment if needed.
+ }
+
+ @Override
+ public void onDetach() {
+ // Logic to handle detachment if needed.
+ }
+}
diff --git a/src/main/java/engine/scene/audio/AudioSystem.java b/src/main/java/engine/scene/audio/AudioSystem.java
new file mode 100644
index 00000000..dc25fb71
--- /dev/null
+++ b/src/main/java/engine/scene/audio/AudioSystem.java
@@ -0,0 +1,22 @@
+package engine.scene.audio;
+
+import java.util.List;
+
+public class AudioSystem {
+
+ private AudioListener listener;
+
+ public void update(List