diff options
Diffstat (limited to 'src/main/java/dev/figboot/cuberender/state/Framebuffer.java')
| -rw-r--r-- | src/main/java/dev/figboot/cuberender/state/Framebuffer.java | 162 |
1 files changed, 162 insertions, 0 deletions
diff --git a/src/main/java/dev/figboot/cuberender/state/Framebuffer.java b/src/main/java/dev/figboot/cuberender/state/Framebuffer.java new file mode 100644 index 0000000..56e218e --- /dev/null +++ b/src/main/java/dev/figboot/cuberender/state/Framebuffer.java @@ -0,0 +1,162 @@ +package dev.figboot.cuberender.state; + +import dev.figboot.cuberender.math.Matrix3f; +import dev.figboot.cuberender.math.Vector3f; +import dev.figboot.cuberender.math.Vector4f; +import lombok.Getter; +import lombok.Setter; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.Arrays; + +public class Framebuffer { + public static int FB_CLEAR_COLOR = 0x01; + public static int FB_CLEAR_DEPTH = 0x02; + + public static int FB_DEPTH_USE = 0x01; + public static int FB_DEPTH_COMMIT = 0x02; + public static int FB_DEPTH_COMMIT_TRANSPARENT = 0x04; + + @Getter private final int width, height; + + @Getter private final BufferedImage color; + private final float[] depth; + + private int depthMode = FB_DEPTH_USE | FB_DEPTH_COMMIT; + + @Setter private Matrix3f transform; + + @Setter private BlendMode blendMode = BlendMode.DISABLE; + + public Framebuffer(int width, int height) { + this.width = width; + this.height = height; + + this.color = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + depth = new float[width * height]; + } + + public void setDepthMode(int mode) { + this.depthMode = mode; + } + + public void clear(int bits, int color) { + if ((bits & FB_CLEAR_COLOR) != 0) { + Graphics gfx = this.color.getGraphics(); + gfx.setColor(new Color(color, true)); + gfx.fillRect(0, 0, width, height); + } + + if ((bits & FB_CLEAR_DEPTH) != 0) { + Arrays.fill(depth, Float.NEGATIVE_INFINITY); + } + } + + public void drawMesh(Mesh<?> mesh) { + // this seems redundant but it saves us having to check it each loop iteration + if (mesh.indices != null) { + drawIndexedMesh(mesh); + } else { + drawFlatMesh(mesh); + } + } + + @SuppressWarnings("unchecked") + private void drawIndexedMesh(Mesh<?> mesh) { + int ntris = mesh.indices.length / 3; + Sampleable<Object> s = (Sampleable<Object>)mesh; + + int i0, i1, i2; + + for (int tri = 0; tri < ntris; ++tri) { + i0 = mesh.indices[tri * 3]; + i1 = mesh.indices[tri * 3 + 1]; + i2 = mesh.indices[tri * 3 + 2]; + + Vector3f vert0 = mesh.vertices[i0]; + Vector3f vert1 = mesh.vertices[i1]; + Vector3f vert2 = mesh.vertices[i2]; + + drawTriangle(vert0, vert1, vert2, mesh.normals[tri], s, i0, i1, i2); + } + } + + @SuppressWarnings("unchecked") + private void drawFlatMesh(Mesh<?> mesh) { + int ntris = mesh.vertices.length / 3; + Sampleable<Object> s = (Sampleable<Object>)mesh; + + int i0, i1, i2; + + for (int tri = 0; tri < ntris; ++tri) { + i0 = tri * 3; + i1 = tri * 3 + 1; + i2 = tri * 3 + 2; + + Vector3f vert0 = mesh.vertices[i0]; + Vector3f vert1 = mesh.vertices[i1]; + Vector3f vert2 = mesh.vertices[i2]; + + drawTriangle(vert0, vert1, vert2, mesh.normals[tri], s, i0, i1, i2); + } + } + + private float logToScrX(float x) { + return ((x + 1f) / 2) * width; + } + + private float logToScrY(float y) { + return ((y + 1f) / 2) * height; + } + + // triangles have flat normals (we don't need anything more than that in this renderer and it saves us the trouble of interpolating between 3 normal vectors) + private void drawTriangle(Vector3f vert0, Vector3f vert1, Vector3f vert2, Vector3f normal, Sampleable<Object> sampleable, int i0, int i1, int i2) { + Vector4f outColor = new Vector4f(), prevColor = new Vector4f(); + + vert0 = transform.transform(vert0); + vert1 = transform.transform(vert1); + vert2 = transform.transform(vert2); + normal = transform.transform(normal).normalize(); + + float sx0 = logToScrX(vert0.x), sy0 = logToScrY(vert0.y); + float sx1 = logToScrX(vert1.x), sy1 = logToScrY(vert1.y); + float sx2 = logToScrX(vert2.x), sy2 = logToScrY(vert2.y); + + // optimization: Math.floor and Math.ceil convert float arguments to double + int minX = (int)Math.floor(Math.max(0, Math.min(sx0, Math.min(sx1, sx2)))); + int maxX = (int)Math.ceil(Math.min(width - 1, Math.max(sx0, Math.max(sx1, sx2)))); + + int minY = (int)Math.floor(Math.max(0, Math.min(sy0, Math.min(sy1, sy2)))); + int maxY = (int)Math.ceil(Math.min(height - 1, Math.max(sy0, Math.max(sy1, sy2)))); + + float area = (sy0 - sy2) * (sx1 - sx2) + (sy1 - sy2) * (sx2 - sx0); + + for (int y = minY; y <= maxY; ++y) { + for (int x = minX; x < maxX; ++x) { + float b0 = ((y - sy2) * (sx1 - sx2) + (sy1 - sy2) * (sx2 - x)) / area; + float b1 = ((y - sy0) * (sx2 - sx0) + (sy2 - sy0) * (sx0 - x)) / area; + float b2 = ((y - sy1) * (sx0 - sx1) + (sy0 - sy1) * (sx1 - x)) / area; + + if (b0 < 0 || b0 >= 1 || b1 < 0 || b1 >= 1 || b2 < 0 || b2 >= 1) continue; + + float z = b0 * vert0.z + b1 * vert1.z + b2 * vert2.z; + if ((depthMode & FB_DEPTH_USE) != 0 && z <= depth[y * width + x]) continue; + + if ((depthMode & FB_DEPTH_COMMIT) != 0 && outColor.w > 0) { + depth[y * width + x] = z; + } + + prevColor.fromARGB(color.getRGB(x, y)); + sampleable.sample(b0, b1, b2, normal, sampleable.extra(i0), sampleable.extra(i1), sampleable.extra(i2), outColor); + + if ((depthMode & FB_DEPTH_COMMIT_TRANSPARENT) != 0 && outColor.w > 0) { + depth[y * width + x] = z; + } + + blendMode.getFunction().blend(outColor, prevColor); + color.setRGB(x, y, outColor.toARGB()); + } + } + } +} |
