From 7fadfa9e211ed1216efba34a86118b8be34b1c8f Mon Sep 17 00:00:00 2001
From: Svjatoslav Agejenko
Date: Sat, 28 Mar 2026 14:50:34 +0200
Subject: [PATCH] feat(demos): add CSG and axis demos, reorganize into
essentials subpackage
Add two new demonstration apps:
- CSGDemo: showcases boolean operations (subtract, union, intersect) on
3D shapes using the BSP-based CSG system from sixth-3d
- AxisArrowsDemo: displays coordinate system axes with colored arrows
(X=red, Y=green, Z=blue) and text labels
Reorganize demo structure:
- Move basic demos (MinimalExample, WindingOrderDemo, ShapeGalleryDemo)
to examples.essentials subpackage for cleaner organization
- Rename ShadedShapesDemo to ShapeGalleryDemo for clarity
- Remove RandomPolygonsDemo (redundant with other polygon demos)
Update launcher with two-tier layout: "Essentials" section for core
demos, "Demos" section for advanced applications. Simplify arrow
constructors in ArrowDemo and ShapeGalleryDemo using auto-calculated
tip dimensions.
---
AGENTS.md | 14 +-
.../sixth/e3d/examples/ArrowDemo.java | 50 +---
.../sixth/e3d/examples/OctreeDemo.java | 71 +++---
.../e3d/examples/RandomPolygonsDemo.java | 110 --------
.../examples/essentials/AxisArrowsDemo.java | 148 +++++++++++
.../e3d/examples/essentials/CSGDemo.java | 236 ++++++++++++++++++
.../{ => essentials}/MinimalExample.java | 5 +-
.../ShapeGalleryDemo.java} | 40 ++-
.../{ => essentials}/WindingOrderDemo.java | 2 +-
.../launcher/ApplicationListPanel.java | 231 +++++++++++------
.../sixth/e3d/examples/life_demo/Main.java | 72 +++++-
.../sixth/e3d/examples/life_demo/Matrix.java | 28 ++-
.../examples/terrain_demo/TerrainDemo.java | 2 +-
13 files changed, 703 insertions(+), 306 deletions(-)
delete mode 100755 src/main/java/eu/svjatoslav/sixth/e3d/examples/RandomPolygonsDemo.java
create mode 100644 src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/AxisArrowsDemo.java
create mode 100644 src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/CSGDemo.java
rename src/main/java/eu/svjatoslav/sixth/e3d/examples/{ => essentials}/MinimalExample.java (92%)
rename src/main/java/eu/svjatoslav/sixth/e3d/examples/{ShadedShapesDemo.java => essentials/ShapeGalleryDemo.java} (87%)
rename src/main/java/eu/svjatoslav/sixth/e3d/examples/{ => essentials}/WindingOrderDemo.java (97%)
diff --git a/AGENTS.md b/AGENTS.md
index e6360a7..d5e1345 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -34,15 +34,6 @@ mvn javadoc:javadoc
```bash
# Run the launcher
java -cp target/sixth-3d-demos.jar eu.svjatoslav.sixth.e3d.examples.launcher.Main
-
-# Run Game of Life demo
-java -cp target/sixth-3d-demos.jar eu.svjatoslav.sixth.e3d.examples.life_demo.Main
-
-# 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
@@ -70,7 +61,9 @@ src/main/java/eu/svjatoslav/sixth/e3d/examples/
â âââ PointCloudDemo.java
âââ RandomPolygonsDemo.java
âââ OctreeDemo.java
-âââ GraphDemo.java
+âââ graph_demo/ - 3D graph visualization demos
+â âââ MathGraphsDemo.java
+â âââ SurfaceGraph3D.java
âââ TextEditorDemo.java
âââ TextEditorDemo2.java
âââ RainingNumbersDemo.java
@@ -127,6 +120,7 @@ Implement `MouseInteractionController` for mouse events, or extend input tracker
### 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
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/ArrowDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/ArrowDemo.java
index d457318..a04c237 100644
--- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/ArrowDemo.java
+++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/ArrowDemo.java
@@ -52,11 +52,7 @@ public class ArrowDemo {
final SolidPolygonArrow redArrow = new SolidPolygonArrow(
new Point3D(0, 150, 0),
new Point3D(0, -150, 0),
- 8, // body radius
- 20, // tip radius
- 40, // tip length
- 16, // segments
- Color.RED
+ 8, Color.RED
);
shapes.addShape(redArrow);
@@ -64,11 +60,7 @@ public class ArrowDemo {
final SolidPolygonArrow greenArrow = new SolidPolygonArrow(
new Point3D(-200, 0, 0),
new Point3D(0, 0, 0),
- 6,
- 15,
- 30,
- 12,
- Color.GREEN
+ 6, Color.GREEN
);
shapes.addShape(greenArrow);
@@ -76,11 +68,7 @@ public class ArrowDemo {
final SolidPolygonArrow blueArrow = new SolidPolygonArrow(
new Point3D(0, 0, -200),
new Point3D(0, 0, 0),
- 6,
- 15,
- 30,
- 12,
- Color.BLUE
+ 6, Color.BLUE
);
shapes.addShape(blueArrow);
@@ -88,11 +76,7 @@ public class ArrowDemo {
final SolidPolygonArrow yellowArrow = new SolidPolygonArrow(
new Point3D(100, 100, 100),
new Point3D(300, -100, 300),
- 10,
- 25,
- 50,
- 16,
- Color.YELLOW
+ 10, Color.YELLOW
);
shapes.addShape(yellowArrow);
@@ -100,11 +84,7 @@ public class ArrowDemo {
final SolidPolygonArrow transparentCyanArrow = new SolidPolygonArrow(
new Point3D(-150, 50, -100),
new Point3D(-50, -100, 100),
- 8,
- 20,
- 40,
- 12,
- new Color(0, 255, 255, 128)
+ 8, new Color(0, 255, 255, 128)
);
shapes.addShape(transparentCyanArrow);
@@ -112,11 +92,7 @@ public class ArrowDemo {
final SolidPolygonArrow transparentMagentaArrow = new SolidPolygonArrow(
new Point3D(50, 200, 50),
new Point3D(50, 0, 50),
- 12,
- 30,
- 60,
- 20,
- new Color(255, 0, 255, 64)
+ 12, new Color(255, 0, 255, 64)
);
shapes.addShape(transparentMagentaArrow);
@@ -129,15 +105,11 @@ public class ArrowDemo {
final double endX = (radius + 80) * Math.cos(angle);
final double endZ = (radius + 80) * Math.sin(angle);
- final SolidPolygonArrow circleArrow = new SolidPolygonArrow(
- new Point3D(startX, -80, startZ),
- new Point3D(endX, -80, endZ),
- 4,
- 10,
- 20,
- 8,
- Color.WHITE
- );
+final SolidPolygonArrow circleArrow = new SolidPolygonArrow(
+ new Point3D(startX, -80, startZ),
+ new Point3D(endX, -80, endZ),
+ 4, Color.WHITE
+ );
shapes.addShape(circleArrow);
}
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/OctreeDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/OctreeDemo.java
index ead6346..aedc08c 100755
--- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/OctreeDemo.java
+++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/OctreeDemo.java
@@ -1,8 +1,8 @@
/*
- * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
* This project is released under Creative Commons Zero (CC0) license.
*
-*/
+ */
package eu.svjatoslav.sixth.e3d.examples;
@@ -13,8 +13,8 @@ import eu.svjatoslav.sixth.e3d.gui.humaninput.WorldNavigationUserInputTracker;
import eu.svjatoslav.sixth.e3d.math.Transform;
import eu.svjatoslav.sixth.e3d.renderer.octree.IntegerPoint;
import eu.svjatoslav.sixth.e3d.renderer.octree.OctreeVolume;
-import eu.svjatoslav.sixth.e3d.renderer.octree.raytracer.RaytracingCamera;
import eu.svjatoslav.sixth.e3d.renderer.octree.raytracer.RayTracer;
+import eu.svjatoslav.sixth.e3d.renderer.octree.raytracer.RaytracingCamera;
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;
@@ -36,12 +36,8 @@ import java.util.Vector;
public class OctreeDemo extends WorldNavigationUserInputTracker {
/**
- * Creates a new OctreeDemo instance.
+ * Scale factor for rendering octree voxels in the scene.
*/
- public OctreeDemo() {
- }
-
- /** Scale factor for rendering octree voxels in the scene. */
private static final double magnification = 5;
private final LineAppearance gridAppearance = new LineAppearance(40, new Color(255,
0, 0, 60));
@@ -49,9 +45,15 @@ public class OctreeDemo extends WorldNavigationUserInputTracker {
private OctreeVolume octreeVolume;
private ShapeCollection shapeCollection;
private ViewPanel viewPanel;
+ /**
+ * Creates a new OctreeDemo instance.
+ */
+ public OctreeDemo() {
+ }
/**
* Entry point for the octree demo.
+ *
* @param args command line arguments (ignored)
*/
public static void main(final String[] args) {
@@ -60,12 +62,13 @@ public class OctreeDemo extends WorldNavigationUserInputTracker {
/**
* Adds a light source to both the raytracer and rasterizer lighting systems.
- * @param location position in octree space (will be scaled by magnification for display)
- * @param color color of the light
+ *
+ * @param location position in octree space (will be scaled by magnification for display)
+ * @param color color of the light
* @param brightness intensity of the light
*/
private void addLightToBothSystems(final Point3D location, final Color color,
- final float brightness) {
+ final float brightness) {
shapeCollection.addShape(new LightSourceMarker(new Point3D(location)
.scaleUp(magnification), color));
@@ -76,7 +79,9 @@ public class OctreeDemo extends WorldNavigationUserInputTracker {
new Point3D(location).scaleUp(magnification), color, brightness * 50));
}
- /** Creates a colorful spiral pattern of voxels in the octree. */
+ /**
+ * Creates a colorful spiral pattern of voxels in the octree.
+ */
private void dotSpiral() {
for (double i = 0; i < 20; i = i + .1) {
@@ -96,9 +101,10 @@ public class OctreeDemo extends WorldNavigationUserInputTracker {
/**
* Recursively creates a fractal pattern of rectangles.
- * @param x center X coordinate
- * @param y center Y coordinate
- * @param z center Z coordinate
+ *
+ * @param x center X coordinate
+ * @param y center Y coordinate
+ * @param z center Z coordinate
* @param size size of the current rectangle
* @param step recursion depth counter
*/
@@ -109,8 +115,8 @@ public class OctreeDemo extends WorldNavigationUserInputTracker {
final double c3 = (Math.cos(z / 12f) * 100f) + 127;
putRect(
- new IntegerPoint( x - size, y - size, z - size),
- new IntegerPoint( x + size, y + size, z + size),
+ new IntegerPoint(x - size, y - size, z - size),
+ new IntegerPoint(x + size, y + size, z + size),
new Color((int) c1, (int) c2, (int) c3, 200));
if (size > 1) {
@@ -120,10 +126,12 @@ public class OctreeDemo extends WorldNavigationUserInputTracker {
}
}
- /** Initializes the demo scene with grid, lights, shapes, and fractal pattern. */
+ /**
+ * Initializes the demo scene with grid, lights, shapes, and fractal pattern.
+ */
private void init() {
- final ViewFrame viewFrame = new ViewFrame("Volumetric Octree");
+ final ViewFrame viewFrame = new ViewFrame("Volumetric octree");
viewPanel = viewFrame.getViewPanel();
viewPanel.getCamera().getTransform().set(104.13, -65.04, -370.53, 0.12, 0.14, 0);
@@ -149,7 +157,7 @@ public class OctreeDemo extends WorldNavigationUserInputTracker {
new Color(200, 255, 200, 100));
putRect(new IntegerPoint(-3, 0, -30),
- new IntegerPoint( 12, 3, 300),
+ new IntegerPoint(12, 3, 300),
new Color(255, 200, 200, 100));
putRect(new IntegerPoint(-20, 20, -20),
@@ -171,7 +179,8 @@ public class OctreeDemo extends WorldNavigationUserInputTracker {
/**
* Handles keyboard input for triggering raytracing.
- * @param event the key event
+ *
+ * @param event the key event
* @param viewPanel the view panel
* @return true if the event was consumed
*/
@@ -187,9 +196,10 @@ public class OctreeDemo extends WorldNavigationUserInputTracker {
/**
* Places a single voxel at the specified position with the given color.
- * @param x X coordinate in octree space
- * @param y Y coordinate in octree space
- * @param z Z coordinate in octree space
+ *
+ * @param x X coordinate in octree space
+ * @param y Y coordinate in octree space
+ * @param z Z coordinate in octree space
* @param color the color of the voxel
*/
private void putPixel(final int x, final int y, final int z,
@@ -202,8 +212,9 @@ public class OctreeDemo extends WorldNavigationUserInputTracker {
/**
* Fills a rectangular region in the octree with the specified color.
- * @param p1 first corner of the rectangle
- * @param p2 opposite corner of the rectangle
+ *
+ * @param p1 first corner of the rectangle
+ * @param p2 opposite corner of the rectangle
* @param color the color to fill
*/
private void putRect(IntegerPoint p1, IntegerPoint p2, final Color color) {
@@ -215,7 +226,9 @@ public class OctreeDemo extends WorldNavigationUserInputTracker {
octreeVolume.fillRectangle(p1, p2, color);
}
- /** Starts a raytracing render of the current view in a background thread. */
+ /**
+ * Starts a raytracing render of the current view in a background thread.
+ */
private void raytrace() {
// create and add camera object to scene
final RaytracingCamera raytracingCamera = new RaytracingCamera(viewPanel.getCamera(), magnification);
@@ -228,7 +241,9 @@ public class OctreeDemo extends WorldNavigationUserInputTracker {
thread.start();
}
- /** Creates a tiled floor pattern using small rectangular tiles. */
+ /**
+ * Creates a tiled floor pattern using small rectangular tiles.
+ */
private void tiledFloor() {
final int step = 40;
final int size = step - 15;
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/RandomPolygonsDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/RandomPolygonsDemo.java
deleted file mode 100755
index 8b7d7d5..0000000
--- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/RandomPolygonsDemo.java
+++ /dev/null
@@ -1,110 +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.Point3D;
-import eu.svjatoslav.sixth.e3d.gui.ViewFrame;
-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.basic.solidpolygon.SolidPolygon;
-import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.Grid3D;
-
-/**
- * Demo showing 1000 randomly positioned and colored triangles in 3D space.
- * Demonstrates the engine's ability to render many semi-transparent polygons
- * with proper depth sorting.
- */
-public class RandomPolygonsDemo {
-
- /**
- * Creates a new RandomPolygonsDemo instance.
- */
- public RandomPolygonsDemo() {
- }
-
- /** Average size of each random polygon. */
- private static final double POLYGON_AVERAGE_SIZE = 130;
- /** Number of polygons to generate. */
- private static final int POLYGON_COUNT = 1000;
-
- /**
- * Adds a single random triangle to the scene.
- * @param geometryCollection the collection to add the polygon to
- */
- private static void addRandomPolygon(final ShapeCollection geometryCollection) {
- final Point3D polygonLocation = getRandomPoint(1000);
-
- final Point3D point1 = new Point3D(polygonLocation);
- point1.add(getRandomPoint(POLYGON_AVERAGE_SIZE));
-
- final Point3D point2 = new Point3D(polygonLocation);
- point2.add(getRandomPoint(POLYGON_AVERAGE_SIZE));
-
- final Point3D point3 = new Point3D(polygonLocation);
- point3.add(getRandomPoint(POLYGON_AVERAGE_SIZE));
-
- final Color color = new Color(
- getColorChannelBrightness(),
- getColorChannelBrightness(),
- getColorChannelBrightness(),
- 1);
-
- final SolidPolygon polygon = new SolidPolygon(point1, point2, point3,
- color);
- geometryCollection.addShape(polygon);
- }
-
- /**
- * Generates a random color channel brightness.
- * Ensures minimum brightness of 0.3 to avoid very dark polygons.
- * @return a brightness value between 0.3 and 1.0
- */
- private static double getColorChannelBrightness() {
- return Math.random() * 0.7 + 0.3f;
- }
-
- /**
- * Generates a random 3D point within a cube centered at the origin.
- * @param amplitude the half-size of the cube
- * @return a random point within [-amplitude, amplitude] on each axis
- */
- private static Point3D getRandomPoint(final double amplitude) {
- return new Point3D((Math.random() * amplitude * 2d) - amplitude,
- (Math.random() * amplitude * 2d) - amplitude, (Math.random()
- * amplitude * 2d)
- - amplitude);
- }
-
- /**
- * Entry point for the random polygons demo.
- * @param args command line arguments (ignored)
- */
- public static void main(final String[] args) {
-
- final ViewFrame viewFrame = new ViewFrame("Random Polygons");
-
- viewFrame.getViewPanel().getCamera().getTransform().set(-52.96, -239.61, -1293.29, -0.09, -0.36, 0);
-
- final ShapeCollection shapeCollection = viewFrame.getViewPanel()
- .getRootShapeCollection();
-
- // add grid
- final LineAppearance appearance = new LineAppearance(5, new Color(100,
- 100, 255, 60));
-
- shapeCollection.addShape(new Grid3D(new Point3D(1000, -1000, -1000),
- new Point3D(-1000, 1000, 1000), 300, appearance));
-
- // add random polygons
- for (int i = 0; i < POLYGON_COUNT; i++)
- addRandomPolygon(shapeCollection);
-
- viewFrame.getViewPanel().repaintDuringNextViewUpdate();
-
- }
-}
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/AxisArrowsDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/AxisArrowsDemo.java
new file mode 100644
index 0000000..8bcd0a0
--- /dev/null
+++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/AxisArrowsDemo.java
@@ -0,0 +1,148 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+package eu.svjatoslav.sixth.e3d.examples.essentials;
+
+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.line.LineAppearance;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.ForwardOrientedTextBlock;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonArrow;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.Grid3D;
+
+/**
+ * Demo displaying coordinate system axes using 3D arrows.
+ *
+ * This demo illustrates the Sixth 3D coordinate system with three colored
+ * arrows originating from the origin (0,0,0), each pointing along a positive
+ * axis direction:
+ *
+ *
+ * - Red arrow â +X axis (points RIGHT)
+ * - Green arrow â +Y axis (points DOWN visually)
+ * - Blue arrow â +Z axis (points AWAY from viewer)
+ *
+ *
+ * A reference grid at Y=0 provides spatial context. Text labels identify
+ * each axis.
+ *
+ * @see SolidPolygonArrow
+ * @see ForwardOrientedTextBlock
+ */
+public class AxisArrowsDemo {
+
+ /**
+ * Length of each axis arrow.
+ */
+ private static final double ARROW_LENGTH = 200;
+
+ /**
+ * Radius of the arrow body (cylindrical shaft).
+ */
+ private static final double BODY_RADIUS = 6;
+
+ /**
+ * Distance from arrow tip to text label position.
+ */
+ private static final double LABEL_OFFSET = 20;
+
+ /**
+ * Scale factor for text labels.
+ */
+ private static final double LABEL_SCALE = 20.0;
+
+ /**
+ * Entry point for the axis arrows demo.
+ *
+ * @param args command line arguments (ignored)
+ */
+ public static void main(final String[] args) {
+ final ViewFrame viewFrame = new ViewFrame("Axis Arrows Demo");
+ final ViewPanel viewPanel = viewFrame.getViewPanel();
+ final ShapeCollection shapes = viewPanel.getRootShapeCollection();
+
+ // Position camera to view all three axes
+ viewPanel.getCamera().getTransform().set(130.66, -65.49, -248.18, -0.06, -0.36, -0.00);
+
+ // Create reference grid at Y=0 plane
+ createReferenceGrid(shapes);
+
+ // Create axis arrows
+ createAxisArrows(shapes);
+
+ viewPanel.repaintDuringNextViewUpdate();
+ }
+
+ /**
+ * Creates a reference grid on the Y=0 plane.
+ *
+ * @param shapes the shape collection to add the grid to
+ */
+ private static void createReferenceGrid(final ShapeCollection shapes) {
+ final LineAppearance gridAppearance = new LineAppearance(
+ 1, new Color(80, 80, 100));
+ final Grid3D grid = new Grid3D(
+ new Point3D(-300, 0, -300),
+ new Point3D(300, 0, 300),
+ 50,
+ gridAppearance
+ );
+ shapes.addShape(grid);
+ }
+
+ /**
+ * Creates the three axis arrows and their labels.
+ *
+ * @param shapes the shape collection to add the arrows to
+ */
+ private static void createAxisArrows(final ShapeCollection shapes) {
+ // X-axis arrow (RED) - points in positive X direction (RIGHT)
+ final SolidPolygonArrow xArrow = new SolidPolygonArrow(
+ new Point3D(0, 0, 0),
+ new Point3D(ARROW_LENGTH, 0, 0),
+ BODY_RADIUS, new Color(255, 0, 0, 180)
+ );
+ shapes.addShape(xArrow);
+ createLabel(shapes, "X", new Point3D(ARROW_LENGTH + LABEL_OFFSET, 0, 0), Color.RED);
+
+ // Y-axis arrow (GREEN) - points in positive Y direction (DOWN)
+ final SolidPolygonArrow yArrow = new SolidPolygonArrow(
+ new Point3D(0, 0, 0),
+ new Point3D(0, ARROW_LENGTH, 0),
+ BODY_RADIUS, new Color(0, 255, 0, 180)
+ );
+ shapes.addShape(yArrow);
+ createLabel(shapes, "Y", new Point3D(0, ARROW_LENGTH + LABEL_OFFSET, 0), Color.GREEN);
+
+ // Z-axis arrow (BLUE) - points in positive Z direction (AWAY)
+ final SolidPolygonArrow zArrow = new SolidPolygonArrow(
+ new Point3D(0, 0, 0),
+ new Point3D(0, 0, ARROW_LENGTH),
+ BODY_RADIUS, new Color(0, 0, 255, 180)
+ );
+ shapes.addShape(zArrow);
+ createLabel(shapes, "Z", new Point3D(0, 0, ARROW_LENGTH + LABEL_OFFSET), Color.BLUE);
+ }
+
+ /**
+ * Creates a text label at the specified position.
+ *
+ * @param shapes the shape collection to add the label to
+ * @param text the label text
+ * @param position the 3D position of the label
+ * @param color the text color
+ */
+ private static void createLabel(final ShapeCollection shapes,
+ final String text,
+ final Point3D position,
+ final Color color) {
+ final ForwardOrientedTextBlock label = new ForwardOrientedTextBlock(
+ position, LABEL_SCALE, 2, text, color
+ );
+ shapes.addShape(label);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/CSGDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/CSGDemo.java
new file mode 100644
index 0000000..b8d2cda
--- /dev/null
+++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/CSGDemo.java
@@ -0,0 +1,236 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ *
+ */
+package eu.svjatoslav.sixth.e3d.examples.essentials;
+
+import eu.svjatoslav.sixth.e3d.csg.CSG;
+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.lighting.LightSource;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.ForwardOrientedTextBlock;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonCube;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonMesh;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonSphere;
+
+/**
+ * Demo showcasing Constructive Solid Geometry (CSG) boolean operations.
+ *
+ * This demo displays three CSG operations side by side:
+ *
+ * - Subtract: A cube with a spherical cavity carved out
+ * - Union: A cube merged with a sphere
+ * - Intersect: The volume shared by a cube and sphere
+ *
+ *
+ * Shapes are rendered with slight transparency (85% opacity) to allow
+ * seeing internal structure. The demo demonstrates how existing composite
+ * shapes like {@link SolidPolygonCube} and {@link SolidPolygonSphere} can
+ * be used with CSG operations.
+ *
+ * Run this demo:
+ * {@code
+ * java -cp target/sixth-3d-demos.jar eu.svjatoslav.sixth.e3d.examples.CSGDemo
+ * }
+ *
+ * @see CSG the CSG solid class
+ * @see SolidPolygonMesh the renderable mesh
+ */
+public class CSGDemo {
+
+ /**
+ * Distance between shapes on the X axis.
+ */
+ private static final double SPACING = 500;
+
+ /**
+ * Size of the cube (half-edge length).
+ */
+ private static final double CUBE_SIZE = 80;
+
+ /**
+ * Radius of the sphere (slightly larger for interesting intersection).
+ */
+ private static final double SPHERE_RADIUS = 96;
+
+ /**
+ * Number of segments for the sphere (smoothness).
+ */
+ private static final int SPHERE_SEGMENTS = 12;
+
+ /**
+ * Entry point for the CSG demo.
+ *
+ * @param args command line arguments (ignored)
+ */
+ public static void main(final String[] args) {
+ final ViewFrame viewFrame = new ViewFrame("CSG Demo - Boolean Operations");
+ final ViewPanel viewPanel = viewFrame.getViewPanel();
+ final ShapeCollection shapes = viewPanel.getRootShapeCollection();
+
+ // Position camera to view all three shapes
+ viewPanel.getCamera().getTransform().set(0, -150, -600, 0, 0, 0);
+
+ // Set up lighting
+ viewPanel.getLightingManager().setAmbientLight(new Color(60, 60, 70));
+
+ // Create lights
+ createLights(viewPanel, shapes);
+
+ // Create the three CSG demonstrations
+ createSubtractDemo(shapes, -SPACING, 0, 0);
+ createUnionDemo(shapes, 0, 0, 0);
+ createIntersectDemo(shapes, SPACING, 0, 0);
+
+ // Add labels
+ createLabels(shapes);
+
+ viewPanel.repaintDuringNextViewUpdate();
+ }
+
+ /**
+ * Creates the subtract operation demo: cube - sphere.
+ * Results in a cube with a spherical cavity.
+ *
+ * @param shapes the shape collection
+ * @param x the X position
+ * @param y the Y position
+ * @param z the Z position
+ */
+ private static void createSubtractDemo(final ShapeCollection shapes,
+ final double x, final double y, final double z) {
+ final SolidPolygonCube cube = new SolidPolygonCube(
+ new Point3D(0, 0, 0), CUBE_SIZE, Color.WHITE);
+ final SolidPolygonSphere sphere = new SolidPolygonSphere(
+ new Point3D(0, 0, 0), SPHERE_RADIUS, SPHERE_SEGMENTS, Color.WHITE);
+
+ final CSG cubeCSG = CSG.fromCompositeShape(cube);
+ final CSG sphereCSG = CSG.fromCompositeShape(sphere);
+
+ final CSG result = cubeCSG.subtract(sphereCSG);
+
+ final SolidPolygonMesh mesh = result.toMesh(new Color(255, 100, 100, 217), new Point3D(x, y, z));
+ mesh.setShadingEnabled(true);
+ mesh.setBackfaceCulling(true);
+
+ shapes.addShape(mesh);
+
+ System.out.println("Subtract (cube - sphere): " + mesh.getTriangleCount() + " triangles");
+ }
+
+ /**
+ * Creates the union operation demo: cube + sphere.
+ * Results in a combined shape.
+ *
+ * @param shapes the shape collection
+ * @param x the X position
+ * @param y the Y position
+ * @param z the Z position
+ */
+ private static void createUnionDemo(final ShapeCollection shapes,
+ final double x, final double y, final double z) {
+ final SolidPolygonCube cube = new SolidPolygonCube(
+ new Point3D(0, 0, 0), CUBE_SIZE, Color.WHITE);
+ final SolidPolygonSphere sphere = new SolidPolygonSphere(
+ new Point3D(0, 0, 0), SPHERE_RADIUS, SPHERE_SEGMENTS, Color.WHITE);
+
+ final CSG cubeCSG = CSG.fromCompositeShape(cube);
+ final CSG sphereCSG = CSG.fromCompositeShape(sphere);
+
+ final CSG result = cubeCSG.union(sphereCSG);
+
+ final SolidPolygonMesh mesh = result.toMesh(new Color(100, 255, 100, 217), new Point3D(x, y, z));
+ mesh.setShadingEnabled(true);
+ mesh.setBackfaceCulling(true);
+
+ shapes.addShape(mesh);
+
+ System.out.println("Union (cube + sphere): " + mesh.getTriangleCount() + " triangles");
+ }
+
+ /**
+ * Creates the intersect operation demo: cube â© sphere.
+ * Results in the volume shared by both shapes.
+ *
+ * @param shapes the shape collection
+ * @param x the X position
+ * @param y the Y position
+ * @param z the Z position
+ */
+ private static void createIntersectDemo(final ShapeCollection shapes,
+ final double x, final double y, final double z) {
+ final SolidPolygonCube cube = new SolidPolygonCube(
+ new Point3D(0, 0, 0), CUBE_SIZE, Color.WHITE);
+ final SolidPolygonSphere sphere = new SolidPolygonSphere(
+ new Point3D(0, 0, 0), SPHERE_RADIUS, SPHERE_SEGMENTS, Color.WHITE);
+
+ final CSG cubeCSG = CSG.fromCompositeShape(cube);
+ final CSG sphereCSG = CSG.fromCompositeShape(sphere);
+
+ final CSG result = cubeCSG.intersect(sphereCSG);
+
+ final SolidPolygonMesh mesh = result.toMesh(new Color(100, 150, 255, 217), new Point3D(x, y, z));
+ mesh.setShadingEnabled(true);
+ mesh.setBackfaceCulling(true);
+
+ shapes.addShape(mesh);
+
+ System.out.println("Intersect (cube â© sphere): " + mesh.getTriangleCount() + " triangles");
+ }
+
+ /**
+ * Creates labels for each operation.
+ *
+ * @param shapes the shape collection
+ */
+ private static void createLabels(final ShapeCollection shapes) {
+ final double labelY = -150;
+ final double labelZ = 0;
+
+ // Subtract label
+ shapes.addShape(new ForwardOrientedTextBlock(
+ new Point3D(-SPACING, labelY, labelZ),
+ 8.0, 2, "Subtract", Color.RED
+ ));
+
+ // Union label
+ shapes.addShape(new ForwardOrientedTextBlock(
+ new Point3D(0, labelY, labelZ),
+ 8.0, 2, "Union", Color.GREEN
+ ));
+
+ // Intersect label
+ shapes.addShape(new ForwardOrientedTextBlock(
+ new Point3D(SPACING, labelY, labelZ),
+ 8.0, 2, "Intersect", Color.BLUE
+ ));
+ }
+
+ /**
+ * Creates lighting for the scene.
+ *
+ * @param viewPanel the view panel
+ * @param shapes the shape collection
+ */
+ private static void createLights(final ViewPanel viewPanel, final ShapeCollection shapes) {
+ // Main light from above-front
+ final LightSource mainLight = new LightSource(
+ new Point3D(0, -300, -400),
+ new Color(255, 255, 255),
+ 1.5
+ );
+ viewPanel.getLightingManager().addLight(mainLight);
+
+ // Fill light from the side
+ final LightSource fillLight = new LightSource(
+ new Point3D(500, 100, -200),
+ new Color(150, 150, 200),
+ 0.8
+ );
+ viewPanel.getLightingManager().addLight(fillLight);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/MinimalExample.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/MinimalExample.java
similarity index 92%
rename from src/main/java/eu/svjatoslav/sixth/e3d/examples/MinimalExample.java
rename to src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/MinimalExample.java
index 86e43ce..b526eb3 100644
--- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/MinimalExample.java
+++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/MinimalExample.java
@@ -3,7 +3,7 @@
* This project is released under Creative Commons Zero (CC0) license.
*
*/
-package eu.svjatoslav.sixth.e3d.examples;
+package eu.svjatoslav.sixth.e3d.examples.essentials;
import eu.svjatoslav.sixth.e3d.geometry.Point3D;
import eu.svjatoslav.sixth.e3d.gui.ViewFrame;
@@ -22,10 +22,11 @@ public class MinimalExample {
/**
* Entry point for the minimal scene demo.
+ *
* @param args command line arguments (ignored)
*/
public static void main(String[] args) {
- ViewFrame viewFrame = new ViewFrame("Minimal Example");
+ ViewFrame viewFrame = new ViewFrame("Minimal example");
ShapeCollection shapes = viewFrame.getViewPanel().getRootShapeCollection();
viewFrame.getViewPanel().getCamera().getTransform().setTranslation(new Point3D(0, -100, -300));
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/ShadedShapesDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/ShapeGalleryDemo.java
similarity index 87%
rename from src/main/java/eu/svjatoslav/sixth/e3d/examples/ShadedShapesDemo.java
rename to src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/ShapeGalleryDemo.java
index ccdc182..28e6337 100644
--- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/ShadedShapesDemo.java
+++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/ShapeGalleryDemo.java
@@ -1,4 +1,4 @@
-package eu.svjatoslav.sixth.e3d.examples;
+package eu.svjatoslav.sixth.e3d.examples.essentials;
import eu.svjatoslav.sixth.e3d.geometry.Point3D;
import eu.svjatoslav.sixth.e3d.gui.FrameListener;
@@ -24,18 +24,7 @@ import static eu.svjatoslav.sixth.e3d.math.Transform.fromAngles;
*
* This demo displays a 7x2 grid of shapes organized by type (columns) and
* rendering style (rows). The top row shows solid polygon shapes, and the bottom
- * row shows wireframe shapes. Each column represents a different shape type:
- *
- *
- * Arrow Cone Cube Cylinder Pyramid Box Sphere
- * ââââââââââ¬âââââââââ¬âââââââââ¬âââââââââ¬âââââââââ¬âââââââââ¬âââââââââ
- * Solid â ââââ â /\\ â ââââ â âââ â /\\ â ââââ â () â
- * â ââââ â / \\ â ââââ â âââ â /__\\ â ââââ â ( ) â
- * ââââââââââ¼âââââââââ¼âââââââââ¼âââââââââ¼âââââââââ¼âââââââââ¼âââââââââ¤
- * Wirefrm â ââââ â /\\ â ââââ â âââ â /\\ â ââââ â () â
- * â ââââ â / \\ â ââââ â âââ â /__\\ â ââââ â ( ) â
- * ââââââââââ´âââââââââ´âââââââââ´âââââââââ´âââââââââ´âââââââââ´âââââââââ
- *
+ * row shows wireframe shapes. Each column represents a different shape type.
*
* Features:
*
@@ -44,7 +33,7 @@ import static eu.svjatoslav.sixth.e3d.math.Transform.fromAngles;
* - Text labels identifying each shape type
*
*/
-public class ShadedShapesDemo {
+public class ShapeGalleryDemo {
/**
* Number of columns (shape types).
@@ -168,6 +157,7 @@ public class ShadedShapesDemo {
// Row 1: Wireframe shapes
final Point3D wireframePos = new Point3D(x, 0, CELL_SIZE);
createWireframeShape(shapes, col, wireframePos, color);
+ createLabel(shapes, wireframePos, "Wireframe " + name, color);
}
}
@@ -194,9 +184,9 @@ public class ShadedShapesDemo {
switch (col) {
case 0: // Arrow
final SolidPolygonArrow arrow = new SolidPolygonArrow(
- new Point3D(pos.x, SHAPE_RADIUS / 2, pos.z),
- new Point3D(pos.x, -SHAPE_RADIUS / 2, pos.z),
- 8, 20, 40, 12, color);
+ new Point3D(pos.x, SHAPE_RADIUS, pos.z),
+ new Point3D(pos.x, -SHAPE_RADIUS, pos.z),
+ 8, color);
arrow.setShadingEnabled(true);
shapes.addShape(arrow);
break;
@@ -236,8 +226,8 @@ public class ShadedShapesDemo {
case 5: // RectangularBox
final SolidPolygonRectangularBox box = new SolidPolygonRectangularBox(
- new Point3D(pos.x - SHAPE_RADIUS * 0.7, pos.y - SHAPE_RADIUS * 0.5, pos.z - SHAPE_RADIUS * 0.6),
- new Point3D(pos.x + SHAPE_RADIUS * 0.7, pos.y + SHAPE_RADIUS * 0.5, pos.z + SHAPE_RADIUS * 0.6),
+ new Point3D(pos.x - SHAPE_RADIUS * 0.35, pos.y - SHAPE_RADIUS, pos.z - SHAPE_RADIUS * 0.35),
+ new Point3D(pos.x + SHAPE_RADIUS * 0.35, pos.y + SHAPE_RADIUS, pos.z + SHAPE_RADIUS * 0.35),
color);
box.setShadingEnabled(true);
shapes.addShape(box);
@@ -269,9 +259,9 @@ public class ShadedShapesDemo {
switch (col) {
case 0: // Arrow
final WireframeArrow arrow = new WireframeArrow(
- new Point3D(pos.x, SHAPE_RADIUS / 2, pos.z),
- new Point3D(pos.x, -SHAPE_RADIUS / 2, pos.z),
- 8, 20, 40, 12, appearance);
+ new Point3D(pos.x, SHAPE_RADIUS, pos.z),
+ new Point3D(pos.x, -SHAPE_RADIUS, pos.z),
+ 8, appearance);
shapes.addShape(arrow);
break;
@@ -306,8 +296,8 @@ public class ShadedShapesDemo {
case 5: // Box (WireframeBox)
final WireframeBox box = new WireframeBox(
- new Point3D(pos.x - SHAPE_RADIUS * 0.7, pos.y - SHAPE_RADIUS * 0.5, pos.z - SHAPE_RADIUS * 0.6),
- new Point3D(pos.x + SHAPE_RADIUS * 0.7, pos.y + SHAPE_RADIUS * 0.5, pos.z + SHAPE_RADIUS * 0.6),
+ new Point3D(pos.x - SHAPE_RADIUS * 0.35, pos.y - SHAPE_RADIUS, pos.z - SHAPE_RADIUS * 0.35),
+ new Point3D(pos.x + SHAPE_RADIUS * 0.35, pos.y + SHAPE_RADIUS, pos.z + SHAPE_RADIUS * 0.35),
appearance);
shapes.addShape(box);
break;
@@ -334,7 +324,7 @@ public class ShadedShapesDemo {
final String text, final Color color) {
final Point3D labelPos = new Point3D(pos.x, pos.y + LABEL_Y_OFFSET, pos.z);
final ForwardOrientedTextBlock label = new ForwardOrientedTextBlock(
- labelPos, 0.8, 2, text, color);
+ labelPos, 8.0, 2, text, color);
shapes.addShape(label);
}
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/WindingOrderDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/WindingOrderDemo.java
similarity index 97%
rename from src/main/java/eu/svjatoslav/sixth/e3d/examples/WindingOrderDemo.java
rename to src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/WindingOrderDemo.java
index df022df..250fc62 100644
--- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/WindingOrderDemo.java
+++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/WindingOrderDemo.java
@@ -3,7 +3,7 @@
* This project is released under Creative Commons Zero (CC0) license.
*
*/
-package eu.svjatoslav.sixth.e3d.examples;
+package eu.svjatoslav.sixth.e3d.examples.essentials;
import eu.svjatoslav.sixth.e3d.geometry.Point3D;
import eu.svjatoslav.sixth.e3d.gui.ViewFrame;
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/ApplicationListPanel.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/ApplicationListPanel.java
index dabb69b..86cda8a 100644
--- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/ApplicationListPanel.java
+++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/ApplicationListPanel.java
@@ -1,79 +1,91 @@
/*
- * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
* This project is released under Creative Commons Zero (CC0) license.
*
*/
package eu.svjatoslav.sixth.e3d.examples.launcher;
-import eu.svjatoslav.sixth.e3d.examples.terrain_demo.TerrainDemo;
-import eu.svjatoslav.sixth.e3d.examples.SineHeightmap;
-import eu.svjatoslav.sixth.e3d.examples.graph_demo.MathGraphsDemo;
-import eu.svjatoslav.sixth.e3d.examples.MinimalExample;
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.SineHeightmap;
import eu.svjatoslav.sixth.e3d.examples.TextEditorDemo;
import eu.svjatoslav.sixth.e3d.examples.TextEditorDemo2;
+import eu.svjatoslav.sixth.e3d.examples.essentials.AxisArrowsDemo;
+import eu.svjatoslav.sixth.e3d.examples.essentials.CSGDemo;
+import eu.svjatoslav.sixth.e3d.examples.essentials.MinimalExample;
+import eu.svjatoslav.sixth.e3d.examples.essentials.ShapeGalleryDemo;
+import eu.svjatoslav.sixth.e3d.examples.essentials.WindingOrderDemo;
import eu.svjatoslav.sixth.e3d.examples.benchmark.GraphicsBenchmark;
import eu.svjatoslav.sixth.e3d.examples.galaxy_demo.PointCloudDemo;
+import eu.svjatoslav.sixth.e3d.examples.graph_demo.MathGraphsDemo;
+import eu.svjatoslav.sixth.e3d.examples.terrain_demo.TerrainDemo;
import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import java.awt.*;
import java.awt.event.ActionEvent;
/**
* Panel containing buttons to launch each demo application.
- * Displays a vertical list with button and description for each demo.
+ * Displays demos organized into sections with header labels.
*/
class ApplicationListPanel extends JPanel {
private static final long serialVersionUID = 2012721856427052560L;
private static final int BUTTON_WIDTH = 160;
private static final int GAP = 12;
+ private static final int SECTION_TOP_MARGIN = 16;
+
+ private static final DemoEntry[] ESSENTIALS = {
+ new DemoEntry("Minimal example",
+ "Minimal example showing a single red box",
+ new ShowMinimalExample()),
+ new DemoEntry("Winding order",
+ "Triangle demo for winding order & backface culling",
+ new ShowWindingOrder()),
+ new DemoEntry("Axis arrows",
+ "Coordinate system axes: X (red), Y (green), Z (blue)",
+ new ShowAxisArrows()),
+ new DemoEntry("Shape gallery",
+ "All 3D shapes with orbiting colored light sources",
+ new ShowShadedShapes()),
+ new DemoEntry("CSG Demo",
+ "Boolean operations: union, subtract, intersect on 3D shapes",
+ new ShowCSG()),
+ };
- private record DemoEntry(String name, String description, AbstractAction action) {}
-
- private static final DemoEntry[] DEMOS = {
- new DemoEntry("Minimal Example",
- "Minimal example showing a single red box",
- new ShowMinimalExample()),
- new DemoEntry("Volumetric Octree",
- "Octree-based rendering with on-demand raytracing",
- new ShowOctree()),
- new DemoEntry("Sine heightmap",
- "Two wobbly sine wave surfaces with central sphere",
- new ShowSineHeightmap()),
- new DemoEntry("Math graphs demo",
- "Function graphs (sin, cos, tan) rendered in 3D",
- new ShowMathGraphs()),
- new DemoEntry("Point cloud galaxy",
- "Spiral galaxy with 10,000 glowing points",
- new ShowPointCloud()),
- new DemoEntry("Raining numbers",
- "Numbers falling through 3D space like rain",
- new ShowRain()),
- new DemoEntry("Text editors",
- "5x5 grid of 3D text editor components",
- new ShowTextEditors()),
- new DemoEntry("Text editors city",
- "3D city of text editor panels as buildings",
- new ShowTextEditors2()),
- new DemoEntry("Game of Life",
- "Conway's Game of Life with 3D visualization",
- new ShowGameOfLife()),
- new DemoEntry("Random polygons",
- "1000 semi-transparent triangles with depth sorting",
- new ShowRandomPolygons()),
- new DemoEntry("Shaded Shapes",
- "Shapes lit by orbiting colored light sources",
- new ShowShadedShapes()),
- new DemoEntry("Procedural Terrain",
- "Procedural mountains with 3 colored light sources",
- new ShowTerrainDemo()),
- new DemoEntry("Graphics Benchmark",
- "Automated performance measuring FPS across modes",
- new ShowGraphicsBenchmark()),
+ private static final DemoEntry[] OTHER_DEMOS = {
+ new DemoEntry("Volumetric Octree",
+ "Octree-based rendering with on-demand raytracing",
+ new ShowOctree()),
+ new DemoEntry("Sine heightmap",
+ "Two wobbly sine wave surfaces with central sphere",
+ new ShowSineHeightmap()),
+ new DemoEntry("Math graphs demo",
+ "Function graphs (sin, cos, tan) rendered in 3D",
+ new ShowMathGraphs()),
+ new DemoEntry("Point cloud galaxy",
+ "Spiral galaxy with 10,000 glowing points",
+ new ShowPointCloud()),
+ new DemoEntry("Raining numbers",
+ "Numbers falling through 3D space like rain",
+ new ShowRain()),
+ new DemoEntry("Text editors",
+ "5x5 grid of 3D text editor components",
+ new ShowTextEditors()),
+ new DemoEntry("Text editors city",
+ "3D city of text editor panels as buildings",
+ new ShowTextEditors2()),
+ new DemoEntry("Game of Life",
+ "Conway's Game of Life with 3D visualization",
+ new ShowGameOfLife()),
+ new DemoEntry("Procedural Terrain",
+ "Procedural mountains with 3 colored light sources",
+ new ShowTerrainDemo()),
+ new DemoEntry("Graphics benchmark",
+ "Automated performance measuring FPS across modes",
+ new ShowGraphicsBenchmark()),
};
ApplicationListPanel() {
@@ -87,18 +99,66 @@ class ApplicationListPanel extends JPanel {
final GroupLayout.ParallelGroup horizontalButtonGroup = layout.createParallelGroup(GroupLayout.Alignment.LEADING);
final GroupLayout.ParallelGroup horizontalDescGroup = layout.createParallelGroup(GroupLayout.Alignment.LEADING);
- final JLabel headerLabel = new JLabel("Choose an example to launch:");
- headerLabel.setFont(headerLabel.getFont().deriveFont(java.awt.Font.BOLD));
+ addSectionHeader("Essentials", verticalGroup, horizontalButtonGroup, horizontalDescGroup, layout, false);
+ addDemoEntries(ESSENTIALS, verticalGroup, horizontalButtonGroup, horizontalDescGroup, layout);
+
+ addSectionHeader("Demos", verticalGroup, horizontalButtonGroup, horizontalDescGroup, layout, true);
+ addDemoEntries(OTHER_DEMOS, verticalGroup, horizontalButtonGroup, horizontalDescGroup, layout);
+
+ layout.setVerticalGroup(verticalGroup);
+ layout.setHorizontalGroup(layout.createSequentialGroup()
+ .addGroup(horizontalButtonGroup)
+ .addGap(GAP)
+ .addGroup(horizontalDescGroup));
+ }
+
+ /**
+ * Adds a section header label.
+ *
+ * @param title the section title
+ * @param verticalGroup the vertical layout group
+ * @param horizontalButtonGroup the horizontal group for buttons
+ * @param horizontalDescGroup the horizontal group for descriptions
+ * @param layout the GroupLayout
+ * @param addTopMargin whether to add extra top margin for visual separation
+ */
+ private void addSectionHeader(final String title,
+ final GroupLayout.SequentialGroup verticalGroup,
+ final GroupLayout.ParallelGroup horizontalButtonGroup,
+ final GroupLayout.ParallelGroup horizontalDescGroup,
+ final GroupLayout layout,
+ final boolean addTopMargin) {
+ final JLabel headerLabel = new JLabel(title);
+ headerLabel.setFont(headerLabel.getFont().deriveFont(Font.BOLD, 14f));
+ if (addTopMargin) {
+ headerLabel.setBorder(new EmptyBorder(SECTION_TOP_MARGIN, 0, 0, 0));
+ }
+
verticalGroup.addComponent(headerLabel);
horizontalButtonGroup.addComponent(headerLabel);
horizontalDescGroup.addComponent(headerLabel);
+ }
- for (final DemoEntry entry : DEMOS) {
+ /**
+ * Adds demo entry buttons to the layout.
+ *
+ * @param entries the demo entries to add
+ * @param verticalGroup the vertical layout group
+ * @param horizontalButtonGroup the horizontal group for buttons
+ * @param horizontalDescGroup the horizontal group for descriptions
+ * @param layout the GroupLayout
+ */
+ private void addDemoEntries(final DemoEntry[] entries,
+ final GroupLayout.SequentialGroup verticalGroup,
+ final GroupLayout.ParallelGroup horizontalButtonGroup,
+ final GroupLayout.ParallelGroup horizontalDescGroup,
+ final GroupLayout layout) {
+ for (final DemoEntry entry : entries) {
final JButton button = new JButton(entry.action());
- button.setPreferredSize(new java.awt.Dimension(BUTTON_WIDTH, button.getPreferredSize().height));
+ button.setPreferredSize(new Dimension(BUTTON_WIDTH, button.getPreferredSize().height));
final JLabel descLabel = new JLabel(entry.description());
- descLabel.setFont(descLabel.getFont().deriveFont(java.awt.Font.PLAIN));
+ descLabel.setFont(descLabel.getFont().deriveFont(Font.PLAIN));
verticalGroup.addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
.addComponent(button)
@@ -107,12 +167,9 @@ class ApplicationListPanel extends JPanel {
horizontalButtonGroup.addComponent(button);
horizontalDescGroup.addComponent(descLabel);
}
+ }
- layout.setVerticalGroup(verticalGroup);
- layout.setHorizontalGroup(layout.createSequentialGroup()
- .addGroup(horizontalButtonGroup)
- .addGap(GAP)
- .addGroup(horizontalDescGroup));
+ private record DemoEntry(String name, String description, AbstractAction action) {
}
private static class ShowMinimalExample extends AbstractAction {
@@ -126,6 +183,17 @@ class ApplicationListPanel extends JPanel {
}
}
+ private static class ShowWindingOrder extends AbstractAction {
+ ShowWindingOrder() {
+ putValue(NAME, "Winding Order");
+ }
+
+ @Override
+ public void actionPerformed(final ActionEvent e) {
+ WindingOrderDemo.main(null);
+ }
+ }
+
private static class ShowTextEditors extends AbstractAction {
ShowTextEditors() {
putValue(NAME, "Text editors");
@@ -192,20 +260,9 @@ class ApplicationListPanel extends JPanel {
}
}
- private static class ShowRandomPolygons extends AbstractAction {
- ShowRandomPolygons() {
- putValue(NAME, "Random polygons");
- }
-
- @Override
- public void actionPerformed(final ActionEvent e) {
- RandomPolygonsDemo.main(null);
- }
- }
-
private static class ShowOctree extends AbstractAction {
ShowOctree() {
- putValue(NAME, "Volumetric Octree");
+ putValue(NAME, "Volumetric octree");
}
@Override
@@ -227,18 +284,29 @@ class ApplicationListPanel extends JPanel {
private static class ShowShadedShapes extends AbstractAction {
ShowShadedShapes() {
- putValue(NAME, "Shaded Shapes");
+ putValue(NAME, "Shape gallery");
+ }
+
+ @Override
+ public void actionPerformed(final ActionEvent e) {
+ ShapeGalleryDemo.main(null);
+ }
+ }
+
+ private static class ShowAxisArrows extends AbstractAction {
+ ShowAxisArrows() {
+ putValue(NAME, "Axis arrows");
}
@Override
public void actionPerformed(final ActionEvent e) {
- ShadedShapesDemo.main(null);
+ AxisArrowsDemo.main(null);
}
}
private static class ShowTerrainDemo extends AbstractAction {
ShowTerrainDemo() {
- putValue(NAME, "Procedural Terrain");
+ putValue(NAME, "Procedural terrain");
}
@Override
@@ -249,7 +317,7 @@ class ApplicationListPanel extends JPanel {
private static class ShowGraphicsBenchmark extends AbstractAction {
ShowGraphicsBenchmark() {
- putValue(NAME, "Graphics Benchmark");
+ putValue(NAME, "Graphics benchmark");
}
@Override
@@ -257,4 +325,15 @@ class ApplicationListPanel extends JPanel {
GraphicsBenchmark.main(null);
}
}
+
+ private static class ShowCSG extends AbstractAction {
+ ShowCSG() {
+ putValue(NAME, "CSG Demo");
+ }
+
+ @Override
+ public void actionPerformed(final ActionEvent e) {
+ CSGDemo.main(null);
+ }
+ }
}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Main.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Main.java
index 0666abe..0e77299 100644
--- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Main.java
+++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Main.java
@@ -2,6 +2,7 @@ package eu.svjatoslav.sixth.e3d.examples.life_demo;
import eu.svjatoslav.sixth.e3d.geometry.Point3D;
import eu.svjatoslav.sixth.e3d.geometry.Rectangle;
+import eu.svjatoslav.sixth.e3d.gui.TextPointer;
import eu.svjatoslav.sixth.e3d.gui.ViewFrame;
import eu.svjatoslav.sixth.e3d.gui.ViewPanel;
import eu.svjatoslav.sixth.e3d.gui.humaninput.WorldNavigationUserInputTracker;
@@ -9,6 +10,7 @@ import eu.svjatoslav.sixth.e3d.math.Transform;
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.textcanvas.TextCanvas;
import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.Grid2D;
import java.awt.event.KeyEvent;
@@ -18,7 +20,7 @@ import java.awt.event.KeyEvent;
* Main entry point for Conway's Game of Life 3D demo.
* Creates a 30x30 cell matrix where cells evolve based on Conway's rules.
* The user can interact with cells by clicking to toggle their state.
- *
+ *
* Key controls:
*
* - Space - Evolve one generation
@@ -29,18 +31,21 @@ import java.awt.event.KeyEvent;
public class Main extends WorldNavigationUserInputTracker {
/**
- * Creates a new Main instance for the Game of Life demo.
+ * The game of life matrix, centered at the origin.
*/
- public Main() {
- }
-
- /** The game of life matrix, centered at the origin. */
private static final Matrix MATRIX = new Matrix(
new Point3D() // position matrix in the center of the scene
);
+ /**
+ * Creates a new Main instance for the Game of Life demo.
+ */
+ public Main() {
+ }
+
/**
* Entry point for the Game of Life demo.
+ *
* @param args command line arguments (ignored)
*/
public static void main(final String[] args) {
@@ -68,20 +73,25 @@ public class Main extends WorldNavigationUserInputTracker {
return true;
}
- /** Initializes and displays the Game of Life demo. */
+ /**
+ * Initializes and displays the Game of Life demo.
+ */
private void run() {
// create application frame visible to the user
final ViewFrame viewFrame = new ViewFrame("Game of Life");
final ViewPanel viewPanel = viewFrame.getViewPanel();
- viewPanel.getCamera().getTransform().set(100, -50, -200, 0.2f, -0.7f, 0);
+ viewPanel.getCamera().getTransform().set(855.83, -174.40, -31.46, 1.58, -0.02, -0.00);
final ShapeCollection shapeCollection = viewPanel.getRootShapeCollection();
// add matrix
shapeCollection.addShape(MATRIX);
+ // add help panel explaining rules and controls
+ shapeCollection.addShape(createHelpPanel());
+
// add wire-frame grid (optional)
shapeCollection.addShape(createGrid());
@@ -94,6 +104,7 @@ public class Main extends WorldNavigationUserInputTracker {
/**
* Creates a pink wire-frame grid below the matrix for decorative purposes.
+ *
* @return a Grid2D positioned below the game matrix
*/
private Grid2D createGrid() {
@@ -110,4 +121,49 @@ public class Main extends WorldNavigationUserInputTracker {
);
}
+ /**
+ * Creates a help panel displaying game rules and controls.
+ * The panel is positioned to the right of the matrix and faces toward the viewer.
+ *
+ * @return a TextCanvas with help information
+ */
+ private TextCanvas createHelpPanel() {
+ final String helpText =
+ "=== CONWAY'S GAME OF LIFE ===\n" +
+ "\n" +
+ "RULES:\n" +
+ "- Live cell with 2-3 neighbors survives\n" +
+ "- Dead cell with 3 neighbors becomes alive\n" +
+ "- All other cells die or stay dead\n" +
+ "\n" +
+ "KEYBOARD:\n" +
+ "Space - Evolve one generation\n" +
+ "Enter - Evolve with history trail\n" +
+ "C - Clear the matrix\n" +
+ "Arrows - Move camera\n" +
+ "\n" +
+ "MOUSE:\n" +
+ "Click - Toggle cell state\n" +
+ "Hover - Highlight cell";
+
+ final Transform location = Transform.fromAngles(
+ new Point3D(500, -80, 0),
+ -Math.PI / 2,
+ 0
+ );
+
+ final TextPointer dimensions = new TextPointer(16, 45);
+
+ final TextCanvas panel = new TextCanvas(
+ location,
+ dimensions,
+ Color.WHITE,
+ new Color(0, 0, 80, 200)
+ );
+
+ panel.setText(helpText);
+
+ return panel;
+ }
+
}
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Matrix.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Matrix.java
index 44fedcc..4626ae6 100644
--- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Matrix.java
+++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Matrix.java
@@ -8,29 +8,40 @@ import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.SubShape;
* Represents the 2D game board for Conway's Game of Life.
* Contains a 30x30 grid of cells that can evolve according to Conway's rules.
* Supports optional history tracking where stars mark previously active cells.
- *
+ *
* @see Cell
* @see Star
*/
class Matrix extends AbstractCompositeShape {
- /** Empty space between adjacent cells. */
+ /**
+ * Empty space between adjacent cells.
+ */
private static final int BORDER = 5;
- /** Number of cells along each dimension (30x30 grid). */
+ /**
+ * Number of cells along each dimension (30x30 grid).
+ */
private static final int SIZE = 30;
- /** Group ID for history tracking stars. */
+ /**
+ * Group ID for history-tracking stars.
+ */
private static final String GROUP_STARS = "stars";
- /** Group ID for the cell surface. */
+ /**
+ * Group ID for the cell surface.
+ */
private static final String GROUP_SURFACE = "surface";
- /** 2D array of cells forming the game board. */
+ /**
+ * 2D array of cells forming the game board.
+ */
private final Cell[][] cells = new Cell[SIZE][];
/**
* Constructs a game matrix at the specified location.
+ *
* @param location the center position of the matrix in 3D space
*/
public Matrix(final Point3D location) {
@@ -84,6 +95,7 @@ class Matrix extends AbstractCompositeShape {
/**
* Processes a single cell to determine if it survives to the next generation.
+ *
* @param x the X coordinate of the cell
* @param y the Y coordinate of the cell
*/
@@ -99,6 +111,7 @@ class Matrix extends AbstractCompositeShape {
/**
* Counts the number of alive neighbors around the specified cell.
+ *
* @param x the X coordinate of the cell
* @param y the Y coordinate of the cell
* @return the count of alive neighboring cells
@@ -116,6 +129,7 @@ class Matrix extends AbstractCompositeShape {
/**
* Checks if the cell at the specified coordinates is alive.
+ *
* @param x the X coordinate of the cell
* @param y the Y coordinate of the cell
* @return true if the cell is within bounds and active
@@ -130,6 +144,7 @@ class Matrix extends AbstractCompositeShape {
/**
* Evolves the matrix by one generation.
+ *
* @param preserveHistory if true, places stars at positions of previously active cells
*/
public void evolve(final boolean preserveHistory) {
@@ -143,6 +158,7 @@ class Matrix extends AbstractCompositeShape {
/**
* Computes the world position of a cell given its grid coordinates.
+ *
* @param x the X grid coordinate
* @param z the Z grid coordinate
* @return the 3D world position of the cell center
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/TerrainDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/TerrainDemo.java
index c2749c3..1ad9d43 100644
--- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/TerrainDemo.java
+++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/TerrainDemo.java
@@ -27,7 +27,7 @@ public class TerrainDemo {
* @param args command line arguments (ignored)
*/
public static void main(final String[] args) {
- final ViewFrame viewFrame = new ViewFrame("Procedural Terrain");
+ final ViewFrame viewFrame = new ViewFrame("Procedural terrain");
ViewPanel viewPanel = viewFrame.getViewPanel();
viewPanel.getCamera().getTransform().set(-76.43, -72.05, -159.08, -0.64, -0.46, -0.00);
--
2.20.1