feat(demos): add shape gallery and arrow demo, enhance terrain demo
authorSvjatoslav Agejenko <svjatoslav@svjatoslav.eu>
Wed, 25 Mar 2026 16:36:44 +0000 (18:36 +0200)
committerSvjatoslav Agejenko <svjatoslav@svjatoslav.eu>
Wed, 25 Mar 2026 16:36:44 +0000 (18:36 +0200)
Add ArrowDemo showcasing SolidPolygonArrow and SolidPolygonCone shapes
with various colors, sizes, orientations, and transparency levels.

Transform ShadedShapesDemo into a comprehensive 3D Shape Gallery
displaying all 7 shape types (arrow, cone, cube, cylinder, pyramid,
box, sphere) in both solid polygon and wireframe variants.

Rename diamondsquare_demo to terrain_demo and add FractalTree shape.
DiamondSquareTerrain now exposes getHeightAt() for terrain height queries.

Add descriptive window titles to all demo applications.

22 files changed:
AGENTS.md
doc/index.org
src/main/java/eu/svjatoslav/sixth/e3d/examples/ArrowDemo.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/MinimalExample.java
src/main/java/eu/svjatoslav/sixth/e3d/examples/OctreeDemo.java
src/main/java/eu/svjatoslav/sixth/e3d/examples/RainingNumbersDemo.java
src/main/java/eu/svjatoslav/sixth/e3d/examples/RandomPolygonsDemo.java
src/main/java/eu/svjatoslav/sixth/e3d/examples/ShadedShapesDemo.java
src/main/java/eu/svjatoslav/sixth/e3d/examples/SineHeightmap.java
src/main/java/eu/svjatoslav/sixth/e3d/examples/TextEditorDemo.java
src/main/java/eu/svjatoslav/sixth/e3d/examples/TextEditorDemo2.java
src/main/java/eu/svjatoslav/sixth/e3d/examples/WindingOrderDemo.java
src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/GraphicsBenchmark.java
src/main/java/eu/svjatoslav/sixth/e3d/examples/diamondsquare_demo/DiamondSquareTerrain.java [deleted file]
src/main/java/eu/svjatoslav/sixth/e3d/examples/diamondsquare_demo/TerrainDemo.java [deleted file]
src/main/java/eu/svjatoslav/sixth/e3d/examples/galaxy_demo/PointCloudDemo.java
src/main/java/eu/svjatoslav/sixth/e3d/examples/graph_demo/MathGraphsDemo.java
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/terrain_demo/DiamondSquareTerrain.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/FractalTree.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/TerrainDemo.java [new file with mode: 0644]

index 872d3c3..e6360a7 100644 (file)
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -61,9 +61,10 @@ src/main/java/eu/svjatoslav/sixth/e3d/examples/
 │   ├── Cell.java
 │   ├── Matrix.java
 │   └── Star.java
-├── diamondsquare_demo/   - Procedural terrain generation demo
-│   ├── DiamondSquareLandscape.java
-│   └── DiamondSquareTerrain.java
+├── terrain_demo/         - Procedural terrain generation demo
+│   ├── DiamondSquareTerrain.java
+│   ├── FractalTree.java
+│   └── TerrainDemo.java
 ├── galaxy_demo/        - Galaxy simulation demo
 │   ├── Galaxy.java
 │   └── PointCloudDemo.java
index 807bb65..cb10d9b 100644 (file)
@@ -242,7 +242,7 @@ Solid Cubes                  49.65
 Lit Solid Cubes              41.40
 Textured Cubes               32.80
 Wireframe Cubes              42.84
