From: Svjatoslav Agejenko Date: Wed, 25 Mar 2026 16:36:44 +0000 (+0200) Subject: feat(demos): add shape gallery and arrow demo, enhance terrain demo X-Git-Url: http://www2.svjatoslav.eu/gitweb/?a=commitdiff_plain;h=dd50d4e7df7cfd81e3fc7a68f1efbc4deb6f4a42;p=sixth-3d-demos.git 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. --- 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/diamondsquare_demo/DiamondSquareTerrain.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/diamondsquare_demo/DiamondSquareTerrain.java deleted file mode 100644 index 32bd138..0000000 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/diamondsquare_demo/DiamondSquareTerrain.java +++ /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 index 70ca2c6..0000000 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/diamondsquare_demo/TerrainDemo.java +++ /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 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/terrain_demo/DiamondSquareTerrain.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/DiamondSquareTerrain.java new file mode 100644 index 0000000..47a96aa --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/DiamondSquareTerrain.java @@ -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 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/terrain_demo/TerrainDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/TerrainDemo.java new file mode 100644 index 0000000..c2749c3 --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/TerrainDemo.java @@ -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