Skip to content
77 changes: 65 additions & 12 deletions src/main/java/engine/components/ControlWASD.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@

/**
* 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.
* input (W/A/S/D) or custom key mappings. It integrates with the scene's transformation system to
* control position changes, allowing basic 3D navigation in a scene graph.
*
* <p>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.
*
* <p>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.
* <p>Example usage: Attach this component to a {@link SceneNode} to allow movement using 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 {

Expand All @@ -26,6 +26,18 @@ public class ControlWASD extends AbstractComponent {
/** The Input instance to monitor keyboard events (injected dependency). */
private Input input;

/** Key used for moving left. Default is 'A'. */
private Key leftKey = Key.A;

/** Key used for moving right. Default is 'D'. */
private Key rightKey = Key.D;

/** Key used for moving forward. Default is 'W'. */
private Key forwardKey = Key.W;

/** Key used for moving backward. Default is 'S'. */
private Key backwardKey = Key.S;

/**
* Constructs a new ControlWASD component, injecting the Input logic needed for detecting key
* presses.
Expand Down Expand Up @@ -63,7 +75,7 @@ public ControlWASD(Input input, float speed) {
/**
* Updates the movement of the owning node based on keyboard input.
*
* <p>This method calculates the velocity vector by processing the WASD input and applies it to
* <p>This method calculates the velocity vector by processing the input keys 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).
*
Expand All @@ -81,19 +93,20 @@ public void update(float 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.
* Processes keyboard input to determine velocity. Handles the configured 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);
if (input.isKeyPressed(forwardKey)) velocity.addLocal(0, 0, -1);
if (input.isKeyPressed(leftKey)) velocity.addLocal(-1, 0, 0);
if (input.isKeyPressed(backwardKey)) velocity.addLocal(0, 0, 1);
if (input.isKeyPressed(rightKey)) velocity.addLocal(1, 0, 0);

// Normalize diagonal movement to prevent unintended speed boosts
if (velocity.length() > 0) {
Expand Down Expand Up @@ -128,9 +141,49 @@ public void setSpeed(float speed) {
this.speed = speed;
}

/**
* Sets the key used to move left.
*
* @param leftKey The new key to use for left movement.
*/
public void setLeftKey(Key leftKey) {
this.leftKey = leftKey;
}

/**
* Sets the key used to move right.
*
* @param rightKey The new key to use for right movement.
*/
public void setRightKey(Key rightKey) {
this.rightKey = rightKey;
}

/**
* Sets the key used to move forward.
*
* @param forwardKey The new key to use for forward movement.
*/
public void setForwardKey(Key forwardKey) {
this.forwardKey = forwardKey;
}

/**
* Sets the key used to move backward.
*
* @param backwardKey The new key to use for backward movement.
*/
public void setBackwardKey(Key backwardKey) {
this.backwardKey = backwardKey;
}

/** Called when the component is attached to a {@link SceneNode}. Override for custom behavior. */
@Override
public void onAttach() {}

/**
* Called when the component is detached from a {@link SceneNode}. Override for custom behavior.
*/
@Override
public void onDetach() {}
}
160 changes: 160 additions & 0 deletions src/main/java/engine/components/RoundReticle.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package engine.components;

import math.Color;
import workspace.ui.Graphics;

