feat(benchmark): add lit solid cubes test
authorSvjatoslav Agejenko <svjatoslav@svjatoslav.eu>
Mon, 16 Mar 2026 20:53:58 +0000 (22:53 +0200)
committerSvjatoslav Agejenko <svjatoslav@svjatoslav.eu>
Mon, 16 Mar 2026 20:53:58 +0000 (22:53 +0200)
Adds a benchmark test for opaque solid cubes with dynamic lighting.
Three orbiting light sources illuminate a 16x16x16 cube grid to test
shaded polygon rasterization performance.

- Add LitSolidCubesTest with animated orbiting lights
- Add setViewPanel to BenchmarkTest interface for animation support
- Update benchmark results with new test data

doc/index.org
src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/BenchmarkTest.java
src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/GraphicsBenchmark.java
src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/LitSolidCubesTest.java [new file with mode: 0644]

index 0cf513a..c53fbf7 100644 (file)
@@ -235,6 +235,7 @@ Cores:       24
 Test                         Avg FPS
 --------------------------------------------------------------------------------
 Solid Cubes                  49.65
+Lit Solid Cubes              41.40
 Textured Cubes               32.80
 Wireframe Cubes              42.84
 Star Grid                    304.59
index dec1514..14ab974 100644 (file)
@@ -5,6 +5,7 @@
 
 package eu.svjatoslav.sixth.e3d.examples.benchmark;
 
+import eu.svjatoslav.sixth.e3d.gui.ViewPanel;
 import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
 
 /**
@@ -30,4 +31,12 @@ public interface BenchmarkTest {
      * @param shapes the shape collection to clean up
      */
     void teardown(ShapeCollection shapes);
+
+    /**
+     * Called after setup to provide the view panel for tests that need animation.
+     * Default implementation does nothing.
+     * @param viewPanel the view panel
+     */
+    default void setViewPanel(ViewPanel viewPanel) {
+    }
 }
\ No newline at end of file
index 1f9728a..cc20649 100644 (file)
@@ -34,7 +34,8 @@ import java.util.List;
  * 
  * <p>Available tests:</p>
  * <ul>
- *   <li>{@link SolidCubesTest} - Solid-color polygon rendering</li>
+ *   <li>{@link SolidCubesTest} - Semi-transparent solid polygon rendering</li>
+ *   <li>{@link LitSolidCubesTest} - Opaque solid polygons with dynamic lighting</li>
  *   <li>{@link TexturedCubesTest} - Textured polygon rendering</li>
  *   <li>{@link WireframeCubesTest} - Line rendering</li>
  *   <li>{@link StarGridTest} - Billboard (glowing point) rendering</li>
