From 7fadfa9e211ed1216efba34a86118b8be34b1c8f Mon Sep 17 00:00:00 2001 From: Svjatoslav Agejenko Date: Sat, 28 Mar 2026 14:50:34 +0200 Subject: [PATCH] feat(demos): add CSG and axis demos, reorganize into essentials subpackage Add two new demonstration apps: - CSGDemo: showcases boolean operations (subtract, union, intersect) on 3D shapes using the BSP-based CSG system from sixth-3d - AxisArrowsDemo: displays coordinate system axes with colored arrows (X=red, Y=green, Z=blue) and text labels Reorganize demo structure: - Move basic demos (MinimalExample, WindingOrderDemo, ShapeGalleryDemo) to examples.essentials subpackage for cleaner organization - Rename ShadedShapesDemo to ShapeGalleryDemo for clarity - Remove RandomPolygonsDemo (redundant with other polygon demos) Update launcher with two-tier layout: "Essentials" section for core demos, "Demos" section for advanced applications. Simplify arrow constructors in ArrowDemo and ShapeGalleryDemo using auto-calculated tip dimensions. --- AGENTS.md | 14 +- .../sixth/e3d/examples/ArrowDemo.java | 50 +--- .../sixth/e3d/examples/OctreeDemo.java | 71 +++--- .../e3d/examples/RandomPolygonsDemo.java | 110 -------- .../examples/essentials/AxisArrowsDemo.java | 148 +++++++++++ .../e3d/examples/essentials/CSGDemo.java | 236 ++++++++++++++++++ .../{ => essentials}/MinimalExample.java | 5 +- .../ShapeGalleryDemo.java} | 40 ++- .../{ => essentials}/WindingOrderDemo.java | 2 +- .../launcher/ApplicationListPanel.java | 231 +++++++++++------ .../sixth/e3d/examples/life_demo/Main.java | 72 +++++- .../sixth/e3d/examples/life_demo/Matrix.java | 28 ++- .../examples/terrain_demo/TerrainDemo.java | 2 +- 13 files changed, 703 insertions(+), 306 deletions(-) delete mode 100755 src/main/java/eu/svjatoslav/sixth/e3d/examples/RandomPolygonsDemo.java create mode 100644 src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/AxisArrowsDemo.java create mode 100644 src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/CSGDemo.java rename src/main/java/eu/svjatoslav/sixth/e3d/examples/{ => essentials}/MinimalExample.java (92%) rename src/main/java/eu/svjatoslav/sixth/e3d/examples/{ShadedShapesDemo.java => essentials/ShapeGalleryDemo.java} (87%) rename src/main/java/eu/svjatoslav/sixth/e3d/examples/{ => essentials}/WindingOrderDemo.java (97%) diff --git a/AGENTS.md b/AGENTS.md index e6360a7..d5e1345 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -34,15 +34,6 @@ mvn javadoc:javadoc ```bash # Run the launcher java -cp target/sixth-3d-demos.jar eu.svjatoslav.sixth.e3d.examples.launcher.Main - -# Run Game of Life demo -java -cp target/sixth-3d-demos.jar eu.svjatoslav.sixth.e3d.examples.life_demo.Main - -# Run Point Cloud Galaxy demo -java -cp target/sixth-3d-demos.jar eu.svjatoslav.sixth.e3d.examples.galaxy_demo.PointCloudDemo - -# Run Winding Order demo (tests backface culling) -java -cp target/sixth-3d-demos.jar eu.svjatoslav.sixth.e3d.examples.WindingOrderDemo ``` ### Testing @@ -70,7 +61,9 @@ src/main/java/eu/svjatoslav/sixth/e3d/examples/ │ └── PointCloudDemo.java ├── RandomPolygonsDemo.java ├── OctreeDemo.java -├── GraphDemo.java +├── graph_demo/ - 3D graph visualization demos +│ ├── MathGraphsDemo.java +│ └── SurfaceGraph3D.java ├── TextEditorDemo.java ├── TextEditorDemo2.java ├── RainingNumbersDemo.java @@ -127,6 +120,7 @@ Implement `MouseInteractionController` for mouse events, or extend input tracker ### Polygon Winding Order When creating triangles with backface culling enabled, use CCW winding in screen space: + - Vertex order: top → lower-left → lower-right (as seen from camera) - `signedArea < 0` = front-facing = visible - See `WindingOrderDemo.java` for a minimal example diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/ArrowDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/ArrowDemo.java index d457318..a04c237 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/ArrowDemo.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/ArrowDemo.java @@ -52,11 +52,7 @@ public class ArrowDemo { final SolidPolygonArrow redArrow = new SolidPolygonArrow( new Point3D(0, 150, 0), new Point3D(0, -150, 0), - 8, // body radius - 20, // tip radius - 40, // tip length - 16, // segments - Color.RED + 8, Color.RED ); shapes.addShape(redArrow); @@ -64,11 +60,7 @@ public class ArrowDemo { final SolidPolygonArrow greenArrow = new SolidPolygonArrow( new Point3D(-200, 0, 0), new Point3D(0, 0, 0), - 6, - 15, - 30, - 12, - Color.GREEN + 6, Color.GREEN ); shapes.addShape(greenArrow); @@ -76,11 +68,7 @@ public class ArrowDemo { final SolidPolygonArrow blueArrow = new SolidPolygonArrow( new Point3D(0, 0, -200), new Point3D(0, 0, 0), - 6, - 15, - 30, - 12, - Color.BLUE + 6, Color.BLUE ); shapes.addShape(blueArrow); @@ -88,11 +76,7 @@ public class ArrowDemo { final SolidPolygonArrow yellowArrow = new SolidPolygonArrow( new Point3D(100, 100, 100), new Point3D(300, -100, 300), - 10, - 25, - 50, - 16, - Color.YELLOW + 10, Color.YELLOW ); shapes.addShape(yellowArrow); @@ -100,11 +84,7 @@ public class ArrowDemo { final SolidPolygonArrow transparentCyanArrow = new SolidPolygonArrow( new Point3D(-150, 50, -100), new Point3D(-50, -100, 100), - 8, - 20, - 40, - 12, - new Color(0, 255, 255, 128) + 8, new Color(0, 255, 255, 128) ); shapes.addShape(transparentCyanArrow); @@ -112,11 +92,7 @@ public class ArrowDemo { final SolidPolygonArrow transparentMagentaArrow = new SolidPolygonArrow( new Point3D(50, 200, 50), new Point3D(50, 0, 50), - 12, - 30, - 60, - 20, - new Color(255, 0, 255, 64) + 12, new Color(255, 0, 255, 64) ); shapes.addShape(transparentMagentaArrow); @@ -129,15 +105,11 @@ public class ArrowDemo { final double endX = (radius + 80) * Math.cos(angle); final double endZ = (radius + 80) * Math.sin(angle); - final SolidPolygonArrow circleArrow = new SolidPolygonArrow( - new Point3D(startX, -80, startZ), - new Point3D(endX, -80, endZ), - 4, - 10, - 20, - 8, - Color.WHITE - ); +final SolidPolygonArrow circleArrow = new SolidPolygonArrow( + new Point3D(startX, -80, startZ), + new Point3D(endX, -80, endZ), + 4, Color.WHITE + ); shapes.addShape(circleArrow); } diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/OctreeDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/OctreeDemo.java index ead6346..aedc08c 100755 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/OctreeDemo.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/OctreeDemo.java @@ -1,8 +1,8 @@ /* - * Sixth 3D engine demos. Author: Svjatoslav Agejenko. + * Sixth 3D engine demos. Author: Svjatoslav Agejenko. * This project is released under Creative Commons Zero (CC0) license. * -*/ + */ package eu.svjatoslav.sixth.e3d.examples; @@ -13,8 +13,8 @@ import eu.svjatoslav.sixth.e3d.gui.humaninput.WorldNavigationUserInputTracker; import eu.svjatoslav.sixth.e3d.math.Transform; import eu.svjatoslav.sixth.e3d.renderer.octree.IntegerPoint; import eu.svjatoslav.sixth.e3d.renderer.octree.OctreeVolume; -import eu.svjatoslav.sixth.e3d.renderer.octree.raytracer.RaytracingCamera; import eu.svjatoslav.sixth.e3d.renderer.octree.raytracer.RayTracer; +import eu.svjatoslav.sixth.e3d.renderer.octree.raytracer.RaytracingCamera; import eu.svjatoslav.sixth.e3d.renderer.raster.Color; import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection; import eu.svjatoslav.sixth.e3d.renderer.raster.lighting.LightSource; @@ -36,12 +36,8 @@ import java.util.Vector; public class OctreeDemo extends WorldNavigationUserInputTracker { /** - * Creates a new OctreeDemo instance. + * Scale factor for rendering octree voxels in the scene. */ - public OctreeDemo() { - } - - /** Scale factor for rendering octree voxels in the scene. */ private static final double magnification = 5; private final LineAppearance gridAppearance = new LineAppearance(40, new Color(255, 0, 0, 60)); @@ -49,9 +45,15 @@ public class OctreeDemo extends WorldNavigationUserInputTracker { private OctreeVolume octreeVolume; private ShapeCollection shapeCollection; private ViewPanel viewPanel; + /** + * Creates a new OctreeDemo instance. + */ + public OctreeDemo() { + } /** * Entry point for the octree demo. + * * @param args command line arguments (ignored) */ public static void main(final String[] args) { @@ -60,12 +62,13 @@ public class OctreeDemo extends WorldNavigationUserInputTracker { /** * Adds a light source to both the raytracer and rasterizer lighting systems. - * @param location position in octree space (will be scaled by magnification for display) - * @param color color of the light + * + * @param location position in octree space (will be scaled by magnification for display) + * @param color color of the light * @param brightness intensity of the light */ private void addLightToBothSystems(final Point3D location, final Color color, - final float brightness) { + final float brightness) { shapeCollection.addShape(new LightSourceMarker(new Point3D(location) .scaleUp(magnification), color)); @@ -76,7 +79,9 @@ public class OctreeDemo extends WorldNavigationUserInputTracker { new Point3D(location).scaleUp(magnification), color, brightness * 50)); } - /** Creates a colorful spiral pattern of voxels in the octree. */ + /** + * Creates a colorful spiral pattern of voxels in the octree. + */ private void dotSpiral() { for (double i = 0; i < 20; i = i + .1) { @@ -96,9 +101,10 @@ public class OctreeDemo extends WorldNavigationUserInputTracker { /** * Recursively creates a fractal pattern of rectangles. - * @param x center X coordinate - * @param y center Y coordinate - * @param z center Z coordinate + * + * @param x center X coordinate + * @param y center Y coordinate + * @param z center Z coordinate * @param size size of the current rectangle * @param step recursion depth counter */ @@ -109,8 +115,8 @@ public class OctreeDemo extends WorldNavigationUserInputTracker { final double c3 = (Math.cos(z / 12f) * 100f) + 127; putRect( - new IntegerPoint( x - size, y - size, z - size), - new IntegerPoint( x + size, y + size, z + size), + new IntegerPoint(x - size, y - size, z - size), + new IntegerPoint(x + size, y + size, z + size), new Color((int) c1, (int) c2, (int) c3, 200)); if (size > 1) { @@ -120,10 +126,12 @@ public class OctreeDemo extends WorldNavigationUserInputTracker { } } - /** Initializes the demo scene with grid, lights, shapes, and fractal pattern. */ + /** + * Initializes the demo scene with grid, lights, shapes, and fractal pattern. + */ private void init() { - final ViewFrame viewFrame = new ViewFrame("Volumetric Octree"); + final ViewFrame viewFrame = new ViewFrame("Volumetric octree"); viewPanel = viewFrame.getViewPanel(); viewPanel.getCamera().getTransform().set(104.13, -65.04, -370.53, 0.12, 0.14, 0); @@ -149,7 +157,7 @@ public class OctreeDemo extends WorldNavigationUserInputTracker { new Color(200, 255, 200, 100)); putRect(new IntegerPoint(-3, 0, -30), - new IntegerPoint( 12, 3, 300), + new IntegerPoint(12, 3, 300), new Color(255, 200, 200, 100)); putRect(new IntegerPoint(-20, 20, -20), @@ -171,7 +179,8 @@ public class OctreeDemo extends WorldNavigationUserInputTracker { /** * Handles keyboard input for triggering raytracing. - * @param event the key event + * + * @param event the key event * @param viewPanel the view panel * @return true if the event was consumed */ @@ -187,9 +196,10 @@ public class OctreeDemo extends WorldNavigationUserInputTracker { /** * Places a single voxel at the specified position with the given color. - * @param x X coordinate in octree space - * @param y Y coordinate in octree space - * @param z Z coordinate in octree space + * + * @param x X coordinate in octree space + * @param y Y coordinate in octree space + * @param z Z coordinate in octree space * @param color the color of the voxel */ private void putPixel(final int x, final int y, final int z, @@ -202,8 +212,9 @@ public class OctreeDemo extends WorldNavigationUserInputTracker { /** * Fills a rectangular region in the octree with the specified color. - * @param p1 first corner of the rectangle - * @param p2 opposite corner of the rectangle + * + * @param p1 first corner of the rectangle + * @param p2 opposite corner of the rectangle * @param color the color to fill */ private void putRect(IntegerPoint p1, IntegerPoint p2, final Color color) { @@ -215,7 +226,9 @@ public class OctreeDemo extends WorldNavigationUserInputTracker { octreeVolume.fillRectangle(p1, p2, color); } - /** Starts a raytracing render of the current view in a background thread. */ + /** + * Starts a raytracing render of the current view in a background thread. + */ private void raytrace() { // create and add camera object to scene final RaytracingCamera raytracingCamera = new RaytracingCamera(viewPanel.getCamera(), magnification); @@ -228,7 +241,9 @@ public class OctreeDemo extends WorldNavigationUserInputTracker { thread.start(); } - /** Creates a tiled floor pattern using small rectangular tiles. */ + /** + * Creates a tiled floor pattern using small rectangular tiles. + */ private void tiledFloor() { final int step = 40; final int size = step - 15; diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/RandomPolygonsDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/RandomPolygonsDemo.java deleted file mode 100755 index 8b7d7d5..0000000 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/RandomPolygonsDemo.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Sixth 3D engine demos. Author: Svjatoslav Agejenko. - * This project is released under Creative Commons Zero (CC0) license. - * - */ - -package eu.svjatoslav.sixth.e3d.examples; - -import eu.svjatoslav.sixth.e3d.geometry.Point3D; -import eu.svjatoslav.sixth.e3d.gui.ViewFrame; -import eu.svjatoslav.sixth.e3d.renderer.raster.Color; -import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection; -import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.line.LineAppearance; -import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.solidpolygon.SolidPolygon; -import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.Grid3D; - -/** - * Demo showing 1000 randomly positioned and colored triangles in 3D space. - * Demonstrates the engine's ability to render many semi-transparent polygons - * with proper depth sorting. - */ -public class RandomPolygonsDemo { - - /** - * Creates a new RandomPolygonsDemo instance. - */ - public RandomPolygonsDemo() { - } - - /** Average size of each random polygon. */ - private static final double POLYGON_AVERAGE_SIZE = 130; - /** Number of polygons to generate. */ - private static final int POLYGON_COUNT = 1000; - - /** - * Adds a single random triangle to the scene. - * @param geometryCollection the collection to add the polygon to - */ - private static void addRandomPolygon(final ShapeCollection geometryCollection) { - final Point3D polygonLocation = getRandomPoint(1000); - - final Point3D point1 = new Point3D(polygonLocation); - point1.add(getRandomPoint(POLYGON_AVERAGE_SIZE)); - - final Point3D point2 = new Point3D(polygonLocation); - point2.add(getRandomPoint(POLYGON_AVERAGE_SIZE)); - - final Point3D point3 = new Point3D(polygonLocation); - point3.add(getRandomPoint(POLYGON_AVERAGE_SIZE)); - - final Color color = new Color( - getColorChannelBrightness(), - getColorChannelBrightness(), - getColorChannelBrightness(), - 1); - - final SolidPolygon polygon = new SolidPolygon(point1, point2, point3, - color); - geometryCollection.addShape(polygon); - } - - /** - * Generates a random color channel brightness. - * Ensures minimum brightness of 0.3 to avoid very dark polygons. - * @return a brightness value between 0.3 and 1.0 - */ - private static double getColorChannelBrightness() { - return Math.random() * 0.7 + 0.3f; - } - - /** - * Generates a random 3D point within a cube centered at the origin. - * @param amplitude the half-size of the cube - * @return a random point within [-amplitude, amplitude] on each axis - */ - private static Point3D getRandomPoint(final double amplitude) { - return new Point3D((Math.random() * amplitude * 2d) - amplitude, - (Math.random() * amplitude * 2d) - amplitude, (Math.random() - * amplitude * 2d) - - amplitude); - } - - /** - * Entry point for the random polygons demo. - * @param args command line arguments (ignored) - */ - public static void main(final String[] args) { - - final ViewFrame viewFrame = new ViewFrame("Random Polygons"); - - viewFrame.getViewPanel().getCamera().getTransform().set(-52.96, -239.61, -1293.29, -0.09, -0.36, 0); - - final ShapeCollection shapeCollection = viewFrame.getViewPanel() - .getRootShapeCollection(); - - // add grid - final LineAppearance appearance = new LineAppearance(5, new Color(100, - 100, 255, 60)); - - shapeCollection.addShape(new Grid3D(new Point3D(1000, -1000, -1000), - new Point3D(-1000, 1000, 1000), 300, appearance)); - - // add random polygons - for (int i = 0; i < POLYGON_COUNT; i++) - addRandomPolygon(shapeCollection); - - viewFrame.getViewPanel().repaintDuringNextViewUpdate(); - - } -} diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/AxisArrowsDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/AxisArrowsDemo.java new file mode 100644 index 0000000..8bcd0a0 --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/AxisArrowsDemo.java @@ -0,0 +1,148 @@ +/* + * Sixth 3D engine demos. Author: Svjatoslav Agejenko. + * This project is released under Creative Commons Zero (CC0) license. + */ +package eu.svjatoslav.sixth.e3d.examples.essentials; + +import eu.svjatoslav.sixth.e3d.geometry.Point3D; +import eu.svjatoslav.sixth.e3d.gui.ViewFrame; +import eu.svjatoslav.sixth.e3d.gui.ViewPanel; +import eu.svjatoslav.sixth.e3d.renderer.raster.Color; +import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection; +import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.line.LineAppearance; +import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.ForwardOrientedTextBlock; +import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonArrow; +import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.Grid3D; + +/** + * Demo displaying coordinate system axes using 3D arrows. + * + *

