# Run Point Cloud Galaxy demo
java -cp target/sixth-3d-demos.jar eu.svjatoslav.sixth.e3d.examples.galaxy_demo.PointCloudDemo
+
+# Run Winding Order demo (tests backface culling)
+java -cp target/sixth-3d-demos.jar eu.svjatoslav.sixth.e3d.examples.WindingOrderDemo
```
### Testing
├── TextEditorDemo.java
├── TextEditorDemo2.java
├── RainingNumbersDemo.java
+├── WindingOrderDemo.java - Tests winding order & backface culling
└── package-info.java
```
Implement `MouseInteractionController` for mouse events, or extend input tracker classes for keyboard input.
+### Polygon Winding Order
+
+When creating triangles with backface culling enabled, use CCW winding in screen space:
+- Vertex order: top → lower-left → lower-right (as seen from camera)
+- `signedArea < 0` = front-facing = visible
+- See `WindingOrderDemo.java` for a minimal example
+
## Documentation
Always make sure that documentation in`doc/index.org` stays up to date.
the scene to raytrace current view through compressed voxel
datastructure.
-** Fill-rate test
+** Graphics Benchmark
:PROPERTIES:
-:CUSTOM_ID: fill-rate-test
+:CUSTOM_ID: graphics-benchmark
:ID: e5f6a7b8-c9d0-1234-ef01-345678901234
:END:
-A benchmark for polygon rasterization performance testing. This demo
-tests pixel fill throughput by rendering 40 large screen-filling quads
-(600x600) layered at different Z depths.
+An automated graphics benchmark that measures the engine's rendering
+performance across different rendering modes.
+
+The benchmark creates a 16x16x16 grid of cubes (4096 total) and runs
+three tests sequentially, each for 30 seconds:
+
+- *Solid Cubes* - Tests solid-color polygon rasterization
+- *Textured Cubes* - Tests textured polygon rendering with texture sampling
+- *Wireframe Cubes* - Tests line rendering performance
+
+The camera follows a deterministic orbital path around the scene,
+ensuring reproducible results across runs.
+
+Example benchmark results:
+#+begin_example
+================================================================================
+ GRAPHICS BENCHMARK RESULTS
+================================================================================
+Date: 2026-03-15 20:16:01
+Resolution: 1920x1080
+Cubes: 4096 (16x16x16 grid)
+Duration: 30 seconds per test
+
+--------------------------------------------------------------------------------
+SYSTEM INFORMATION
+--------------------------------------------------------------------------------
+CPU Name: AMD Ryzen AI 9 HX 370 w/ Radeon 890M
+Arch: amd64
+Cores: 24
+
+--------------------------------------------------------------------------------
+Test Avg FPS
+--------------------------------------------------------------------------------
+Solid Cubes 35.65
+Textured Cubes 26.08
+Wireframe Cubes 33.67
+Star Grid 256.88
+================================================================================
+#+end_example
-The low vertex count (4 vertices per quad) combined with high overdraw
-isolates the pixel fill cost from vertex processing overhead, allowing
-you to measure pure rasterization performance.
-
-Controls:
-| key | result |
-|-----+---------------------|
-| 1 | Solid colored quads |
-| 2 | Textured quads |
-
-Compare FPS between solid and textured modes to see exactly what
-texture sampling costs on your hardware.
* Source code
:PROPERTIES:
addWobblySurface(geometryCollection, -200);
setCameraLocation(viewFrame);
+
+ // Ensure the render thread is started
+ viewFrame.getViewPanel().ensureRenderThreadStarted();
}
/**
viewPanel.getKeyboardFocusStack().pushFocusOwner(this);
viewPanel.repaintDuringNextViewUpdate();
+
+ // Ensure the render thread is started
+ viewPanel.ensureRenderThreadStarted();
}
/**
}
viewFrame.getViewPanel().addFrameListener(this);
+
+ // Ensure the render thread is started
+ viewFrame.getViewPanel().ensureRenderThreadStarted();
}
}
for (int i = 0; i < POLYGON_COUNT; i++)
addRandomPolygon(shapeCollection);
+ // Ensure the render thread is started
+ viewFrame.getViewPanel().ensureRenderThreadStarted();
+
}
}
viewPanel.addFrameListener(animator);
viewPanel.repaintDuringNextViewUpdate();
+
+ // Ensure the render thread is started
+ viewPanel.ensureRenderThreadStarted();
}
/**
addGrid(shapeCollection);
addTextEditors(viewPanel, shapeCollection);
+
+ // Ensure the render thread is started
+ viewFrame.getViewPanel().ensureRenderThreadStarted();
}
/**
addGrid(shapeCollection);
addCity(viewPanel, shapeCollection);
+
+ // Ensure the render thread is started
+ viewFrame.getViewPanel().ensureRenderThreadStarted();
}
/**
--- /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;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.gui.ViewFrame;
+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.shapes.basic.solidpolygon.SolidPolygon;
+
+/**
+ * Demo to test winding order and backface culling documentation.
+ * <p>
+ * Creates one triangle with CCW winding (front face) following the docs:
+ * <ul>
+ * <li>upper-center → lower-left → lower-right</li>
+ * <li>Backface culling enabled</li>
+ * </ul>
+ * <p>
+ * Expected: green triangle visible (CCW = front face).
+ */
+public class WindingOrderDemo {
+
+ public static void main(String[] args) {
+ ViewFrame viewFrame = new ViewFrame();
+ ViewPanel viewPanel = viewFrame.getViewPanel();
+ ShapeCollection shapes = viewPanel.getRootShapeCollection();
+
+ double size = 150;
+
+ Point3D upperCenter = new Point3D(0, -size, 0);
+ Point3D lowerLeft = new Point3D(-size, +size, 0);
+ Point3D lowerRight = new Point3D(+size, +size, 0);
+
+ SolidPolygon triangle = new SolidPolygon(upperCenter, lowerLeft, lowerRight, Color.GREEN);
+ triangle.setBackfaceCulling(true);
+
+ shapes.addShape(triangle);
+
+ viewPanel.getCamera().getTransform().setTranslation(new Point3D(0, 0, -500));
+
+ viewPanel.ensureRenderThreadStarted();
+ }
+}
\ No newline at end of file
--- /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.renderer.raster.ShapeCollection;
+
+/**
+ * Interface for a single benchmark test.
+ * Implementations define how to set up and tear down the test scene.
+ */
+public interface BenchmarkTest {
+
+ /**
+ * Returns the display name for this benchmark test.
+ * @return the test name
+ */
+ String getName();
+
+ /**
+ * Sets up the test scene by adding shapes to the collection.
+ * @param shapes the shape collection to populate
+ */
+ void setup(ShapeCollection shapes);
+
+ /**
+ * Tears down the test scene by removing all shapes added during setup.
+ * @param shapes the shape collection to clean up
+ */
+ void teardown(ShapeCollection shapes);
+}
\ No newline at end of file
--- /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.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.KeyboardInputHandler;
+import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
+
+import javax.swing.SwingUtilities;
+import java.awt.event.KeyEvent;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Automated graphics benchmark that tests the engine's rendering performance.
+ * Runs multiple tests sequentially, each for a fixed duration, and outputs
+ * reproducible benchmark results to standard output.
+ *
+ * <p>The benchmark creates a 16x16x16 grid of cubes (4096 total) with the camera
+ * following a deterministic orbital path. Each test runs for 30 seconds by default.</p>
+ *
+ * <p>Press <b>Space</b> to skip to the next test immediately.</p>
+ *
+ * <p>Available tests:</p>
+ * <ul>
+ * <li>{@link SolidCubesTest} - Solid-color polygon rendering</li>
+ * <li>{@link TexturedCubesTest} - Textured polygon rendering</li>
+ * <li>{@link WireframeCubesTest} - Line rendering</li>
+ * <li>{@link StarGridTest} - Billboard (glowing point) rendering</li>
+ * </ul>
+ */
+public class GraphicsBenchmark implements FrameListener, KeyboardInputHandler {
+
+ private static final int WINDOW_WIDTH = 1920;
+ private static final int WINDOW_HEIGHT = 1080;
+ private static final int GRID_SIZE = 16;
+ private static final double ORBIT_DISTANCE = 1200;
+ private static final double ORBIT_SPEED = 0.0003;
+ private static final double WOBBLE_AMPLITUDE = 800;
+ private static final int TEST_DURATION_MS = 30000;
+
+ private ViewFrame viewFrame;
+ private ViewPanel viewPanel;
+ private ShapeCollection shapes;
+ private Camera camera;
+
+ private double orbitAngle = 0;
+ private long testStartTime;
+ private long frameCount;
+ private BenchmarkTest currentTest;
+ private boolean testFinished = false;
+ private boolean benchmarkFinished = false;
+ private boolean pendingTestTransition = false;
+ private final List<TestResult> results = new ArrayList<>();
+ private final List<BenchmarkTest> tests = new ArrayList<>();
+
+ /**
+ * Entry point for the graphics benchmark.
+ * @param args command line arguments (ignored)
+ */
+ public static void main(String[] args) {
+ new GraphicsBenchmark();
+ }
+
+/**
+ * Constructs and runs the graphics benchmark.
+ */
+ public GraphicsBenchmark() {
+ registerTests(); // populate tests FIRST, before render thread starts
+ initializeWindow(); // now onFrame can safely access the tests list
+ }
+
+ private void initializeWindow() {
+ viewFrame = new ViewFrame(WINDOW_WIDTH, WINDOW_HEIGHT);
+ viewPanel = viewFrame.getViewPanel();
+ viewPanel.setFrameRate(0);
+ shapes = viewPanel.getRootShapeCollection();
+ viewPanel.addFrameListener(this);
+ viewPanel.getKeyboardFocusStack().pushFocusOwner(this);
+ camera = viewPanel.getCamera();
+ // Now explicitly start the render thread after all listeners are registered
+ viewPanel.ensureRenderThreadStarted();
+ }
+
+ private void registerTests() {
+ tests.add(new SolidCubesTest());
+ tests.add(new TexturedCubesTest());
+ tests.add(new WireframeCubesTest());
+ tests.add(new StarGridTest());
+ }
+
+ private void startNextTest() {
+ int nextIndex = results.size();
+ if (nextIndex >= tests.size()) {
+ scheduleBenchmarkFinish();
+ return;
+ }
+
+ currentTest = tests.get(nextIndex);
+ testFinished = false;
+ orbitAngle = 0;
+ frameCount = 0;
+ testStartTime = System.currentTimeMillis();
+
+ currentTest.setup(shapes);
+ }
+
+ private void finishCurrentTest() {
+ if (currentTest == null || testFinished) {
+ return;
+ }
+
+ testFinished = true;
+ long elapsed = System.currentTimeMillis() - testStartTime;
+ double durationSeconds = elapsed / 1000.0;
+ results.add(new TestResult(currentTest.getName(), frameCount, durationSeconds));
+
+ // Defer test transition to safe point (beginning of next frame)
+ pendingTestTransition = true;
+ }
+
+ private void performTestTransition() {
+ if (currentTest != null) {
+ currentTest.teardown(shapes);
+ }
+ startNextTest();
+ }
+
+ private void scheduleBenchmarkFinish() {
+ benchmarkFinished = true;
+ SwingUtilities.invokeLater(this::finishBenchmark);
+ }
+
+ private void finishBenchmark() {
+ currentTest = null;
+ viewPanel.getKeyboardFocusStack().popFocusOwner();
+ viewPanel.removeFrameListener(this);
+ viewPanel.stop();
+ viewFrame.dispose();
+ printResults();
+ }
+
+ private void printResults() {
+ String separator = "================================================================================";
+ String thinSeparator = "--------------------------------------------------------------------------------";
+
+ Runtime runtime = Runtime.getRuntime();
+
+ System.out.println(separator);
+ System.out.println(" GRAPHICS BENCHMARK RESULTS");
+ System.out.println(separator);
+ System.out.println("Date: " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
+ System.out.println("Resolution: " + WINDOW_WIDTH + "x" + WINDOW_HEIGHT);
+ System.out.println("Cubes: " + (GRID_SIZE * GRID_SIZE * GRID_SIZE) + " (" + GRID_SIZE + "x" + GRID_SIZE + "x" + GRID_SIZE + " grid)");
+ System.out.println("Duration: " + (TEST_DURATION_MS / 1000) + " seconds per test");
+ System.out.println();
+ System.out.println(thinSeparator);
+ System.out.println("SYSTEM INFORMATION");
+ System.out.println(thinSeparator);
+ System.out.println("CPU Name: " + getCpuName());
+ System.out.println("Arch: " + System.getProperty("os.arch"));
+ System.out.println("Cores: " + runtime.availableProcessors());
+ System.out.println();
+ System.out.println(thinSeparator);
+ System.out.printf("%-28s %s%n", "Test", "Avg FPS");
+ System.out.println(thinSeparator);
+
+ for (TestResult result : results) {
+ System.out.printf("%-28s %.2f%n", result.testName, result.averageFps);
+ }
+
+ System.out.println(separator);
+ }
+
+ private String getCpuName() {
+ try {
+ java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.FileReader("/proc/cpuinfo"));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (line.startsWith("model name")) {
+ reader.close();
+ return line.substring(line.indexOf(':') + 1).trim();
+ }
+ }
+ reader.close();
+ } catch (Exception ignored) {
+ }
+ return System.getProperty("java.vm.name", "Unknown");
+ }
+
+ @Override
+ public boolean onFrame(ViewPanel viewPanel, int millisecondsSinceLastFrame) {
+ // Perform deferred test transition at safe point (before render cycle starts)
+ if (pendingTestTransition) {
+ pendingTestTransition = false;
+ performTestTransition();
+ }
+
+ // Deferred first-test start: runs on the render thread, before renderFrame()
+ if (currentTest == null && !benchmarkFinished && results.isEmpty()) {
+ startNextTest();
+ }
+
+ if (benchmarkFinished) {
+ return false;
+ }
+
+ if (currentTest == null) {
+ return false;
+ }
+
+ 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++;
+
+ long elapsed = System.currentTimeMillis() - testStartTime;
+ if (elapsed >= TEST_DURATION_MS && !testFinished) {
+ finishCurrentTest();
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean keyPressed(KeyEvent event, ViewPanel viewPanel) {
+ if (event.getKeyChar() == ' ') {
+ finishCurrentTest();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean keyReleased(KeyEvent event, ViewPanel viewPanel) {
+ return false;
+ }
+
+ @Override
+ public boolean focusLost(ViewPanel viewPanel) {
+ return false;
+ }
+
+ @Override
+ public boolean focusReceived(ViewPanel viewPanel) {
+ return false;
+ }
+}
\ No newline at end of file
--- /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.renderer.raster.Color;
+import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
+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-color cubes.
+ * Renders a grid of cubes with random semi-transparent colors to test
+ * solid polygon rasterization performance.
+ */
+public class SolidCubesTest 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<Object> cubes = new ArrayList<>();
+
+ @Override
+ public String getName() {
+ return "Solid Cubes";
+ }
+
+ @Override
+ public void setup(ShapeCollection shapes) {
+ random.setSeed(RANDOM_SEED);
+ 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(
+ 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);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void teardown(ShapeCollection shapes) {
+ for (Object cube : cubes) {
+ shapes.getShapes().remove(cube);
+ }
+ cubes.clear();
+ }
+}
\ No newline at end of file
--- /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.renderer.raster.Color;
+import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.GlowingPoint;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Benchmark test for glowing point (star) billboards.
+ * Renders a grid of glowing points arranged in a cube formation to test
+ * billboard rendering and texture blending performance.
+ */
+public class StarGridTest implements BenchmarkTest {
+
+ private static final int GRID_SIZE = 16;
+ private static final double SPACING = 80;
+ private static final double STAR_SIZE = 20;
+ private static final int UNIQUE_COLORS_COUNT = 30;
+ private static final long RANDOM_SEED = 42;
+
+ private final Random random = new Random(RANDOM_SEED);
+ private final List<Object> stars = new ArrayList<>();
+ private List<Color> colors;
+
+ @Override
+ public String getName() {
+ return "Star Grid";
+ }
+
+ @Override
+ public void setup(ShapeCollection shapes) {
+ initializeColors();
+ random.setSeed(RANDOM_SEED);
+ 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 = colors.get(random.nextInt(colors.size()));
+ GlowingPoint star = new GlowingPoint(
+ new Point3D(px, py, pz),
+ STAR_SIZE,
+ color
+ );
+ shapes.addShape(star);
+ stars.add(star);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void teardown(ShapeCollection shapes) {
+ for (Object star : stars) {
+ shapes.getShapes().remove(star);
+ }
+ stars.clear();
+ }
+
+ private void initializeColors() {
+ colors = new ArrayList<>();
+ random.setSeed(RANDOM_SEED);
+ for (int i = 0; i < UNIQUE_COLORS_COUNT; i++) {
+ colors.add(new Color(
+ random.nextDouble() + 0.5,
+ random.nextDouble() + 0.5,
+ random.nextDouble() + 0.5,
+ 255
+ ));
+ }
+ }
+}
\ No newline at end of file
--- /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;
+
+/**
+ * Holds the results of a single benchmark test.
+ */
+public class TestResult {
+
+ /** The name of the benchmark test. */
+ public final String testName;
+
+ /** Total number of frames rendered during the test. */
+ public final long frameCount;
+
+ /** Duration of the test in seconds. */
+ public final double durationSeconds;
+
+ /** Average frames per second achieved during the test. */
+ public final double averageFps;
+
+ /**
+ * Creates a test result record.
+ * @param testName the name of the test
+ * @param frameCount total frames rendered
+ * @param durationSeconds test duration in seconds
+ */
+ public TestResult(String testName, long frameCount, double durationSeconds) {
+ this.testName = testName;
+ this.frameCount = frameCount;
+ this.durationSeconds = durationSeconds;
+ this.averageFps = frameCount / durationSeconds;
+ }
+}
\ No newline at end of file
--- /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.Point2D;
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.math.Vertex;
+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.texture.Texture;
+
+/**
+ * A cube composed of textured polygons.
+ * Used by the {@link TexturedCubesTest} benchmark to measure texture rendering performance.
+ */
+public class TexturedCube extends AbstractCompositeShape {
+
+ /**
+ * Creates a textured cube centered at the given position.
+ * @param center the center position of the cube
+ * @param size half the edge length of the cube
+ * @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);
+ }
+
+ 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
--- /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.renderer.raster.ShapeCollection;
+import eu.svjatoslav.sixth.e3d.renderer.raster.texture.Texture;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Benchmark test for textured cubes.
+ * Renders a grid of cubes with textures to test textured polygon rendering
+ * and texture sampling performance.
+ */
+public class TexturedCubesTest 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 int TEXTURE_COUNT = 20;
+ private static final long RANDOM_SEED = 42;
+
+ private final Random random = new Random(RANDOM_SEED);
+ private final List<Object> cubes = new ArrayList<>();
+ private Texture[] textures;
+ private int[] cubeTextureIndices;
+
+ @Override
+ public String getName() {
+ return "Textured Cubes";
+ }
+
+ @Override
+ public void setup(ShapeCollection shapes) {
+ initializeTextures();
+ initializeCubeTextureIndices();
+
+ 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;
+
+ Texture tex = textures[cubeTextureIndices[idx]];
+ TexturedCube cube = new TexturedCube(
+ new Point3D(px, py, pz),
+ CUBE_SIZE,
+ tex
+ );
+ shapes.addShape(cube);
+ cubes.add(cube);
+ idx++;
+ }
+ }
+ }
+ }
+
+ @Override
+ public void teardown(ShapeCollection shapes) {
+ for (Object cube : cubes) {
+ shapes.getShapes().remove(cube);
+ }
+ cubes.clear();
+ }
+
+ private void initializeTextures() {
+ textures = new Texture[TEXTURE_COUNT];
+ for (int i = 0; i < TEXTURE_COUNT; i++) {
+ textures[i] = createGlowTexture(
+ 50 + random.nextInt(200),
+ 50 + random.nextInt(200),
+ 50 + random.nextInt(200)
+ );
+ }
+ }
+
+ private void initializeCubeTextureIndices() {
+ random.setSeed(RANDOM_SEED);
+ cubeTextureIndices = new int[GRID_SIZE * GRID_SIZE * GRID_SIZE];
+ for (int i = 0; i < cubeTextureIndices.length; i++) {
+ cubeTextureIndices[i] = random.nextInt(TEXTURE_COUNT);
+ }
+ }
+
+ 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;
+ }
+}
\ No newline at end of file
--- /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.renderer.raster.Color;
+import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.line.LineAppearance;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.WireframeBox;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Benchmark test for wireframe cubes.
+ * Renders a grid of wireframe cube outlines to test line rendering performance.
+ */
+public class WireframeCubesTest 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<Object> cubes = new ArrayList<>();
+
+ @Override
+ public String getName() {
+ return "Wireframe Cubes";
+ }
+
+ @Override
+ public void setup(ShapeCollection shapes) {
+ random.setSeed(RANDOM_SEED);
+ 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(
+ 100 + random.nextInt(155),
+ 100 + random.nextInt(155),
+ 100 + random.nextInt(155)
+ );
+ LineAppearance appearance = new LineAppearance(5.5, color);
+
+ Point3D center = new Point3D(px, py, pz);
+ Point3D p1 = new Point3D(center.x - CUBE_SIZE, center.y - CUBE_SIZE, center.z - CUBE_SIZE);
+ Point3D p2 = new Point3D(center.x + CUBE_SIZE, center.y + CUBE_SIZE, center.z + CUBE_SIZE);
+
+ WireframeBox cube = new WireframeBox(p1, p2, appearance);
+ shapes.addShape(cube);
+ cubes.add(cube);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void teardown(ShapeCollection shapes) {
+ for (Object cube : cubes) {
+ shapes.getShapes().remove(cube);
+ }
+ cubes.clear();
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+/**
+ * Graphics benchmark components for measuring the Sixth 3D engine's rendering performance.
+ *
+ * <p>The benchmark suite includes:</p>
+ * <ul>
+ * <li>{@link eu.svjatoslav.sixth.e3d.examples.benchmark.GraphicsBenchmark} - Main benchmark runner</li>
+ * <li>{@link eu.svjatoslav.sixth.e3d.examples.benchmark.BenchmarkTest} - Interface for test implementations</li>
+ * <li>{@link eu.svjatoslav.sixth.e3d.examples.benchmark.SolidCubesTest} - Solid-color cube rendering test</li>
+ * <li>{@link eu.svjatoslav.sixth.e3d.examples.benchmark.TexturedCubesTest} - Textured cube rendering test</li>
+ * <li>{@link eu.svjatoslav.sixth.e3d.examples.benchmark.WireframeCubesTest} - Wireframe cube rendering test</li>
+ * <li>{@link eu.svjatoslav.sixth.e3d.examples.benchmark.StarGridTest} - Billboard (glowing point) rendering test</li>
+ * </ul>
+ */
+
+package eu.svjatoslav.sixth.e3d.examples.benchmark;
\ No newline at end of file
// add galaxy
geometryCollection.addShape(new Galaxy(500, 3, 10000, transform));
+ // Ensure the render thread is started
+ viewFrame.getViewPanel().ensureRenderThreadStarted();
+
}
}
package eu.svjatoslav.sixth.e3d.examples.launcher;
-import eu.svjatoslav.sixth.e3d.examples.*;
-import eu.svjatoslav.sixth.e3d.examples.galaxy_demo.PointCloudDemo;
+import eu.svjatoslav.sixth.e3d.examples.GraphDemo;
+import eu.svjatoslav.sixth.e3d.examples.OctreeDemo;
+import eu.svjatoslav.sixth.e3d.examples.RandomPolygonsDemo;
+import eu.svjatoslav.sixth.e3d.examples.RainingNumbersDemo;
import eu.svjatoslav.sixth.e3d.examples.ShadedShapesDemo;
+import eu.svjatoslav.sixth.e3d.examples.TextEditorDemo;
+import eu.svjatoslav.sixth.e3d.examples.TextEditorDemo2;
+import eu.svjatoslav.sixth.e3d.examples.benchmark.GraphicsBenchmark;
+import eu.svjatoslav.sixth.e3d.examples.galaxy_demo.PointCloudDemo;
import javax.swing.*;
import java.awt.event.ActionEvent;
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. */
}
}
- /** 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);
}
}
// Done! World is built. So ensure screen is updated too.
viewPanel.repaintDuringNextViewUpdate();
+
+ // Ensure the render thread is started
+ viewPanel.ensureRenderThreadStarted();
}
/**