/**
* A component that renders a round reticle in the center of the screen. The reticle consists of an
* outer circle and a smaller, filled inner circle, both customizable in terms of radius and color.
*/
public class RoundReticle extends AbstractComponent implements RenderableComponent {

private float outerRadius;

private float innerRadius;

private Color color;

/**
* Creates a default RoundReticle with preset values. Default configuration: - Outer radius: 30 -
* Inner radius: 6 - Color: White
*/
public RoundReticle() {
this(30, 6, Color.WHITE);
}

/**
* Creates a RoundReticle with specified outer radius, inner radius, and color.
*
* @param outerRadius the radius of the outer circle.
* @param innerRadius the radius of the inner filled circle.
* @param color the color of the reticle.
* @throws IllegalArgumentException if any radius is non-positive or color is null.
*/
public RoundReticle(float outerRadius, float innerRadius, Color color) {
setOuterRadius(outerRadius);
setInnerRadius(innerRadius);
setColor(color);
}

/**
* Renders the reticle on the screen, centered in the viewport. The reticle includes an outer
* circle and a filled inner circle.
*
* @param g the {@link Graphics} context used for rendering.
*/
@Override
public void render(Graphics g) {
int centerX = g.getWidth() / 2;
int centerY = g.getHeight() / 2;

// Render reticle circles
g.setColor(color);
renderCenteredOval(g, centerX, centerY, outerRadius, false);
renderCenteredOval(g, centerX, centerY, innerRadius, true);
}

/**
* Draws a centered oval at the specified position.
*
* @param g the {@link Graphics} context used for rendering.
* @param centerX the x-coordinate of the oval's center.
* @param centerY the y-coordinate of the oval's center.
* @param radius the radius of the oval.
* @param filled whether the oval should be filled.
*/
private void renderCenteredOval(
Graphics g, int centerX, int centerY, float radius, boolean filled) {
int diameter = (int) radius;
int topLeftX = centerX - diameter / 2;
int topLeftY = centerY - diameter / 2;

if (filled) {
g.fillOval(topLeftX, topLeftY, diameter, diameter);
} else {
g.drawOval(topLeftX, topLeftY, diameter, diameter);
}
}

/**
* Updates the component. Currently, this method does nothing.
*
* @param tpf time per frame, used for animations or updates.
*/
@Override
public void update(float tpf) {}

/** Called when the component is attached to a {@link engine.SceneNode}. */
@Override
public void onAttach() {}

/** Called when the component is detached from a {@link engine.SceneNode}. */
@Override
public void onDetach() {}

/**
* Gets the outer radius of the reticle.
*
* @return the outer radius.
*/
public float getOuterRadius() {
return outerRadius;
}

/**
* Sets the outer radius of the reticle.
*
* @param outerRadius the new outer radius.
* @throws IllegalArgumentException if the radius is non-positive.
*/
public void setOuterRadius(float outerRadius) {
if (outerRadius <= 0) {
throw new IllegalArgumentException("Outer radius must be greater than 0.");
}
this.outerRadius = outerRadius;
}

/**
* Gets the inner radius of the reticle.
*
* @return the inner radius.
*/
public float getInnerRadius() {
return innerRadius;
}

/**
* Sets the inner radius of the reticle.
*
* @param innerRadius the new inner radius.
* @throws IllegalArgumentException if the radius is non-positive.
*/
public void setInnerRadius(float innerRadius) {
if (innerRadius <= 0) {
throw new IllegalArgumentException("Inner radius must be greater than 0.");
}
this.innerRadius = innerRadius;
}

/**
* Gets the color of the reticle.
*
* @return the color.
*/
public Color getColor() {
return color;
}

/**
* Sets the color of the reticle.
*
* @param color the new color.
* @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;
}
}
3 changes: 3 additions & 0 deletions src/main/java/engine/debug/DebugInfoUpdater.java
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ private void updateCameraInfo(Camera camera) {
setInfo(CATEGORY_CAMERA, "FOV", Mathf.toDegrees(camera.getFieldOfView()));
setInfo(CATEGORY_CAMERA, "Near", camera.getNearPlane());
setInfo(CATEGORY_CAMERA, "Far", camera.getFarPlane());
setInfo(CATEGORY_CAMERA, "PositionX", camera.getTransform().getPosition().getX());
setInfo(CATEGORY_CAMERA, "PositionY", camera.getTransform().getPosition().getY());
setInfo(CATEGORY_CAMERA, "PositionZ", camera.getTransform().getPosition().getZ());
}

private void updateOsMetrics() {
Expand Down
20 changes: 17 additions & 3 deletions src/main/java/engine/processing/LightRendererImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,24 @@ public void render(SpotLight light) {

public void render(PointLight light) {
renderCommon(light.getColor(), light.getIntensity());

float intensity = light.getIntensity();

// Retrieve light color
float red = light.getColor().getRed(); // Gives a value from 0 to 1
float green = light.getColor().getGreen(); // Same for green
float blue = light.getColor().getBlue(); // Same for blue

// Apply intensity to each color component
red *= intensity;
green *= intensity;
blue *= intensity;

// Call pointLight() with adjusted color
p.pointLight(
light.getColor().getRedInt(),
light.getColor().getGreenInt(),
light.getColor().getBlueInt(),
red * 255, // Scale back to 0-255 range for Processing's pointLight
green * 255, // Same for green
blue * 255, // Same for blue
light.getPosition().getX(),
light.getPosition().getY(),
light.getPosition().getZ());
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/engine/processing/ProcessingTexture.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import engine.resources.FilterMode;
import engine.resources.Texture;
import engine.resources.TextureWrapMode;
import processing.core.PImage;

public class ProcessingTexture implements Texture {
Expand All @@ -10,9 +11,12 @@ public class ProcessingTexture implements Texture {

private FilterMode filterMode;

private TextureWrapMode textureWrapMode;

public ProcessingTexture(PImage image) {
this.image = image;
this.filterMode = FilterMode.BILINEAR;
this.textureWrapMode = TextureWrapMode.CLAMP;
}

@Override
Expand Down Expand Up @@ -61,6 +65,16 @@ public void setFilterMode(FilterMode filterMode) {
this.filterMode = filterMode;
}

@Override
public TextureWrapMode getTextureWrapMode() {
return textureWrapMode;
}

@Override
public void setTextureWrapMode(TextureWrapMode textureWrapMode) {
this.textureWrapMode = textureWrapMode;
}

@Override
public Texture getBackendTexture() {
return this;
Expand Down
23 changes: 23 additions & 0 deletions src/main/java/engine/resources/Texture.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,29 @@ public interface Texture {
*/
void setFilterMode(FilterMode filterMode);

/**
* Gets the current texture wrapping mode.
*
* <p>The texture wrap mode determines how the texture is applied when texture coordinates exceed
* the [0, 1] range. For example, in {@link TextureWrapMode#REPEAT}, the texture will repeat,
* whereas in {@link TextureWrapMode#CLAMP}, the texture will extend the edge pixels.
*
* @return the {@link TextureWrapMode} currently applied to the texture.
*/
TextureWrapMode getTextureWrapMode();

/**
* Sets the texture wrapping mode.
*
* <p>This method controls how the texture is mapped outside the standard [0, 1] texture
* coordinate range. Use {@link TextureWrapMode#REPEAT} to tile the texture across a surface, or
* {@link TextureWrapMode#CLAMP} to stretch the texture's edge pixels when coordinates exceed the
* valid range.
*
* @param textureWrapMode the new {@link TextureWrapMode} to apply to the texture.
*/
void setTextureWrapMode(TextureWrapMode textureWrapMode);

/**
* Gets the underlying backend texture implementation. This is useful for accessing
* engine-specific or platform-specific features.
Expand Down
Loading
Loading