From: Svjatoslav Agejenko Date: Wed, 18 Mar 2026 19:49:20 +0000 (+0200) Subject: feat(graph): add SurfaceGraph3D class for 3D surface visualization X-Git-Url: http://www2.svjatoslav.eu/gitweb/?a=commitdiff_plain;h=948e66369cc520fe6ea8288ac7461fb961eaf1b1;p=sixth-3d-demos.git 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. --- diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/MathGraphsDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/MathGraphsDemo.java deleted file mode 100644 index 49bdd88..0000000 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/MathGraphsDemo.java +++ /dev/null @@ -1,189 +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.Point2D; -import eu.svjatoslav.sixth.e3d.geometry.Point3D; -import eu.svjatoslav.sixth.e3d.gui.ViewFrame; -import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection; -import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.Graph; - -import java.util.ArrayList; -import java.util.List; - -/** - * Demo showing mathematical function graphs rendered in 3D. - * Displays sine, cosine, tangent, and composite function graphs. - */ -public class MathGraphsDemo { - - private static final double GRAPH_SCALE = 50d; - - /** - * Creates a new MathGraphsDemo instance. - */ - public MathGraphsDemo() { - } - - /** - * Creates a graph of the cosine function. - * @param location the position of the graph in 3D space - * @return a Graph component showing y = cos(x) - */ - private static Graph getCosineGraph(final Point3D location) { - final List data = new ArrayList<>(); - for (double x = 0; x < 20; x += 0.25) { - final double y = Math.cos(x); - - final Point2D p = new Point2D(x, y); - data.add(p); - } - - return new Graph(GRAPH_SCALE, data, "Cosine", location); - } - - /** - * Creates a graph of y = sin(tan(x)). - * @param location the position of the graph in 3D space - * @return a Graph component showing the composite function - */ - private static Graph getFormula1Graph(final Point3D location) { - final List data = new ArrayList<>(); - for (double x = 0; x < 20; x += 0.25) { - final double y = Math.sin(Math.tan(x)); - - final Point2D p = new Point2D(x, y); - data.add(p); - } - - return new Graph(GRAPH_SCALE, data, "y = sin(tan(x))", location); - } - - /** - * Creates a graph of y = (10-x)^2 / 30. - * @param location the position of the graph in 3D space - * @return a Graph component showing the parabola - */ - private static Graph getFormula2Graph(final Point3D location) { - final List data = new ArrayList<>(); - for (double x = 0; x < 20; x += 0.25) { - final double y = (Math.pow((10 - x), 2) / 30) - 2; - - final Point2D p = new Point2D(x, y); - data.add(p); - } - - return new Graph(GRAPH_SCALE, data, "y = ( (10-x)^2 ) / 30", location); - } - - /** - * Creates a graph of y = sin(x/2) + sin(x/1.26). - * @param location the position of the graph in 3D space - * @return a Graph component showing the composite sine wave - */ - private static Graph getFormula3Graph(final Point3D location) { - final List data = new ArrayList<>(); - for (double x = 0; x < 20; x += 0.25) { - final double y = Math.sin(x / 2) + Math.sin(x / 1.26); - - final Point2D p = new Point2D(x, y); - data.add(p); - } - - return new Graph(GRAPH_SCALE, data, "y = sin(x/2) + sin(x/1.26)", location); - } - - /** - * Creates a graph of the sine function. - * @param location the position of the graph in 3D space - * @return a Graph component showing y = sin(x) - */ - private static Graph getSineGraph(final Point3D location) { - final List data = new ArrayList<>(); - for (double x = 0; x < 20; x += 0.25) { - final double y = Math.sin(x); - - final Point2D p = new Point2D(x, y); - data.add(p); - } - - return new Graph(GRAPH_SCALE, data, "Sine", location); - } - - /** - * Creates a graph of the tangent function with clamped values. - * @param location the position of the graph in 3D space - * @return a Graph component showing y = tan(x) with clamped range - */ - private static Graph getTangentGraph(final Point3D location) { - final List data = new ArrayList<>(); - for (double x = 0; x < 20; x += 0.25) { - double y = Math.tan(x); - - if (y > 2) - y = 2; - if (y < -2) - y = -2; - - final Point2D p = new Point2D(x, y); - data.add(p); - } - - return new Graph(GRAPH_SCALE, data, "Tangent", location); - } - - /** - * Entry point for the math graphs demo. - * @param args command line arguments (ignored) - */ - public static void main(final String[] args) { - - final ViewFrame viewFrame = new ViewFrame(); - final ShapeCollection geometryCollection = viewFrame.getViewPanel() - .getRootShapeCollection(); - - addMathFormulas(geometryCollection); - - setCameraLocation(viewFrame); - - viewFrame.getViewPanel().ensureRenderThreadStarted(); - } - - /** - * Adds all mathematical formula graphs to the scene. - * @param geometryCollection the collection to add graphs to - */ - private static void addMathFormulas(ShapeCollection geometryCollection) { - int z = 1000; - Point3D location = new Point3D(-600, -300, z); - geometryCollection.addShape(getSineGraph(location)); - - location = new Point3D(600, -300, z); - geometryCollection.addShape(getFormula1Graph(location)); - - location = new Point3D(-600, 0, z); - geometryCollection.addShape(getCosineGraph(location)); - - location = new Point3D(600, 0, z); - geometryCollection.addShape(getFormula2Graph(location)); - - location = new Point3D(-600, 300, z); - geometryCollection.addShape(getTangentGraph(location)); - - location = new Point3D(600, 300, z); - geometryCollection.addShape(getFormula3Graph(location)); - } - - /** - * Sets the camera to an initial viewing position. - * @param viewFrame the view frame whose camera to configure - */ - private static void setCameraLocation(ViewFrame viewFrame) { - viewFrame.getViewPanel().getCamera().getTransform().setTranslation(new Point3D(0, 0, -500)); - } - -} \ No newline at end of file diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/graph_demo/MathGraphsDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/graph_demo/MathGraphsDemo.java new file mode 100644 index 0000000..b69ef47 --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/graph_demo/MathGraphsDemo.java @@ -0,0 +1,216 @@ +/* + * 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.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; + +import java.util.ArrayList; +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 { + + private static final double GRAPH_SCALE = 50d; + + /** + * Creates a new MathGraphsDemo instance. + */ + public MathGraphsDemo() { + } + + /** + * Creates a graph of the cosine function. + * @param location the position of the graph in 3D space + * @return a Graph component showing y = cos(x) + */ + private static Graph getCosineGraph(final Point3D location) { + final List data = new ArrayList<>(); + for (double x = 0; x < 20; x += 0.25) { + final double y = Math.cos(x); + + final Point2D p = new Point2D(x, y); + data.add(p); + } + + return new Graph(GRAPH_SCALE, data, "Cosine", location); + } + + /** + * Creates a graph of y = sin(tan(x)). + * @param location the position of the graph in 3D space + * @return a Graph component showing the composite function + */ + private static Graph getFormula1Graph(final Point3D location) { + final List data = new ArrayList<>(); + for (double x = 0; x < 20; x += 0.25) { + final double y = Math.sin(Math.tan(x)); + + final Point2D p = new Point2D(x, y); + data.add(p); + } + + return new Graph(GRAPH_SCALE, data, "y = sin(tan(x))", location); + } + + /** + * Creates a graph of y = (10-x)^2 / 30. + * @param location the position of the graph in 3D space + * @return a Graph component showing the parabola + */ + private static Graph getFormula2Graph(final Point3D location) { + final List data = new ArrayList<>(); + for (double x = 0; x < 20; x += 0.25) { + final double y = (Math.pow((10 - x), 2) / 30) - 2; + + final Point2D p = new Point2D(x, y); + data.add(p); + } + + return new Graph(GRAPH_SCALE, data, "y = ( (10-x)^2 ) / 30", location); + } + + /** + * Creates a graph of y = sin(x/2) + sin(x/1.26). + * @param location the position of the graph in 3D space + * @return a Graph component showing the composite sine wave + */ + private static Graph getFormula3Graph(final Point3D location) { + final List data = new ArrayList<>(); + for (double x = 0; x < 20; x += 0.25) { + final double y = Math.sin(x / 2) + Math.sin(x / 1.26); + + final Point2D p = new Point2D(x, y); + data.add(p); + } + + return new Graph(GRAPH_SCALE, data, "y = sin(x/2) + sin(x/1.26)", location); + } + + /** + * Creates a graph of the sine function. + * @param location the position of the graph in 3D space + * @return a Graph component showing y = sin(x) + */ + private static Graph getSineGraph(final Point3D location) { + final List data = new ArrayList<>(); + for (double x = 0; x < 20; x += 0.25) { + final double y = Math.sin(x); + + final Point2D p = new Point2D(x, y); + data.add(p); + } + + return new Graph(GRAPH_SCALE, data, "Sine", location); + } + + /** + * Creates a graph of the tangent function with clamped values. + * @param location the position of the graph in 3D space + * @return a Graph component showing y = tan(x) with clamped range + */ + private static Graph getTangentGraph(final Point3D location) { + final List data = new ArrayList<>(); + for (double x = 0; x < 20; x += 0.25) { + double y = Math.tan(x); + + if (y > 2) + y = 2; + if (y < -2) + y = -2; + + final Point2D p = new Point2D(x, y); + data.add(p); + } + + return new Graph(GRAPH_SCALE, data, "Tangent", location); + } + + /** + * Entry point for the math graphs demo. + * @param args command line arguments (ignored) + */ + public static void main(final String[] args) { + + final ViewFrame viewFrame = new ViewFrame(); + final ShapeCollection geometryCollection = viewFrame.getViewPanel() + .getRootShapeCollection(); + + addMathFormulas(geometryCollection); + addSurfaceGraph(geometryCollection); + + setCameraLocation(viewFrame); + + viewFrame.getViewPanel().ensureRenderThreadStarted(); + } + + /** + * Adds all mathematical formula graphs to the scene. + * @param geometryCollection the collection to add graphs to + */ + private static void addMathFormulas(ShapeCollection geometryCollection) { + int z = 1000; + Point3D location = new Point3D(-600, -300, z); + geometryCollection.addShape(getSineGraph(location)); + + location = new Point3D(600, -300, z); + geometryCollection.addShape(getFormula1Graph(location)); + + location = new Point3D(-600, 0, z); + geometryCollection.addShape(getCosineGraph(location)); + + location = new Point3D(600, 0, z); + geometryCollection.addShape(getFormula2Graph(location)); + + location = new Point3D(-600, 300, z); + geometryCollection.addShape(getTangentGraph(location)); + + location = new Point3D(600, 300, z); + 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 + */ + private static void setCameraLocation(ViewFrame viewFrame) { + viewFrame.getViewPanel().getCamera().getTransform().setTranslation(new Point3D(0, 0, -500)); + } + +} \ No newline at end of file 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;