This demo illustrates the Sixth 3D coordinate system with three colored + * arrows originating from the origin (0,0,0), each pointing along a positive + * axis direction:

+ * + * + * + *

A reference grid at Y=0 provides spatial context. Text labels identify + * each axis.

+ * + * @see SolidPolygonArrow + * @see ForwardOrientedTextBlock + */ +public class AxisArrowsDemo { + + /** + * Length of each axis arrow. + */ + private static final double ARROW_LENGTH = 200; + + /** + * Radius of the arrow body (cylindrical shaft). + */ + private static final double BODY_RADIUS = 6; + + /** + * Distance from arrow tip to text label position. + */ + private static final double LABEL_OFFSET = 20; + + /** + * Scale factor for text labels. + */ + private static final double LABEL_SCALE = 20.0; + + /** + * Entry point for the axis arrows demo. + * + * @param args command line arguments (ignored) + */ + public static void main(final String[] args) { + final ViewFrame viewFrame = new ViewFrame("Axis Arrows Demo"); + final ViewPanel viewPanel = viewFrame.getViewPanel(); + final ShapeCollection shapes = viewPanel.getRootShapeCollection(); + + // Position camera to view all three axes + viewPanel.getCamera().getTransform().set(130.66, -65.49, -248.18, -0.06, -0.36, -0.00); + + // Create reference grid at Y=0 plane + createReferenceGrid(shapes); + + // Create axis arrows + createAxisArrows(shapes); + + viewPanel.repaintDuringNextViewUpdate(); + } + + /** + * Creates a reference grid on the Y=0 plane. + * + * @param shapes the shape collection to add the grid to + */ + private static void createReferenceGrid(final ShapeCollection shapes) { + final LineAppearance gridAppearance = new LineAppearance( + 1, new Color(80, 80, 100)); + final Grid3D grid = new Grid3D( + new Point3D(-300, 0, -300), + new Point3D(300, 0, 300), + 50, + gridAppearance + ); + shapes.addShape(grid); + } + + /** + * Creates the three axis arrows and their labels. + * + * @param shapes the shape collection to add the arrows to + */ + private static void createAxisArrows(final ShapeCollection shapes) { + // X-axis arrow (RED) - points in positive X direction (RIGHT) + final SolidPolygonArrow xArrow = new SolidPolygonArrow( + new Point3D(0, 0, 0), + new Point3D(ARROW_LENGTH, 0, 0), + BODY_RADIUS, new Color(255, 0, 0, 180) + ); + shapes.addShape(xArrow); + createLabel(shapes, "X", new Point3D(ARROW_LENGTH + LABEL_OFFSET, 0, 0), Color.RED); + + // Y-axis arrow (GREEN) - points in positive Y direction (DOWN) + final SolidPolygonArrow yArrow = new SolidPolygonArrow( + new Point3D(0, 0, 0), + new Point3D(0, ARROW_LENGTH, 0), + BODY_RADIUS, new Color(0, 255, 0, 180) + ); + shapes.addShape(yArrow); + createLabel(shapes, "Y", new Point3D(0, ARROW_LENGTH + LABEL_OFFSET, 0), Color.GREEN); + + // Z-axis arrow (BLUE) - points in positive Z direction (AWAY) + final SolidPolygonArrow zArrow = new SolidPolygonArrow( + new Point3D(0, 0, 0), + new Point3D(0, 0, ARROW_LENGTH), + BODY_RADIUS, new Color(0, 0, 255, 180) + ); + shapes.addShape(zArrow); + createLabel(shapes, "Z", new Point3D(0, 0, ARROW_LENGTH + LABEL_OFFSET), Color.BLUE); + } + + /** + * Creates a text label at the specified position. + * + * @param shapes the shape collection to add the label to + * @param text the label text + * @param position the 3D position of the label + * @param color the text color + */ + private static void createLabel(final ShapeCollection shapes, + final String text, + final Point3D position, + final Color color) { + final ForwardOrientedTextBlock label = new ForwardOrientedTextBlock( + position, LABEL_SCALE, 2, text, color + ); + shapes.addShape(label); + } +} \ No newline at end of file diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/CSGDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/CSGDemo.java new file mode 100644 index 0000000..b8d2cda --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/CSGDemo.java @@ -0,0 +1,236 @@ +/* + * Sixth 3D engine demos. Author: Svjatoslav Agejenko. + * This project is released under Creative Commons Zero (CC0) license. + * + */ +package eu.svjatoslav.sixth.e3d.examples.essentials; + +import eu.svjatoslav.sixth.e3d.csg.CSG; +import eu.svjatoslav.sixth.e3d.geometry.Point3D; +import eu.svjatoslav.sixth.e3d.gui.ViewFrame; +import eu.svjatoslav.sixth.e3d.gui.ViewPanel; +import eu.svjatoslav.sixth.e3d.renderer.raster.Color; +import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection; +import eu.svjatoslav.sixth.e3d.renderer.raster.lighting.LightSource; +import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.ForwardOrientedTextBlock; +import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonCube; +import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonMesh; +import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonSphere; + +/** + * Demo showcasing Constructive Solid Geometry (CSG) boolean operations. + * + *

This demo displays three CSG operations side by side:

+ * + * + *

Shapes are rendered with slight transparency (85% opacity) to allow + * seeing internal structure. The demo demonstrates how existing composite + * shapes like {@link SolidPolygonCube} and {@link SolidPolygonSphere} can + * be used with CSG operations.

+ * + *

Run this demo:

+ *
{@code
+ * java -cp target/sixth-3d-demos.jar eu.svjatoslav.sixth.e3d.examples.CSGDemo
+ * }
+ * + * @see CSG the CSG solid class + * @see SolidPolygonMesh the renderable mesh + */ +public class CSGDemo { + + /** + * Distance between shapes on the X axis. + */ + private static final double SPACING = 500; + + /** + * Size of the cube (half-edge length). + */ + private static final double CUBE_SIZE = 80; + + /** + * Radius of the sphere (slightly larger for interesting intersection). + */ + private static final double SPHERE_RADIUS = 96; + + /** + * Number of segments for the sphere (smoothness). + */ + private static final int SPHERE_SEGMENTS = 12; + + /** + * Entry point for the CSG demo. + * + * @param args command line arguments (ignored) + */ + public static void main(final String[] args) { + final ViewFrame viewFrame = new ViewFrame("CSG Demo - Boolean Operations"); + final ViewPanel viewPanel = viewFrame.getViewPanel(); + final ShapeCollection shapes = viewPanel.getRootShapeCollection(); + + // Position camera to view all three shapes + viewPanel.getCamera().getTransform().set(0, -150, -600, 0, 0, 0); + + // Set up lighting + viewPanel.getLightingManager().setAmbientLight(new Color(60, 60, 70)); + + // Create lights + createLights(viewPanel, shapes); + + // Create the three CSG demonstrations + createSubtractDemo(shapes, -SPACING, 0, 0); + createUnionDemo(shapes, 0, 0, 0); + createIntersectDemo(shapes, SPACING, 0, 0); + + // Add labels + createLabels(shapes); + + viewPanel.repaintDuringNextViewUpdate(); + } + + /** + * Creates the subtract operation demo: cube - sphere. + * Results in a cube with a spherical cavity. + * + * @param shapes the shape collection + * @param x the X position + * @param y the Y position + * @param z the Z position + */ + private static void createSubtractDemo(final ShapeCollection shapes, + final double x, final double y, final double z) { + final SolidPolygonCube cube = new SolidPolygonCube( + new Point3D(0, 0, 0), CUBE_SIZE, Color.WHITE); + final SolidPolygonSphere sphere = new SolidPolygonSphere( + new Point3D(0, 0, 0), SPHERE_RADIUS, SPHERE_SEGMENTS, Color.WHITE); + + final CSG cubeCSG = CSG.fromCompositeShape(cube); + final CSG sphereCSG = CSG.fromCompositeShape(sphere); + + final CSG result = cubeCSG.subtract(sphereCSG); + + final SolidPolygonMesh mesh = result.toMesh(new Color(255, 100, 100, 217), new Point3D(x, y, z)); + mesh.setShadingEnabled(true); + mesh.setBackfaceCulling(true); + + shapes.addShape(mesh); + + System.out.println("Subtract (cube - sphere): " + mesh.getTriangleCount() + " triangles"); + } + + /** + * Creates the union operation demo: cube + sphere. + * Results in a combined shape. + * + * @param shapes the shape collection + * @param x the X position + * @param y the Y position + * @param z the Z position + */ + private static void createUnionDemo(final ShapeCollection shapes, + final double x, final double y, final double z) { + final SolidPolygonCube cube = new SolidPolygonCube( + new Point3D(0, 0, 0), CUBE_SIZE, Color.WHITE); + final SolidPolygonSphere sphere = new SolidPolygonSphere( + new Point3D(0, 0, 0), SPHERE_RADIUS, SPHERE_SEGMENTS, Color.WHITE); + + final CSG cubeCSG = CSG.fromCompositeShape(cube); + final CSG sphereCSG = CSG.fromCompositeShape(sphere); + + final CSG result = cubeCSG.union(sphereCSG); + + final SolidPolygonMesh mesh = result.toMesh(new Color(100, 255, 100, 217), new Point3D(x, y, z)); + mesh.setShadingEnabled(true); + mesh.setBackfaceCulling(true); + + shapes.addShape(mesh); + + System.out.println("Union (cube + sphere): " + mesh.getTriangleCount() + " triangles"); + } + + /** + * Creates the intersect operation demo: cube ∩ sphere. + * Results in the volume shared by both shapes. + * + * @param shapes the shape collection + * @param x the X position + * @param y the Y position + * @param z the Z position + */ + private static void createIntersectDemo(final ShapeCollection shapes, + final double x, final double y, final double z) { + final SolidPolygonCube cube = new SolidPolygonCube( + new Point3D(0, 0, 0), CUBE_SIZE, Color.WHITE); + final SolidPolygonSphere sphere = new SolidPolygonSphere( + new Point3D(0, 0, 0), SPHERE_RADIUS, SPHERE_SEGMENTS, Color.WHITE); + + final CSG cubeCSG = CSG.fromCompositeShape(cube); + final CSG sphereCSG = CSG.fromCompositeShape(sphere); + + final CSG result = cubeCSG.intersect(sphereCSG); + + final SolidPolygonMesh mesh = result.toMesh(new Color(100, 150, 255, 217), new Point3D(x, y, z)); + mesh.setShadingEnabled(true); + mesh.setBackfaceCulling(true); + + shapes.addShape(mesh); + + System.out.println("Intersect (cube ∩ sphere): " + mesh.getTriangleCount() + " triangles"); + } + + /** + * Creates labels for each operation. + * + * @param shapes the shape collection + */ + private static void createLabels(final ShapeCollection shapes) { + final double labelY = -150; + final double labelZ = 0; + + // Subtract label + shapes.addShape(new ForwardOrientedTextBlock( + new Point3D(-SPACING, labelY, labelZ), + 8.0, 2, "Subtract", Color.RED + )); + + // Union label + shapes.addShape(new ForwardOrientedTextBlock( + new Point3D(0, labelY, labelZ), + 8.0, 2, "Union", Color.GREEN + )); + + // Intersect label + shapes.addShape(new ForwardOrientedTextBlock( + new Point3D(SPACING, labelY, labelZ), + 8.0, 2, "Intersect", Color.BLUE + )); + } + + /** + * Creates lighting for the scene. + * + * @param viewPanel the view panel + * @param shapes the shape collection + */ + private static void createLights(final ViewPanel viewPanel, final ShapeCollection shapes) { + // Main light from above-front + final LightSource mainLight = new LightSource( + new Point3D(0, -300, -400), + new Color(255, 255, 255), + 1.5 + ); + viewPanel.getLightingManager().addLight(mainLight); + + // Fill light from the side + final LightSource fillLight = new LightSource( + new Point3D(500, 100, -200), + new Color(150, 150, 200), + 0.8 + ); + viewPanel.getLightingManager().addLight(fillLight); + } +} \ No newline at end of file diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/MinimalExample.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/MinimalExample.java similarity index 92% rename from src/main/java/eu/svjatoslav/sixth/e3d/examples/MinimalExample.java rename to src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/MinimalExample.java index 86e43ce..b526eb3 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/MinimalExample.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/MinimalExample.java @@ -3,7 +3,7 @@ * This project is released under Creative Commons Zero (CC0) license. * */ -package eu.svjatoslav.sixth.e3d.examples; +package eu.svjatoslav.sixth.e3d.examples.essentials; import eu.svjatoslav.sixth.e3d.geometry.Point3D; import eu.svjatoslav.sixth.e3d.gui.ViewFrame; @@ -22,10 +22,11 @@ public class MinimalExample { /** * Entry point for the minimal scene demo. + * * @param args command line arguments (ignored) */ public static void main(String[] args) { - ViewFrame viewFrame = new ViewFrame("Minimal Example"); + 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/ShadedShapesDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/ShapeGalleryDemo.java similarity index 87% rename from src/main/java/eu/svjatoslav/sixth/e3d/examples/ShadedShapesDemo.java rename to src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/ShapeGalleryDemo.java index ccdc182..28e6337 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/ShadedShapesDemo.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/ShapeGalleryDemo.java @@ -1,4 +1,4 @@ -package eu.svjatoslav.sixth.e3d.examples; +package eu.svjatoslav.sixth.e3d.examples.essentials; import eu.svjatoslav.sixth.e3d.geometry.Point3D; import eu.svjatoslav.sixth.e3d.gui.FrameListener; @@ -24,18 +24,7 @@ import static eu.svjatoslav.sixth.e3d.math.Transform.fromAngles; * *

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 │  ╔══╗  │  /\\   │ ╔══╗   │  ║║║   │  /\\   │ ╔══╗   │  ()   │
- *         │  ╚══╝  │ /  \\  │ ╚══╝   │  ║║║   │ /__\\  │ ╚══╝   │ (  )  │
- *         └────────┴────────┴────────┴────────┴────────┴────────┴────────┘
- * 
+ * row shows wireframe shapes. Each column represents a different shape type.

* *

Features:

* */ -public class ShadedShapesDemo { +public class ShapeGalleryDemo { /** * Number of columns (shape types). @@ -168,6 +157,7 @@ public class ShadedShapesDemo { // Row 1: Wireframe shapes final Point3D wireframePos = new Point3D(x, 0, CELL_SIZE); createWireframeShape(shapes, col, wireframePos, color); + createLabel(shapes, wireframePos, "Wireframe " + name, color); } } @@ -194,9 +184,9 @@ public class ShadedShapesDemo { 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); + new Point3D(pos.x, SHAPE_RADIUS, pos.z), + new Point3D(pos.x, -SHAPE_RADIUS, pos.z), + 8, color); arrow.setShadingEnabled(true); shapes.addShape(arrow); break; @@ -236,8 +226,8 @@ public class ShadedShapesDemo { 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), + new Point3D(pos.x - SHAPE_RADIUS * 0.35, pos.y - SHAPE_RADIUS, pos.z - SHAPE_RADIUS * 0.35), + new Point3D(pos.x + SHAPE_RADIUS * 0.35, pos.y + SHAPE_RADIUS, pos.z + SHAPE_RADIUS * 0.35), color); box.setShadingEnabled(true); shapes.addShape(box); @@ -269,9 +259,9 @@ public class ShadedShapesDemo { 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); + new Point3D(pos.x, SHAPE_RADIUS, pos.z), + new Point3D(pos.x, -SHAPE_RADIUS, pos.z), + 8, appearance); shapes.addShape(arrow); break; @@ -306,8 +296,8 @@ public class ShadedShapesDemo { 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), + new Point3D(pos.x - SHAPE_RADIUS * 0.35, pos.y - SHAPE_RADIUS, pos.z - SHAPE_RADIUS * 0.35), + new Point3D(pos.x + SHAPE_RADIUS * 0.35, pos.y + SHAPE_RADIUS, pos.z + SHAPE_RADIUS * 0.35), appearance); shapes.addShape(box); break; @@ -334,7 +324,7 @@ public class ShadedShapesDemo { 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); + labelPos, 8.0, 2, text, color); shapes.addShape(label); } diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/WindingOrderDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/WindingOrderDemo.java similarity index 97% rename from src/main/java/eu/svjatoslav/sixth/e3d/examples/WindingOrderDemo.java rename to src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/WindingOrderDemo.java index df022df..250fc62 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/WindingOrderDemo.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/WindingOrderDemo.java @@ -3,7 +3,7 @@ * This project is released under Creative Commons Zero (CC0) license. * */ -package eu.svjatoslav.sixth.e3d.examples; +package eu.svjatoslav.sixth.e3d.examples.essentials; import eu.svjatoslav.sixth.e3d.geometry.Point3D; import eu.svjatoslav.sixth.e3d.gui.ViewFrame; 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 dabb69b..86cda8a 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 @@ -1,79 +1,91 @@ /* - * Sixth 3D engine demos. Author: Svjatoslav Agejenko. + * Sixth 3D engine demos. Author: Svjatoslav Agejenko. * This project is released under Creative Commons Zero (CC0) license. * */ package eu.svjatoslav.sixth.e3d.examples.launcher; -import eu.svjatoslav.sixth.e3d.examples.terrain_demo.TerrainDemo; -import eu.svjatoslav.sixth.e3d.examples.SineHeightmap; -import eu.svjatoslav.sixth.e3d.examples.graph_demo.MathGraphsDemo; -import eu.svjatoslav.sixth.e3d.examples.MinimalExample; import eu.svjatoslav.sixth.e3d.examples.OctreeDemo; -import eu.svjatoslav.sixth.e3d.examples.RandomPolygonsDemo; import eu.svjatoslav.sixth.e3d.examples.RainingNumbersDemo; -import eu.svjatoslav.sixth.e3d.examples.ShadedShapesDemo; +import eu.svjatoslav.sixth.e3d.examples.SineHeightmap; import eu.svjatoslav.sixth.e3d.examples.TextEditorDemo; import eu.svjatoslav.sixth.e3d.examples.TextEditorDemo2; +import eu.svjatoslav.sixth.e3d.examples.essentials.AxisArrowsDemo; +import eu.svjatoslav.sixth.e3d.examples.essentials.CSGDemo; +import eu.svjatoslav.sixth.e3d.examples.essentials.MinimalExample; +import eu.svjatoslav.sixth.e3d.examples.essentials.ShapeGalleryDemo; +import eu.svjatoslav.sixth.e3d.examples.essentials.WindingOrderDemo; import eu.svjatoslav.sixth.e3d.examples.benchmark.GraphicsBenchmark; import eu.svjatoslav.sixth.e3d.examples.galaxy_demo.PointCloudDemo; +import eu.svjatoslav.sixth.e3d.examples.graph_demo.MathGraphsDemo; +import eu.svjatoslav.sixth.e3d.examples.terrain_demo.TerrainDemo; import javax.swing.*; +import javax.swing.border.EmptyBorder; +import java.awt.*; import java.awt.event.ActionEvent; /** * Panel containing buttons to launch each demo application. - * Displays a vertical list with button and description for each demo. + * Displays demos organized into sections with header labels. */ class ApplicationListPanel extends JPanel { private static final long serialVersionUID = 2012721856427052560L; private static final int BUTTON_WIDTH = 160; private static final int GAP = 12; + private static final int SECTION_TOP_MARGIN = 16; + + private static final DemoEntry[] ESSENTIALS = { + new DemoEntry("Minimal example", + "Minimal example showing a single red box", + new ShowMinimalExample()), + new DemoEntry("Winding order", + "Triangle demo for winding order & backface culling", + new ShowWindingOrder()), + new DemoEntry("Axis arrows", + "Coordinate system axes: X (red), Y (green), Z (blue)", + new ShowAxisArrows()), + new DemoEntry("Shape gallery", + "All 3D shapes with orbiting colored light sources", + new ShowShadedShapes()), + new DemoEntry("CSG Demo", + "Boolean operations: union, subtract, intersect on 3D shapes", + new ShowCSG()), + }; - private record DemoEntry(String name, String description, AbstractAction action) {} - - private static final DemoEntry[] DEMOS = { - new DemoEntry("Minimal Example", - "Minimal example showing a single red box", - new ShowMinimalExample()), - new DemoEntry("Volumetric Octree", - "Octree-based rendering with on-demand raytracing", - new ShowOctree()), - new DemoEntry("Sine heightmap", - "Two wobbly sine wave surfaces with central sphere", - new ShowSineHeightmap()), - new DemoEntry("Math graphs demo", - "Function graphs (sin, cos, tan) rendered in 3D", - new ShowMathGraphs()), - new DemoEntry("Point cloud galaxy", - "Spiral galaxy with 10,000 glowing points", - new ShowPointCloud()), - new DemoEntry("Raining numbers", - "Numbers falling through 3D space like rain", - new ShowRain()), - new DemoEntry("Text editors", - "5x5 grid of 3D text editor components", - new ShowTextEditors()), - new DemoEntry("Text editors city", - "3D city of text editor panels as buildings", - new ShowTextEditors2()), - new DemoEntry("Game of Life", - "Conway's Game of Life with 3D visualization", - new ShowGameOfLife()), - new DemoEntry("Random polygons", - "1000 semi-transparent triangles with depth sorting", - new ShowRandomPolygons()), - new DemoEntry("Shaded Shapes", - "Shapes lit by orbiting colored light sources", - new ShowShadedShapes()), - new DemoEntry("Procedural Terrain", - "Procedural mountains with 3 colored light sources", - new ShowTerrainDemo()), - new DemoEntry("Graphics Benchmark", - "Automated performance measuring FPS across modes", - new ShowGraphicsBenchmark()), + private static final DemoEntry[] OTHER_DEMOS = { + new DemoEntry("Volumetric Octree", + "Octree-based rendering with on-demand raytracing", + new ShowOctree()), + new DemoEntry("Sine heightmap", + "Two wobbly sine wave surfaces with central sphere", + new ShowSineHeightmap()), + new DemoEntry("Math graphs demo", + "Function graphs (sin, cos, tan) rendered in 3D", + new ShowMathGraphs()), + new DemoEntry("Point cloud galaxy", + "Spiral galaxy with 10,000 glowing points", + new ShowPointCloud()), + new DemoEntry("Raining numbers", + "Numbers falling through 3D space like rain", + new ShowRain()), + new DemoEntry("Text editors", + "5x5 grid of 3D text editor components", + new ShowTextEditors()), + new DemoEntry("Text editors city", + "3D city of text editor panels as buildings", + new ShowTextEditors2()), + new DemoEntry("Game of Life", + "Conway's Game of Life with 3D visualization", + new ShowGameOfLife()), + new DemoEntry("Procedural Terrain", + "Procedural mountains with 3 colored light sources", + new ShowTerrainDemo()), + new DemoEntry("Graphics benchmark", + "Automated performance measuring FPS across modes", + new ShowGraphicsBenchmark()), }; ApplicationListPanel() { @@ -87,18 +99,66 @@ class ApplicationListPanel extends JPanel { final GroupLayout.ParallelGroup horizontalButtonGroup = layout.createParallelGroup(GroupLayout.Alignment.LEADING); final GroupLayout.ParallelGroup horizontalDescGroup = layout.createParallelGroup(GroupLayout.Alignment.LEADING); - final JLabel headerLabel = new JLabel("Choose an example to launch:"); - headerLabel.setFont(headerLabel.getFont().deriveFont(java.awt.Font.BOLD)); + addSectionHeader("Essentials", verticalGroup, horizontalButtonGroup, horizontalDescGroup, layout, false); + addDemoEntries(ESSENTIALS, verticalGroup, horizontalButtonGroup, horizontalDescGroup, layout); + + addSectionHeader("Demos", verticalGroup, horizontalButtonGroup, horizontalDescGroup, layout, true); + addDemoEntries(OTHER_DEMOS, verticalGroup, horizontalButtonGroup, horizontalDescGroup, layout); + + layout.setVerticalGroup(verticalGroup); + layout.setHorizontalGroup(layout.createSequentialGroup() + .addGroup(horizontalButtonGroup) + .addGap(GAP) + .addGroup(horizontalDescGroup)); + } + + /** + * Adds a section header label. + * + * @param title the section title + * @param verticalGroup the vertical layout group + * @param horizontalButtonGroup the horizontal group for buttons + * @param horizontalDescGroup the horizontal group for descriptions + * @param layout the GroupLayout + * @param addTopMargin whether to add extra top margin for visual separation + */ + private void addSectionHeader(final String title, + final GroupLayout.SequentialGroup verticalGroup, + final GroupLayout.ParallelGroup horizontalButtonGroup, + final GroupLayout.ParallelGroup horizontalDescGroup, + final GroupLayout layout, + final boolean addTopMargin) { + final JLabel headerLabel = new JLabel(title); + headerLabel.setFont(headerLabel.getFont().deriveFont(Font.BOLD, 14f)); + if (addTopMargin) { + headerLabel.setBorder(new EmptyBorder(SECTION_TOP_MARGIN, 0, 0, 0)); + } + verticalGroup.addComponent(headerLabel); horizontalButtonGroup.addComponent(headerLabel); horizontalDescGroup.addComponent(headerLabel); + } - for (final DemoEntry entry : DEMOS) { + /** + * Adds demo entry buttons to the layout. + * + * @param entries the demo entries to add + * @param verticalGroup the vertical layout group + * @param horizontalButtonGroup the horizontal group for buttons + * @param horizontalDescGroup the horizontal group for descriptions + * @param layout the GroupLayout + */ + private void addDemoEntries(final DemoEntry[] entries, + final GroupLayout.SequentialGroup verticalGroup, + final GroupLayout.ParallelGroup horizontalButtonGroup, + final GroupLayout.ParallelGroup horizontalDescGroup, + final GroupLayout layout) { + for (final DemoEntry entry : entries) { final JButton button = new JButton(entry.action()); - button.setPreferredSize(new java.awt.Dimension(BUTTON_WIDTH, button.getPreferredSize().height)); + button.setPreferredSize(new Dimension(BUTTON_WIDTH, button.getPreferredSize().height)); final JLabel descLabel = new JLabel(entry.description()); - descLabel.setFont(descLabel.getFont().deriveFont(java.awt.Font.PLAIN)); + descLabel.setFont(descLabel.getFont().deriveFont(Font.PLAIN)); verticalGroup.addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(button) @@ -107,12 +167,9 @@ class ApplicationListPanel extends JPanel { horizontalButtonGroup.addComponent(button); horizontalDescGroup.addComponent(descLabel); } + } - layout.setVerticalGroup(verticalGroup); - layout.setHorizontalGroup(layout.createSequentialGroup() - .addGroup(horizontalButtonGroup) - .addGap(GAP) - .addGroup(horizontalDescGroup)); + private record DemoEntry(String name, String description, AbstractAction action) { } private static class ShowMinimalExample extends AbstractAction { @@ -126,6 +183,17 @@ class ApplicationListPanel extends JPanel { } } + private static class ShowWindingOrder extends AbstractAction { + ShowWindingOrder() { + putValue(NAME, "Winding Order"); + } + + @Override + public void actionPerformed(final ActionEvent e) { + WindingOrderDemo.main(null); + } + } + private static class ShowTextEditors extends AbstractAction { ShowTextEditors() { putValue(NAME, "Text editors"); @@ -192,20 +260,9 @@ class ApplicationListPanel extends JPanel { } } - private static class ShowRandomPolygons extends AbstractAction { - ShowRandomPolygons() { - putValue(NAME, "Random polygons"); - } - - @Override - public void actionPerformed(final ActionEvent e) { - RandomPolygonsDemo.main(null); - } - } - private static class ShowOctree extends AbstractAction { ShowOctree() { - putValue(NAME, "Volumetric Octree"); + putValue(NAME, "Volumetric octree"); } @Override @@ -227,18 +284,29 @@ class ApplicationListPanel extends JPanel { private static class ShowShadedShapes extends AbstractAction { ShowShadedShapes() { - putValue(NAME, "Shaded Shapes"); + putValue(NAME, "Shape gallery"); + } + + @Override + public void actionPerformed(final ActionEvent e) { + ShapeGalleryDemo.main(null); + } + } + + private static class ShowAxisArrows extends AbstractAction { + ShowAxisArrows() { + putValue(NAME, "Axis arrows"); } @Override public void actionPerformed(final ActionEvent e) { - ShadedShapesDemo.main(null); + AxisArrowsDemo.main(null); } } private static class ShowTerrainDemo extends AbstractAction { ShowTerrainDemo() { - putValue(NAME, "Procedural Terrain"); + putValue(NAME, "Procedural terrain"); } @Override @@ -249,7 +317,7 @@ class ApplicationListPanel extends JPanel { private static class ShowGraphicsBenchmark extends AbstractAction { ShowGraphicsBenchmark() { - putValue(NAME, "Graphics Benchmark"); + putValue(NAME, "Graphics benchmark"); } @Override @@ -257,4 +325,15 @@ class ApplicationListPanel extends JPanel { GraphicsBenchmark.main(null); } } + + private static class ShowCSG extends AbstractAction { + ShowCSG() { + putValue(NAME, "CSG Demo"); + } + + @Override + public void actionPerformed(final ActionEvent e) { + CSGDemo.main(null); + } + } } \ No newline at end of file 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 0666abe..0e77299 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 @@ -2,6 +2,7 @@ package eu.svjatoslav.sixth.e3d.examples.life_demo; import eu.svjatoslav.sixth.e3d.geometry.Point3D; import eu.svjatoslav.sixth.e3d.geometry.Rectangle; +import eu.svjatoslav.sixth.e3d.gui.TextPointer; import eu.svjatoslav.sixth.e3d.gui.ViewFrame; import eu.svjatoslav.sixth.e3d.gui.ViewPanel; import eu.svjatoslav.sixth.e3d.gui.humaninput.WorldNavigationUserInputTracker; @@ -9,6 +10,7 @@ import eu.svjatoslav.sixth.e3d.math.Transform; import eu.svjatoslav.sixth.e3d.renderer.raster.Color; import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection; import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.line.LineAppearance; +import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.textcanvas.TextCanvas; import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.Grid2D; import java.awt.event.KeyEvent; @@ -18,7 +20,7 @@ import java.awt.event.KeyEvent; * Main entry point for Conway's Game of Life 3D demo. * Creates a 30x30 cell matrix where cells evolve based on Conway's rules. * The user can interact with cells by clicking to toggle their state. - * + * *

Key controls: *