From dd50d4e7df7cfd81e3fc7a68f1efbc4deb6f4a42 Mon Sep 17 00:00:00 2001 From: Svjatoslav Agejenko Date: Wed, 25 Mar 2026 18:36:44 +0200 Subject: [PATCH] feat(demos): add shape gallery and arrow demo, enhance terrain demo 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. --- AGENTS.md | 7 +- doc/index.org | 2 +- .../sixth/e3d/examples/ArrowDemo.java | 156 +++++++ .../sixth/e3d/examples/MinimalExample.java | 2 +- .../sixth/e3d/examples/OctreeDemo.java | 2 +- .../e3d/examples/RainingNumbersDemo.java | 2 +- .../e3d/examples/RandomPolygonsDemo.java | 2 +- .../sixth/e3d/examples/ShadedShapesDemo.java | 416 ++++++++++++++---- .../sixth/e3d/examples/SineHeightmap.java | 2 +- .../sixth/e3d/examples/TextEditorDemo.java | 2 +- .../sixth/e3d/examples/TextEditorDemo2.java | 2 +- .../sixth/e3d/examples/WindingOrderDemo.java | 2 +- .../examples/benchmark/GraphicsBenchmark.java | 2 +- .../examples/galaxy_demo/PointCloudDemo.java | 2 +- .../examples/graph_demo/MathGraphsDemo.java | 2 +- .../launcher/ApplicationListPanel.java | 2 +- .../sixth/e3d/examples/life_demo/Main.java | 2 +- .../DiamondSquareTerrain.java | 30 +- .../examples/terrain_demo/FractalTree.java | 54 +++ .../TerrainDemo.java | 21 +- 20 files changed, 599 insertions(+), 113 deletions(-) create mode 100644 src/main/java/eu/svjatoslav/sixth/e3d/examples/ArrowDemo.java rename src/main/java/eu/svjatoslav/sixth/e3d/examples/{diamondsquare_demo => terrain_demo}/DiamondSquareTerrain.java (72%) create mode 100644 src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/FractalTree.java rename src/main/java/eu/svjatoslav/sixth/e3d/examples/{diamondsquare_demo => terrain_demo}/TerrainDemo.java (77%) diff --git a/AGENTS.md b/AGENTS.md index 872d3c3..e6360a7 100644 --- 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 diff --git a/doc/index.org b/doc/index.org index 807bb65..cb10d9b 100644 --- a/doc/index.org +++ b/doc/index.org @@ -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 index 0000000..d457318 --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/ArrowDemo.java @@ -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. + * + *

This demo displays arrows pointing in different directions to demonstrate + * the flexibility of the arrow shape. A 3D grid provides spatial reference.

+ */ +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 diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/MinimalExample.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/MinimalExample.java index 41e6e53..86e43ce 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/MinimalExample.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/MinimalExample.java @@ -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)); diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/OctreeDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/OctreeDemo.java index e2ed165..ead6346 100755 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/OctreeDemo.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/OctreeDemo.java @@ -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); diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/RainingNumbersDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/RainingNumbersDemo.java index da0b33d..c2c82cd 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/RainingNumbersDemo.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/RainingNumbersDemo.java @@ -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); diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/RandomPolygonsDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/RandomPolygonsDemo.java index 54d25f4..8b7d7d5 100755 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/RandomPolygonsDemo.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/RandomPolygonsDemo.java @@ -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); diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/ShadedShapesDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/ShadedShapesDemo.java index e9dc664..ccdc182 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/ShadedShapesDemo.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/ShadedShapesDemo.java @@ -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. + * + *

This demo displays a 7x2 grid of shapes organized by type (columns) and + * rendering style (rows). The top row shows solid polygon shapes, and the bottom + * row shows wireframe shapes. Each column represents a different shape type:

+ * + *
+ *            Arrow    Cone     Cube   Cylinder  Pyramid   Box    Sphere
+ *         ┌────────┬────────┬────────┬────────┬────────┬────────┬────────┐
+ * Solid   │  ████  │  /\\   │ ████   │  ║║║   │  /\\   │ ████   │  ()   │
+ *         │  ████  │ /  \\  │ ████   │  ║║║   │ /__\\  │ ████   │ (  )  │
+ *         ├────────┼────────┼────────┼────────┼────────┼────────┼────────┤
+ * Wirefrm │  ╔══╗  │  /\\   │ ╔══╗   │  ║║║   │  /\\   │ ╔══╗   │  ()   │
+ *         │  ╚══╝  │ /  \\  │ ╚══╝   │  ║║║   │ /__\\  │ ╚══╝   │ (  )  │
+ *         └────────┴────────┴────────┴────────┴────────┴────────┴────────┘
+ * 
+ * + *

Features:

