diff --git a/src/main/java/engine/application/BasicApplication.java b/src/main/java/engine/application/BasicApplication.java index 3c3dab78..aa70cff2 100644 --- a/src/main/java/engine/application/BasicApplication.java +++ b/src/main/java/engine/application/BasicApplication.java @@ -105,8 +105,8 @@ public void update() { lastZ = input.isKeyPressed(Key.Z); } - timer.update(); + input.update(); fpsGraph.update(timer); debugInfoUpdater.update(timer, activeScene, input); @@ -139,7 +139,6 @@ public void render(Graphics g) { renderUi(g); renderDebugUi(g); - g.enableDepthTest(); } diff --git a/src/main/java/engine/processing/ProcessingMouseInput.java b/src/main/java/engine/processing/ProcessingMouseInput.java index 4799451a..aa89eb23 100644 --- a/src/main/java/engine/processing/ProcessingMouseInput.java +++ b/src/main/java/engine/processing/ProcessingMouseInput.java @@ -10,6 +10,11 @@ public class ProcessingMouseInput implements MouseInput { private final PApplet applet; private float mouseWheelDelta = 0; + + private float mouseX; + private float mouseY; + private float pMouseX; + private float pMouseY; private Robot robot; @@ -51,32 +56,32 @@ public float getScreenHeight() { @Override public float getMouseX() { - return applet.mouseX; + return mouseX; } @Override public float getMouseY() { - return applet.mouseY; + return mouseY; } @Override public float getLastMouseX() { - return applet.pmouseX; + return pMouseX; } @Override public float getLastMouseY() { - return applet.pmouseY; + return pMouseY; } @Override public float getMouseDeltaX() { - return applet.mouseX - applet.pmouseX; + return mouseX - pMouseX; } @Override public float getMouseDeltaY() { - return applet.mouseY - applet.pmouseY; + return mouseY - pMouseY; } @Override @@ -87,7 +92,12 @@ public float getMouseWheelDelta() { } @Override - public void updateMouseState() {} + public void updateMouseState() { + this.mouseX = applet.mouseX; + this.mouseY = applet.mouseY; + this.pMouseX = applet.pmouseX; + this.pMouseY = applet.pmouseY; + } @Override public void center() { diff --git a/src/main/java/engine/processing/ProcessingTexture.java b/src/main/java/engine/processing/ProcessingTexture.java index 585e6b31..3712dcba 100644 --- a/src/main/java/engine/processing/ProcessingTexture.java +++ b/src/main/java/engine/processing/ProcessingTexture.java @@ -1,5 +1,6 @@ package engine.processing; +import engine.resources.FilterMode; import engine.resources.Texture; import processing.core.PImage; @@ -7,8 +8,11 @@ public class ProcessingTexture implements Texture { private final PImage image; + private FilterMode filterMode; + public ProcessingTexture(PImage image) { this.image = image; + this.filterMode = FilterMode.BILINEAR; } @Override @@ -36,7 +40,29 @@ public void delete() { // Processing handles memory management automatically } + @Override + public void setPixels(int[] pixels) { + image.loadPixels(); + image.pixels = pixels; + image.updatePixels(); + } + public PImage getImage() { return image; } + + @Override + public FilterMode getFilterMode() { + return filterMode; + } + + @Override + public void setFilterMode(FilterMode filterMode) { + this.filterMode = filterMode; + } + + @Override + public Texture getBackendTexture() { + return this; + } } diff --git a/src/main/java/engine/processing/ProcessingTextureLoader.java b/src/main/java/engine/processing/ProcessingTextureLoader.java index b1daa208..60fd4991 100644 --- a/src/main/java/engine/processing/ProcessingTextureLoader.java +++ b/src/main/java/engine/processing/ProcessingTextureLoader.java @@ -1,5 +1,7 @@ package engine.processing; +import java.awt.Image; + import engine.resources.Texture; import engine.resources.TextureLoader; import processing.core.PApplet; @@ -24,4 +26,16 @@ public Texture loadTexture(String filePath) { ProcessingTexture texture = new ProcessingTexture(image); return texture; } + + @Override + public Texture createTexture(Image image) { + PImage pImage = new PImage(image); + return new ProcessingTexture(pImage); + } + + @Override + public Texture createTexture(int width, int height) { + PImage pImage = new PImage(width, height); + return new ProcessingTexture(pImage); + } } diff --git a/src/main/java/engine/resources/FilterMode.java b/src/main/java/engine/resources/FilterMode.java new file mode 100644 index 00000000..dd8a8387 --- /dev/null +++ b/src/main/java/engine/resources/FilterMode.java @@ -0,0 +1,51 @@ +package engine.resources; + +/** + * Enum representing the various filter modes available for texture sampling. These filter modes + * determine how textures are sampled and filtered when applied to 3D models or surfaces, affecting + * the appearance of textures when viewed at different distances or angles. + * + *

The available filter modes are: + * + *

+ * + * Each mode offers a different trade-off between performance and visual quality. + * + * @see OpenGL Wiki on Texture Filtering + */ +public enum FilterMode { + /** + * Nearest-neighbor filtering, where the closest texel (texture pixel) is chosen. Produces a + * blocky appearance when viewed from a distance. + */ + POINT, + + /** + * Linear interpolation between two nearest texels, offering smoother transitions compared to + * POINT. + */ + LINEAR, + + /** + * Bilinear interpolation that considers the four nearest texels, interpolating in both x and y + * directions. Provides smoother results than LINEAR. + */ + BILINEAR, + + /** + * Trilinear interpolation that blends between multiple mipmap levels in addition to performing + * bilinear interpolation on each level. It smooths transitions between textures at varying + * distances from the camera. + */ + TRILINEAR +} diff --git a/src/main/java/engine/resources/Texture.java b/src/main/java/engine/resources/Texture.java index 53857bc4..f4e8998c 100644 --- a/src/main/java/engine/resources/Texture.java +++ b/src/main/java/engine/resources/Texture.java @@ -11,4 +11,12 @@ public interface Texture { void unbind(); void delete(); + + void setPixels(int[] pixels); + + FilterMode getFilterMode(); + + void setFilterMode(FilterMode filterMode); + + Texture getBackendTexture(); } diff --git a/src/main/java/engine/resources/Texture2D.java b/src/main/java/engine/resources/Texture2D.java new file mode 100644 index 00000000..c8822caa --- /dev/null +++ b/src/main/java/engine/resources/Texture2D.java @@ -0,0 +1,55 @@ +package engine.resources; + +public class Texture2D implements Texture { + + private Texture texture; + + public Texture2D(int width, int height) { + texture = TextureManager.getInstance().createTexture(width, height); + } + + @Override + public int getWidth() { + return texture.getWidth(); + } + + @Override + public int getHeight() { + return texture.getHeight(); + } + + @Override + public void bind(int unit) { + texture.bind(unit); + } + + @Override + public void unbind() { + texture.unbind(); + } + + @Override + public void delete() { + texture.delete(); + } + + @Override + public void setPixels(int[] pixels) { + texture.setPixels(pixels); + } + + @Override + public FilterMode getFilterMode() { + return texture.getFilterMode(); + } + + @Override + public void setFilterMode(FilterMode filterMode) { + texture.setFilterMode(filterMode); + } + + @Override + public Texture getBackendTexture() { + return texture; + } +} diff --git a/src/main/java/engine/resources/TextureLoader.java b/src/main/java/engine/resources/TextureLoader.java index 6ea10108..b58a5e1e 100644 --- a/src/main/java/engine/resources/TextureLoader.java +++ b/src/main/java/engine/resources/TextureLoader.java @@ -1,6 +1,13 @@ package engine.resources; +import java.awt.Image; + public interface TextureLoader { Texture loadTexture(String filePath); + + Texture createTexture(Image image); + + Texture createTexture(int width, int height); + } diff --git a/src/main/java/engine/resources/TextureManager.java b/src/main/java/engine/resources/TextureManager.java index bc70210e..82f79c4a 100644 --- a/src/main/java/engine/resources/TextureManager.java +++ b/src/main/java/engine/resources/TextureManager.java @@ -1,5 +1,6 @@ package engine.resources; +import java.awt.Image; import java.util.HashMap; import java.util.Map; @@ -7,7 +8,7 @@ public class TextureManager { private static TextureManager instance; - private TextureLoader imageLoader; + private TextureLoader textureLoader; private final Map resourceCache = new HashMap<>(); @@ -21,7 +22,7 @@ public static TextureManager getInstance() { } public void setTextureLoader(TextureLoader loader) { - this.imageLoader = loader; + this.textureLoader = loader; } public Texture loadTexture(String path) { @@ -29,11 +30,11 @@ public Texture loadTexture(String path) { return resourceCache.get(path); // Return cached resource } - if (imageLoader == null) { - throw new IllegalStateException("ImageLoader is not set!"); + if (textureLoader == null) { + throw new IllegalStateException("TextureLoader is not set."); } - Texture texture = imageLoader.loadTexture(path); + Texture texture = textureLoader.loadTexture(path); resourceCache.put(path, texture); return texture; @@ -42,4 +43,12 @@ public Texture loadTexture(String path) { public void unloadImage(String path) { resourceCache.remove(path); // Optionally handle cleanup for backend-specific resources } + + public Texture createTexture(Image image) { + return textureLoader.createTexture(image); + } + + public Texture createTexture(int width, int height) { + return textureLoader.createTexture(width, height); + } } diff --git a/src/main/java/math/Mathf.java b/src/main/java/math/Mathf.java index f1cda93e..589f209f 100644 --- a/src/main/java/math/Mathf.java +++ b/src/main/java/math/Mathf.java @@ -645,6 +645,29 @@ public static float lerp(float from, float to, float t) { return from + (to - from) * clamp01(t); } + /** + * Calculates the interpolation factor of a value within a specified range. + * + *

The `inverseLerp` function computes the normalized position of a value `t` within the range + * defined by `from` and `to`. It determines how far `t` lies between `from` and `to` on a scale + * from 0 to 1. If `t` is less than `from`, it returns a negative value. If `t` is greater than + * `to`, it returns a value greater than 1. + * + * @param from The start of the range. + * @param to The end of the range. + * @param t The value to normalize within the range. + * @return The normalized position of `t` in the range `[from, to]`. + * @throws IllegalArgumentException if `from` equals `to`, as this would result in division by + * zero. + */ + public static float inverseLerp(float from, float to, float t) { + if (from == to) { + throw new IllegalArgumentException( + "The start and end of the range cannot be the same (division by zero)."); + } + return (t - from) / (to - from); + } + /** * Returns the next power of two greater than or equal to the given value. * diff --git a/src/main/java/math/Plane.java b/src/main/java/math/Plane.java index ae8d5b28..78c07fdd 100644 --- a/src/main/java/math/Plane.java +++ b/src/main/java/math/Plane.java @@ -31,6 +31,27 @@ public Plane() { this.distance = 0; } + /** + * Constructs a plane with a given normal vector and distance. + * + *

The normal vector is automatically normalized during this operation. + * + * @param normal the normal vector of the plane. + * @param distance the distance of the plane from the origin along its normal vector. + */ + public Plane(Vector3f normal, float distance) { + this.normal = new Vector3f(normal); + this.normal.normalizeLocal(); + this.distance = distance; + } + + /** Flips the orientation of the plane by inverting its normal and distance. */ + public void flip() { + normal.negateLocal(); + normal.normalizeLocal(); // Ensure the normal vector is normalized after negation + distance = distance == 0 ? 0 : -distance; // Avoid -0 + } + /** * Sets the plane parameters using its coefficients. * @@ -43,9 +64,10 @@ public Plane() { * @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; + float length = (float) Math.sqrt(a * a + b * b + c * c); + if (length == 0) throw new IllegalArgumentException("Plane normal cannot be zero."); + this.normal.set(a / length, b / length, c / length); + this.distance = d / length; // Normalize the distance as well } /** @@ -86,4 +108,9 @@ public Vector3f getNormal() { public float getDistance() { return distance; } + + @Override + public String toString() { + return "Plane [normal=" + normal + ", distance=" + distance + "]"; + } } diff --git a/src/main/java/mesh/Mesh3D.java b/src/main/java/mesh/Mesh3D.java index ede95ebd..e9daa5d5 100644 --- a/src/main/java/mesh/Mesh3D.java +++ b/src/main/java/mesh/Mesh3D.java @@ -272,6 +272,21 @@ public Face3D getFaceAt(int index) { return faces.get(index); } + /** + * Adds a UV coordinate to the mesh. + * + *

This method appends a new UV coordinate, specified by the parameters {@code u} and {@code + * v}, to the list of UV coordinates for this mesh. UV coordinates are used for mapping textures + * to the surface of the mesh, where {@code u} and {@code v} typically represent the horizontal + * and vertical texture coordinates, respectively. + * + * @param u The horizontal component of the UV coordinate. + * @param v The vertical component of the UV coordinate. + */ + public void addUvCoordinate(float u, float v) { + uvs.add(new Vector2f(u, v)); + } + /** * Sets the UV coordinates for this mesh. * diff --git a/src/main/java/workspace/GraphicsPImpl.java b/src/main/java/workspace/GraphicsPImpl.java index 35a234bf..12ceadf2 100644 --- a/src/main/java/workspace/GraphicsPImpl.java +++ b/src/main/java/workspace/GraphicsPImpl.java @@ -6,6 +6,7 @@ import engine.processing.LightRendererImpl; import engine.processing.ProcessingTexture; import engine.render.Material; +import engine.resources.FilterMode; import engine.resources.Image; import engine.resources.Texture; import engine.scene.camera.Camera; @@ -47,7 +48,7 @@ public class GraphicsPImpl implements Graphics { public static int vertexCount = 0; - private PImage texture; + private ProcessingTexture texture; @Override public void setAmbientColor(math.Color ambientColor) { @@ -133,6 +134,62 @@ private void applyTransform(Matrix4f transform) { matrix[15]); } + /** + * Applies the specified texture to the current rendering context, setting the appropriate texture + * sampling mode based on the filter mode of the texture. + * + *

This method checks the texture's filter mode and applies the corresponding texture sampling + * mode. It also ensures that the texture is properly initialized before usage. + * + *

The available filter modes are mapped to the following sampling modes: + * + *

+ * + * If an unexpected filter mode is encountered, a warning is logged, and the default BILINEAR mode + * (sampling mode 4) is applied. + * + * @throws IllegalArgumentException if the filter mode is unexpected and no fallback is defined. + * @see processing.opengl.Texture + */ + private void applyTexture() { + if (texture == null || texture.getImage() == null) { + return; // Ensure texture is properly initialized before applying it. + } + + FilterMode filterMode = texture.getFilterMode(); + int textureSampling; + + switch (filterMode) { + case POINT: + textureSampling = 2; + break; + case LINEAR: + textureSampling = 3; + break; + case BILINEAR: + textureSampling = 4; + break; + case TRILINEAR: + textureSampling = 5; + break; + default: + // Log a warning for unexpected filter modes (in case of future extensions). + System.err.println("Warning: Unexpected filter mode value: " + filterMode); + textureSampling = 4; // Default to BILINEAR if undefined + break; + } + + // Apply the texture with the corresponding sampling mode + ((PGraphicsOpenGL) g).textureSampling(textureSampling); + g.texture(texture.getImage()); + g.textureMode(PApplet.NORMAL); + } + private void drawMeshFaces(Mesh3D mesh) { for (Face3D f : mesh.getFaces()) { if (f.indices.length == 3) { @@ -143,10 +200,7 @@ private void drawMeshFaces(Mesh3D mesh) { g.beginShape(PApplet.POLYGON); } - if (texture != null) { - g.texture(texture); - g.textureMode(PApplet.NORMAL); - } + applyTexture(); int[] indices = f.indices; for (int i = 0; i < indices.length; i++) { @@ -391,8 +445,17 @@ public void setMaterial(Material material) { this.texture = null; - // Extract material properties math.Color color = material.getColor(); + // Apply material properties + setColor(color != null ? color : math.Color.WHITE); // Default to white + + if (!material.isUseLighting()) { + g.noLights(); + return; + } + + // Extract material properties + float[] ambient = material.getAmbient(); float[] diffuse = material.getDiffuse(); float[] specular = material.getSpecular(); @@ -415,9 +478,6 @@ public void setMaterial(Material material) { "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); @@ -440,8 +500,8 @@ public void bindTexture(Texture texture, int unit) { // TODO Auto-generated meth // if (unit == 1) { // g.textureMode(PApplet.NORMAL); // } - ProcessingTexture texture2 = (ProcessingTexture) texture; - this.texture = texture2.getImage(); + ProcessingTexture texture2 = (ProcessingTexture) texture.getBackendTexture(); + this.texture = texture2; } @Override diff --git a/src/main/java/workspace/ui/Graphics2D.java b/src/main/java/workspace/ui/Graphics2D.java index a1cbf700..029d2bfc 100644 --- a/src/main/java/workspace/ui/Graphics2D.java +++ b/src/main/java/workspace/ui/Graphics2D.java @@ -237,7 +237,31 @@ public interface Graphics2D { */ void clear(math.Color color); + /** + * Draws an image at the specified coordinates (x, y). + * + *

This method renders the provided image at the given position (x, y) on the screen, using the + * image's natural dimensions without scaling. + * + * @param image The {@link Image} object to render. + * @param x The x-coordinate where the image's top-left corner will be placed. + * @param y The y-coordinate where the image's top-left corner will be placed. + */ void drawImage(Image image, float x, float y); + /** + * Draws an image at the specified coordinates (x, y) and scales it to the specified width and + * height. + * + *

This method renders the provided image at the given position (x, y) on the screen, scaling + * the image to fit the specified width and height. The aspect ratio of the image may be distorted + * if the aspect ratio of the width and height differs from the image's natural dimensions. + * + * @param image The {@link Image} object to render. + * @param x The x-coordinate where the image's top-left corner will be placed. + * @param y The y-coordinate where the image's top-left corner will be placed. + * @param width The width to scale the image to. + * @param height The height to scale the image to. + */ void drawImage(Image image, float x, float y, float width, float height); }