From: Svjatoslav Agejenko Date: Sat, 28 Mar 2026 12:50:34 +0000 (+0200) Subject: feat(demos): add CSG and axis demos, reorganize into essentials subpackage X-Git-Url: http://www2.svjatoslav.eu/gitweb/?a=commitdiff_plain;h=7fadfa9e211ed1216efba34a86118b8be34b1c8f;p=sixth-3d-demos.git 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. --- 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/MinimalExample.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/MinimalExample.java deleted file mode 100644 index 86e43ce..0000000 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/MinimalExample.java +++ /dev/null @@ -1,44 +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.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.composite.solid.SolidPolygonRectangularBox; - -/** - * Minimal example demonstrating how to create a basic 3D scene. - *

- * Creates a window with a single red box. This is the "Create Your First 3D Scene" - * example from the Sixth 3D documentation. - */ -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"); - ShapeCollection shapes = viewFrame.getViewPanel().getRootShapeCollection(); - - viewFrame.getViewPanel().getCamera().getTransform().setTranslation(new Point3D(0, -100, -300)); - - Transform boxTransform = Transform.fromAngles(0, 0, 0, 0, 0, 0); - SolidPolygonRectangularBox box = new SolidPolygonRectangularBox( - new Point3D(-50, -50, -50), - new Point3D(50, 50, 50), - Color.RED - ); - box.setTransform(boxTransform); - shapes.addShape(box); - - viewFrame.getViewPanel().repaintDuringNextViewUpdate(); - } -} \ No newline at end of file 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/ShadedShapesDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/ShadedShapesDemo.java deleted file mode 100644 index ccdc182..0000000 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/ShadedShapesDemo.java +++ /dev/null @@ -1,449 +0,0 @@ -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.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.*; -import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.*; - -import java.util.ArrayList; -import java.util.List; -import java.util.Random; - -import static eu.svjatoslav.sixth.e3d.math.Transform.fromAngles; - -/** - * 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 { - - /** - * Number of columns (shape types). - */ - private static final int COLUMN_COUNT = 7; - - /** - * Size of each cell in world units. - */ - private static final double CELL_SIZE = 250; - - /** - * 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("3D Shape Gallery"); - final ViewPanel viewPanel = viewFrame.getViewPanel(); - final ShapeCollection shapes = viewPanel.getRootShapeCollection(); - - // 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; - - 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<>(); - - for (int i = 0; i < LIGHT_COUNT; i++) { - final Color color = new Color( - random.nextInt(256), - random.nextInt(256), - random.nextInt(256) - ); - - 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() * 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, CELL_SIZE / 2), color, intensity); - final LightSourceMarker marker = new LightSourceMarker(light.getPosition(), color); - - viewPanel.getLightingManager().addLight(light); - shapes.addShape(marker); - - orbitingLights.add(new OrbitingLight( - light, marker, - orbitRadius, speed, angleOffset, axis, ellipseFactor - )); - } - - final MultiLightAnimator animator = new MultiLightAnimator(orbitingLights); - viewPanel.addFrameListener(animator); - } - - /** - * Represents a light source that orbits around the scene center. - */ - private static class OrbitingLight { - final LightSource light; - final LightSourceMarker marker; - final double orbitRadius; - final double speed; - final int axis; - final double ellipseFactor; - double angle; - - 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; - this.speed = speed; - this.angle = angleOffset; - this.axis = axis; - this.ellipseFactor = ellipseFactor; - } - } - - /** - * Frame listener that animates all orbiting lights. - */ - private record MultiLightAnimator(List lights) implements FrameListener { - - @Override - 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; - - final double r = orbitingLight.orbitRadius; - final double e = orbitingLight.ellipseFactor; - - double x, y, z; - switch (orbitingLight.axis) { - case 0: - x = r * Math.cos(orbitingLight.angle); - y = r * e * Math.sin(orbitingLight.angle); - z = r * 0.5 * Math.sin(orbitingLight.angle * 2); - break; - case 1: - x = r * e * Math.sin(orbitingLight.angle * 2); - y = r * Math.cos(orbitingLight.angle); - z = r * 0.5 * Math.sin(orbitingLight.angle); - break; - default: - x = r * 0.5 * Math.sin(orbitingLight.angle); - y = r * Math.sin(orbitingLight.angle * 2); - z = r * e * Math.cos(orbitingLight.angle); - break; - } - - 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)); - } - return true; - } - } -} \ No newline at end of file diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/WindingOrderDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/WindingOrderDemo.java deleted file mode 100644 index df022df..0000000 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/WindingOrderDemo.java +++ /dev/null @@ -1,58 +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.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.solidpolygon.SolidPolygon; - -/** - * Demo to test winding order and backface culling documentation. - *

