feat(demos): add CSG and axis demos, reorganize into essentials subpackage master
authorSvjatoslav Agejenko <svjatoslav@svjatoslav.eu>
Sat, 28 Mar 2026 12:50:34 +0000 (14:50 +0200)
committerSvjatoslav Agejenko <svjatoslav@svjatoslav.eu>
Sat, 28 Mar 2026 12:50:34 +0000 (14:50 +0200)
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.

16 files changed:
AGENTS.md
src/main/java/eu/svjatoslav/sixth/e3d/examples/ArrowDemo.java
src/main/java/eu/svjatoslav/sixth/e3d/examples/MinimalExample.java [deleted file]
src/main/java/eu/svjatoslav/sixth/e3d/examples/OctreeDemo.java
src/main/java/eu/svjatoslav/sixth/e3d/examples/RandomPolygonsDemo.java [deleted file]
src/main/java/eu/svjatoslav/sixth/e3d/examples/ShadedShapesDemo.java [deleted file]
src/main/java/eu/svjatoslav/sixth/e3d/examples/WindingOrderDemo.java [deleted file]
src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/AxisArrowsDemo.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/CSGDemo.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/MinimalExample.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/ShapeGalleryDemo.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/WindingOrderDemo.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/ApplicationListPanel.java
src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Main.java
src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Matrix.java
src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/TerrainDemo.java

