From 2113ae54df2da867f553df3a9ee457c0a3856a33 Mon Sep 17 00:00:00 2001 From: bigfoot547 Date: Fri, 14 Jun 2024 20:29:43 -0500 Subject: initial commit --- .../dev/figboot/cuberender/test/GraphicsPanel.java | 387 +++++++++++++++++++++ 1 file changed, 387 insertions(+) create mode 100644 src/main/java/dev/figboot/cuberender/test/GraphicsPanel.java (limited to 'src/main/java/dev/figboot/cuberender/test/GraphicsPanel.java') diff --git a/src/main/java/dev/figboot/cuberender/test/GraphicsPanel.java b/src/main/java/dev/figboot/cuberender/test/GraphicsPanel.java new file mode 100644 index 0000000..38dea64 --- /dev/null +++ b/src/main/java/dev/figboot/cuberender/test/GraphicsPanel.java @@ -0,0 +1,387 @@ +package dev.figboot.cuberender.test; + +import dev.figboot.cuberender.math.Matrix3f; +import dev.figboot.cuberender.math.Vector2f; +import dev.figboot.cuberender.math.Vector3f; +import dev.figboot.cuberender.state.BlendMode; +import dev.figboot.cuberender.state.Framebuffer; +import dev.figboot.cuberender.state.Mesh; +import dev.figboot.cuberender.state.Texture; + +import javax.imageio.ImageIO; +import javax.swing.*; +import java.awt.*; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.EnumMap; + +public class GraphicsPanel extends JPanel { + private Framebuffer framebuffer; + + private final EnumMap> meshes = new EnumMap<>(BodyPart.class); + private float xRot = 0, yRot = 0; + + private long[] clrTime = new long[32]; + private long[] meshTime = new long[32]; + private int tidx = 0; + boolean rollOver = false; + + private static void addCuboid(Mesh.Builder mb, float x1, float y1, float z1, float x2, float y2, float z2, float tx, float ty, float tspanX, float tspanY, float tspanZ, int ibase) { + mb.vertex(new Vector3f(x1, y1, z2), /* front */ + new Vector3f(x1, y2, z2), + new Vector3f(x2, y2, z2), + new Vector3f(x2, y1, z2), + + new Vector3f(x2, y1, z2), /* +X side */ + new Vector3f(x2, y2, z2), + new Vector3f(x2, y2, z1), + new Vector3f(x2, y1, z1), + + new Vector3f(x2, y1, z1), /* back */ + new Vector3f(x2, y2, z1), + new Vector3f(x1, y2, z1), + new Vector3f(x1, y1, z1), + + new Vector3f(x1, y1, z1), /* -X side */ + new Vector3f(x1, y2, z1), + new Vector3f(x1, y2, z2), + new Vector3f(x1, y1, z2), + + new Vector3f(x1, y1, z1), /* top */ + new Vector3f(x1, y1, z2), + new Vector3f(x2, y1, z2), + new Vector3f(x2, y1, z1), + + new Vector3f(x1, y2, z1), /* bottom */ + new Vector3f(x1, y2, z2), + new Vector3f(x2, y2, z2), + new Vector3f(x2, y2, z1)) + .normals(new Vector3f(0, 0, 1), + new Vector3f(0, 0, 1), + + new Vector3f(1, 0, 0), + new Vector3f(1, 0, 0), + + new Vector3f(0, 0, -1), + new Vector3f(0, 0, -1), + + new Vector3f(-1, 0, 0), + new Vector3f(-1, 0, 0), + + new Vector3f(0, -1, 0), + new Vector3f(0, -1, 0), + + new Vector3f(0, 1, 0), + new Vector3f(0, 1, 0)) + .texCoords(new Vector2f(tx, ty), + new Vector2f(tx, ty - tspanY), + new Vector2f(tx + tspanX, ty - tspanY), + new Vector2f(tx + tspanX, ty), + + new Vector2f(tx + tspanX, ty), + new Vector2f(tx + tspanX, ty - tspanY), + new Vector2f(tx + tspanX + tspanZ, ty - tspanY), + new Vector2f(tx + tspanX + tspanZ, ty), + + new Vector2f(tx + tspanX + tspanZ, ty), + new Vector2f(tx + tspanX + tspanZ, ty - tspanY), + new Vector2f(tx + 2 * tspanX + tspanZ, ty - tspanY), + new Vector2f(tx + 2 * tspanX + tspanZ, ty), + + new Vector2f(tx - tspanZ, ty), + new Vector2f(tx - tspanZ, ty - tspanY), + new Vector2f(tx, ty - tspanY), + new Vector2f(tx, ty), + + new Vector2f(tx, ty + tspanZ), + new Vector2f(tx, ty), + new Vector2f(tx + tspanX, ty), + new Vector2f(tx + tspanX, ty + tspanZ), + + new Vector2f(tx + tspanX, ty + tspanZ), + new Vector2f(tx + tspanX, ty), + new Vector2f(tx + 2 * tspanX, ty), + new Vector2f(tx + 2 * tspanX, ty + tspanZ)) + .indices(ibase, ibase + 1, ibase + 2, ibase, ibase + 2, ibase + 3, + ibase + 4, ibase + 5, ibase + 6, ibase + 4, ibase + 6, ibase + 7, + ibase + 8, ibase + 9, ibase + 10, ibase + 8, ibase + 10, ibase + 11, + ibase + 12, ibase + 13, ibase + 14, ibase + 12, ibase + 14, ibase + 15, + ibase + 16, ibase + 17, ibase + 18, ibase + 16, ibase + 18, ibase + 19, + ibase + 20, ibase + 21, ibase + 22, ibase + 20, ibase + 22, ibase + 23); + } + + private void copyLimbFlipped(BufferedImage src, BufferedImage target) { + for (int y = 4, maxY = src.getHeight(); y < maxY; ++y) { + for (int x = 0; x < 4; ++x) { + target.setRGB(x, y, src.getRGB(11 - x, y)); + target.setRGB(x + 4, y, src.getRGB(7 - x, y)); + target.setRGB(x + 8, y, src.getRGB(3 - x, y)); + target.setRGB(x + 12, y, src.getRGB(15 - x, y)); + } + } + + for (int y = 0; y < 4; ++y) { + for (int x = 0; x < 4; ++x) { + target.setRGB(x + 4, y, src.getRGB(7 - x, y)); + target.setRGB(x + 8, y, src.getRGB(11 - x, y)); + } + } + } + + private BufferedImage convertToModernSkin(BufferedImage bi) { + BufferedImage realBI = new BufferedImage(64, 64, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2d = realBI.createGraphics(); + + g2d.drawImage(bi, 0, 0, 64, 32, null); + + // TODO: this is incomplete. the actual game mirrors the arm (so there is always an "inside" and an "outside") + copyLimbFlipped(bi.getSubimage(0, 16, 16, 16), realBI.getSubimage(16, 48, 16, 16)); + copyLimbFlipped(bi.getSubimage(40, 16, 16, 16), realBI.getSubimage(32, 48, 16, 16)); + + try { + ImageIO.write(realBI, "PNG", new File("test.png")); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + + return realBI; + } + + private static Mesh.Builder defaultBuilder() { + return new Mesh.Builder().attach(Mesh.AttachmentType.LIGHT_FACTOR, 1f) + .attach(Mesh.AttachmentType.LIGHT_VECTOR, new Vector3f(0, 0, 1)); + } + + public GraphicsPanel() { + addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + handleResize(getWidth(), getHeight()); + } + }); + + BufferedImage bi; + try (InputStream is = getClass().getResourceAsStream("/skin2.png")) { + bi = ImageIO.read(is); + } catch (IOException e) { + throw new RuntimeException(e); + } + + BufferedImage capeBI; + try (InputStream is = getClass().getResourceAsStream("/cape.png")) { + capeBI = ImageIO.read(is); + } catch (IOException e) { + throw new RuntimeException(e); + } + + if (bi.getHeight() == 32) { + bi = convertToModernSkin(bi); + } + + Texture tex = new Texture(bi); + + Mesh.Builder bodyBuilder = defaultBuilder().texture(tex); + + // TODO: slim model + boolean slim = false; + float overlayOffset = 1/64f; + + int idxbase = -24; + // head + addCuboid(bodyBuilder, + -4 / 16f, -16 / 16f, -4 / 16f, + 4 / 16f, -8 / 16f, 4 / 16f, + 1 / 8f, 7 / 8f, + 1 / 8f, 1 / 8f, 1 / 8f, idxbase += 24); + + // torso + addCuboid(bodyBuilder, + -4 / 16f, -8 / 16f, -2 / 16f, + 4 / 16f, 4 / 16f, 2 / 16f, + 5 / 16f, 11 / 16f, + 1 / 8f, 3 / 16f, 1 / 16f, idxbase += 24); + + // left+right arm + addCuboid(bodyBuilder, + (slim ? -7 : -8) / 16f, -8 / 16f, -2 / 16f, + -4 / 16f, 4 / 16f, 2 / 16f, + 11 / 16f, 11 / 16f, + (slim ? 3 / 64f : 1 / 16f), 3 / 16f, 1 / 16f, idxbase += 24); + addCuboid(bodyBuilder, + 4 / 16f, -8 / 16f, -2 / 16f, + (slim ? 7 : 8) / 16f, 4 / 16f, 2 / 16f, + 9 / 16f, 3 / 16f, + (slim ? 3 / 64f : 1 / 16f), 3 / 16f, 1 / 16f, idxbase += 24); + + // left+right leg + addCuboid(bodyBuilder, + -4 / 16f, 4 / 16f, -2 / 16f, + 0 / 16f, 16 / 16f, 2 / 16f, + 1 / 16f, 11 / 16f, + 1 / 16f, 3 / 16f, 1 / 16f, idxbase += 24); + addCuboid(bodyBuilder, + 0 / 16f, 4 / 16f, -2 / 16f, + 4 / 16f, 16 / 16f, 2 / 16f, + 5 / 16f, 3 / 16f, + 1 / 16f, 3 / 16f, 1 / 16f, idxbase += 24); + meshes.put(BodyPart.MAIN, bodyBuilder.build()); + + Mesh.Builder hatBuilder = defaultBuilder().texture(tex); + addCuboid(hatBuilder, + -4 / 16f - overlayOffset, -16 / 16f - overlayOffset, -4 / 16f - overlayOffset, + 4 / 16f + overlayOffset, -8 / 16f + overlayOffset, 4 / 16f + overlayOffset, + 5 / 8f, 7 / 8f, + 1 / 8f, 1 / 8f, 1 / 8f, 0); + + meshes.put(BodyPart.HAT, hatBuilder.build()); + + overlayOffset *= 1.01f; + Mesh.Builder torsoBuilder = defaultBuilder().texture(tex); + addCuboid(torsoBuilder, + -4 / 16f - overlayOffset, -8 / 16f - overlayOffset, -2 / 16f - overlayOffset, + 4 / 16f + overlayOffset, 4 / 16f + overlayOffset, 2 / 16f + overlayOffset, + 5 / 16f, 7 / 16f, + 1 / 8f, 3 / 16f, 1 / 16f, 0); + + meshes.put(BodyPart.TORSO_OVERLAY, torsoBuilder.build()); + + overlayOffset /= 1.01f; + Mesh.Builder leftArmBuilder = defaultBuilder().texture(tex); + addCuboid(leftArmBuilder, + (slim ? -7 : -8) / 16f - overlayOffset, -8 / 16f - overlayOffset, -2 / 16f - overlayOffset, + -4 / 16f + overlayOffset, 4 / 16f + overlayOffset, 2 / 16f + overlayOffset, + 13 / 16f, 3 / 16f, + (slim ? 3 / 64f : 1 / 16f), 3 / 16f, 1 / 16f, 0); + + meshes.put(BodyPart.LEFT_ARM_OVERLAY, leftArmBuilder.build()); + + Mesh.Builder rightArmBuilder = defaultBuilder().texture(tex); + addCuboid(rightArmBuilder, + 4 / 16f - overlayOffset, -8 / 16f - overlayOffset, -2 / 16f - overlayOffset, + (slim ? 7 : 8) / 16f + overlayOffset, 4 / 16f + overlayOffset, 2 / 16f + overlayOffset, + 11 / 16f, 7 / 16f, + (slim ? 3 / 64f : 1 / 16f), 3 / 16f, 1 / 16f, 0); + + meshes.put(BodyPart.RIGHT_ARM_OVERLAY, rightArmBuilder.build()); + + overlayOffset /= 1.01f; + Mesh.Builder leftLegBuilder = defaultBuilder().texture(tex); + addCuboid(leftLegBuilder, + -4 / 16f - overlayOffset, 4 / 16f - overlayOffset, -2 / 16f - overlayOffset, + 0 / 16f + overlayOffset, 16 / 16f + overlayOffset, 2 / 16f + overlayOffset, + 1 / 16f, 7 / 16f, + 1 / 16f, 3 / 16f, 1 / 16f, 0); + meshes.put(BodyPart.LEFT_LEG_OVERLAY, leftLegBuilder.build()); + + overlayOffset /= 1.01f; + Mesh.Builder rightLegBuilder = defaultBuilder().texture(tex); + addCuboid(rightLegBuilder, + 0 / 16f - overlayOffset, 4 / 16f - overlayOffset, -2 / 16f - overlayOffset, + 4 / 16f + overlayOffset, 16 / 16f + overlayOffset, 2 / 16f + overlayOffset, + 1 / 16f, 3 / 16f, + 1 / 16f, 3 / 16f, 1 / 16f, 0); + meshes.put(BodyPart.RIGHT_LEG_OVERLAY, rightLegBuilder.build()); + + Mesh.Builder capeBuilder = defaultBuilder().texture(new Texture(capeBI)); + addCuboid(capeBuilder, + 0 / 16f - overlayOffset, 4 / 16f - overlayOffset, -2 / 16f - overlayOffset, + 4 / 16f + overlayOffset, 16 / 16f + overlayOffset, 2 / 16f + overlayOffset, + 1 / 16f, 3 / 16f, + 1 / 16f, 3 / 16f, 1 / 16f, 0); + meshes.put(BodyPart.RIGHT_LEG_OVERLAY, rightLegBuilder.build()); + } + + private void handleResize(int width, int height) { + framebuffer = new Framebuffer(width, height); + updateTransform(); + } + + private void updateTransform() { + framebuffer.setTransform(Matrix3f.rotateY(yRot).times(Matrix3f.rotateX(xRot)).times(Matrix3f.scaleX(0.5f).times(Matrix3f.scaleY(0.5f).times(Matrix3f.scaleZ(0.5f))))); + } + + @Override + public void paintComponent(Graphics g) { + if (framebuffer == null) handleResize(getWidth(), getHeight()); + + long start = System.nanoTime(); + framebuffer.setBlendMode(BlendMode.DISABLE); + framebuffer.clear(Framebuffer.FB_CLEAR_COLOR | Framebuffer.FB_CLEAR_DEPTH, 0xFF000000); + long t1 = System.nanoTime(); + framebuffer.setDepthMode(Framebuffer.FB_DEPTH_COMMIT | Framebuffer.FB_DEPTH_USE); + framebuffer.drawMesh(meshes.get(BodyPart.MAIN)); + + framebuffer.setDepthMode(Framebuffer.FB_DEPTH_USE | Framebuffer.FB_DEPTH_COMMIT_TRANSPARENT); + framebuffer.setBlendMode(BlendMode.BLEND_OVER); + framebuffer.drawMesh(meshes.get(BodyPart.HAT)); + framebuffer.drawMesh(meshes.get(BodyPart.TORSO_OVERLAY)); + framebuffer.drawMesh(meshes.get(BodyPart.LEFT_ARM_OVERLAY)); + framebuffer.drawMesh(meshes.get(BodyPart.RIGHT_ARM_OVERLAY)); + framebuffer.drawMesh(meshes.get(BodyPart.LEFT_LEG_OVERLAY)); + framebuffer.drawMesh(meshes.get(BodyPart.RIGHT_LEG_OVERLAY)); + long t2 = System.nanoTime(); + + g.clearRect(0, 0, getWidth(), getHeight()); + g.drawImage(framebuffer.getColor(), 0, 0, null); + + g.setColor(Color.RED); + + addTiming(t1 - start, t2 - t1); + + int y = -2; + g.drawString(String.format("tot %.02fms", (t2 - start) / 1000000.), 10, y += 12); + g.drawString(String.format("clr %.02fms", (t1 - start) / 1000000.), 10, y += 12); + g.drawString(String.format("msh %.02fms", (t2 - t1) / 1000000.), 10, y += 12); + g.drawString(getAvgClr(), 10, y += 12); + g.drawString(String.format("%dx%d", framebuffer.getWidth(), framebuffer.getHeight()), 10, y += 12); + } + + private void addTiming(long clr, long msh) { + clrTime[tidx] = clr; + meshTime[tidx] = msh; + + if (++tidx >= 32) { + tidx = 0; + rollOver = true; + } + } + + private String getAvgClr() { + int n = rollOver ? 32 : tidx; + if (n == 0) return "avg ???"; + + long sumClr = 0, sumMsh = 0; + + for (int i = 0; i < n; ++i) { + sumClr += clrTime[i]; + sumMsh += meshTime[i]; + } + + return String.format("avg %.02fms clr %.02fms msh", sumClr / (double)n / 1000000, sumMsh / (double)n / 1000000); + } + + public void setYRot(float rad) { + this.yRot = rad; + updateTransform(); + } + + public void setXRot(float rad) { + this.xRot = rad; + updateTransform(); + } + + public enum BodyPart { + MAIN, + HAT, + TORSO_OVERLAY, + LEFT_ARM_OVERLAY, + RIGHT_ARM_OVERLAY, + LEFT_LEG_OVERLAY, + RIGHT_LEG_OVERLAY, + CAPE + } +} -- cgit v1.2.3-70-g09d2