--- /dev/null
+/*
+ * 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