@@ -122,6 +123,7 @@ public class GraphicsBenchmark implements FrameListener, KeyboardInputHandler {
 
     private void registerTests() {
         tests.add(new SolidCubesTest());
+        tests.add(new LitSolidCubesTest());
         tests.add(new TexturedCubesTest());
         tests.add(new WireframeCubesTest());
         tests.add(new StarGridTest());
@@ -141,6 +143,7 @@ public class GraphicsBenchmark implements FrameListener, KeyboardInputHandler {
         testStartTime = System.currentTimeMillis();
 
         currentTest.setup(shapes);
+        currentTest.setViewPanel(viewPanel);
     }
 
     private void finishCurrentTest() {
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/LitSolidCubesTest.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/LitSolidCubesTest.java
new file mode 100644 (file)
index 0000000..0651938
--- /dev/null
@@ -0,0 +1,185 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+package eu.svjatoslav.sixth.e3d.examples.benchmark;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.gui.FrameListener;
+import eu.svjatoslav.sixth.e3d.gui.ViewPanel;
+import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
+import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
+import eu.svjatoslav.sixth.e3d.renderer.raster.lighting.LightSource;
+import eu.svjatoslav.sixth.e3d.renderer.raster.lighting.LightingManager;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.LightSourceMarker;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonCube;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Benchmark test for solid opaque cubes with dynamic lighting.
+ * Renders a grid of fully opaque cubes lit by three orbiting light sources
+ * to test shaded polygon rasterization performance.
+ */
+public class LitSolidCubesTest implements BenchmarkTest {
+
+    private static final int GRID_SIZE = 16;
+    private static final double SPACING = 80;
+    private static final double CUBE_SIZE = 25;
+    private static final long RANDOM_SEED = 42;
+
+    private final Random random = new Random(RANDOM_SEED);
+    private final List<SolidPolygonCube> cubes = new ArrayList<>();
+    private final List<OrbitingLight> orbitingLights = new ArrayList<>();
+    private LightingManager lightingManager;
+    private FrameListener animator;
+    private ViewPanel viewPanel;
+
+    @Override
+    public String getName() {
+        return "Lit Solid Cubes";
+    }
+
+    @Override
+    public void setup(ShapeCollection shapes) {
+        random.setSeed(RANDOM_SEED);
+
+        lightingManager = new LightingManager();
+        lightingManager.setAmbientLight(new Color(15, 15, 20));
+
+        Color[] lightColors = {
+                new Color(255, 100, 100),
+                new Color(100, 255, 100),
+                new Color(100, 100, 255)
+        };
+
+        for (int i = 0; i < 3; i++) {
+            double angleOffset = i * (Math.PI * 2 / 3);
+            LightSource light = new LightSource(
+                    new Point3D(0, 0, 0),
+                    lightColors[i],
+                    2.5
+            );
+            lightingManager.addLight(light);
+
+            LightSourceMarker marker = new LightSourceMarker(light.getPosition(), lightColors[i]);
+            shapes.addShape(marker);
+
+            orbitingLights.add(new OrbitingLight(light, marker, 600, 0.002, angleOffset, i));
+        }
+
+        double offset = -(GRID_SIZE - 1) * SPACING / 2;
+
+        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;
+
+                    Color color = new Color(
+                            150 + random.nextInt(105),
+                            150 + random.nextInt(105),
+                            150 + random.nextInt(105)
+                    );
+
+                    SolidPolygonCube cube = new SolidPolygonCube(
+                            new Point3D(px, py, pz),
+                            CUBE_SIZE,
+                            color
+                    );
+                    cube.setLightingManager(lightingManager);
+                    shapes.addShape(cube);
+                    cubes.add(cube);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void teardown(ShapeCollection shapes) {
+        for (SolidPolygonCube cube : cubes) {
+            shapes.getShapes().remove(cube);
+        }
+        cubes.clear();
+
+        for (OrbitingLight ol : orbitingLights) {
+            shapes.getShapes().remove(ol.marker);
+        }
+        orbitingLights.clear();
+
+        if (viewPanel != null && animator != null) {
+            viewPanel.removeFrameListener(animator);
+        }
+        viewPanel = null;
+        animator = null;
+        lightingManager = null;
+    }
+
+    /**
+     * Sets the view panel for animation callbacks.
+     * @param viewPanel the view panel
+     */
+    public void setViewPanel(ViewPanel viewPanel) {
+        this.viewPanel = viewPanel;
+        if (viewPanel != null) {
+            animator = new LightAnimator();
+            viewPanel.addFrameListener(animator);
+        }
+    }
+
+    private static class OrbitingLight {
+        final LightSource light;
+        final LightSourceMarker marker;
+        final double orbitRadius;
+        final double speed;
+        final int axisIndex;
+        double angle;
+
+        OrbitingLight(LightSource light, LightSourceMarker marker,
+                      double orbitRadius, double speed, double angleOffset, int axisIndex) {
+            this.light = light;
+            this.marker = marker;
+            this.orbitRadius = orbitRadius;
+            this.speed = speed;
+            this.angle = angleOffset;
+            this.axisIndex = axisIndex;
+        }
+    }
+
+    private class LightAnimator implements FrameListener {
+        @Override
+        public boolean onFrame(ViewPanel vp, int millisecondsSinceLastFrame) {
+            for (OrbitingLight ol : orbitingLights) {
+                ol.angle += ol.speed * millisecondsSinceLastFrame;
+
+                double x, y, z;
+                switch (ol.axisIndex) {
+                    case 0:
+                        x = ol.orbitRadius * Math.cos(ol.angle);
+                        y = ol.orbitRadius * 0.3 * Math.sin(ol.angle * 2);
+                        z = ol.orbitRadius * Math.sin(ol.angle);
+                        break;
+                    case 1:
+                        x = ol.orbitRadius * Math.sin(ol.angle);
+                        y = ol.orbitRadius * Math.cos(ol.angle);
+                        z = ol.orbitRadius * 0.3 * Math.sin(ol.angle * 2);
+                        break;
+                    default:
+                        x = ol.orbitRadius * 0.3 * Math.sin(ol.angle * 2);
+                        y = ol.orbitRadius * Math.sin(ol.angle);
+                        z = ol.orbitRadius * Math.cos(ol.angle);
+                        break;
+                }
+
+                Point3D newPos = new Point3D(x, y, z);
+                ol.light.setPosition(newPos);
+                ol.marker.setTransform(new eu.svjatoslav.sixth.e3d.math.Transform(newPos, 0, 0));
+            }
+            return true;
+        }
+    }
+}
\ No newline at end of file