+ * */ 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 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 lights; + private record MultiLightAnimator(List lights) implements FrameListener { - /** - * Creates an animator for the specified lights. - * @param lights the list of lights to animate - */ - MultiLightAnimator(List 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 diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/SineHeightmap.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/SineHeightmap.java index dd8cee5..25642dd 100755 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/SineHeightmap.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/SineHeightmap.java @@ -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(); diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/TextEditorDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/TextEditorDemo.java index 748edc1..676c3fc 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/TextEditorDemo.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/TextEditorDemo.java @@ -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); diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/TextEditorDemo2.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/TextEditorDemo2.java index 81c145e..28b0246 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/TextEditorDemo2.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/TextEditorDemo2.java @@ -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); diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/WindingOrderDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/WindingOrderDemo.java index 613397b..df022df 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/WindingOrderDemo.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/WindingOrderDemo.java @@ -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(); diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/GraphicsBenchmark.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/GraphicsBenchmark.java index a5e1dcf..f44bd6e 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/GraphicsBenchmark.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/GraphicsBenchmark.java @@ -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/galaxy_demo/PointCloudDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/galaxy_demo/PointCloudDemo.java index 7e94245..b382ad1 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/galaxy_demo/PointCloudDemo.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/galaxy_demo/PointCloudDemo.java @@ -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); diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/graph_demo/MathGraphsDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/graph_demo/MathGraphsDemo.java index abbb2c3..1f28002 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/graph_demo/MathGraphsDemo.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/graph_demo/MathGraphsDemo.java @@ -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(); diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/ApplicationListPanel.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/ApplicationListPanel.java index a97b692..dabb69b 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/ApplicationListPanel.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/ApplicationListPanel.java @@ -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; diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Main.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Main.java index b2a6b30..0666abe 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Main.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Main.java @@ -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/diamondsquare_demo/DiamondSquareTerrain.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/DiamondSquareTerrain.java similarity index 72% rename from src/main/java/eu/svjatoslav/sixth/e3d/examples/diamondsquare_demo/DiamondSquareTerrain.java rename to src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/DiamondSquareTerrain.java index 32bd138..47a96aa 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/diamondsquare_demo/DiamondSquareTerrain.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/DiamondSquareTerrain.java @@ -3,7 +3,7 @@ * This project is released under Creative Commons Zero (CC0) license. */ -package eu.svjatoslav.sixth.e3d.examples.diamondsquare_demo; +package eu.svjatoslav.sixth.e3d.examples.terrain_demo; import eu.svjatoslav.sixth.e3d.geometry.Point3D; import eu.svjatoslav.sixth.e3d.math.DiamondSquare; @@ -19,6 +19,9 @@ 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. * @@ -30,11 +33,34 @@ public class DiamondSquareTerrain extends AbstractCompositeShape { public DiamondSquareTerrain(final int gridSize, final double minHeight, final double maxHeight, final long seed) { super(); - final double[][] heightmap = DiamondSquare.generateMap(gridSize, minHeight, maxHeight, seed); + 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. */ 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 index 0000000..8b78e72 --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/FractalTree.java @@ -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/diamondsquare_demo/TerrainDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/TerrainDemo.java similarity index 77% rename from src/main/java/eu/svjatoslav/sixth/e3d/examples/diamondsquare_demo/TerrainDemo.java rename to src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/TerrainDemo.java index 70ca2c6..c2749c3 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/diamondsquare_demo/TerrainDemo.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/TerrainDemo.java @@ -3,7 +3,7 @@ * This project is released under Creative Commons Zero (CC0) license. */ -package eu.svjatoslav.sixth.e3d.examples.diamondsquare_demo; +package eu.svjatoslav.sixth.e3d.examples.terrain_demo; import eu.svjatoslav.sixth.e3d.geometry.Point3D; import eu.svjatoslav.sixth.e3d.gui.ViewFrame; @@ -17,33 +17,40 @@ import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.LightSourceMarke /** * 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. + * 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(); + final ViewFrame viewFrame = new ViewFrame("Procedural Terrain"); ViewPanel viewPanel = viewFrame.getViewPanel(); - viewPanel.getCamera().getTransform().set(-307.96, -847.50, -768.20, -0.52, -0.66, 0); + viewPanel.getCamera().getTransform().set(-76.43, -72.05, -159.08, -0.64, -0.46, -0.00); final ShapeCollection shapes = viewPanel.getRootShapeCollection(); - + setLights(viewFrame, shapes); - shapes.addShape(new DiamondSquareTerrain(129, 0.0, 300.0, 42)); + 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 + * @param shapes the shape collection to add light source markers to */ private static void setLights(ViewFrame viewFrame, ShapeCollection shapes) { LightingManager lightingManager = viewFrame.getViewPanel().getLightingManager(); -- 2.20.1