From: Svjatoslav Agejenko Date: Sun, 22 Mar 2026 19:16:32 +0000 (+0200) Subject: feat(demo): add diamond-square procedural terrain demo X-Git-Url: http://www2.svjatoslav.eu/gitweb/?a=commitdiff_plain;h=ea65c2444979b8a923674597e9f4fb1d39d81ecb;p=sixth-3d-demos.git feat(demo): add diamond-square procedural terrain demo Add DiamondSquareLandscape demo showcasing procedural terrain generation with 3 colored light sources. Update all demos to use global LightingManager from ViewPanel instead of creating per-demo instances. Update Quaternion API calls from setQuaternion() to set(). Squashed commits: - feat(demo): add diamond-square procedural terrain demo - refactor(demos): update demos to use global LightingManager - refactor: update demos to use Quaternion.set() instead of setQuaternion() --- diff --git a/AGENTS.md b/AGENTS.md index 899d46f..872d3c3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -61,6 +61,9 @@ src/main/java/eu/svjatoslav/sixth/e3d/examples/ │ ├── Cell.java │ ├── Matrix.java │ └── Star.java +├── diamondsquare_demo/ - Procedural terrain generation demo +│ ├── DiamondSquareLandscape.java +│ └── DiamondSquareTerrain.java ├── galaxy_demo/ - Galaxy simulation demo │ ├── Galaxy.java │ └── PointCloudDemo.java 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 49f121a..f8232c5 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/ShadedShapesDemo.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/ShadedShapesDemo.java @@ -6,7 +6,6 @@ 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; import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonCube; import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonCylinder; @@ -36,36 +35,36 @@ public class ShadedShapesDemo { * Entry point for the shaded shapes demo. * @param args command line arguments (ignored) */ - public static void main(String[] args) { - ViewFrame viewFrame = new ViewFrame(); - ViewPanel viewPanel = viewFrame.getViewPanel(); - ShapeCollection shapes = viewPanel.getRootShapeCollection(); + public static void main(final String[] args) { + final ViewFrame viewFrame = new ViewFrame(); + final ViewPanel viewPanel = viewFrame.getViewPanel(); + final ShapeCollection shapes = viewPanel.getRootShapeCollection(); - LightingManager lightingManager = new LightingManager(); - lightingManager.setAmbientLight(new Color(25, 25, 25)); + // Use the global lighting manager from ViewPanel + viewPanel.getLightingManager().setAmbientLight(new Color(25, 25, 25)); - Random random = new Random(42); - List orbitingLights = new ArrayList<>(); + final Random random = new Random(42); + final List orbitingLights = new ArrayList<>(); for (int i = 0; i < LIGHT_COUNT; i++) { - Color color = new Color( + final Color color = new Color( random.nextInt(256), random.nextInt(256), random.nextInt(256) ); - double orbitRadius = 500 + random.nextInt(200); - double speed = 0.01 + random.nextDouble() * 0.03; - double angleOffset = random.nextDouble() * Math.PI * 2; - double intensity = 2.0 + random.nextDouble() * 3.0; + final double orbitRadius = 500 + random.nextInt(200); + final double speed = 0.01 + random.nextDouble() * 0.03; + final double angleOffset = random.nextDouble() * Math.PI * 2; + final double intensity = 2.0 + random.nextDouble() * 3.0; - int axis = random.nextInt(3); - double ellipseFactor = 0.7 + random.nextDouble() * 0.3; + final int axis = random.nextInt(3); + final double ellipseFactor = 0.7 + random.nextDouble() * 0.3; - LightSource light = new LightSource(new Point3D(0, 0, 0), color, intensity); - LightSourceMarker marker = new LightSourceMarker(light.getPosition(), color); + final LightSource light = new LightSource(new Point3D(0, 0, 0), color, intensity); + final LightSourceMarker marker = new LightSourceMarker(light.getPosition(), color); - lightingManager.addLight(light); + viewPanel.getLightingManager().addLight(light); shapes.addShape(marker); orbitingLights.add(new OrbitingLight( @@ -75,37 +74,37 @@ public class ShadedShapesDemo { } // Sphere - SolidPolygonSphere sphere = new SolidPolygonSphere( + final SolidPolygonSphere sphere = new SolidPolygonSphere( new Point3D(-400, 0, 0), 150, 28, new Color(200, 210, 255) ); - sphere.setLightingManager(lightingManager); + sphere.setShadingEnabled(true); shapes.addShape(sphere); // Pyramid - SolidPolygonPyramid pyramid = new SolidPolygonPyramid( + final SolidPolygonPyramid pyramid = new SolidPolygonPyramid( new Point3D(0, 130, 0), 150, 260, new Color(255, 200, 200) ); - pyramid.setLightingManager(lightingManager); + pyramid.setShadingEnabled(true); shapes.addShape(pyramid); // Cube - SolidPolygonCube cube = new SolidPolygonCube( + final SolidPolygonCube cube = new SolidPolygonCube( new Point3D(400, 0, 0), 150, new Color(200, 255, 200) ); - cube.setLightingManager(lightingManager); + cube.setShadingEnabled(true); shapes.addShape(cube); // Cylinder - SolidPolygonCylinder cylinder = new SolidPolygonCylinder( + final SolidPolygonCylinder cylinder = new SolidPolygonCylinder( new Point3D(800, 0, 0), 120, 260, 24, new Color(255, 220, 180) ); - cylinder.setLightingManager(lightingManager); + cylinder.setShadingEnabled(true); shapes.addShape(cylinder); // Camera viewPanel.getCamera().getTransform().setTranslation(new Point3D(200, -250, -900)); - MultiLightAnimator animator = new MultiLightAnimator(orbitingLights); + final MultiLightAnimator animator = new MultiLightAnimator(orbitingLights); viewPanel.addFrameListener(animator); viewPanel.repaintDuringNextViewUpdate(); 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 49640dd..773fe43 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/TextEditorDemo.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/TextEditorDemo.java @@ -96,6 +96,6 @@ public class TextEditorDemo { private static void setCameraLocation(ViewPanel viewPanel) { Camera camera = viewPanel.getCamera(); camera.getTransform().setTranslation(new Point3D(500, -300, -800)); - camera.getTransform().getRotation().setQuaternion(Quaternion.fromAngles(0.6, -0.5)); + camera.getTransform().getRotation().set(Quaternion.fromAngles(0.6, -0.5)); } } 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 0539fb4..75a4413 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/TextEditorDemo2.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/TextEditorDemo2.java @@ -194,6 +194,6 @@ public class TextEditorDemo2 { private static void setCameraLocation(ViewPanel viewPanel) { Camera camera = viewPanel.getCamera(); camera.getTransform().setTranslation(new Point3D(500, -300, -800)); - camera.getTransform().getRotation().setQuaternion(Quaternion.fromAngles(0.6, -0.5)); + camera.getTransform().getRotation().set(Quaternion.fromAngles(0.6, -0.5)); } } diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/LitSolidCubesTest.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/LitSolidCubesTest.java index c45246c..9fd491e 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/LitSolidCubesTest.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/LitSolidCubesTest.java @@ -19,6 +19,8 @@ import java.util.ArrayList; import java.util.List; import java.util.Random; +import static eu.svjatoslav.sixth.e3d.math.Transform.*; + /** * Benchmark test for solid opaque cubes with dynamic lighting. * Renders a grid of fully opaque cubes lit by three orbiting light sources @@ -34,7 +36,6 @@ public class LitSolidCubesTest implements BenchmarkTest { private final Random random = new Random(RANDOM_SEED); private final List cubes = new ArrayList<>(); private final List orbitingLights = new ArrayList<>(); - private LightingManager lightingManager; private FrameListener animator; private ViewPanel viewPanel; @@ -44,54 +45,51 @@ public class LitSolidCubesTest implements BenchmarkTest { } @Override - public void setup(ShapeCollection shapes) { + public void setup(final ShapeCollection shapes) { random.setSeed(RANDOM_SEED); - lightingManager = new LightingManager(); - lightingManager.setAmbientLight(new Color(15, 15, 20)); - - Color[] lightColors = { + // Note: lights will be added when setViewPanel is called + final Color[] lightColors = { new Color(255, 100, 100), new Color(100, 255, 100), new Color(100, 100, 255) }; for (int i = 0; i < 3; i++) { - double angleOffset = i * (Math.PI * 2 / 3); - LightSource light = new LightSource( + final double angleOffset = i * (Math.PI * 2 / 3); + final LightSource light = new LightSource( new Point3D(0, 0, 0), lightColors[i], 2.5 ); - lightingManager.addLight(light); - LightSourceMarker marker = new LightSourceMarker(light.getPosition(), lightColors[i]); + final LightSourceMarker marker = new LightSourceMarker(light.getPosition(), lightColors[i]); shapes.addShape(marker); orbitingLights.add(new OrbitingLight(light, marker, 600, 0.002, angleOffset, i)); } - double offset = -(GRID_SIZE - 1) * SPACING / 2; + final double offset = -(GRID_SIZE - 1) * SPACING / 2; for (int x = 0; x < GRID_SIZE; x++) { for (int y = 0; y < GRID_SIZE; y++) { for (int z = 0; z < GRID_SIZE; z++) { - double px = offset + x * SPACING; - double py = offset + y * SPACING; - double pz = offset + z * SPACING; + final double px = offset + x * SPACING; + final double py = offset + y * SPACING; + final double pz = offset + z * SPACING; - Color color = new Color( + final Color color = new Color( 150 + random.nextInt(105), 150 + random.nextInt(105), 150 + random.nextInt(105) ); - SolidPolygonCube cube = new SolidPolygonCube( + final SolidPolygonCube cube = new SolidPolygonCube( new Point3D(px, py, pz), CUBE_SIZE, color ); - cube.setLightingManager(lightingManager); + cube.setShadingEnabled(true); shapes.addShape(cube); cubes.add(cube); } @@ -100,14 +98,18 @@ public class LitSolidCubesTest implements BenchmarkTest { } @Override - public void teardown(ShapeCollection shapes) { - for (SolidPolygonCube cube : cubes) { + public void teardown(final ShapeCollection shapes) { + for (final SolidPolygonCube cube : cubes) { shapes.getShapes().remove(cube); } cubes.clear(); - for (OrbitingLight ol : orbitingLights) { + for (final OrbitingLight ol : orbitingLights) { shapes.getShapes().remove(ol.marker); + // Remove light from global lighting manager + if (viewPanel != null) { + viewPanel.getLightingManager().removeLight(ol.light); + } } orbitingLights.clear(); @@ -116,16 +118,22 @@ public class LitSolidCubesTest implements BenchmarkTest { } viewPanel = null; animator = null; - lightingManager = null; } /** * Sets the view panel for animation callbacks. * @param viewPanel the view panel */ - public void setViewPanel(ViewPanel viewPanel) { + public void setViewPanel(final ViewPanel viewPanel) { this.viewPanel = viewPanel; if (viewPanel != null) { + // Configure global lighting + LightingManager lightingManager = viewPanel.getLightingManager(); + lightingManager.setAmbientLight(new Color(15, 15, 20)); + for (final OrbitingLight ol : orbitingLights) { + lightingManager.addLight(ol.light); + } + animator = new LightAnimator(); viewPanel.addFrameListener(animator); } @@ -139,8 +147,8 @@ public class LitSolidCubesTest implements BenchmarkTest { final int axisIndex; double angle; - OrbitingLight(LightSource light, LightSourceMarker marker, - double orbitRadius, double speed, double angleOffset, int axisIndex) { + OrbitingLight(final LightSource light, final LightSourceMarker marker, + final double orbitRadius, final double speed, final double angleOffset, final int axisIndex) { this.light = light; this.marker = marker; this.orbitRadius = orbitRadius; @@ -152,8 +160,8 @@ public class LitSolidCubesTest implements BenchmarkTest { private class LightAnimator implements FrameListener { @Override - public boolean onFrame(ViewPanel vp, int millisecondsSinceLastFrame) { - for (OrbitingLight ol : orbitingLights) { + public boolean onFrame(final ViewPanel vp, final int millisecondsSinceLastFrame) { + for (final OrbitingLight ol : orbitingLights) { ol.angle += ol.speed * millisecondsSinceLastFrame; double x, y, z; @@ -175,9 +183,9 @@ public class LitSolidCubesTest implements BenchmarkTest { break; } - Point3D newPos = new Point3D(x, y, z); + final Point3D newPos = new Point3D(x, y, z); ol.light.setPosition(newPos); - ol.marker.setTransform(eu.svjatoslav.sixth.e3d.math.Transform.fromAngles(newPos, 0, 0)); + ol.marker.setTransform(fromAngles(newPos, 0, 0)); } return true; } diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/diamondsquare_demo/DiamondSquareLandscape.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/diamondsquare_demo/DiamondSquareLandscape.java new file mode 100644 index 0000000..2328e4d --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/diamondsquare_demo/DiamondSquareLandscape.java @@ -0,0 +1,73 @@ +/* + * 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 DiamondSquareLandscape { + + /** + * Entry point for the diamond-square landscape demo. + * @param args command line arguments (ignored) + */ + public static void main(final String[] args) { + final ViewFrame viewFrame = new ViewFrame(); + ViewPanel viewPanel = viewFrame.getViewPanel(); + + final ShapeCollection shapes = viewPanel.getRootShapeCollection(); + + setLights(viewFrame, shapes); + + shapes.addShape(new DiamondSquareTerrain(129, 0.0, 300.0, 42)); + + viewPanel.getCamera().getTransform() + .setTranslation(new Point3D(0, -600, -800)); + + viewPanel.ensureRenderThreadStarted(); + } + + // TODO: add javadoc here + 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/diamondsquare_demo/DiamondSquareTerrain.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/diamondsquare_demo/DiamondSquareTerrain.java new file mode 100644 index 0000000..32bd138 --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/diamondsquare_demo/DiamondSquareTerrain.java @@ -0,0 +1,76 @@ +/* + * 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/launcher/ApplicationListPanel.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/ApplicationListPanel.java index 7aa936d..7e978aa 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,6 +6,7 @@ package eu.svjatoslav.sixth.e3d.examples.launcher; +import eu.svjatoslav.sixth.e3d.examples.diamondsquare_demo.DiamondSquareLandscape; import eu.svjatoslav.sixth.e3d.examples.SineHeightmap; import eu.svjatoslav.sixth.e3d.examples.graph_demo.MathGraphsDemo; import eu.svjatoslav.sixth.e3d.examples.MinimalExample; @@ -67,6 +68,9 @@ class ApplicationListPanel extends JPanel { new DemoEntry("Shaded Shapes", "Shapes lit by orbiting colored light sources", new ShowShadedShapes()), + new DemoEntry("Diamond-Square Landscape", + "Procedural mountains with 3 colored light sources", + new ShowDiamondSquareLandscape()), new DemoEntry("Graphics Benchmark", "Automated performance measuring FPS across modes", new ShowGraphicsBenchmark()), @@ -232,6 +236,17 @@ class ApplicationListPanel extends JPanel { } } + private static class ShowDiamondSquareLandscape extends AbstractAction { + ShowDiamondSquareLandscape() { + putValue(NAME, "Diamond-Square Landscape"); + } + + @Override + public void actionPerformed(final ActionEvent e) { + DiamondSquareLandscape.main(null); + } + } + private static class ShowGraphicsBenchmark extends AbstractAction { ShowGraphicsBenchmark() { putValue(NAME, "Graphics Benchmark"); 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 0c1b4b6..eaa2d28 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 @@ -131,7 +131,7 @@ public class Main extends WorldNavigationUserInputTracker { */ private void setCameraOrientation(final Camera camera) { camera.getTransform().setTranslation(new Point3D(100, -50, -200)); - camera.getTransform().getRotation().setQuaternion(Quaternion.fromAngles(0.2f, -0.7f)); + camera.getTransform().getRotation().set(Quaternion.fromAngles(0.2f, -0.7f)); } }