From 948e66369cc520fe6ea8288ac7461fb961eaf1b1 Mon Sep 17 00:00:00 2001 From: Svjatoslav Agejenko Date: Wed, 18 Mar 2026 21:49:20 +0200 Subject: [PATCH] feat(graph): add SurfaceGraph3D class for 3D surface visualization Add new SurfaceGraph3D composite shape that renders mathematical functions z = f(x,y) as colored surface grids with wireframe overlay. Includes intersection curve visualization for planes at constant X or Y values. Also reorganize MathGraphsDemo into graph_demo package and integrate the new surface graph demonstrating a saddle function with intersection curves. --- .../{ => graph_demo}/MathGraphsDemo.java | 29 ++- .../examples/graph_demo/SurfaceGraph3D.java | 197 ++++++++++++++++++ .../launcher/ApplicationListPanel.java | 2 +- 3 files changed, 226 insertions(+), 2 deletions(-) rename src/main/java/eu/svjatoslav/sixth/e3d/examples/{ => graph_demo}/MathGraphsDemo.java (84%) create mode 100644 src/main/java/eu/svjatoslav/sixth/e3d/examples/graph_demo/SurfaceGraph3D.java diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/MathGraphsDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/graph_demo/MathGraphsDemo.java similarity index 84% rename from src/main/java/eu/svjatoslav/sixth/e3d/examples/MathGraphsDemo.java rename to src/main/java/eu/svjatoslav/sixth/e3d/examples/graph_demo/MathGraphsDemo.java index 49bdd88..b69ef47 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/MathGraphsDemo.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/graph_demo/MathGraphsDemo.java @@ -4,11 +4,12 @@ * */ -package eu.svjatoslav.sixth.e3d.examples; +package eu.svjatoslav.sixth.e3d.examples.graph_demo; import eu.svjatoslav.sixth.e3d.geometry.Point2D; 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.composite.Graph; @@ -18,6 +19,8 @@ import java.util.List; /** * Demo showing mathematical function graphs rendered in 3D. * Displays sine, cosine, tangent, and composite function graphs. + * Also includes a 3D surface graph showing z = (x² - y²) / 20 with + * intersecting planes and highlighted intersection curves. */ public class MathGraphsDemo { @@ -147,6 +150,7 @@ public class MathGraphsDemo { .getRootShapeCollection(); addMathFormulas(geometryCollection); + addSurfaceGraph(geometryCollection); setCameraLocation(viewFrame); @@ -178,6 +182,29 @@ public class MathGraphsDemo { geometryCollection.addShape(getFormula3Graph(location)); } + private static void addSurfaceGraph(ShapeCollection geometryCollection) { + final double range = 10; + final double step = 0.5; + final double scale = 30; + + final SurfaceGraph3D.MathFunction3D function = (x, y) -> (x * x - y * y) / 20; + + SurfaceGraph3D surface = new SurfaceGraph3D( + -range, range, step, + -range, range, step, + function, + new Color(150, 150, 150, 180), + Color.WHITE, + scale, + new Point3D(0, 0, 0) + ); + + surface.addYIntersectionCurve(4, function, new Color(255, 255, 0, 220), 1.2); + surface.addXIntersectionCurve(-4, function, new Color(255, 120, 0, 220), 1.2); + + geometryCollection.addShape(surface); + } + /** * Sets the camera to an initial viewing position. * @param viewFrame the view frame whose camera to configure diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/graph_demo/SurfaceGraph3D.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/graph_demo/SurfaceGraph3D.java new file mode 100644 index 0000000..661a854 --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/graph_demo/SurfaceGraph3D.java @@ -0,0 +1,197 @@ +/* + * Sixth 3D engine demos. Author: Svjatoslav Agejenko. + * This project is released under Creative Commons Zero (CC0) license. + */ +package eu.svjatoslav.sixth.e3d.examples.graph_demo; + +import eu.svjatoslav.sixth.e3d.geometry.Point3D; +import eu.svjatoslav.sixth.e3d.renderer.raster.Color; +import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.line.Line; +import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.solidpolygon.SolidPolygon; +import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.AbstractCompositeShape; + +/** + * A 3D surface graph that visualizes mathematical functions of the form z = f(x, y). + * + *

The surface is rendered as a grid of quadrilaterals (split into triangles) with + * optional wireframe overlay. The surface color can be customized, and the wireframe + * lines are drawn in a contrasting color.

+ * + *

Usage example:

+ *
{@code
+ * // Create a saddle surface: z = x^2 - y^2
+ * SurfaceGraph3D saddle = new SurfaceGraph3D(
+ *     -5, 5, 0.5,      // x range and step
+ *     -5, 5, 0.5,      // y range and step
+ *     (x, y) -> x * x - y * y,  // function z = f(x, y)
+ *     new Color(180, 180, 180, 200),  // gray semi-transparent surface
+ *     new Color(255, 255, 255),       // white grid lines
+ *     new Point3D(0, 0, 0)            // position in 3D space
+ * );
+ * shapeCollection.addShape(saddle);
+ * }
+ * + * @see AbstractCompositeShape + * @see SolidPolygon + * @see Line + */ +public class SurfaceGraph3D extends AbstractCompositeShape { + + private final double xMin; + private final double xMax; + private final double xStep; + private final double yMin; + private final double yMax; + private final double yStep; + private final double scale; + private final Color surfaceColor; + private final Color gridColor; + private final double lineWidth; + + /** + * Creates a 3D surface graph at the specified location. + * + * @param xMin minimum X value of the domain + * @param xMax maximum X value of the domain + * @param xStep step size between grid points along X axis + * @param yMin minimum Y value of the domain + * @param yMax maximum Y value of the domain + * @param yStep step size between grid points along Y axis + * @param function the mathematical function z = f(x, y) to visualize + * @param surfaceColor color of the surface polygons (use semi-transparent for see-through effect) + * @param gridColor color of the wireframe grid lines + * @param lineWidth width of grid lines in world units + * @param scale scale factor applied to all generated vertices + * @param location the 3D position of the graph's origin + */ + public SurfaceGraph3D(final double xMin, final double xMax, final double xStep, + final double yMin, final double yMax, final double yStep, + final MathFunction3D function, + final Color surfaceColor, final Color gridColor, + final double lineWidth, final double scale, final Point3D location) { + super(location); + + this.xMin = xMin; + this.xMax = xMax; + this.yMin = yMin; + this.yMax = yMax; + this.xStep = xStep; + this.yStep = yStep; + this.scale = scale; + this.surfaceColor = surfaceColor; + this.gridColor = gridColor; + this.lineWidth = lineWidth; + + generateSurface(function); + } + + public SurfaceGraph3D(final double xMin, final double xMax, final double xStep, + final double yMin, final double yMax, final double yStep, + final MathFunction3D function, + final Color surfaceColor, final Color gridColor, + final double scale, final Point3D location) { + this(xMin, xMax, xStep, yMin, yMax, yStep, function, surfaceColor, gridColor, 0.1, scale, location); + } + + private void generateSurface(final MathFunction3D function) { + final int xCount = (int) ((xMax - xMin) / xStep) + 1; + final int yCount = (int) ((yMax - yMin) / yStep) + 1; + + final Point3D[][] vertices = new Point3D[xCount][yCount]; + + for (int i = 0; i < xCount; i++) { + for (int j = 0; j < yCount; j++) { + final double x = xMin + i * xStep; + final double y = yMin + j * yStep; + final double z = function.apply(x, y); + vertices[i][j] = new Point3D(x * scale, -z * scale, y * scale); + } + } + + for (int i = 0; i < xCount - 1; i++) { + for (int j = 0; j < yCount - 1; j++) { + final Point3D p1 = vertices[i][j]; + final Point3D p2 = vertices[i + 1][j]; + final Point3D p3 = vertices[i][j + 1]; + final Point3D p4 = vertices[i + 1][j + 1]; + + addQuad(p1, p2, p3, p4); + addGridLines(p1, p2, p3, p4); + } + } + } + + private void addQuad(final Point3D p1, final Point3D p2, final Point3D p3, final Point3D p4) { + final SolidPolygon poly1 = new SolidPolygon(p1, p2, p3, surfaceColor); + poly1.setBackfaceCulling(false); + addShape(poly1); + + final SolidPolygon poly2 = new SolidPolygon(p2, p4, p3, surfaceColor); + poly2.setBackfaceCulling(false); + addShape(poly2); + } + + private void addGridLines(final Point3D p1, final Point3D p2, final Point3D p3, final Point3D p4) { + addShape(new Line(p1, p2, gridColor, lineWidth)); + addShape(new Line(p2, p4, gridColor, lineWidth)); + addShape(new Line(p3, p4, gridColor, lineWidth)); + addShape(new Line(p1, p3, gridColor, lineWidth)); + } + + /** + * Adds a curve highlighting the intersection of the surface with a vertical plane at constant X. + * + * @param xValue the X coordinate of the intersecting plane + * @param function the mathematical function z = f(x, y) + * @param color the color of the intersection curve + * @param width the line width + */ + public void addXIntersectionCurve(final double xValue, final MathFunction3D function, + final Color color, final double width) { + Point3D prevPoint = null; + for (double y = yMin; y <= yMax; y += 0.15) { + final double z = function.apply(xValue, y); + final Point3D currentPoint = new Point3D(xValue * scale, -z * scale, y * scale); + if (prevPoint != null) { + addShape(new Line(prevPoint, currentPoint, color, width)); + } + prevPoint = currentPoint; + } + } + + /** + * Adds a curve highlighting the intersection of the surface with a vertical plane at constant Y. + * + * @param yValue the Y coordinate of the intersecting plane + * @param function the mathematical function z = f(x, y) + * @param color the color of the intersection curve + * @param width the line width + */ + public void addYIntersectionCurve(final double yValue, final MathFunction3D function, + final Color color, final double width) { + Point3D prevPoint = null; + for (double x = xMin; x <= xMax; x += 0.15) { + final double z = function.apply(x, yValue); + final Point3D currentPoint = new Point3D(x * scale, -z * scale, yValue * scale); + if (prevPoint != null) { + addShape(new Line(prevPoint, currentPoint, color, width)); + } + prevPoint = currentPoint; + } + } + + /** + * Functional interface for 3D mathematical functions of the form z = f(x, y). + */ + @FunctionalInterface + public interface MathFunction3D { + /** + * Computes the Z value for given X and Y coordinates. + * + * @param x the X coordinate + * @param y the Y coordinate + * @return the Z value (height) at the given (x, y) position + */ + double apply(double x, double y); + } +} 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 272261d..7aa936d 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 @@ -7,7 +7,7 @@ package eu.svjatoslav.sixth.e3d.examples.launcher; import eu.svjatoslav.sixth.e3d.examples.SineHeightmap; -import eu.svjatoslav.sixth.e3d.examples.MathGraphsDemo; +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; -- 2.20.1