refactor(examples): rename FillRateTest to GraphicsBenchmark
authorSvjatoslav Agejenko <svjatoslav@svjatoslav.eu>
Sun, 15 Mar 2026 02:21:54 +0000 (04:21 +0200)
committerSvjatoslav Agejenko <svjatoslav@svjatoslav.eu>
Sun, 15 Mar 2026 02:21:54 +0000 (04:21 +0200)
Rename the fill-rate test class to GraphicsBenchmark for better clarity
about its purpose as a general graphics performance benchmark.

src/main/java/eu/svjatoslav/sixth/e3d/examples/FillRateTest.java [deleted file]
src/main/java/eu/svjatoslav/sixth/e3d/examples/GraphicsBenchmark.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/ApplicationListPanel.java

diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/FillRateTest.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/FillRateTest.java
deleted file mode 100644 (file)
index 74f415b..0000000
+++ /dev/null
@@ -1,351 +0,0 @@
-/*
- * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
- * This project is released under Creative Commons Zero (CC0) license.
- */
-
-package eu.svjatoslav.sixth.e3d.examples;
-
-import eu.svjatoslav.sixth.e3d.geometry.Point2D;
-import eu.svjatoslav.sixth.e3d.geometry.Point3D;
-import eu.svjatoslav.sixth.e3d.gui.Camera;
-import eu.svjatoslav.sixth.e3d.gui.FrameListener;
-import eu.svjatoslav.sixth.e3d.gui.ViewFrame;
-import eu.svjatoslav.sixth.e3d.gui.ViewPanel;
-import eu.svjatoslav.sixth.e3d.gui.humaninput.WorldNavigationUserInputTracker;
-import eu.svjatoslav.sixth.e3d.math.Vertex;
-import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
-import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
-import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.texturedpolygon.TexturedPolygon;
-import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.AbstractCompositeShape;
-import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonCube;
-import eu.svjatoslav.sixth.e3d.renderer.raster.texture.Texture;
-
-import java.awt.event.KeyEvent;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Random;
-
-/**
- * Fill-rate benchmark demo that tests the engine's rendering performance.
- * Creates a 16x16x16 grid of cubes (4096 total) that orbit around the center point.
- * The camera follows a wobbling orbital path while FPS statistics are printed to console.
- * 
- * <p>Key controls:
- * <ul>
- *   <li>Press 1 - Switch to solid color cubes (faster rendering)</li>
- *   <li>Press 2 - Switch to textured cubes (slower rendering, tests texture fill-rate)</li>
- * </ul>
- * 
- * @see SolidPolygonCube
- * @see TexturedPolygon
- */
-public class FillRateTest extends WorldNavigationUserInputTracker implements FrameListener {
-
-    /** Number of cubes along each axis of the grid. */
-    private static final int GRID_SIZE = 16;
-    /** Spacing between cube centers in world units. */
-    private static final double SPACING = 80;
-    /** Half-size of each cube. */
-    private static final double CUBE_SIZE = 25;
-    /** Distance of camera from center during orbit. */
-    private static final double ORBIT_DISTANCE = 1200;
-    /** Angular speed of camera orbit in radians per millisecond. */
-    private static final double ORBIT_SPEED = 0.0003;
-    /** Amplitude of vertical wobble during orbit. */
-    private static final double WOBBLE_AMPLITUDE = 800;
-    /** Number of unique textures to create for textured mode. */
-    private static final int TEXTURE_COUNT = 20;
-
-    private final ViewFrame viewFrame;
-    private final ViewPanel viewPanel;
-    private final ShapeCollection shapes;
-    private final Random random = new Random(42);
-    private final Camera camera;
-
-    private final List<Object> cubes = new ArrayList<>();
-    private final Texture[] textures = new Texture[TEXTURE_COUNT];
-    private final int[] cubeTextureIndices;
-
-    private boolean texturedMode = false;
-    private double orbitAngle = 0;
-
-    private long frameCount = 0;
-    private long fpsStartTime = System.currentTimeMillis();
-    private long totalFrameCount = 0;
-    private long appStartTime = System.currentTimeMillis();
-
-    /** Resets FPS statistics counters. */
-    private void resetFpsStats() {
-        frameCount = 0;
-        fpsStartTime = System.currentTimeMillis();
-        totalFrameCount = 0;
-        appStartTime = fpsStartTime;
-    }
-
-    /**
-     * Entry point for the fill-rate test demo.
-     * @param args command line arguments (ignored)
-     */
-    public static void main(String[] args) {
-        new FillRateTest();
-    }
-
-    /**
-     * Constructs the fill-rate test demo with a 1920x1080 window.
-     * Creates the cube grid and initializes camera orbit animation.
-     */
-    public FillRateTest() {
-        viewFrame = new ViewFrame(1920, 1080);
-        viewPanel = viewFrame.getViewPanel();
-        viewPanel.setFrameRate(0);
-        shapes = viewPanel.getRootShapeCollection();
-
-        viewPanel.addFrameListener(this);
-        viewPanel.getKeyboardFocusStack().pushFocusOwner(this);
-
-        camera = viewPanel.getCamera();
-
-        cubeTextureIndices = new int[GRID_SIZE * GRID_SIZE * GRID_SIZE];
-        for (int i = 0; i < cubeTextureIndices.length; i++) {
-            cubeTextureIndices[i] = random.nextInt(TEXTURE_COUNT);
-        }
-
-        createTextures();
-        createCubes(false);
-    }
-
-    /** Creates the pool of randomly colored glow textures. */
-    private void createTextures() {
-        for (int i = 0; i < TEXTURE_COUNT; i++) {
-            textures[i] = createGlowTexture(
-                    50 + random.nextInt(200),
-                    50 + random.nextInt(200),
-                    50 + random.nextInt(200)
-            );
-        }
-    }
-
-    /**
-     * Creates a glow texture with the specified RGB color.
-     * @param r red component (0-255)
-     * @param g green component (0-255)
-     * @param b blue component (0-255)
-     * @return a 64x64 texture with glowing border effect
-     */
-    private Texture createGlowTexture(int r, int g, int b) {
-        int texSize = 64;
-        Texture texture = new Texture(texSize, texSize, 2);
-
-        java.awt.Graphics2D gr = texture.graphics;
-        gr.setBackground(new java.awt.Color(r, g, b, 80));
-        gr.clearRect(0, 0, texSize, texSize);
-
-        int glowWidth = 6;
-        for (int i = 0; i < glowWidth; i++) {
-            int intensity = (int) (255.0 * (glowWidth - i) / glowWidth);
-            java.awt.Color glowColor = new java.awt.Color(
-                    Math.min(255, r + intensity),
-                    Math.min(255, g + intensity),
-                    Math.min(255, b + intensity),
-                    200 - i * 30
-            );
-            gr.setColor(glowColor);
-            gr.drawRect(i, i, texSize - 1 - 2 * i, texSize - 1 - 2 * i);
-        }
-
-        gr.dispose();
-        texture.resetResampledBitmapCache();
-        return texture;
-    }
-
-    /** Removes all cubes from the scene. */
-    private void clearCubes() {
-        for (Object cube : cubes) {
-            shapes.getShapes().remove(cube);
-        }
-        cubes.clear();
-    }
-
-    /**
-     * Creates the grid of cubes in the scene.
-     * @param textured if true, creates textured cubes; if false, creates solid-colored cubes
-     */
-    private void createCubes(boolean textured) {
-        clearCubes();
-        random.setSeed(42);
-
-        double offset = -(GRID_SIZE - 1) * SPACING / 2;
-        int idx = 0;
-
-        for (int x = 0; x < GRID_SIZE; x++) {
-            for (int y = 0; y < GRID_SIZE; y++) {
-                for (int z = 0; z < GRID_SIZE; z++) {
-                    double px = offset + x * SPACING;
-                    double py = offset + y * SPACING;
-                    double pz = offset + z * SPACING;
-
-                    if (textured) {
-                        Texture tex = textures[cubeTextureIndices[idx]];
-                        TexturedCube cube = new TexturedCube(
-                                new Point3D(px, py, pz),
-                                CUBE_SIZE,
-                                tex
-                        );
-                        shapes.addShape(cube);
-                        cubes.add(cube);
-                    } else {
-                        Color color = new Color(
-                                50 + random.nextInt(150),
-                                100 + random.nextInt(155),
-                                50 + random.nextInt(150),
-                                40
-                        );
-                        SolidPolygonCube cube = new SolidPolygonCube(
-                                new Point3D(px, py, pz),
-                                CUBE_SIZE,
-                                color
-                        );
-                        shapes.addShape(cube);
-                        cubes.add(cube);
-                    }
-                    idx++;
-                }
-            }
-        }
-    }
-
-    /**
-     * Called each frame to animate the camera orbit and track FPS.
-     * @param viewPanel the view panel rendering the scene
-     * @param millisecondsSinceLastFrame time elapsed since last frame
-     * @return true to continue rendering
-     */
-    @Override
-    public boolean onFrame(ViewPanel viewPanel, int millisecondsSinceLastFrame) {
-        orbitAngle += ORBIT_SPEED * millisecondsSinceLastFrame;
-
-        double x = Math.sin(orbitAngle) * ORBIT_DISTANCE;
-        double z = Math.cos(orbitAngle) * ORBIT_DISTANCE;
-        double y = Math.sin(orbitAngle * 1.8934) * WOBBLE_AMPLITUDE;
-
-        camera.getTransform().setTranslation(new Point3D(x, y, z));
-        camera.lookAt(new Point3D(0, 0, 0));
-
-        frameCount++;
-        totalFrameCount++;
-        long now = System.currentTimeMillis();
-        long elapsed = now - fpsStartTime;
-        if (elapsed >= 1000) {
-            int fps = (int) (frameCount * 1000 / elapsed);
-            long totalElapsed = now - appStartTime;
-            double avgFps = totalFrameCount * 1000.0 / totalElapsed;
-            System.out.println("current: " + fps + " average: " + String.format("%.2f", avgFps));
-            frameCount = 0;
-            fpsStartTime = now;
-        }
-
-        return true;
-    }
-
-    /**
-     * Handles keyboard input for switching between rendering modes.
-     * @param event the key event
-     * @param viewPanel the view panel
-     * @return true if the event was consumed
-     */
-    @Override
-    public boolean keyPressed(KeyEvent event, ViewPanel viewPanel) {
-        switch (event.getKeyCode()) {
-            case KeyEvent.VK_1:
-                if (texturedMode) {
-                    texturedMode = false;
-                    createCubes(false);
-                    resetFpsStats();
-                }
-                return true;
-            case KeyEvent.VK_2:
-                if (!texturedMode) {
-                    texturedMode = true;
-                    createCubes(true);
-                    resetFpsStats();
-                }
-                return true;
-        }
-        return super.keyPressed(event, viewPanel);
-    }
-
-    /**
-     * A cube composed of textured polygons.
-     * Each face is rendered as two triangles with UV coordinates for texture mapping.
-     */
-    private static class TexturedCube extends AbstractCompositeShape {
-
-        /**
-         * Constructs a textured cube at the specified position.
-         * @param center the center position of the cube
-         * @param size half-size of the cube (total width is 2*size)
-         * @param texture the texture to apply to all faces
-         */
-        public TexturedCube(Point3D center, double size, Texture texture) {
-            double s = size;
-            Point3D p1 = new Point3D(center.x - s, center.y - s, center.z - s);
-            Point3D p7 = new Point3D(center.x + s, center.y + s, center.z + s);
-
-            Point3D p2 = new Point3D(p7.x, p1.y, p1.z);
-            Point3D p3 = new Point3D(p7.x, p1.y, p7.z);
-            Point3D p4 = new Point3D(p1.x, p1.y, p7.z);
-            Point3D p5 = new Point3D(p1.x, p7.y, p1.z);
-            Point3D p6 = new Point3D(p7.x, p7.y, p1.z);
-            Point3D p8 = new Point3D(p1.x, p7.y, p7.z);
-
-            Point2D t00 = new Point2D(0, 0);
-            Point2D t10 = new Point2D(64, 0);
-            Point2D t01 = new Point2D(0, 64);
-            Point2D t11 = new Point2D(64, 64);
-
-            addTexturedFace(p1, p2, p3, p4, t00, t10, t11, t01, texture);
-            addTexturedFace(p5, p8, p7, p6, t00, t01, t11, t10, texture);
-            addTexturedFace(p1, p5, p6, p2, t00, t01, t11, t10, texture);
-            addTexturedFace(p3, p7, p8, p4, t00, t10, t11, t01, texture);
-            addTexturedFace(p1, p4, p8, p5, t00, t10, t11, t01, texture);
-            addTexturedFace(p2, p6, p7, p3, t00, t01, t11, t10, texture);
-
-            setBackfaceCulling(true);
-        }
-
-        /**
-         * Adds a quad face as two textured triangles.
-         * @param p1 first corner position
-         * @param p2 second corner position
-         * @param p3 third corner position
-         * @param p4 fourth corner position
-         * @param t1 texture coordinate for p1
-         * @param t2 texture coordinate for p2
-         * @param t3 texture coordinate for p3
-         * @param t4 texture coordinate for p4
-         * @param texture the texture to apply
-         */
-        private void addTexturedFace(Point3D p1, Point3D p2, Point3D p3, Point3D p4,
-                                     Point2D t1, Point2D t2, Point2D t3, Point2D t4,
-                                     Texture texture) {
-            TexturedPolygon tri1 = new TexturedPolygon(
-                    new Vertex(p1, t1),
-                    new Vertex(p2, t2),
-                    new Vertex(p3, t3),
-                    texture
-            );
-            tri1.setBackfaceCulling(true);
-
-            TexturedPolygon tri2 = new TexturedPolygon(
-                    new Vertex(p1, t1),
-                    new Vertex(p3, t3),
-                    new Vertex(p4, t4),
-                    texture
-            );
-            tri2.setBackfaceCulling(true);
-
-            addShape(tri1);
-            addShape(tri2);
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/GraphicsBenchmark.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/GraphicsBenchmark.java
new file mode 100644 (file)
index 0000000..a861c4b
--- /dev/null
@@ -0,0 +1,351 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+package eu.svjatoslav.sixth.e3d.examples;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point2D;
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.gui.Camera;
+import eu.svjatoslav.sixth.e3d.gui.FrameListener;
+import eu.svjatoslav.sixth.e3d.gui.ViewFrame;
+import eu.svjatoslav.sixth.e3d.gui.ViewPanel;
+import eu.svjatoslav.sixth.e3d.gui.humaninput.WorldNavigationUserInputTracker;
+import eu.svjatoslav.sixth.e3d.math.Vertex;
+import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
+import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.texturedpolygon.TexturedPolygon;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.AbstractCompositeShape;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonCube;
+import eu.svjatoslav.sixth.e3d.renderer.raster.texture.Texture;
+
+import java.awt.event.KeyEvent;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Fill-rate benchmark demo that tests the engine's rendering performance.
+ * Creates a 16x16x16 grid of cubes (4096 total) that orbit around the center point.
+ * The camera follows a wobbling orbital path while FPS statistics are printed to console.
+ * 
+ * <p>Key controls:
+ * <ul>
+ *   <li>Press 1 - Switch to solid color cubes (faster rendering)</li>
+ *   <li>Press 2 - Switch to textured cubes (slower rendering, tests texture fill-rate)</li>
+ * </ul>
+ * 
+ * @see SolidPolygonCube
+ * @see TexturedPolygon
+ */
+public class GraphicsBenchmark extends WorldNavigationUserInputTracker implements FrameListener {
+
+    /** Number of cubes along each axis of the grid. */
+    private static final int GRID_SIZE = 16;
+    /** Spacing between cube centers in world units. */
+    private static final double SPACING = 80;
+    /** Half-size of each cube. */
+    private static final double CUBE_SIZE = 25;
+    /** Distance of camera from center during orbit. */
+    private static final double ORBIT_DISTANCE = 1200;
+    /** Angular speed of camera orbit in radians per millisecond. */
+    private static final double ORBIT_SPEED = 0.0003;
+    /** Amplitude of vertical wobble during orbit. */
+    private static final double WOBBLE_AMPLITUDE = 800;
+    /** Number of unique textures to create for textured mode. */
+    private static final int TEXTURE_COUNT = 20;
+
+    private final ViewFrame viewFrame;
+    private final ViewPanel viewPanel;
+    private final ShapeCollection shapes;
+    private final Random random = new Random(42);
+    private final Camera camera;
+
+    private final List<Object> cubes = new ArrayList<>();
+    private final Texture[] textures = new Texture[TEXTURE_COUNT];
+    private final int[] cubeTextureIndices;
+
+    private boolean texturedMode = false;
+    private double orbitAngle = 0;
+
+    private long frameCount = 0;
+    private long fpsStartTime = System.currentTimeMillis();
+    private long totalFrameCount = 0;
+    private long appStartTime = System.currentTimeMillis();
+
+    /** Resets FPS statistics counters. */
+    private void resetFpsStats() {
+        frameCount = 0;
+        fpsStartTime = System.currentTimeMillis();
+        totalFrameCount = 0;
+        appStartTime = fpsStartTime;
+    }
+
+    /**
+     * Entry point for the graphics benchmark demo.
+     * @param args command line arguments (ignored)
+     */
+    public static void main(String[] args) {
+        new GraphicsBenchmark();
+    }
+
+    /**
+     * Constructs the graphics benchmark demo with a 1920x1080 window.
+     * Creates the cube grid and initializes camera orbit animation.
+     */
+    public GraphicsBenchmark() {
+        viewFrame = new ViewFrame(1920, 1080);
+        viewPanel = viewFrame.getViewPanel();
+        viewPanel.setFrameRate(0);
+        shapes = viewPanel.getRootShapeCollection();
+
+        viewPanel.addFrameListener(this);
+        viewPanel.getKeyboardFocusStack().pushFocusOwner(this);
+
+        camera = viewPanel.getCamera();
+
+        cubeTextureIndices = new int[GRID_SIZE * GRID_SIZE * GRID_SIZE];
+        for (int i = 0; i < cubeTextureIndices.length; i++) {
+            cubeTextureIndices[i] = random.nextInt(TEXTURE_COUNT);
+        }
+
+        createTextures();
+        createCubes(false);
+    }
+
+    /** Creates the pool of randomly colored glow textures. */
+    private void createTextures() {
+        for (int i = 0; i < TEXTURE_COUNT; i++) {
+            textures[i] = createGlowTexture(
+                    50 + random.nextInt(200),
+                    50 + random.nextInt(200),
+                    50 + random.nextInt(200)
+            );
+        }
+    }
+
+    /**
+     * Creates a glow texture with the specified RGB color.
+     * @param r red component (0-255)
+     * @param g green component (0-255)
+     * @param b blue component (0-255)
+     * @return a 64x64 texture with glowing border effect
+     */
+    private Texture createGlowTexture(int r, int g, int b) {
+        int texSize = 64;
+        Texture texture = new Texture(texSize, texSize, 2);
+
+        java.awt.Graphics2D gr = texture.graphics;
+        gr.setBackground(new java.awt.Color(r, g, b, 80));
+        gr.clearRect(0, 0, texSize, texSize);
+
+        int glowWidth = 6;
+        for (int i = 0; i < glowWidth; i++) {
+            int intensity = (int) (255.0 * (glowWidth - i) / glowWidth);
+            java.awt.Color glowColor = new java.awt.Color(
+                    Math.min(255, r + intensity),
+                    Math.min(255, g + intensity),
+                    Math.min(255, b + intensity),
+                    200 - i * 30
+            );
+            gr.setColor(glowColor);
+            gr.drawRect(i, i, texSize - 1 - 2 * i, texSize - 1 - 2 * i);
+        }
+
+        gr.dispose();
+        texture.resetResampledBitmapCache();
+        return texture;
+    }
+
+    /** Removes all cubes from the scene. */
+    private void clearCubes() {
+        for (Object cube : cubes) {
+            shapes.getShapes().remove(cube);
+        }
+        cubes.clear();
+    }
+
+    /**
+     * Creates the grid of cubes in the scene.
+     * @param textured if true, creates textured cubes; if false, creates solid-colored cubes
+     */
+    private void createCubes(boolean textured) {
+        clearCubes();
+        random.setSeed(42);
+
+        double offset = -(GRID_SIZE - 1) * SPACING / 2;
+        int idx = 0;
+
+        for (int x = 0; x < GRID_SIZE; x++) {
+            for (int y = 0; y < GRID_SIZE; y++) {
+                for (int z = 0; z < GRID_SIZE; z++) {
+                    double px = offset + x * SPACING;
+                    double py = offset + y * SPACING;
+                    double pz = offset + z * SPACING;
+
+                    if (textured) {
+                        Texture tex = textures[cubeTextureIndices[idx]];
+                        TexturedCube cube = new TexturedCube(
+                                new Point3D(px, py, pz),
+                                CUBE_SIZE,
+                                tex
+                        );
+                        shapes.addShape(cube);
+                        cubes.add(cube);
+                    } else {
+                        Color color = new Color(
+                                50 + random.nextInt(150),
+                                100 + random.nextInt(155),
+                                50 + random.nextInt(150),
+                                40
+                        );
+                        SolidPolygonCube cube = new SolidPolygonCube(
+                                new Point3D(px, py, pz),
+                                CUBE_SIZE,
+                                color
+                        );
+                        shapes.addShape(cube);
+                        cubes.add(cube);
+                    }
+                    idx++;
+                }
+            }
+        }
+    }
+
+    /**
+     * Called each frame to animate the camera orbit and track FPS.
+     * @param viewPanel the view panel rendering the scene
+     * @param millisecondsSinceLastFrame time elapsed since last frame
+     * @return true to continue rendering
+     */
+    @Override
+    public boolean onFrame(ViewPanel viewPanel, int millisecondsSinceLastFrame) {
+        orbitAngle += ORBIT_SPEED * millisecondsSinceLastFrame;
+
+        double x = Math.sin(orbitAngle) * ORBIT_DISTANCE;
+        double z = Math.cos(orbitAngle) * ORBIT_DISTANCE;
+        double y = Math.sin(orbitAngle * 1.8934) * WOBBLE_AMPLITUDE;
+
+        camera.getTransform().setTranslation(new Point3D(x, y, z));
+        camera.lookAt(new Point3D(0, 0, 0));
+
+        frameCount++;
+        totalFrameCount++;
+        long now = System.currentTimeMillis();
+        long elapsed = now - fpsStartTime;
+        if (elapsed >= 1000) {
+            int fps = (int) (frameCount * 1000 / elapsed);
+            long totalElapsed = now - appStartTime;
+            double avgFps = totalFrameCount * 1000.0 / totalElapsed;
+            System.out.println("current: " + fps + " average: " + String.format("%.2f", avgFps));
+            frameCount = 0;
+            fpsStartTime = now;
+        }
+
+        return true;
+    }
+
+    /**
+     * Handles keyboard input for switching between rendering modes.
+     * @param event the key event
+     * @param viewPanel the view panel
+     * @return true if the event was consumed
+     */
+    @Override
+    public boolean keyPressed(KeyEvent event, ViewPanel viewPanel) {
+        switch (event.getKeyCode()) {
+            case KeyEvent.VK_1:
+                if (texturedMode) {
+                    texturedMode = false;
+                    createCubes(false);
+                    resetFpsStats();
+                }
+                return true;
+            case KeyEvent.VK_2:
+                if (!texturedMode) {
+                    texturedMode = true;
+                    createCubes(true);
+                    resetFpsStats();
+                }
+                return true;
+        }
+        return super.keyPressed(event, viewPanel);
+    }
+
+    /**
+     * A cube composed of textured polygons.
+     * Each face is rendered as two triangles with UV coordinates for texture mapping.
+     */
+    private static class TexturedCube extends AbstractCompositeShape {
+
+        /**
+         * Constructs a textured cube at the specified position.
+         * @param center the center position of the cube
+         * @param size half-size of the cube (total width is 2*size)
+         * @param texture the texture to apply to all faces
+         */
+        public TexturedCube(Point3D center, double size, Texture texture) {
+            double s = size;
+            Point3D p1 = new Point3D(center.x - s, center.y - s, center.z - s);
+            Point3D p7 = new Point3D(center.x + s, center.y + s, center.z + s);
+
+            Point3D p2 = new Point3D(p7.x, p1.y, p1.z);
+            Point3D p3 = new Point3D(p7.x, p1.y, p7.z);
+            Point3D p4 = new Point3D(p1.x, p1.y, p7.z);
+            Point3D p5 = new Point3D(p1.x, p7.y, p1.z);
+            Point3D p6 = new Point3D(p7.x, p7.y, p1.z);
+            Point3D p8 = new Point3D(p1.x, p7.y, p7.z);
+
+            Point2D t00 = new Point2D(0, 0);
+            Point2D t10 = new Point2D(64, 0);
+            Point2D t01 = new Point2D(0, 64);
+            Point2D t11 = new Point2D(64, 64);
+
+            addTexturedFace(p1, p2, p3, p4, t00, t10, t11, t01, texture);
+            addTexturedFace(p5, p8, p7, p6, t00, t01, t11, t10, texture);
+            addTexturedFace(p1, p5, p6, p2, t00, t01, t11, t10, texture);
+            addTexturedFace(p3, p7, p8, p4, t00, t10, t11, t01, texture);
+            addTexturedFace(p1, p4, p8, p5, t00, t10, t11, t01, texture);
+            addTexturedFace(p2, p6, p7, p3, t00, t01, t11, t10, texture);
+
+            setBackfaceCulling(true);
+        }
+
+        /**
+         * Adds a quad face as two textured triangles.
+         * @param p1 first corner position
+         * @param p2 second corner position
+         * @param p3 third corner position
+         * @param p4 fourth corner position
+         * @param t1 texture coordinate for p1
+         * @param t2 texture coordinate for p2
+         * @param t3 texture coordinate for p3
+         * @param t4 texture coordinate for p4
+         * @param texture the texture to apply
+         */
+        private void addTexturedFace(Point3D p1, Point3D p2, Point3D p3, Point3D p4,
+                                     Point2D t1, Point2D t2, Point2D t3, Point2D t4,
+                                     Texture texture) {
+            TexturedPolygon tri1 = new TexturedPolygon(
+                    new Vertex(p1, t1),
+                    new Vertex(p2, t2),
+                    new Vertex(p3, t3),
+                    texture
+            );
+            tri1.setBackfaceCulling(true);
+
+            TexturedPolygon tri2 = new TexturedPolygon(
+                    new Vertex(p1, t1),
+                    new Vertex(p3, t3),
+                    new Vertex(p4, t4),
+                    texture
+            );
+            tri2.setBackfaceCulling(true);
+
+            addShape(tri1);
+            addShape(tri2);
+        }
+    }
+}
\ No newline at end of file
index 424406d..64689e0 100644 (file)
@@ -34,7 +34,7 @@ class ApplicationListPanel extends JPanel {
         sequentialGroup.addComponent(new JButton(new ShowGameOfLife()));
         sequentialGroup.addComponent(new JButton(new ShowRandomPolygons()));
         sequentialGroup.addComponent(new JButton(new ShowShadedShapes()));
-        sequentialGroup.addComponent(new JButton(new ShowFillRateTest()));
+        sequentialGroup.addComponent(new JButton(new ShowGraphicsBenchmark()));
     }
 
     /** Action to launch the TextEditorDemo. */
@@ -146,15 +146,15 @@ class ApplicationListPanel extends JPanel {
         }
     }
 
-    /** Action to launch the FillRateTest benchmark. */
-    private static class ShowFillRateTest extends AbstractAction {
-        ShowFillRateTest() {
-            putValue(NAME, "Fill-rate Test");
+    /** Action to launch the GraphicsBenchmark. */
+    private static class ShowGraphicsBenchmark extends AbstractAction {
+        ShowGraphicsBenchmark() {
+            putValue(NAME, "Graphics Benchmark");
         }
 
         @Override
         public void actionPerformed(final ActionEvent e) {
-            FillRateTest.main(null);
+            GraphicsBenchmark.main(null);
         }
     }