- * Creates one triangle with CCW winding (front face) following the docs: - *

    - *
  • upper-center → lower-left → lower-right
  • - *
  • Backface culling enabled
  • - *
- *

- * Expected: green triangle visible (CCW = front face). - */ -public class WindingOrderDemo { - - /** - * Creates a new WindingOrderDemo instance. - */ - public WindingOrderDemo() { - } - - /** - * Entry point for the winding order demo. - * @param args command line arguments (ignored) - */ - public static void main(String[] args) { - ViewFrame viewFrame = new ViewFrame("Winding Order Demo"); - ViewPanel viewPanel = viewFrame.getViewPanel(); - ShapeCollection shapes = viewPanel.getRootShapeCollection(); - - viewPanel.getCamera().getTransform().setTranslation(new Point3D(0, 0, -500)); - - double size = 150; - - Point3D upperCenter = new Point3D(0, -size, 0); - Point3D lowerLeft = new Point3D(-size, +size, 0); - Point3D lowerRight = new Point3D(+size, +size, 0); - - SolidPolygon triangle = new SolidPolygon(upperCenter, lowerLeft, lowerRight, Color.GREEN); - triangle.setBackfaceCulling(true); - - shapes.addShape(triangle); - - viewPanel.repaintDuringNextViewUpdate(); - } -} \ No newline at end of file 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:

+ * + *
    + *
  • Red arrow — +X axis (points RIGHT)
  • + *
  • Green arrow — +Y axis (points DOWN visually)
  • + *
  • Blue arrow — +Z axis (points AWAY from viewer)
  • + *
+ * + *

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:

+ *
    + *
  • Subtract: A cube with a spherical cavity carved out
  • + *
  • Union: A cube merged with a sphere
  • + *
  • Intersect: The volume shared by a cube and sphere
  • + *
+ * + *

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/essentials/MinimalExample.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/MinimalExample.java new file mode 100644 index 0000000..b526eb3 --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/MinimalExample.java @@ -0,0 +1,45 @@ +/* + * 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.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.composite.solid.SolidPolygonRectangularBox; + +/** + * Minimal example demonstrating how to create a basic 3D scene. + *

+ * Creates a window with a single red box. This is the "Create Your First 3D Scene" + * example from the Sixth 3D documentation. + */ +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"); + ShapeCollection shapes = viewFrame.getViewPanel().getRootShapeCollection(); + + viewFrame.getViewPanel().getCamera().getTransform().setTranslation(new Point3D(0, -100, -300)); + + Transform boxTransform = Transform.fromAngles(0, 0, 0, 0, 0, 0); + SolidPolygonRectangularBox box = new SolidPolygonRectangularBox( + new Point3D(-50, -50, -50), + new Point3D(50, 50, 50), + Color.RED + ); + box.setTransform(boxTransform); + shapes.addShape(box); + + viewFrame.getViewPanel().repaintDuringNextViewUpdate(); + } +} \ No newline at end of file diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/ShapeGalleryDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/ShapeGalleryDemo.java new file mode 100644 index 0000000..28e6337 --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/ShapeGalleryDemo.java @@ -0,0 +1,439 @@ +package eu.svjatoslav.sixth.e3d.examples.essentials; + +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.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.*; +import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import static eu.svjatoslav.sixth.e3d.math.Transform.fromAngles; + +/** + * 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.

+ * + *

Features:

+ *
    + *
  • Orbiting colored lights for dynamic lighting demonstration
  • + *
  • Floor grid showing the layout structure
  • + *
  • Text labels identifying each shape type
  • + *