-Star Grid                    304.59
+Star Grid                    318.97
 ================================================================================
 #+end_example
 
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/ArrowDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/ArrowDemo.java
new file mode 100644 (file)
index 0000000..d457318
--- /dev/null
@@ -0,0 +1,156 @@
+/*
+ * 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.line.LineAppearance;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonArrow;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonCone;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.Grid3D;
+
+/**
+ * Demo showcasing the SolidPolygonArrow shape with various colors, sizes,
+ * orientations, and transparency levels.
+ *
+ * <p>This demo displays arrows pointing in different directions to demonstrate
+ * the flexibility of the arrow shape. A 3D grid provides spatial reference.</p>
+ */
+public class ArrowDemo {
+
+    /**
+     * Entry point for the arrow demo.
+     *
+     * @param args command line arguments (ignored)
+     */
+    public static void main(final String[] args) {
+        final ViewFrame viewFrame = new ViewFrame("Arrow Demo");
+        final ViewPanel viewPanel = viewFrame.getViewPanel();
+        final ShapeCollection shapes = viewPanel.getRootShapeCollection();
+
+        // Position camera to view the scene
+        viewPanel.getCamera().getTransform().setTranslation(new Point3D(0, -200, -600));
+
+        // Add a 3D grid for spatial reference
+        final LineAppearance gridAppearance = new LineAppearance(1, new Color(100, 100, 100, 80));
+        final Grid3D grid = new Grid3D(
+                new Point3D(-300, -200, -300),
+                new Point3D(300, 200, 300),
+                100,
+                gridAppearance
+        );
+        shapes.addShape(grid);
+
+        // Create arrows pointing in different directions with different properties
+
+        // Red arrow pointing up (negative Y direction)
+        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
+        );
+        shapes.addShape(redArrow);
+
+        // Green arrow pointing along positive X
+        final SolidPolygonArrow greenArrow = new SolidPolygonArrow(
+                new Point3D(-200, 0, 0),
+                new Point3D(0, 0, 0),
+                6,
+                15,
+                30,
+                12,
+                Color.GREEN
+        );
+        shapes.addShape(greenArrow);
+
+        // Blue arrow pointing along positive Z
+        final SolidPolygonArrow blueArrow = new SolidPolygonArrow(
+                new Point3D(0, 0, -200),
+                new Point3D(0, 0, 0),
+                6,
+                15,
+                30,
+                12,
+                Color.BLUE
+        );
+        shapes.addShape(blueArrow);
+
+        // Yellow arrow pointing diagonally
+        final SolidPolygonArrow yellowArrow = new SolidPolygonArrow(
+                new Point3D(100, 100, 100),
+                new Point3D(300, -100, 300),
+                10,
+                25,
+                50,
+                16,
+                Color.YELLOW
+        );
+        shapes.addShape(yellowArrow);
+
+        // Semi-transparent cyan arrow (50% opacity)
+        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)
+        );
+        shapes.addShape(transparentCyanArrow);
+
+        // Semi-transparent magenta arrow (25% opacity)
+        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)
+        );
+        shapes.addShape(transparentMagentaArrow);
+
+        // Small white arrows forming a circle pattern
+        for (int i = 0; i < 8; i++) {
+            final double angle = i * Math.PI / 4;
+            final double radius = 250;
+            final double startX = radius * Math.cos(angle);
+            final double startZ = radius * Math.sin(angle);
+            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
+            );
+            shapes.addShape(circleArrow);
+        }
+
+        // A standalone cone to demonstrate SolidPolygonCone
+        final SolidPolygonCone standaloneCone = new SolidPolygonCone(
+                new Point3D(-300, 0, 0),
+                40,
+                80,
+                16,
+                new Color(255, 128, 0)
+        );
+        shapes.addShape(standaloneCone);
+
+        viewPanel.repaintDuringNextViewUpdate();
+    }
+}
\ No newline at end of file
index 41e6e53..86e43ce 100644 (file)
@@ -25,7 +25,7 @@ public class MinimalExample {
      * @param args command line arguments (ignored)
      */
     public static void main(String[] args) {
-        ViewFrame viewFrame = new ViewFrame();
+        ViewFrame viewFrame = new ViewFrame("Minimal Example");
         ShapeCollection shapes = viewFrame.getViewPanel().getRootShapeCollection();
 
         viewFrame.getViewPanel().getCamera().getTransform().setTranslation(new Point3D(0, -100, -300));
index e2ed165..ead6346 100755 (executable)
@@ -123,7 +123,7 @@ public class OctreeDemo extends WorldNavigationUserInputTracker {
     /** Initializes the demo scene with grid, lights, shapes, and fractal pattern. */
     private void init() {
 
-        final ViewFrame viewFrame = new ViewFrame();
+        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);
index da0b33d..c2c82cd 100644 (file)
@@ -77,7 +77,7 @@ public class RainingNumbersDemo implements FrameListener {
 
     /** Initializes the demo by creating the view frame and adding falling numbers. */
     private void run() {
-        final ViewFrame viewFrame = new ViewFrame();
+        final ViewFrame viewFrame = new ViewFrame("Raining Numbers");
 
         viewFrame.getViewPanel().getCamera().getTransform().set(-129.75, 228.27, -220.69, -0.55, 0.54, 0);
 
index 54d25f4..8b7d7d5 100755 (executable)
@@ -86,7 +86,7 @@ public class RandomPolygonsDemo {
      */
     public static void main(final String[] args) {
 
-        final ViewFrame viewFrame = new ViewFrame();
+        final ViewFrame viewFrame = new ViewFrame("Random Polygons");
 
         viewFrame.getViewPanel().getCamera().getTransform().set(-52.96, -239.61, -1293.29, -0.09, -0.36, 0);
 
index e9dc664..ccdc182 100644 (file)
@@ -1,17 +1,17 @@
 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.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.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.SolidPolygonCube;
-import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonCylinder;
-import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonPyramid;
-import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonSphere;
+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;
@@ -20,34 +20,331 @@ import java.util.Random;
 import static eu.svjatoslav.sixth.e3d.math.Transform.fromAngles;
 
 /**
- * Demo showing a shaded sphere, cube, pyramid, and cylinder with multiple colored light sources.
- * Ten light sources orbit around the shapes on different paths to demonstrate dynamic lighting.
+ * 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 {
 
     /**
-     * Creates a new ShadedShapesDemo instance.
+     * Number of columns (shape types).
      */
-    public ShadedShapesDemo() {
-    }
+    private static final int COLUMN_COUNT = 7;
+
+    /**
+     * Size of each cell in world units.
+     */
+    private static final double CELL_SIZE = 250;
 
-    /** Number of orbiting light sources in the scene. */
+    /**
+     * 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();
+        final ViewFrame viewFrame = new ViewFrame("3D Shape Gallery");
         final ViewPanel viewPanel = viewFrame.getViewPanel();
         final ShapeCollection shapes = viewPanel.getRootShapeCollection();
 
-        viewPanel.getCamera().getTransform().setTranslation(new Point3D(200, -250, -900));
+        // 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;
 
-        // Use the global lighting manager from ViewPanel
-        viewPanel.getLightingManager().setAmbientLight(new Color(25, 25, 25));
+            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<>();
 
@@ -58,15 +355,15 @@ public class ShadedShapesDemo {
                     random.nextInt(256)
             );
 
-            final double orbitRadius = 500 + random.nextInt(200);
-            final double speed = 0.01 + random.nextDouble() * 0.03;
+            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() * 3.0;
+            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, 0), color, intensity);
+            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);
@@ -78,43 +375,12 @@ public class ShadedShapesDemo {
             ));
         }
 
-        // Sphere
-        final SolidPolygonSphere sphere = new SolidPolygonSphere(
-                new Point3D(-400, 0, 0), 150, 28, new Color(200, 210, 255)
-        );
-        sphere.setShadingEnabled(true);
-        shapes.addShape(sphere);
-
-        // Pyramid
-        final SolidPolygonPyramid pyramid = new SolidPolygonPyramid(
-                new Point3D(0, 130, 0), 150, 260, new Color(255, 200, 200)
-        );
-        pyramid.setShadingEnabled(true);
-        shapes.addShape(pyramid);
-
-        // Cube
-        final SolidPolygonCube cube = new SolidPolygonCube(
-                new Point3D(400, 0, 0), 150, new Color(200, 255, 200)
-        );
-        cube.setShadingEnabled(true);
-        shapes.addShape(cube);
-
-        // Cylinder
-        final SolidPolygonCylinder cylinder = new SolidPolygonCylinder(
-                new Point3D(800, 0, 0), 120, 260, 24, new Color(255, 220, 180)
-        );
-        cylinder.setShadingEnabled(true);
-        shapes.addShape(cylinder);
-
         final MultiLightAnimator animator = new MultiLightAnimator(orbitingLights);
         viewPanel.addFrameListener(animator);
-
-        viewPanel.repaintDuringNextViewUpdate();
     }
 
     /**
      * Represents a light source that orbits around the scene center.
-     * Each light follows an elliptical path on one of three possible axes.
      */
     private static class OrbitingLight {
         final LightSource light;
@@ -125,19 +391,9 @@ public class ShadedShapesDemo {
         final double ellipseFactor;
         double angle;
 
-        /**
-         * Creates an orbiting light with the specified parameters.
-         * @param light the light source to orbit
-         * @param marker the visual marker for the light position
-         * @param orbitRadius the base radius of the orbit
-         * @param speed the angular speed of orbit
-         * @param angleOffset the starting angle offset
-         * @param axis the axis of orbit (0, 1, or 2)
-         * @param ellipseFactor the ellipse distortion factor
-         */
-        OrbitingLight(LightSource light, LightSourceMarker marker,
-                      double orbitRadius, double speed, double angleOffset,
-                      int axis, double ellipseFactor) {
+        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;
@@ -149,33 +405,19 @@ public class ShadedShapesDemo {
     }
 
     /**
-     * Frame listener that animates all orbiting lights each frame.
-     * Updates light positions based on their individual orbital parameters.
+     * Frame listener that animates all orbiting lights.
      */
-    private static class MultiLightAnimator implements eu.svjatoslav.sixth.e3d.gui.FrameListener {
-        private final List<OrbitingLight> lights;
+    private record MultiLightAnimator(List<OrbitingLight> lights) implements FrameListener {
 
-        /**
-         * Creates an animator for the specified lights.
-         * @param lights the list of lights to animate
-         */
-        MultiLightAnimator(List<OrbitingLight> lights) {
-            this.lights = lights;
-        }
-
-        /**
-         * Updates all light positions each frame.
-         * @param viewPanel the view panel
-         * @param millisecondsSinceLastFrame time elapsed since last frame
-         * @return true to continue animation
-         */
         @Override
-        public boolean onFrame(ViewPanel viewPanel, int millisecondsSinceLastFrame) {
-            for (OrbitingLight orbitingLight : lights) {
+        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;
 
-                double r = orbitingLight.orbitRadius;
-                double e = orbitingLight.ellipseFactor;
+                final double r = orbitingLight.orbitRadius;
+                final double e = orbitingLight.ellipseFactor;
 
                 double x, y, z;
                 switch (orbitingLight.axis) {
@@ -196,7 +438,7 @@ public class ShadedShapesDemo {
                         break;
                 }
 
-                Point3D newPosition = new Point3D(x, y + 50, z);
+                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));
@@ -204,4 +446,4 @@ public class ShadedShapesDemo {
             return true;
         }
     }
-}
+}
\ No newline at end of file
index dd8cee5..25642dd 100755 (executable)
@@ -79,7 +79,7 @@ public class SineHeightmap {
      */
     public static void main(final String[] args) {
 
-        final ViewFrame viewFrame = new ViewFrame();
+        final ViewFrame viewFrame = new ViewFrame("Sine Heightmap");
         final ShapeCollection geometryCollection = viewFrame.getViewPanel()
                 .getRootShapeCollection();
 
index 748edc1..676c3fc 100644 (file)
@@ -38,7 +38,7 @@ public class TextEditorDemo {
      */
     public static void main(final String[] args) {
 
-        final ViewFrame viewFrame = new ViewFrame();
+        final ViewFrame viewFrame = new ViewFrame("Text Editors");
         final ViewPanel viewPanel = viewFrame.getViewPanel();
 
         viewPanel.getCamera().getTransform().set(500, -300, -800, 0.6, -0.5, 0);
index 81c145e..28b0246 100644 (file)
@@ -63,7 +63,7 @@ public class TextEditorDemo2 {
      * @throws IOException if demo text file cannot be read
      */
     public void build() throws URISyntaxException, IOException {
-        final ViewFrame viewFrame = new ViewFrame();
+        final ViewFrame viewFrame = new ViewFrame("Text Editors City");
         final ViewPanel viewPanel = viewFrame.getViewPanel();
 
         viewPanel.getCamera().getTransform().set(500, -300, -800, 0.6, -0.5, 0);
index 613397b..df022df 100644 (file)
@@ -36,7 +36,7 @@ public class WindingOrderDemo {
      * @param args command line arguments (ignored)
      */
     public static void main(String[] args) {
-        ViewFrame viewFrame = new ViewFrame();
+        ViewFrame viewFrame = new ViewFrame("Winding Order Demo");
         ViewPanel viewPanel = viewFrame.getViewPanel();
         ShapeCollection shapes = viewPanel.getRootShapeCollection();
 
index a5e1dcf..f44bd6e 100644 (file)
@@ -111,7 +111,7 @@ public class GraphicsBenchmark implements FrameListener, KeyboardInputHandler {
     }
 
     private void initializeWindow() {
-        viewFrame = new ViewFrame(WINDOW_WIDTH, WINDOW_HEIGHT);
+        viewFrame = new ViewFrame("Graphics Benchmark", WINDOW_WIDTH, WINDOW_HEIGHT);
         viewPanel = viewFrame.getViewPanel();
         viewPanel.setFrameRate(0);
         shapes = viewPanel.getRootShapeCollection();
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/diamondsquare_demo/DiamondSquareTerrain.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/diamondsquare_demo/DiamondSquareTerrain.java
deleted file mode 100644 (file)
index 32bd138..0000000
+++ /dev/null
@@ -1,76 +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.diamondsquare_demo;
-
-import eu.svjatoslav.sixth.e3d.geometry.Point3D;
-import eu.svjatoslav.sixth.e3d.math.DiamondSquare;
-import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
-import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.solidpolygon.SolidPolygon;
-import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.AbstractCompositeShape;
-
-/**
- * A procedurally generated terrain mesh using the diamond-square algorithm.
- * Creates a grid of triangular polygons with height variation for mountains.
- */
-public class DiamondSquareTerrain extends AbstractCompositeShape {
-
-    private static final double TERRAIN_SIZE = 800.0;
-
-    /**
-     * Creates a diamond-square terrain with the specified parameters.
-     *
-     * @param gridSize  the size of the heightmap grid (must be 2^n + 1)
-     * @param minHeight the minimum terrain height
-     * @param maxHeight the maximum terrain height
-     * @param seed      random seed for reproducible terrain
-     */
-    public DiamondSquareTerrain(final int gridSize, final double minHeight, final double maxHeight, final long seed) {
-        super();
-
-        final double[][] heightmap = DiamondSquare.generateMap(gridSize, minHeight, maxHeight, seed);
-        createPolygons(gridSize, heightmap);
-        setBackfaceCulling(true);
-    }
-
-    /**
-     * Creates triangular polygons from the heightmap.
-     */
-    private void createPolygons(final int gridSize, final double[][] heightmap) {
-        final double cellSize = TERRAIN_SIZE / (gridSize - 1);
-        final double offsetX = -TERRAIN_SIZE / 2;
-        final double offsetZ = -TERRAIN_SIZE / 2;
-
-        for (int z = 0; z < gridSize - 1; z++) {
-            for (int x = 0; x < gridSize - 1; x++) {
-                final double x0 = offsetX + x * cellSize;
-                final double x1 = offsetX + (x + 1) * cellSize;
-                final double z0 = offsetZ + z * cellSize;
-                final double z1 = offsetZ + (z + 1) * cellSize;
-
-                final double y00 = heightmap[z][x];
-                final double y10 = heightmap[z][x + 1];
-                final double y01 = heightmap[z + 1][x];
-                final double y11 = heightmap[z + 1][x + 1];
-
-                final Color color = new Color(10, 10, 10);
-
-                final Point3D p00 = new Point3D(x0, y00, z0);
-                final Point3D p10 = new Point3D(x1, y10, z0);
-                final Point3D p01 = new Point3D(x0, y01, z1);
-                final Point3D p11 = new Point3D(x1, y11, z1);
-
-                final SolidPolygon tri1 = new SolidPolygon(p00, p10, p01, color);
-                final SolidPolygon tri2 = new SolidPolygon(p10, p11, p01, color);
-
-                tri1.setShadingEnabled(true);
-                tri2.setShadingEnabled(true);
-
-                addShape(tri1);
-                addShape(tri2);
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/diamondsquare_demo/TerrainDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/diamondsquare_demo/TerrainDemo.java
deleted file mode 100644 (file)
index 70ca2c6..0000000
+++ /dev/null
@@ -1,76 +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.diamondsquare_demo;
-
-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.lighting.LightingManager;
-import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.LightSourceMarker;
-
-/**
- * Demo showing a procedurally generated mountain landscape using the diamond-square algorithm.
- * Three colored light sources (warm orange, cool cyan, neutral white) illuminate the terrain
- * from above, demonstrating dynamic flat shading.
- */
-public class TerrainDemo {
-
-    /**
-     * Entry point for the terrain demo.
-     * @param args command line arguments (ignored)
-     */
-    public static void main(final String[] args) {
-        final ViewFrame viewFrame = new ViewFrame();
-        ViewPanel viewPanel = viewFrame.getViewPanel();
-
-        viewPanel.getCamera().getTransform().set(-307.96, -847.50, -768.20, -0.52, -0.66, 0);
-
-        final ShapeCollection shapes = viewPanel.getRootShapeCollection();
-        
-        setLights(viewFrame, shapes);
-
-        shapes.addShape(new DiamondSquareTerrain(129, 0.0, 300.0, 42));
-
-        viewPanel.repaintDuringNextViewUpdate();
-    }
-
-    /**
-     * Sets up the lighting for the terrain scene.
-     * @param viewFrame the view frame containing the lighting manager
-     * @param shapes the shape collection to add light source markers to
-     */
-    private static void setLights(ViewFrame viewFrame, ShapeCollection shapes) {
-        LightingManager lightingManager = viewFrame.getViewPanel().getLightingManager();
-        lightingManager.setAmbientLight(new Color(250, 150, 100));
-
-        final LightSource warmLight = new LightSource(
-                new Point3D(-400, -500, 0),
-                new Color(255, 180, 100),
-                190.0
-        );
-        final LightSource coolLight = new LightSource(
-                new Point3D(400, -500, 0),
-                new Color(100, 200, 255),
-                220.0
-        );
-        final LightSource neutralLight = new LightSource(
-                new Point3D(0, -600, 300),
-                new Color(255, 255, 255),
-                250.0
-        );
-
-        lightingManager.addLight(warmLight);
-        lightingManager.addLight(coolLight);
-        lightingManager.addLight(neutralLight);
-
-        shapes.addShape(new LightSourceMarker(warmLight.getPosition(), new Color(255, 180, 100)));
-        shapes.addShape(new LightSourceMarker(coolLight.getPosition(), new Color(100, 200, 255)));
-        shapes.addShape(new LightSourceMarker(neutralLight.getPosition(), new Color(255, 255, 255)));
-    }
-}
\ No newline at end of file
index 7e94245..b382ad1 100644 (file)
@@ -29,7 +29,7 @@ public class PointCloudDemo {
      */
     public static void main(final String[] args) {
 
-        final ViewFrame viewFrame = new ViewFrame();
+        final ViewFrame viewFrame = new ViewFrame("Point Cloud Galaxy");
 
         viewFrame.getViewPanel().getCamera().getTransform().set(-1099.85, -2862.44, 144.32, -1.09, -0.60, 0);
 
index abbb2c3..1f28002 100644 (file)
@@ -145,7 +145,7 @@ public class MathGraphsDemo {
      */
     public static void main(final String[] args) {
 
-        final ViewFrame viewFrame = new ViewFrame();
+        final ViewFrame viewFrame = new ViewFrame("Math Graphs");
         final ShapeCollection geometryCollection = viewFrame.getViewPanel()
                 .getRootShapeCollection();
 
index a97b692..dabb69b 100644 (file)
@@ -6,7 +6,7 @@
 
 package eu.svjatoslav.sixth.e3d.examples.launcher;
 
-import eu.svjatoslav.sixth.e3d.examples.diamondsquare_demo.TerrainDemo;
+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;
index b2a6b30..0666abe 100644 (file)
@@ -72,7 +72,7 @@ public class Main extends WorldNavigationUserInputTracker {
     private void run() {
 
         // create application frame visible to the user
-        final ViewFrame viewFrame = new ViewFrame();
+        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);
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/DiamondSquareTerrain.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/DiamondSquareTerrain.java
new file mode 100644 (file)
index 0000000..47a96aa
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+package eu.svjatoslav.sixth.e3d.examples.terrain_demo;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.math.DiamondSquare;
+import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.solidpolygon.SolidPolygon;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.AbstractCompositeShape;
+
+/**
+ * A procedurally generated terrain mesh using the diamond-square algorithm.
+ * Creates a grid of triangular polygons with height variation for mountains.
+ */
+public class DiamondSquareTerrain extends AbstractCompositeShape {
+
+    private static final double TERRAIN_SIZE = 800.0;
+
+    private final double[][] heightmap;
+    private final int gridSize;
+
+    /**
+     * Creates a diamond-square terrain with the specified parameters.
+     *
+     * @param gridSize  the size of the heightmap grid (must be 2^n + 1)
+     * @param minHeight the minimum terrain height
+     * @param maxHeight the maximum terrain height
+     * @param seed      random seed for reproducible terrain
+     */
+    public DiamondSquareTerrain(final int gridSize, final double minHeight, final double maxHeight, final long seed) {
+        super();
+
+        this.gridSize = gridSize;
+        this.heightmap = DiamondSquare.generateMap(gridSize, minHeight, maxHeight, seed);
+        createPolygons(gridSize, heightmap);
+        setBackfaceCulling(true);
+    }
+
+    /**
+     * Returns the terrain height at the specified world XZ coordinates.
+     *
+     * @param worldX the world X coordinate
+     * @param worldZ the world Z coordinate
+     * @return the terrain height at that position, or 0 if outside terrain bounds
+     */
+    public double getHeightAt(final double worldX, final double worldZ) {
+        final double cellSize = TERRAIN_SIZE / (gridSize - 1);
+        final double offsetX = -TERRAIN_SIZE / 2;
+        final double offsetZ = -TERRAIN_SIZE / 2;
+
+        final int gridX = (int) Math.round((worldX - offsetX) / cellSize);
+        final int gridZ = (int) Math.round((worldZ - offsetZ) / cellSize);
+
+        if (gridX < 0 || gridX >= gridSize || gridZ < 0 || gridZ >= gridSize) {
+            return 0;
+        }
+
+        return heightmap[gridZ][gridX];
+    }
+
+    /**
+     * Creates triangular polygons from the heightmap.
+     */
+    private void createPolygons(final int gridSize, final double[][] heightmap) {
+        final double cellSize = TERRAIN_SIZE / (gridSize - 1);
+        final double offsetX = -TERRAIN_SIZE / 2;
+        final double offsetZ = -TERRAIN_SIZE / 2;
+
+        for (int z = 0; z < gridSize - 1; z++) {
+            for (int x = 0; x < gridSize - 1; x++) {
+                final double x0 = offsetX + x * cellSize;
+                final double x1 = offsetX + (x + 1) * cellSize;
+                final double z0 = offsetZ + z * cellSize;
+                final double z1 = offsetZ + (z + 1) * cellSize;
+
+                final double y00 = heightmap[z][x];
+                final double y10 = heightmap[z][x + 1];
+                final double y01 = heightmap[z + 1][x];
+                final double y11 = heightmap[z + 1][x + 1];
+
+                final Color color = new Color(10, 10, 10);
+
+                final Point3D p00 = new Point3D(x0, y00, z0);
+                final Point3D p10 = new Point3D(x1, y10, z0);
+                final Point3D p01 = new Point3D(x0, y01, z1);
+                final Point3D p11 = new Point3D(x1, y11, z1);
+
+                final SolidPolygon tri1 = new SolidPolygon(p00, p10, p01, color);
+                final SolidPolygon tri2 = new SolidPolygon(p10, p11, p01, color);
+
+                tri1.setShadingEnabled(true);
+                tri2.setShadingEnabled(true);
+
+                addShape(tri1);
+                addShape(tri2);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/FractalTree.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/FractalTree.java
new file mode 100644 (file)
index 0000000..8b78e72
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+package eu.svjatoslav.sixth.e3d.examples.terrain_demo;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.AbstractCompositeShape;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonCylinder;
+
+/**
+ * A simple tree trunk made of a single cylinder.
+ * Positioned to extend through the full terrain height range.
+ *
+ * @see TerrainDemo
+ * @see SolidPolygonCylinder
+ */
+public class FractalTree extends AbstractCompositeShape {
+
+    private static final Color BROWN = new Color(139, 90, 43);
+
+    /**
+     * Creates a tree trunk at the specified base position.
+     *
+     * @param basePosition the position of the trunk base (on the terrain surface)
+     * @param trunkHeight  the height of the trunk
+     * @param trunkRadius  the radius of the trunk base
+     */
+    public FractalTree(final Point3D basePosition, final double trunkHeight,
+                       final double trunkRadius) {
+        super();
+
+        // In Sixth 3D, Y increases downward (screen-space coordinates).
+        // The trunk extends upward from basePosition, so the top has a smaller Y value.
+        final Point3D topPosition = new Point3D(
+                basePosition.x,
+                basePosition.y - trunkHeight,
+                basePosition.z
+        );
+
+        final SolidPolygonCylinder trunk = new SolidPolygonCylinder(
+                basePosition,    // start (bottom of trunk)
+                topPosition,     // end (top of trunk)
+                trunkRadius,
+                8,
+                BROWN);
+        trunk.setShadingEnabled(true);
+
+        addShape(trunk);
+        setBackfaceCulling(true);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/TerrainDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/TerrainDemo.java
new file mode 100644 (file)
index 0000000..c2749c3
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+package eu.svjatoslav.sixth.e3d.examples.terrain_demo;
+
+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.lighting.LightingManager;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.LightSourceMarker;
+
+/**
+ * Demo showing a procedurally generated mountain landscape using the diamond-square algorithm.
+ * Three colored light sources (warm orange, cool cyan, neutral white) illuminate the terrain
+ * from above, demonstrating dynamic flat shading. A fractal tree stands at the center of the terrain.
+ */
+public class TerrainDemo {
+
+    /**
+     * Entry point for the terrain demo.
+     *
+     * @param args command line arguments (ignored)
+     */
+    public static void main(final String[] args) {
+        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);
+
+        final ShapeCollection shapes = viewPanel.getRootShapeCollection();
+
+        setLights(viewFrame, shapes);
+
+        final DiamondSquareTerrain terrain = new DiamondSquareTerrain(129, 0.0, 100.0, 42);
+        shapes.addShape(terrain);
+
+        // Get terrain height at center and place tree trunk on it
+        final double terrainHeight = terrain.getHeightAt(0, 0);
+        shapes.addShape(new FractalTree(new Point3D(0, terrainHeight, 0), 30, 6));
+
+        viewPanel.repaintDuringNextViewUpdate();
+    }
+
+    /**
+     * Sets up the lighting for the terrain scene.
+     *
+     * @param viewFrame the view frame containing the lighting manager
+     * @param shapes    the shape collection to add light source markers to
+     */
+    private static void setLights(ViewFrame viewFrame, ShapeCollection shapes) {
+        LightingManager lightingManager = viewFrame.getViewPanel().getLightingManager();
+        lightingManager.setAmbientLight(new Color(250, 150, 100));
+
+        final LightSource warmLight = new LightSource(
+                new Point3D(-400, -500, 0),
+                new Color(255, 180, 100),
+                190.0
+        );
+        final LightSource coolLight = new LightSource(
+                new Point3D(400, -500, 0),
+                new Color(100, 200, 255),
+                220.0
+        );
+        final LightSource neutralLight = new LightSource(
+                new Point3D(0, -600, 300),
+                new Color(255, 255, 255),
+                250.0
+        );
+
+        lightingManager.addLight(warmLight);
+        lightingManager.addLight(coolLight);
+        lightingManager.addLight(neutralLight);
+
+        shapes.addShape(new LightSourceMarker(warmLight.getPosition(), new Color(255, 180, 100)));
+        shapes.addShape(new LightSourceMarker(coolLight.getPosition(), new Color(100, 200, 255)));
+        shapes.addShape(new LightSourceMarker(neutralLight.getPosition(), new Color(255, 255, 255)));
+    }
+}
\ No newline at end of file