index e6360a7..d5e1345 100644 (file)
--- 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
index d457318..a04c237 100644 (file)
@@ -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/MinimalExample.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/MinimalExample.java
deleted file mode 100644 (file)
index 86e43ce..0000000
+++ /dev/null
@@ -1,44 +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.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.composite.solid.SolidPolygonRectangularBox;
-
-/**
- * Minimal example demonstrating how to create a basic 3D scene.
- * <p>
- * Creates a window with a single red box. This is the "Create Your First 3D Scene"
- * example from the Sixth 3D documentation.
- */
-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");
-        ShapeCollection shapes = viewFrame.getViewPanel().getRootShapeCollection();
-
-        viewFrame.getViewPanel().getCamera().getTransform().setTranslation(new Point3D(0, -100, -300));
-
-        Transform boxTransform = Transform.fromAngles(0, 0, 0, 0, 0, 0);
-        SolidPolygonRectangularBox box = new SolidPolygonRectangularBox(
-                new Point3D(-50, -50, -50),
-                new Point3D(50, 50, 50),
-                Color.RED
-        );
-        box.setTransform(boxTransform);
-        shapes.addShape(box);
-
-        viewFrame.getViewPanel().repaintDuringNextViewUpdate();
-    }
-}
\ No newline at end of file
index ead6346..aedc08c 100755 (executable)
@@ -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 (executable)
index 8b7d7d5..0000000
+++ /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/ShadedShapesDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/ShadedShapesDemo.java
deleted file mode 100644 (file)
index ccdc182..0000000
+++ /dev/null
@@ -1,449 +0,0 @@
-package eu.svjatoslav.sixth.e3d.examples;
-
-import eu.svjatoslav.sixth.e3d.geometry.Point3D;
-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.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.basic.line.LineAppearance;
-import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.ForwardOrientedTextBlock;
-import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.LightSourceMarker;
-import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.*;
-import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.*;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Random;
-
-import static eu.svjatoslav.sixth.e3d.math.Transform.fromAngles;
-
-/**
- * Gallery demo showcasing all available 3D shapes in the Sixth 3D engine.
- *
- * <p>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:</p>
- *
- * <pre>
- *            Arrow    Cone     Cube   Cylinder  Pyramid   Box    Sphere
- *         ┌────────┬────────┬────────┬────────┬────────┬────────┬────────┐
- * Solid   │  ████  │  /\\   │ ████   │  ║║║   │  /\\   │ ████   │  ()   │
- *         │  ████  │ /  \\  │ ████   │  ║║║   │ /__\\  │ ████   │ (  )  │
- *         ├────────┼────────┼────────┼────────┼────────┼────────┼────────┤
- * Wirefrm │  ╔══╗  │  /\\   │ ╔══╗   │  ║║║   │  /\\   │ ╔══╗   │  ()   │
- *         │  ╚══╝  │ /  \\  │ ╚══╝   │  ║║║   │ /__\\  │ ╚══╝   │ (  )  │
- *         └────────┴────────┴────────┴────────┴────────┴────────┴────────┘
- * </pre>
- *
- * <p>Features:</p>
- * <ul>
- *   <li>Orbiting colored lights for dynamic lighting demonstration</li>
- *   <li>Floor grid showing the layout structure</li>
- *   <li>Text labels identifying each shape type</li>
- * </ul>
- */
-public class ShadedShapesDemo {
-
-    /**
-     * Number of columns (shape types).
-     */
-    private static final int COLUMN_COUNT = 7;
-
-    /**
-     * Size of each cell in world units.
-     */
-    private static final double CELL_SIZE = 250;
-
-    /**
-     * Radius/size for most shapes.
-     */
-    private static final double SHAPE_RADIUS = 50;
-
-    /**
-     * Number of segments for smooth curved shapes.
-     */
-    private static final int SEGMENTS = 16;
-
-    /**
-     * Y offset for labels above shapes.
-     */
-    private static final double LABEL_Y_OFFSET = -100;
-
-    /**
-     * Number of orbiting light sources in the scene.
-     */
-    private static final int LIGHT_COUNT = 10;
-
-    /**
-     * Color palette - one color per column (shape type).
-     */
-    private static final Color[] SHAPE_COLORS = {
-            new Color(255, 100, 100),  // Arrow - Red
-            new Color(255, 180, 100),  // Cone - Orange
-            new Color(255, 255, 100),  // Cube - Yellow
-            new Color(100, 255, 100),  // Cylinder - Green
-            new Color(100, 255, 255),  // Pyramid - Cyan
-            new Color(100, 150, 255),  // RectangularBox - Blue
-            new Color(200, 150, 255),  // Sphere - Purple
-    };
-
-    /**
-     * Shape type names for labels.
-     */
-    private static final String[] SHAPE_NAMES = {
-            "Arrow",
-            "Cone",
-            "Cube",
-            "Cylinder",
-            "Pyramid",
-            "Box",
-            "Sphere",
-    };
-
-    /**
-     * Entry point for the shaded shapes demo.
-     *
-     * @param args command line arguments (ignored)
-     */
-    public static void main(final String[] args) {
-        final ViewFrame viewFrame = new ViewFrame("3D Shape Gallery");
-        final ViewPanel viewPanel = viewFrame.getViewPanel();
-        final ShapeCollection shapes = viewPanel.getRootShapeCollection();
-
-        // Position camera to view the entire gallery
-        viewPanel.getCamera().getTransform().set(-615.31, -299.50, -378.64, -0.62, -0.66, 0.00);
-
-        // Set up lighting
-        viewPanel.getLightingManager().setAmbientLight(new Color(40, 40, 50));
-
-        // Create floor grid
-        createFloorGrid(shapes);
-
-        // Create all shapes
-        createAllShapes(shapes);
-
-        // Create orbiting lights
-        createOrbitingLights(viewPanel, shapes);
-
-        viewPanel.repaintDuringNextViewUpdate();
-    }
-
-    /**
-     * Creates the floor grid that outlines the gallery cells.
-     *
-     * @param shapes the shape collection to add the grid to
-     */
-    private static void createFloorGrid(final ShapeCollection shapes) {
-        final LineAppearance gridAppearance = new LineAppearance(1, new Color(80, 80, 100));
-
-        // Calculate grid bounds: 7 columns, 2 rows, centered around origin
-        final double halfWidth = (COLUMN_COUNT * CELL_SIZE) / 2.0;
-        final double gridDepth = 2 * CELL_SIZE;
-
-        final Point3D cornerA = new Point3D(-halfWidth, 0, -CELL_SIZE / 2);
-        final Point3D cornerB = new Point3D(halfWidth, 0, gridDepth - CELL_SIZE / 2);
-
-        final Grid3D grid = new Grid3D(cornerA, cornerB, CELL_SIZE, gridAppearance);
-        shapes.addShape(grid);
-    }
-
-    /**
-     * Creates all solid and wireframe shapes in the gallery grid.
-     *
-     * @param shapes the shape collection to add shapes to
-     */
-    private static void createAllShapes(final ShapeCollection shapes) {
-        for (int col = 0; col < COLUMN_COUNT; col++) {
-            final double x = getColumnX(col);
-            final Color color = SHAPE_COLORS[col];
-            final String name = SHAPE_NAMES[col];
-
-            // Row 0: Solid polygon shapes
-            final Point3D solidPos = new Point3D(x, 0, 0);
-            createSolidShape(shapes, col, solidPos, color);
-            createLabel(shapes, solidPos, name, color);
-
-            // Row 1: Wireframe shapes
-            final Point3D wireframePos = new Point3D(x, 0, CELL_SIZE);
-            createWireframeShape(shapes, col, wireframePos, color);
-        }
-    }
-
-    /**
-     * Calculates the X coordinate for a given column index.
-     *
-     * @param col the column index (0-6)
-     * @return the X coordinate in world units
-     */
-    private static double getColumnX(final int col) {
-        return (col - (COLUMN_COUNT - 1) / 2.0) * CELL_SIZE;
-    }
-
-    /**
-     * Creates a solid polygon shape for the given column.
-     *
-     * @param shapes the shape collection
-     * @param col    the column index (determines shape type)
-     * @param pos    the center position
-     * @param color  the shape color
-     */
-    private static void createSolidShape(final ShapeCollection shapes, final int col,
-                                         final Point3D pos, final Color color) {
-        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);
-                arrow.setShadingEnabled(true);
-                shapes.addShape(arrow);
-                break;
-
-            case 1: // Cone (pointing upward)
-                final SolidPolygonCone cone = new SolidPolygonCone(
-                        new Point3D(pos.x, -SHAPE_RADIUS, pos.z),  // apex above
-                        new Point3D(pos.x, SHAPE_RADIUS / 2, pos.z), // base below
-                        SHAPE_RADIUS * 0.8, SEGMENTS, color);
-                cone.setShadingEnabled(true);
-                shapes.addShape(cone);
-                break;
-
-            case 2: // Cube
-                final SolidPolygonCube cube = new SolidPolygonCube(pos, SHAPE_RADIUS * 0.8, color);
-                cube.setShadingEnabled(true);
-                shapes.addShape(cube);
-                break;
-
-            case 3: // Cylinder
-                final SolidPolygonCylinder cylinder = new SolidPolygonCylinder(
-                        new Point3D(pos.x, SHAPE_RADIUS, pos.z),
-                        new Point3D(pos.x, -SHAPE_RADIUS, pos.z),
-                        SHAPE_RADIUS * 0.7, SEGMENTS, color);
-                cylinder.setShadingEnabled(true);
-                shapes.addShape(cylinder);
-                break;
-
-            case 4: // Pyramid
-                final SolidPolygonPyramid pyramid = new SolidPolygonPyramid(
-                        new Point3D(pos.x, -SHAPE_RADIUS, pos.z),  // apex above
-                        new Point3D(pos.x, SHAPE_RADIUS / 2, pos.z), // base below
-                        SHAPE_RADIUS * 0.8, color);
-                pyramid.setShadingEnabled(true);
-                shapes.addShape(pyramid);
-                break;
-
-            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),
-                        color);
-                box.setShadingEnabled(true);
-                shapes.addShape(box);
-                break;
-
-            case 6: // Sphere
-                final SolidPolygonSphere sphere = new SolidPolygonSphere(pos, SHAPE_RADIUS, SEGMENTS, color);
-                sphere.setShadingEnabled(true);
-                shapes.addShape(sphere);
-                break;
-
-            default:
-                break;
-        }
-    }
-
-    /**
-     * Creates a wireframe shape for the given column.
-     *
-     * @param shapes the shape collection
-     * @param col    the column index (determines shape type)
-     * @param pos    the center position
-     * @param color  the line color
-     */
-    private static void createWireframeShape(final ShapeCollection shapes, final int col,
-                                             final Point3D pos, final Color color) {
-        final LineAppearance appearance = new LineAppearance(2, color);
-
-        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);
-                shapes.addShape(arrow);
-                break;
-
-            case 1: // Cone
-                final WireframeCone cone = new WireframeCone(
-                        new Point3D(pos.x, -SHAPE_RADIUS, pos.z),  // apex above
-                        new Point3D(pos.x, SHAPE_RADIUS / 2, pos.z), // base below
-                        SHAPE_RADIUS * 0.8, SEGMENTS, appearance);
-                shapes.addShape(cone);
-                break;
-
-            case 2: // Cube
-                final WireframeCube cube = new WireframeCube(pos, SHAPE_RADIUS * 0.8, appearance);
-                shapes.addShape(cube);
-                break;
-
-            case 3: // Cylinder
-                final WireframeCylinder cylinder = new WireframeCylinder(
-                        new Point3D(pos.x, SHAPE_RADIUS, pos.z),
-                        new Point3D(pos.x, -SHAPE_RADIUS, pos.z),
-                        SHAPE_RADIUS * 0.7, SEGMENTS, appearance);
-                shapes.addShape(cylinder);
-                break;
-
-            case 4: // Pyramid
-                final WireframePyramid pyramid = new WireframePyramid(
-                        new Point3D(pos.x, -SHAPE_RADIUS, pos.z),  // apex above
-                        new Point3D(pos.x, SHAPE_RADIUS / 2, pos.z), // base below
-                        SHAPE_RADIUS * 0.8, appearance);
-                shapes.addShape(pyramid);
-                break;
-
-            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),
-                        appearance);
-                shapes.addShape(box);
-                break;
-
-            case 6: // Sphere
-                final WireframeSphere sphere = new WireframeSphere(pos, (float) SHAPE_RADIUS, appearance);
-                shapes.addShape(sphere);
-                break;
-
-            default:
-                break;
-        }
-    }
-
-    /**
-     * Creates a text label above a shape.
-     *
-     * @param shapes the shape collection
-     * @param pos    the shape position
-     * @param text   the label text
-     * @param color  the text color
-     */
-    private static void createLabel(final ShapeCollection shapes, final Point3D pos,
-                                    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);
-        shapes.addShape(label);
-    }
-
-    /**
-     * Creates orbiting light sources around the scene.
-     *
-     * @param viewPanel the view panel
-     * @param shapes    the shape collection
-     */
-    private static void createOrbitingLights(final ViewPanel viewPanel, final ShapeCollection shapes) {
-        final Random random = new Random(42);
-        final List<OrbitingLight> orbitingLights = new ArrayList<>();
-
-        for (int i = 0; i < LIGHT_COUNT; i++) {
-            final Color color = new Color(
-                    random.nextInt(256),
-                    random.nextInt(256),
-                    random.nextInt(256)
-            );
-
-            final double orbitRadius = 600 + random.nextInt(200);
-            final double speed = 0.01 + random.nextDouble() * 0.02;
-            final double angleOffset = random.nextDouble() * Math.PI * 2;
-            final double intensity = 2.0 + random.nextDouble() * 2.0;
-
-            final int axis = random.nextInt(3);
-            final double ellipseFactor = 0.7 + random.nextDouble() * 0.3;
-
-            final LightSource light = new LightSource(new Point3D(0, 0, CELL_SIZE / 2), color, intensity);
-            final LightSourceMarker marker = new LightSourceMarker(light.getPosition(), color);
-
-            viewPanel.getLightingManager().addLight(light);
-            shapes.addShape(marker);
-
-            orbitingLights.add(new OrbitingLight(
-                    light, marker,
-                    orbitRadius, speed, angleOffset, axis, ellipseFactor
-            ));
-        }
-
-        final MultiLightAnimator animator = new MultiLightAnimator(orbitingLights);
-        viewPanel.addFrameListener(animator);
-    }
-
-    /**
-     * Represents a light source that orbits around the scene center.
-     */
-    private static class OrbitingLight {
-        final LightSource light;
-        final LightSourceMarker marker;
-        final double orbitRadius;
-        final double speed;
-        final int axis;
-        final double ellipseFactor;
-        double angle;
-
-        OrbitingLight(final LightSource light, final LightSourceMarker marker,
-                      final double orbitRadius, final double speed, final double angleOffset,
-                      final int axis, final double ellipseFactor) {
-            this.light = light;
-            this.marker = marker;
-            this.orbitRadius = orbitRadius;
-            this.speed = speed;
-            this.angle = angleOffset;
-            this.axis = axis;
-            this.ellipseFactor = ellipseFactor;
-        }
-    }
-
-    /**
-     * Frame listener that animates all orbiting lights.
-     */
-    private record MultiLightAnimator(List<OrbitingLight> lights) implements FrameListener {
-
-        @Override
-        public boolean onFrame(final ViewPanel viewPanel, final int millisecondsSinceLastFrame) {
-            final double centerY = CELL_SIZE / 2;
-
-            for (final OrbitingLight orbitingLight : lights) {
-                orbitingLight.angle += orbitingLight.speed * millisecondsSinceLastFrame / 100;
-
-                final double r = orbitingLight.orbitRadius;
-                final double e = orbitingLight.ellipseFactor;
-
-                double x, y, z;
-                switch (orbitingLight.axis) {
-                    case 0:
-                        x = r * Math.cos(orbitingLight.angle);
-                        y = r * e * Math.sin(orbitingLight.angle);
-                        z = r * 0.5 * Math.sin(orbitingLight.angle * 2);
-                        break;
-                    case 1:
-                        x = r * e * Math.sin(orbitingLight.angle * 2);
-                        y = r * Math.cos(orbitingLight.angle);
-                        z = r * 0.5 * Math.sin(orbitingLight.angle);
-                        break;
-                    default:
-                        x = r * 0.5 * Math.sin(orbitingLight.angle);
-                        y = r * Math.sin(orbitingLight.angle * 2);
-                        z = r * e * Math.cos(orbitingLight.angle);
-                        break;
-                }
-
-                final Point3D newPosition = new Point3D(x, y + centerY, z);
-                orbitingLight.light.setPosition(newPosition);
-                orbitingLight.marker.setTransform(fromAngles(
-                        newPosition.x, newPosition.y, newPosition.z, 0, 0, 0));
-            }
-            return true;
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/WindingOrderDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/WindingOrderDemo.java
deleted file mode 100644 (file)
index df022df..0000000
+++ /dev/null
@@ -1,58 +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.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 {
-
-    /**
-     * Creates a new WindingOrderDemo instance.
-     */
-    public WindingOrderDemo() {
-    }
-
-    /**
-     * Entry point for the winding order demo.
-     * @param args command line arguments (ignored)
-     */
-    public static void main(String[] args) {
-        ViewFrame viewFrame = new ViewFrame("Winding Order Demo");
-        ViewPanel viewPanel = viewFrame.getViewPanel();
-        ShapeCollection shapes = viewPanel.getRootShapeCollection();
-
-        viewPanel.getCamera().getTransform().setTranslation(new Point3D(0, 0, -500));
-
-        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.repaintDuringNextViewUpdate();
-    }
-}
\ No newline at end of file
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 (file)
index 0000000..8bcd0a0
--- /dev/null
@@ -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.
+ *
+ * <p>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:</p>
+ *
+ * <ul>
+ *   <li><b>Red arrow</b> — +X axis (points RIGHT)</li>
+ *   <li><b>Green arrow</b> — +Y axis (points DOWN visually)</li>
+ *   <li><b>Blue arrow</b> — +Z axis (points AWAY from viewer)</li>
+ * </ul>
+ *
+ * <p>A reference grid at Y=0 provides spatial context. Text labels identify
+ * each axis.</p>
+ *
+ * @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 (file)
index 0000000..b8d2cda
--- /dev/null
@@ -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.
+ *
+ * <p>This demo displays three CSG operations side by side:</p>
+ * <ul>
+ *   <li><b>Subtract:</b> A cube with a spherical cavity carved out</li>
+ *   <li><b>Union:</b> A cube merged with a sphere</li>
+ *   <li><b>Intersect:</b> The volume shared by a cube and sphere</li>
+ * </ul>
+ *
+ * <p>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.</p>
+ *
+ * <p><b>Run this demo:</b></p>
+ * <pre>{@code
+ * java -cp target/sixth-3d-demos.jar eu.svjatoslav.sixth.e3d.examples.CSGDemo
+ * }</pre>
+ *
+ * @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/essentials/MinimalExample.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/MinimalExample.java
new file mode 100644 (file)
index 0000000..b526eb3
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * 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.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.composite.solid.SolidPolygonRectangularBox;
+
+/**
+ * Minimal example demonstrating how to create a basic 3D scene.
+ * <p>
+ * Creates a window with a single red box. This is the "Create Your First 3D Scene"
+ * example from the Sixth 3D documentation.
+ */
+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");
+        ShapeCollection shapes = viewFrame.getViewPanel().getRootShapeCollection();
+
+        viewFrame.getViewPanel().getCamera().getTransform().setTranslation(new Point3D(0, -100, -300));
+
+        Transform boxTransform = Transform.fromAngles(0, 0, 0, 0, 0, 0);
+        SolidPolygonRectangularBox box = new SolidPolygonRectangularBox(
+                new Point3D(-50, -50, -50),
+                new Point3D(50, 50, 50),
+                Color.RED
+        );
+        box.setTransform(boxTransform);
+        shapes.addShape(box);
+
+        viewFrame.getViewPanel().repaintDuringNextViewUpdate();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/ShapeGalleryDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/ShapeGalleryDemo.java
new file mode 100644 (file)
index 0000000..28e6337
--- /dev/null
@@ -0,0 +1,439 @@
+package eu.svjatoslav.sixth.e3d.examples.essentials;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+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.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.basic.line.LineAppearance;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.ForwardOrientedTextBlock;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.LightSourceMarker;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.*;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+import static eu.svjatoslav.sixth.e3d.math.Transform.fromAngles;
+
+/**
+ * Gallery demo showcasing all available 3D shapes in the Sixth 3D engine.
+ *
+ * <p>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.</p>
+ *
+ * <p>Features:</p>
+ * <ul>
+ *   <li>Orbiting colored lights for dynamic lighting demonstration</li>
+ *   <li>Floor grid showing the layout structure</li>
+ *   <li>Text labels identifying each shape type</li>
+ * </ul>
+ */
+public class ShapeGalleryDemo {
+
+    /**
+     * Number of columns (shape types).
+     */
+    private static final int COLUMN_COUNT = 7;
+
+    /**
+     * Size of each cell in world units.
+     */
+    private static final double CELL_SIZE = 250;
+
+    /**
+     * Radius/size for most shapes.
+     */
+    private static final double SHAPE_RADIUS = 50;
+
+    /**
+     * Number of segments for smooth curved shapes.
+     */
+    private static final int SEGMENTS = 16;
+
+    /**
+     * Y offset for labels above shapes.
+     */
+    private static final double LABEL_Y_OFFSET = -100;
+
+    /**
+     * Number of orbiting light sources in the scene.
+     */
+    private static final int LIGHT_COUNT = 10;
+
+    /**
+     * Color palette - one color per column (shape type).
+     */
+    private static final Color[] SHAPE_COLORS = {
+            new Color(255, 100, 100),  // Arrow - Red
+            new Color(255, 180, 100),  // Cone - Orange
+            new Color(255, 255, 100),  // Cube - Yellow
+            new Color(100, 255, 100),  // Cylinder - Green
+            new Color(100, 255, 255),  // Pyramid - Cyan
+            new Color(100, 150, 255),  // RectangularBox - Blue
+            new Color(200, 150, 255),  // Sphere - Purple
+    };
+
+    /**
+     * Shape type names for labels.
+     */
+    private static final String[] SHAPE_NAMES = {
+            "Arrow",
+            "Cone",
+            "Cube",
+            "Cylinder",
+            "Pyramid",
+            "Box",
+            "Sphere",
+    };
+
+    /**
+     * Entry point for the shaded shapes demo.
+     *
+     * @param args command line arguments (ignored)
+     */
+    public static void main(final String[] args) {
+        final ViewFrame viewFrame = new ViewFrame("3D Shape Gallery");
+        final ViewPanel viewPanel = viewFrame.getViewPanel();
+        final ShapeCollection shapes = viewPanel.getRootShapeCollection();
+
+        // Position camera to view the entire gallery
+        viewPanel.getCamera().getTransform().set(-615.31, -299.50, -378.64, -0.62, -0.66, 0.00);
+
+        // Set up lighting
+        viewPanel.getLightingManager().setAmbientLight(new Color(40, 40, 50));
+
+        // Create floor grid
+        createFloorGrid(shapes);
+
+        // Create all shapes
+        createAllShapes(shapes);
+
+        // Create orbiting lights
+        createOrbitingLights(viewPanel, shapes);
+
+        viewPanel.repaintDuringNextViewUpdate();
+    }
+
+    /**
+     * Creates the floor grid that outlines the gallery cells.
+     *
+     * @param shapes the shape collection to add the grid to
+     */
+    private static void createFloorGrid(final ShapeCollection shapes) {
+        final LineAppearance gridAppearance = new LineAppearance(1, new Color(80, 80, 100));
+
+        // Calculate grid bounds: 7 columns, 2 rows, centered around origin
+        final double halfWidth = (COLUMN_COUNT * CELL_SIZE) / 2.0;
+        final double gridDepth = 2 * CELL_SIZE;
+
+        final Point3D cornerA = new Point3D(-halfWidth, 0, -CELL_SIZE / 2);
+        final Point3D cornerB = new Point3D(halfWidth, 0, gridDepth - CELL_SIZE / 2);
+
+        final Grid3D grid = new Grid3D(cornerA, cornerB, CELL_SIZE, gridAppearance);
+        shapes.addShape(grid);
+    }
+
+    /**
+     * Creates all solid and wireframe shapes in the gallery grid.
+     *
+     * @param shapes the shape collection to add shapes to
+     */
+    private static void createAllShapes(final ShapeCollection shapes) {
+        for (int col = 0; col < COLUMN_COUNT; col++) {
+            final double x = getColumnX(col);
+            final Color color = SHAPE_COLORS[col];
+            final String name = SHAPE_NAMES[col];
+
+            // Row 0: Solid polygon shapes
+            final Point3D solidPos = new Point3D(x, 0, 0);
+            createSolidShape(shapes, col, solidPos, color);
+            createLabel(shapes, solidPos, name, color);
+
+            // Row 1: Wireframe shapes
+            final Point3D wireframePos = new Point3D(x, 0, CELL_SIZE);
+            createWireframeShape(shapes, col, wireframePos, color);
+            createLabel(shapes, wireframePos, "Wireframe " + name, color);
+        }
+    }
+
+    /**
+     * Calculates the X coordinate for a given column index.
+     *
+     * @param col the column index (0-6)
+     * @return the X coordinate in world units
+     */
+    private static double getColumnX(final int col) {
+        return (col - (COLUMN_COUNT - 1) / 2.0) * CELL_SIZE;
+    }
+
+    /**
+     * Creates a solid polygon shape for the given column.
+     *
+     * @param shapes the shape collection
+     * @param col    the column index (determines shape type)
+     * @param pos    the center position
+     * @param color  the shape color
+     */
+    private static void createSolidShape(final ShapeCollection shapes, final int col,
+                                         final Point3D pos, final Color color) {
+        switch (col) {
+            case 0: // Arrow
+                final SolidPolygonArrow arrow = new SolidPolygonArrow(
+                        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;
+
+            case 1: // Cone (pointing upward)
+                final SolidPolygonCone cone = new SolidPolygonCone(
+                        new Point3D(pos.x, -SHAPE_RADIUS, pos.z),  // apex above
+                        new Point3D(pos.x, SHAPE_RADIUS / 2, pos.z), // base below
+                        SHAPE_RADIUS * 0.8, SEGMENTS, color);
+                cone.setShadingEnabled(true);
+                shapes.addShape(cone);
+                break;
+
+            case 2: // Cube
+                final SolidPolygonCube cube = new SolidPolygonCube(pos, SHAPE_RADIUS * 0.8, color);
+                cube.setShadingEnabled(true);
+                shapes.addShape(cube);
+                break;
+
+            case 3: // Cylinder
+                final SolidPolygonCylinder cylinder = new SolidPolygonCylinder(
+                        new Point3D(pos.x, SHAPE_RADIUS, pos.z),
+                        new Point3D(pos.x, -SHAPE_RADIUS, pos.z),
+                        SHAPE_RADIUS * 0.7, SEGMENTS, color);
+                cylinder.setShadingEnabled(true);
+                shapes.addShape(cylinder);
+                break;
+
+            case 4: // Pyramid
+                final SolidPolygonPyramid pyramid = new SolidPolygonPyramid(
+                        new Point3D(pos.x, -SHAPE_RADIUS, pos.z),  // apex above
+                        new Point3D(pos.x, SHAPE_RADIUS / 2, pos.z), // base below
+                        SHAPE_RADIUS * 0.8, color);
+                pyramid.setShadingEnabled(true);
+                shapes.addShape(pyramid);
+                break;
+
+            case 5: // RectangularBox
+                final SolidPolygonRectangularBox box = new SolidPolygonRectangularBox(
+                        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);
+                break;
+
+            case 6: // Sphere
+                final SolidPolygonSphere sphere = new SolidPolygonSphere(pos, SHAPE_RADIUS, SEGMENTS, color);
+                sphere.setShadingEnabled(true);
+                shapes.addShape(sphere);
+                break;
+
+            default:
+                break;
+        }
+    }
+
+    /**
+     * Creates a wireframe shape for the given column.
+     *
+     * @param shapes the shape collection
+     * @param col    the column index (determines shape type)
+     * @param pos    the center position
+     * @param color  the line color
+     */
+    private static void createWireframeShape(final ShapeCollection shapes, final int col,
+                                             final Point3D pos, final Color color) {
+        final LineAppearance appearance = new LineAppearance(2, color);
+
+        switch (col) {
+            case 0: // Arrow
+                final WireframeArrow arrow = new WireframeArrow(
+                        new Point3D(pos.x, SHAPE_RADIUS, pos.z),
+                        new Point3D(pos.x, -SHAPE_RADIUS, pos.z),
+                        8, appearance);
+                shapes.addShape(arrow);
+                break;
+
+            case 1: // Cone
+                final WireframeCone cone = new WireframeCone(
+                        new Point3D(pos.x, -SHAPE_RADIUS, pos.z),  // apex above
+                        new Point3D(pos.x, SHAPE_RADIUS / 2, pos.z), // base below
+                        SHAPE_RADIUS * 0.8, SEGMENTS, appearance);
+                shapes.addShape(cone);
+                break;
+
+            case 2: // Cube
+                final WireframeCube cube = new WireframeCube(pos, SHAPE_RADIUS * 0.8, appearance);
+                shapes.addShape(cube);
+                break;
+
+            case 3: // Cylinder
+                final WireframeCylinder cylinder = new WireframeCylinder(
+                        new Point3D(pos.x, SHAPE_RADIUS, pos.z),
+                        new Point3D(pos.x, -SHAPE_RADIUS, pos.z),
+                        SHAPE_RADIUS * 0.7, SEGMENTS, appearance);
+                shapes.addShape(cylinder);
+                break;
+
+            case 4: // Pyramid
+                final WireframePyramid pyramid = new WireframePyramid(
+                        new Point3D(pos.x, -SHAPE_RADIUS, pos.z),  // apex above
+                        new Point3D(pos.x, SHAPE_RADIUS / 2, pos.z), // base below
+                        SHAPE_RADIUS * 0.8, appearance);
+                shapes.addShape(pyramid);
+                break;
+
+            case 5: // Box (WireframeBox)
+                final WireframeBox box = new WireframeBox(
+                        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;
+
+            case 6: // Sphere
+                final WireframeSphere sphere = new WireframeSphere(pos, (float) SHAPE_RADIUS, appearance);
+                shapes.addShape(sphere);
+                break;
+
+            default:
+                break;
+        }
+    }
+
+    /**
+     * Creates a text label above a shape.
+     *
+     * @param shapes the shape collection
+     * @param pos    the shape position
+     * @param text   the label text
+     * @param color  the text color
+     */
+    private static void createLabel(final ShapeCollection shapes, final Point3D pos,
+                                    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, 8.0, 2, text, color);
+        shapes.addShape(label);
+    }
+
+    /**
+     * Creates orbiting light sources around the scene.
+     *
+     * @param viewPanel the view panel
+     * @param shapes    the shape collection
+     */
+    private static void createOrbitingLights(final ViewPanel viewPanel, final ShapeCollection shapes) {
+        final Random random = new Random(42);
+        final List<OrbitingLight> orbitingLights = new ArrayList<>();
+
+        for (int i = 0; i < LIGHT_COUNT; i++) {
+            final Color color = new Color(
+                    random.nextInt(256),
+                    random.nextInt(256),
+                    random.nextInt(256)
+            );
+
+            final double orbitRadius = 600 + random.nextInt(200);
+            final double speed = 0.01 + random.nextDouble() * 0.02;
+            final double angleOffset = random.nextDouble() * Math.PI * 2;
+            final double intensity = 2.0 + random.nextDouble() * 2.0;
+
+            final int axis = random.nextInt(3);
+            final double ellipseFactor = 0.7 + random.nextDouble() * 0.3;
+
+            final LightSource light = new LightSource(new Point3D(0, 0, CELL_SIZE / 2), color, intensity);
+            final LightSourceMarker marker = new LightSourceMarker(light.getPosition(), color);
+
+            viewPanel.getLightingManager().addLight(light);
+            shapes.addShape(marker);
+
+            orbitingLights.add(new OrbitingLight(
+                    light, marker,
+                    orbitRadius, speed, angleOffset, axis, ellipseFactor
+            ));
+        }
+
+        final MultiLightAnimator animator = new MultiLightAnimator(orbitingLights);
+        viewPanel.addFrameListener(animator);
+    }
+
+    /**
+     * Represents a light source that orbits around the scene center.
+     */
+    private static class OrbitingLight {
+        final LightSource light;
+        final LightSourceMarker marker;
+        final double orbitRadius;
+        final double speed;
+        final int axis;
+        final double ellipseFactor;
+        double angle;
+
+        OrbitingLight(final LightSource light, final LightSourceMarker marker,
+                      final double orbitRadius, final double speed, final double angleOffset,
+                      final int axis, final double ellipseFactor) {
+            this.light = light;
+            this.marker = marker;
+            this.orbitRadius = orbitRadius;
+            this.speed = speed;
+            this.angle = angleOffset;
+            this.axis = axis;
+            this.ellipseFactor = ellipseFactor;
+        }
+    }
+
+    /**
+     * Frame listener that animates all orbiting lights.
+     */
+    private record MultiLightAnimator(List<OrbitingLight> lights) implements FrameListener {
+
+        @Override
+        public boolean onFrame(final ViewPanel viewPanel, final int millisecondsSinceLastFrame) {
+            final double centerY = CELL_SIZE / 2;
+
+            for (final OrbitingLight orbitingLight : lights) {
+                orbitingLight.angle += orbitingLight.speed * millisecondsSinceLastFrame / 100;
+
+                final double r = orbitingLight.orbitRadius;
+                final double e = orbitingLight.ellipseFactor;
+
+                double x, y, z;
+                switch (orbitingLight.axis) {
+                    case 0:
+                        x = r * Math.cos(orbitingLight.angle);
+                        y = r * e * Math.sin(orbitingLight.angle);
+                        z = r * 0.5 * Math.sin(orbitingLight.angle * 2);
+                        break;
+                    case 1:
+                        x = r * e * Math.sin(orbitingLight.angle * 2);
+                        y = r * Math.cos(orbitingLight.angle);
+                        z = r * 0.5 * Math.sin(orbitingLight.angle);
+                        break;
+                    default:
+                        x = r * 0.5 * Math.sin(orbitingLight.angle);
+                        y = r * Math.sin(orbitingLight.angle * 2);
+                        z = r * e * Math.cos(orbitingLight.angle);
+                        break;
+                }
+
+                final Point3D newPosition = new Point3D(x, y + centerY, z);
+                orbitingLight.light.setPosition(newPosition);
+                orbitingLight.marker.setTransform(fromAngles(
+                        newPosition.x, newPosition.y, newPosition.z, 0, 0, 0));
+            }
+            return true;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/WindingOrderDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/WindingOrderDemo.java
new file mode 100644 (file)
index 0000000..250fc62
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * 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.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 {
+
+    /**
+     * Creates a new WindingOrderDemo instance.
+     */
+    public WindingOrderDemo() {
+    }
+
+    /**
+     * Entry point for the winding order demo.
+     * @param args command line arguments (ignored)
+     */
+    public static void main(String[] args) {
+        ViewFrame viewFrame = new ViewFrame("Winding Order Demo");
+        ViewPanel viewPanel = viewFrame.getViewPanel();
+        ShapeCollection shapes = viewPanel.getRootShapeCollection();
+
+        viewPanel.getCamera().getTransform().setTranslation(new Point3D(0, 0, -500));
+
+        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.repaintDuringNextViewUpdate();
+    }
+}
\ No newline at end of file
index dabb69b..86cda8a 100644 (file)
@@ -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
index 0666abe..0e77299 100644 (file)
@@ -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.
- * 
+ *
  * <p>Key controls:
  * <ul>
  *   <li>Space - Evolve one generation</li>
@@ -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;
+    }
+
 }
index 44fedcc..4626ae6 100644 (file)
@@ -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
index c2749c3..1ad9d43 100644 (file)
@@ -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);