+ */ +public class ShapeGalleryDemo { + + /** + * Number of columns (shape types). + */ + private static final int COLUMN_COUNT = 7; + + /** + * Size of each cell in world units. + */ + private static final double CELL_SIZE = 250; + + /** + * 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("3D Shape Gallery"); + final ViewPanel viewPanel = viewFrame.getViewPanel(); + final ShapeCollection shapes = viewPanel.getRootShapeCollection(); + + // 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); + createLabel(shapes, wireframePos, "Wireframe " + name, 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, pos.z), + new Point3D(pos.x, -SHAPE_RADIUS, pos.z), + 8, 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.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); + 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, pos.z), + new Point3D(pos.x, -SHAPE_RADIUS, pos.z), + 8, appearance); + shapes.addShape(arrow); + break; + + 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.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; + + 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, 8.0, 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<>(); + + for (int i = 0; i < LIGHT_COUNT; i++) { + final Color color = new Color( + random.nextInt(256), + random.nextInt(256), + random.nextInt(256) + ); + + 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() * 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, CELL_SIZE / 2), color, intensity); + final LightSourceMarker marker = new LightSourceMarker(light.getPosition(), color); + + viewPanel.getLightingManager().addLight(light); + shapes.addShape(marker); + + orbitingLights.add(new OrbitingLight( + light, marker, + orbitRadius, speed, angleOffset, axis, ellipseFactor + )); + } + + final MultiLightAnimator animator = new MultiLightAnimator(orbitingLights); + viewPanel.addFrameListener(animator); + } + + /** + * Represents a light source that orbits around the scene center. + */ + private static class OrbitingLight { + final LightSource light; + final LightSourceMarker marker; + final double orbitRadius; + final double speed; + final int axis; + final double ellipseFactor; + double angle; + + 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; + this.speed = speed; + this.angle = angleOffset; + this.axis = axis; + this.ellipseFactor = ellipseFactor; + } + } + + /** + * Frame listener that animates all orbiting lights. + */ + private record MultiLightAnimator(List lights) implements FrameListener { + + @Override + 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; + + final double r = orbitingLight.orbitRadius; + final double e = orbitingLight.ellipseFactor; + + double x, y, z; + switch (orbitingLight.axis) { + case 0: + x = r * Math.cos(orbitingLight.angle); + y = r * e * Math.sin(orbitingLight.angle); + z = r * 0.5 * Math.sin(orbitingLight.angle * 2); + break; + case 1: + x = r * e * Math.sin(orbitingLight.angle * 2); + y = r * Math.cos(orbitingLight.angle); + z = r * 0.5 * Math.sin(orbitingLight.angle); + break; + default: + x = r * 0.5 * Math.sin(orbitingLight.angle); + y = r * Math.sin(orbitingLight.angle * 2); + z = r * e * Math.cos(orbitingLight.angle); + break; + } + + 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)); + } + return true; + } + } +} \ No newline at end of file diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/WindingOrderDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/WindingOrderDemo.java new file mode 100644 index 0000000..250fc62 --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/WindingOrderDemo.java @@ -0,0 +1,58 @@ +/* + * 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.solidpolygon.SolidPolygon; + +/** + * Demo to test winding order and backface culling documentation. + *

+ * Creates one triangle with CCW winding (front face) following the docs: + *

    + *
  • upper-center → lower-left → lower-right
  • + *
  • Backface culling enabled
  • + *
+ *

+ * Expected: green triangle visible (CCW = front face). + */ +public class WindingOrderDemo { + + /** + * Creates a new WindingOrderDemo instance. + */ + public WindingOrderDemo() { + } + + /** + * Entry point for the winding order demo. + * @param args command line arguments (ignored) + */ + public static void main(String[] args) { + ViewFrame viewFrame = new ViewFrame("Winding Order Demo"); + ViewPanel viewPanel = viewFrame.getViewPanel(); + ShapeCollection shapes = viewPanel.getRootShapeCollection(); + + viewPanel.getCamera().getTransform().setTranslation(new Point3D(0, 0, -500)); + + double size = 150; + + Point3D upperCenter = new Point3D(0, -size, 0); + Point3D lowerLeft = new Point3D(-size, +size, 0); + Point3D lowerRight = new Point3D(+size, +size, 0); + + SolidPolygon triangle = new SolidPolygon(upperCenter, lowerLeft, lowerRight, Color.GREEN); + triangle.setBackfaceCulling(true); + + shapes.addShape(triangle); + + viewPanel.repaintDuringNextViewUpdate(); + } +} \ 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 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: *

    *
  • Space - Evolve one generation
  • @@ -29,18 +31,21 @@ import java.awt.event.KeyEvent; public class Main extends WorldNavigationUserInputTracker { /** - * Creates a new Main instance for the Game of Life demo. + * The game of life matrix, centered at the origin. */ - public Main() { - } - - /** The game of life matrix, centered at the origin. */ private static final Matrix MATRIX = new Matrix( new Point3D() // position matrix in the center of the scene ); + /** + * Creates a new Main instance for the Game of Life demo. + */ + public Main() { + } + /** * Entry point for the Game of Life demo. + * * @param args command line arguments (ignored) */ public static void main(final String[] args) { @@ -68,20 +73,25 @@ public class Main extends WorldNavigationUserInputTracker { return true; } - /** Initializes and displays the Game of Life demo. */ + /** + * Initializes and displays the Game of Life demo. + */ private void run() { // create application frame visible to the user 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); + viewPanel.getCamera().getTransform().set(855.83, -174.40, -31.46, 1.58, -0.02, -0.00); final ShapeCollection shapeCollection = viewPanel.getRootShapeCollection(); // add matrix shapeCollection.addShape(MATRIX); + // add help panel explaining rules and controls + shapeCollection.addShape(createHelpPanel()); + // add wire-frame grid (optional) shapeCollection.addShape(createGrid()); @@ -94,6 +104,7 @@ public class Main extends WorldNavigationUserInputTracker { /** * Creates a pink wire-frame grid below the matrix for decorative purposes. + * * @return a Grid2D positioned below the game matrix */ private Grid2D createGrid() { @@ -110,4 +121,49 @@ public class Main extends WorldNavigationUserInputTracker { ); } + /** + * Creates a help panel displaying game rules and controls. + * The panel is positioned to the right of the matrix and faces toward the viewer. + * + * @return a TextCanvas with help information + */ + private TextCanvas createHelpPanel() { + final String helpText = + "=== CONWAY'S GAME OF LIFE ===\n" + + "\n" + + "RULES:\n" + + "- Live cell with 2-3 neighbors survives\n" + + "- Dead cell with 3 neighbors becomes alive\n" + + "- All other cells die or stay dead\n" + + "\n" + + "KEYBOARD:\n" + + "Space - Evolve one generation\n" + + "Enter - Evolve with history trail\n" + + "C - Clear the matrix\n" + + "Arrows - Move camera\n" + + "\n" + + "MOUSE:\n" + + "Click - Toggle cell state\n" + + "Hover - Highlight cell"; + + final Transform location = Transform.fromAngles( + new Point3D(500, -80, 0), + -Math.PI / 2, + 0 + ); + + final TextPointer dimensions = new TextPointer(16, 45); + + final TextCanvas panel = new TextCanvas( + location, + dimensions, + Color.WHITE, + new Color(0, 0, 80, 200) + ); + + panel.setText(helpText); + + return panel; + } + } diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Matrix.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Matrix.java index 44fedcc..4626ae6 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Matrix.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Matrix.java @@ -8,29 +8,40 @@ import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.SubShape; * Represents the 2D game board for Conway's Game of Life. * Contains a 30x30 grid of cells that can evolve according to Conway's rules. * Supports optional history tracking where stars mark previously active cells. - * + * * @see Cell * @see Star */ class Matrix extends AbstractCompositeShape { - /** Empty space between adjacent cells. */ + /** + * Empty space between adjacent cells. + */ private static final int BORDER = 5; - /** Number of cells along each dimension (30x30 grid). */ + /** + * Number of cells along each dimension (30x30 grid). + */ private static final int SIZE = 30; - /** Group ID for history tracking stars. */ + /** + * Group ID for history-tracking stars. + */ private static final String GROUP_STARS = "stars"; - /** Group ID for the cell surface. */ + /** + * Group ID for the cell surface. + */ private static final String GROUP_SURFACE = "surface"; - /** 2D array of cells forming the game board. */ + /** + * 2D array of cells forming the game board. + */ private final Cell[][] cells = new Cell[SIZE][]; /** * Constructs a game matrix at the specified location. + * * @param location the center position of the matrix in 3D space */ public Matrix(final Point3D location) { @@ -84,6 +95,7 @@ class Matrix extends AbstractCompositeShape { /** * Processes a single cell to determine if it survives to the next generation. + * * @param x the X coordinate of the cell * @param y the Y coordinate of the cell */ @@ -99,6 +111,7 @@ class Matrix extends AbstractCompositeShape { /** * Counts the number of alive neighbors around the specified cell. + * * @param x the X coordinate of the cell * @param y the Y coordinate of the cell * @return the count of alive neighboring cells @@ -116,6 +129,7 @@ class Matrix extends AbstractCompositeShape { /** * Checks if the cell at the specified coordinates is alive. + * * @param x the X coordinate of the cell * @param y the Y coordinate of the cell * @return true if the cell is within bounds and active @@ -130,6 +144,7 @@ class Matrix extends AbstractCompositeShape { /** * Evolves the matrix by one generation. + * * @param preserveHistory if true, places stars at positions of previously active cells */ public void evolve(final boolean preserveHistory) { @@ -143,6 +158,7 @@ class Matrix extends AbstractCompositeShape { /** * Computes the world position of a cell given its grid coordinates. + * * @param x the X grid coordinate * @param z the Z grid coordinate * @return the 3D world position of the cell center 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 index c2749c3..1ad9d43 100644 --- 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 @@ -27,7 +27,7 @@ public class TerrainDemo { * @param args command line arguments (ignored) */ public static void main(final String[] args) { - final ViewFrame viewFrame = new ViewFrame("Procedural Terrain"); + 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);