plotboilerplate 1.21.0 → 1.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/index.esm.js +252 -77
- package/dist/index.esm.js.map +1 -1
- package/dist/index.esm.min.js +1 -1
- package/dist/index.esm.min.js.map +1 -1
- package/dist/plotboilerplate.browser.min.js +1 -1
- package/dist/plotboilerplate.browser.min.js.map +1 -1
- package/dist/plotboilerplate.js +522 -338
- package/dist/plotboilerplate.min.js +1 -1
- package/package.json +7 -5
- package/src/cjs/BezierPath.js.map +1 -1
- package/src/cjs/Bounds.js +1 -1
- package/src/cjs/Bounds.js.map +1 -1
- package/src/cjs/Circle.js.map +1 -1
- package/src/cjs/CircleSector.d.ts +64 -1
- package/src/cjs/CircleSector.js +121 -1
- package/src/cjs/CircleSector.js.map +1 -1
- package/src/cjs/CubicBezierCurve.js +2 -2
- package/src/cjs/CubicBezierCurve.js.map +1 -1
- package/src/cjs/Grid.js.map +1 -1
- package/src/cjs/KeyHandler.d.ts +1 -1
- package/src/cjs/KeyHandler.js +13 -13
- package/src/cjs/KeyHandler.js.map +1 -1
- package/src/cjs/Line.js +2 -0
- package/src/cjs/Line.js.map +1 -1
- package/src/cjs/MouseHandler.d.ts +2 -2
- package/src/cjs/MouseHandler.js +7 -5
- package/src/cjs/MouseHandler.js.map +1 -1
- package/src/cjs/PlotBoilerplate.d.ts +1 -1
- package/src/cjs/PlotBoilerplate.js +77 -63
- package/src/cjs/PlotBoilerplate.js.map +1 -1
- package/src/cjs/Polygon.d.ts +13 -1
- package/src/cjs/Polygon.js +30 -1
- package/src/cjs/Polygon.js.map +1 -1
- package/src/cjs/Triangle.js +1 -1
- package/src/cjs/Triangle.js.map +1 -1
- package/src/cjs/UIDGenerator.js +1 -1
- package/src/cjs/UIDGenerator.js.map +1 -1
- package/src/cjs/VEllipse.js.map +1 -1
- package/src/cjs/VEllipseSector.js +1 -1
- package/src/cjs/VEllipseSector.js.map +1 -1
- package/src/cjs/Vector.js +2 -0
- package/src/cjs/Vector.js.map +1 -1
- package/src/cjs/VertTuple.js.map +1 -1
- package/src/cjs/Vertex.d.ts +3 -2
- package/src/cjs/Vertex.js +13 -7
- package/src/cjs/Vertex.js.map +1 -1
- package/src/cjs/VertexAttr.d.ts +2 -1
- package/src/cjs/VertexAttr.js +2 -1
- package/src/cjs/VertexAttr.js.map +1 -1
- package/src/cjs/VertexListeners.d.ts +1 -1
- package/src/cjs/VertexListeners.js.map +1 -1
- package/src/cjs/draw.js +1 -1
- package/src/cjs/draw.js.map +1 -1
- package/src/cjs/drawgl.js.map +1 -1
- package/src/cjs/drawutilssvg.d.ts +17 -3
- package/src/cjs/drawutilssvg.js +97 -91
- package/src/cjs/drawutilssvg.js.map +1 -1
- package/src/cjs/geomutils.js +2 -2
- package/src/cjs/geomutils.js.map +1 -1
- package/src/cjs/index.js +5 -1
- package/src/cjs/index.js.map +1 -1
- package/src/cjs/interfaces/DrawLib.d.ts +2 -2
- package/src/cjs/interfaces/core.d.ts +3 -3
- package/src/cjs/interfaces/index.js +5 -1
- package/src/cjs/interfaces/index.js.map +1 -1
- package/src/cjs/utils/NGons.d.ts +13 -0
- package/src/cjs/utils/NGons.js +36 -0
- package/src/cjs/utils/NGons.js.map +1 -0
- package/src/cjs/utils/Params.d.ts +15 -0
- package/src/cjs/utils/Params.js +49 -0
- package/src/cjs/utils/Params.js.map +1 -0
- package/src/cjs/utils/WebColors.js.map +1 -1
- package/src/cjs/utils/WebColorsContrast.js +1 -1
- package/src/cjs/utils/WebColorsContrast.js.map +1 -1
- package/src/cjs/utils/algorithms/CatmullRomPath.js.map +1 -1
- package/src/cjs/utils/algorithms/CircleIntersections.d.ts +3 -2
- package/src/cjs/utils/algorithms/CircleIntersections.js +12 -20
- package/src/cjs/utils/algorithms/CircleIntersections.js.map +1 -1
- package/src/cjs/utils/algorithms/ContourLineDetection.d.ts +150 -0
- package/src/cjs/utils/algorithms/ContourLineDetection.js +367 -0
- package/src/cjs/utils/algorithms/ContourLineDetection.js.map +1 -0
- package/src/cjs/utils/algorithms/CubicSplinePath.js.map +1 -1
- package/src/cjs/utils/algorithms/HobbyPath.js.map +1 -1
- package/src/cjs/utils/algorithms/Metaballs.d.ts +30 -0
- package/src/cjs/utils/algorithms/Metaballs.js +114 -0
- package/src/cjs/utils/algorithms/Metaballs.js.map +1 -0
- package/src/cjs/utils/algorithms/arrayResize.d.ts +8 -0
- package/src/cjs/utils/algorithms/arrayResize.js +24 -0
- package/src/cjs/utils/algorithms/arrayResize.js.map +1 -0
- package/src/cjs/utils/algorithms/clearDuplicateVertices.d.ts +21 -0
- package/src/cjs/utils/algorithms/clearDuplicateVertices.js +48 -0
- package/src/cjs/utils/algorithms/clearDuplicateVertices.js.map +1 -0
- package/src/cjs/utils/algorithms/clearPolygonDuplicateVertices.js.map +1 -1
- package/src/cjs/utils/algorithms/convexHull.js.map +1 -1
- package/src/cjs/utils/algorithms/convexPolygonIncircle.js.map +1 -1
- package/src/cjs/utils/algorithms/delaunay.js.map +1 -1
- package/src/cjs/utils/algorithms/delaunay2voronoi.js.map +1 -1
- package/src/cjs/utils/algorithms/detectPaths.js.map +1 -1
- package/src/cjs/utils/algorithms/findPolygonSelfIntersections.js.map +1 -1
- package/src/cjs/utils/algorithms/matrixFill.js +1 -1
- package/src/cjs/utils/algorithms/matrixFill.js.map +1 -1
- package/src/cjs/utils/algorithms/splitPolygonToNonIntersecting.js.map +1 -1
- package/src/cjs/utils/algorithms/sutherlandHodgman.js.map +1 -1
- package/src/cjs/utils/cloneVertexArray.js.map +1 -1
- package/src/cjs/utils/creategui.js +20 -12
- package/src/cjs/utils/datastructures/CircularIntervalSet.js.map +1 -1
- package/src/cjs/utils/datastructures/Color.d.ts +2 -1
- package/src/cjs/utils/datastructures/Color.js +35 -16
- package/src/cjs/utils/datastructures/Color.js.map +1 -1
- package/src/cjs/utils/datastructures/DataGrid2d.d.ts +47 -0
- package/src/cjs/utils/datastructures/DataGrid2d.js +20 -0
- package/src/cjs/utils/datastructures/DataGrid2d.js.map +1 -0
- package/src/cjs/utils/datastructures/DataGrid2dListAdapter.d.ts +65 -0
- package/src/cjs/utils/datastructures/DataGrid2dListAdapter.js +84 -0
- package/src/cjs/utils/datastructures/DataGrid2dListAdapter.js.map +1 -0
- package/src/cjs/utils/datastructures/GenericPath.d.ts +14 -3
- package/src/cjs/utils/datastructures/GenericPath.js +20 -3
- package/src/cjs/utils/datastructures/GenericPath.js.map +1 -1
- package/src/cjs/utils/datastructures/Girih.d.ts +3 -2
- package/src/cjs/utils/datastructures/Girih.js +2 -1
- package/src/cjs/utils/datastructures/Girih.js.map +1 -1
- package/src/cjs/utils/datastructures/GirihBowtie.js +2 -0
- package/src/cjs/utils/datastructures/GirihBowtie.js.map +1 -1
- package/src/cjs/utils/datastructures/GirihDecagon.js +2 -0
- package/src/cjs/utils/datastructures/GirihDecagon.js.map +1 -1
- package/src/cjs/utils/datastructures/GirihHexagon.js +2 -0
- package/src/cjs/utils/datastructures/GirihHexagon.js.map +1 -1
- package/src/cjs/utils/datastructures/GirihPenroseRhombus.js +3 -1
- package/src/cjs/utils/datastructures/GirihPenroseRhombus.js.map +1 -1
- package/src/cjs/utils/datastructures/GirihPentagon.js +2 -0
- package/src/cjs/utils/datastructures/GirihPentagon.js.map +1 -1
- package/src/cjs/utils/datastructures/GirihRhombus.js +2 -0
- package/src/cjs/utils/datastructures/GirihRhombus.js.map +1 -1
- package/src/cjs/utils/datastructures/GirihTile.js +4 -2
- package/src/cjs/utils/datastructures/GirihTile.js.map +1 -1
- package/src/cjs/utils/datastructures/Matrix4x4.js.map +1 -1
- package/src/cjs/utils/datastructures/VoronoiCell.js.map +1 -1
- package/src/cjs/utils/datastructures/interfaces.d.ts +3 -3
- package/src/cjs/utils/detectDarkMode.js.map +1 -1
- package/src/cjs/utils/dom/getAvailableContainerSpace.js +1 -1
- package/src/cjs/utils/dom/getAvailableContainerSpace.js.map +1 -1
- package/src/cjs/utils/findInVertexArray.js.map +1 -1
- package/src/cjs/utils/gup.d.ts +8 -0
- package/src/cjs/utils/gup.js +8 -0
- package/src/cjs/utils/gup.js.map +1 -1
- package/src/cjs/utils/helpers/BezierPathInteractionHelper.d.ts +6 -5
- package/src/cjs/utils/helpers/BezierPathInteractionHelper.js +3 -2
- package/src/cjs/utils/helpers/BezierPathInteractionHelper.js.map +1 -1
- package/src/cjs/utils/helpers/CircleHelper.d.ts +38 -3
- package/src/cjs/utils/helpers/CircleHelper.js +51 -11
- package/src/cjs/utils/helpers/CircleHelper.js.map +1 -1
- package/src/cjs/utils/helpers/CircleSectorHelper.d.ts +47 -1
- package/src/cjs/utils/helpers/CircleSectorHelper.js +85 -14
- package/src/cjs/utils/helpers/CircleSectorHelper.js.map +1 -1
- package/src/cjs/utils/io/FileDrop.d.ts +3 -3
- package/src/cjs/utils/io/FileDrop.js.map +1 -1
- package/src/cjs/utils/parsers/OBJParser.d.ts +2 -2
- package/src/cjs/utils/parsers/OBJParser.js.map +1 -1
- package/src/cjs/utils/parsers/STLParser.d.ts +4 -3
- package/src/cjs/utils/parsers/STLParser.js +62 -12
- package/src/cjs/utils/parsers/STLParser.js.map +1 -1
- package/src/cjs/utils/parsers/svg/parseSVGPathData.js +1 -1
- package/src/cjs/utils/parsers/svg/parseSVGPathData.js.map +1 -1
- package/src/cjs/utils/parsers/svg/splitSVGPathData.js.map +1 -1
- package/src/cjs/utils/parsers/svg/types.d.ts +13 -13
- package/src/createDemoDrawables.js +48 -3
- package/src/esm/BezierPath.js.map +1 -1
- package/src/esm/Bounds.js.map +1 -1
- package/src/esm/Circle.js.map +1 -1
- package/src/esm/CircleSector.d.ts +64 -1
- package/src/esm/CircleSector.js +121 -1
- package/src/esm/CircleSector.js.map +1 -1
- package/src/esm/CubicBezierCurve.js.map +1 -1
- package/src/esm/Grid.js.map +1 -1
- package/src/esm/KeyHandler.d.ts +1 -1
- package/src/esm/KeyHandler.js +13 -13
- package/src/esm/KeyHandler.js.map +1 -1
- package/src/esm/Line.js.map +1 -1
- package/src/esm/MouseHandler.d.ts +2 -2
- package/src/esm/MouseHandler.js +4 -4
- package/src/esm/MouseHandler.js.map +1 -1
- package/src/esm/PlotBoilerplate.d.ts +1 -1
- package/src/esm/PlotBoilerplate.js +43 -29
- package/src/esm/PlotBoilerplate.js.map +1 -1
- package/src/esm/Polygon.d.ts +13 -1
- package/src/esm/Polygon.js +30 -1
- package/src/esm/Polygon.js.map +1 -1
- package/src/esm/Triangle.js.map +1 -1
- package/src/esm/VEllipse.js.map +1 -1
- package/src/esm/VEllipseSector.js +1 -1
- package/src/esm/VEllipseSector.js.map +1 -1
- package/src/esm/Vector.js.map +1 -1
- package/src/esm/VertTuple.js.map +1 -1
- package/src/esm/Vertex.d.ts +3 -2
- package/src/esm/Vertex.js +9 -3
- package/src/esm/Vertex.js.map +1 -1
- package/src/esm/VertexAttr.d.ts +2 -1
- package/src/esm/VertexAttr.js +2 -1
- package/src/esm/VertexAttr.js.map +1 -1
- package/src/esm/VertexListeners.d.ts +1 -1
- package/src/esm/VertexListeners.js.map +1 -1
- package/src/esm/draw.js.map +1 -1
- package/src/esm/drawgl.js.map +1 -1
- package/src/esm/drawutilssvg.d.ts +17 -3
- package/src/esm/drawutilssvg.js +28 -22
- package/src/esm/drawutilssvg.js.map +1 -1
- package/src/esm/geomutils.js +2 -2
- package/src/esm/geomutils.js.map +1 -1
- package/src/esm/interfaces/DrawLib.d.ts +2 -2
- package/src/esm/interfaces/core.d.ts +3 -3
- package/src/esm/utils/NGons.d.ts +13 -0
- package/src/esm/utils/NGons.js +33 -0
- package/src/esm/utils/NGons.js.map +1 -0
- package/src/esm/utils/Params.d.ts +15 -0
- package/src/esm/utils/Params.js +44 -0
- package/src/esm/utils/Params.js.map +1 -0
- package/src/esm/utils/WebColors.js.map +1 -1
- package/src/esm/utils/algorithms/CatmullRomPath.js.map +1 -1
- package/src/esm/utils/algorithms/CircleIntersections.d.ts +3 -2
- package/src/esm/utils/algorithms/CircleIntersections.js +11 -19
- package/src/esm/utils/algorithms/CircleIntersections.js.map +1 -1
- package/src/esm/utils/algorithms/ContourLineDetection.d.ts +150 -0
- package/src/esm/utils/algorithms/ContourLineDetection.js +362 -0
- package/src/esm/utils/algorithms/ContourLineDetection.js.map +1 -0
- package/src/esm/utils/algorithms/CubicSplinePath.js.map +1 -1
- package/src/esm/utils/algorithms/HobbyPath.js.map +1 -1
- package/src/esm/utils/algorithms/Metaballs.d.ts +30 -0
- package/src/esm/utils/algorithms/Metaballs.js +109 -0
- package/src/esm/utils/algorithms/Metaballs.js.map +1 -0
- package/src/esm/utils/algorithms/arrayResize.d.ts +8 -0
- package/src/esm/utils/algorithms/arrayResize.js +20 -0
- package/src/esm/utils/algorithms/arrayResize.js.map +1 -0
- package/src/esm/utils/algorithms/clearDuplicateVertices.d.ts +21 -0
- package/src/esm/utils/algorithms/clearDuplicateVertices.js +44 -0
- package/src/esm/utils/algorithms/clearDuplicateVertices.js.map +1 -0
- package/src/esm/utils/algorithms/clearPolygonDuplicateVertices.js.map +1 -1
- package/src/esm/utils/algorithms/convexHull.js.map +1 -1
- package/src/esm/utils/algorithms/convexPolygonIncircle.js.map +1 -1
- package/src/esm/utils/algorithms/delaunay.js.map +1 -1
- package/src/esm/utils/algorithms/delaunay2voronoi.js.map +1 -1
- package/src/esm/utils/algorithms/detectPaths.js.map +1 -1
- package/src/esm/utils/algorithms/findPolygonSelfIntersections.js.map +1 -1
- package/src/esm/utils/algorithms/matrixFill.js.map +1 -1
- package/src/esm/utils/algorithms/splitPolygonToNonIntersecting.js.map +1 -1
- package/src/esm/utils/algorithms/sutherlandHodgman.js.map +1 -1
- package/src/esm/utils/cloneVertexArray.js.map +1 -1
- package/src/esm/utils/datastructures/CircularIntervalSet.js.map +1 -1
- package/src/esm/utils/datastructures/Color.d.ts +2 -1
- package/src/esm/utils/datastructures/Color.js +33 -14
- package/src/esm/utils/datastructures/Color.js.map +1 -1
- package/src/esm/utils/datastructures/DataGrid2d.d.ts +47 -0
- package/src/esm/utils/datastructures/DataGrid2d.js +19 -0
- package/src/esm/utils/datastructures/DataGrid2d.js.map +1 -0
- package/src/esm/utils/datastructures/DataGrid2dListAdapter.d.ts +65 -0
- package/src/esm/utils/datastructures/DataGrid2dListAdapter.js +79 -0
- package/src/esm/utils/datastructures/DataGrid2dListAdapter.js.map +1 -0
- package/src/esm/utils/datastructures/GenericPath.d.ts +14 -3
- package/src/esm/utils/datastructures/GenericPath.js +20 -3
- package/src/esm/utils/datastructures/GenericPath.js.map +1 -1
- package/src/esm/utils/datastructures/Girih.d.ts +3 -2
- package/src/esm/utils/datastructures/Girih.js +2 -1
- package/src/esm/utils/datastructures/Girih.js.map +1 -1
- package/src/esm/utils/datastructures/GirihBowtie.js.map +1 -1
- package/src/esm/utils/datastructures/GirihDecagon.js.map +1 -1
- package/src/esm/utils/datastructures/GirihHexagon.js.map +1 -1
- package/src/esm/utils/datastructures/GirihPenroseRhombus.js +1 -1
- package/src/esm/utils/datastructures/GirihPenroseRhombus.js.map +1 -1
- package/src/esm/utils/datastructures/GirihPentagon.js.map +1 -1
- package/src/esm/utils/datastructures/GirihRhombus.js.map +1 -1
- package/src/esm/utils/datastructures/GirihTile.js.map +1 -1
- package/src/esm/utils/datastructures/Matrix4x4.js.map +1 -1
- package/src/esm/utils/datastructures/VoronoiCell.js.map +1 -1
- package/src/esm/utils/datastructures/interfaces.d.ts +3 -3
- package/src/esm/utils/detectDarkMode.js.map +1 -1
- package/src/esm/utils/findInVertexArray.js.map +1 -1
- package/src/esm/utils/gup.d.ts +8 -0
- package/src/esm/utils/gup.js +8 -0
- package/src/esm/utils/gup.js.map +1 -1
- package/src/esm/utils/helpers/BezierPathInteractionHelper.d.ts +6 -5
- package/src/esm/utils/helpers/BezierPathInteractionHelper.js +3 -2
- package/src/esm/utils/helpers/BezierPathInteractionHelper.js.map +1 -1
- package/src/esm/utils/helpers/CircleHelper.d.ts +38 -3
- package/src/esm/utils/helpers/CircleHelper.js +51 -11
- package/src/esm/utils/helpers/CircleHelper.js.map +1 -1
- package/src/esm/utils/helpers/CircleSectorHelper.d.ts +47 -1
- package/src/esm/utils/helpers/CircleSectorHelper.js +85 -14
- package/src/esm/utils/helpers/CircleSectorHelper.js.map +1 -1
- package/src/esm/utils/io/FileDrop.d.ts +3 -3
- package/src/esm/utils/io/FileDrop.js.map +1 -1
- package/src/esm/utils/parsers/OBJParser.d.ts +2 -2
- package/src/esm/utils/parsers/OBJParser.js.map +1 -1
- package/src/esm/utils/parsers/STLParser.d.ts +4 -3
- package/src/esm/utils/parsers/STLParser.js +63 -13
- package/src/esm/utils/parsers/STLParser.js.map +1 -1
- package/src/esm/utils/parsers/svg/parseSVGPathData.js.map +1 -1
- package/src/esm/utils/parsers/svg/splitSVGPathData.js.map +1 -1
- package/src/esm/utils/parsers/svg/types.d.ts +13 -13
- package/src/index-gl.js +77 -85
- package/src/index.js +124 -129
- package/src/ts/CircleSector.ts +134 -3
- package/src/ts/PlotBoilerplate.ts +20 -11
- package/src/ts/Polygon.ts +31 -1
- package/src/ts/Vertex.ts +8 -3
- package/src/ts/VertexAttr.ts +3 -2
- package/src/ts/drawutilssvg.ts +38 -25
- package/src/ts/geomutils.ts +60 -64
- package/src/ts/interfaces/DrawLib.ts +0 -15
- package/src/ts/utils/NGons.ts +35 -0
- package/src/ts/utils/Params.ts +47 -0
- package/src/ts/utils/algorithms/CircleIntersections.ts +280 -288
- package/src/ts/utils/algorithms/ContourLineDetection.ts +485 -0
- package/src/ts/utils/algorithms/Metaballs.ts +144 -0
- package/src/ts/utils/algorithms/arrayResize.ts +22 -0
- package/src/ts/utils/algorithms/clearDuplicateVertices.ts +49 -0
- package/src/ts/utils/datastructures/Color.ts +39 -15
- package/src/ts/utils/datastructures/DataGrid2d.ts +54 -0
- package/src/ts/utils/datastructures/DataGrid2dListAdapter.ts +121 -0
- package/src/ts/utils/datastructures/GenericPath.ts +21 -3
- package/src/ts/utils/datastructures/Girih.ts +9 -8
- package/src/ts/utils/gup.ts +10 -2
- package/src/ts/utils/helpers/BezierPathInteractionHelper.ts +9 -8
- package/src/ts/utils/helpers/CircleHelper.ts +70 -22
- package/src/ts/utils/helpers/CircleSectorHelper.ts +101 -17
- package/src/ts/utils/parsers/STLParser.ts +50 -16
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A function to detect contour lines from 3D terrain data.
|
|
3
|
+
*
|
|
4
|
+
* For usage see demo `./demos/48-contour-plot`.
|
|
5
|
+
*
|
|
6
|
+
* @requires detectPaths
|
|
7
|
+
* @requires GenericPath
|
|
8
|
+
* @author Ikaros Kappler
|
|
9
|
+
* @date 2023-11-05
|
|
10
|
+
* @modified 2023-11-20 Addig path detection on a triangle based grid.
|
|
11
|
+
* @version 1.0.0
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { Line } from "../../Line";
|
|
15
|
+
import { Vertex } from "../../Vertex";
|
|
16
|
+
import { DataGridFace4, IDataGrid2d } from "../datastructures/DataGrid2d";
|
|
17
|
+
import { detectPaths } from "./detectPaths";
|
|
18
|
+
import { clearDuplicateVertices } from "./clearDuplicateVertices";
|
|
19
|
+
import { GenericPath } from "../datastructures/GenericPath";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Just a helper type for describing triangles on a surface.
|
|
23
|
+
*/
|
|
24
|
+
type DataGridFace3<T> = [T | null, T | null, T | null];
|
|
25
|
+
|
|
26
|
+
export class ContourLineDetection {
|
|
27
|
+
private dataGrid;
|
|
28
|
+
|
|
29
|
+
private rawLinearPathSegments: Array<Line> = [];
|
|
30
|
+
|
|
31
|
+
static CLOSE_GAP_TYPE_NONE = 0;
|
|
32
|
+
static CLOSE_GAP_TYPE_ABOVE = 1;
|
|
33
|
+
static CLOSE_GAP_TYPE_BELOW = 2;
|
|
34
|
+
|
|
35
|
+
// Activates/deactivates warning messages on rare edge cases where local path detection fails.
|
|
36
|
+
public debugMode: boolean = false;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Creates a new instance for calculating contour lines from the given data grid.
|
|
40
|
+
* @param {IDataGrid2d<number>} dataGrid - The data grid to use. Must not contain any NaN or null values.
|
|
41
|
+
* @param {boolean=?} debugMode - (optional) Pass `true` to log warnings on (rare) critical edge cases where the algorithm might fail.
|
|
42
|
+
*/
|
|
43
|
+
constructor(dataGrid: IDataGrid2d<number>, debugMode?: boolean) {
|
|
44
|
+
this.dataGrid = dataGrid;
|
|
45
|
+
this.debugMode = Boolean(debugMode);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Detect contour paths from the underlying data source.
|
|
50
|
+
*
|
|
51
|
+
* @param {number} criticalHeightValue - The height value. If above data's maximum or below data's minimum then the result will be empty (no intersections).
|
|
52
|
+
* @param {number} options.closeGapType - `CLOSE_GAP_TYPE_NONE` or `CLOSE_GAP_TYPE_ABOVE` or `CLOSE_GAP_TYPE_BELOW`.
|
|
53
|
+
* @param {boolean=false} options.useTriangles - If set to true the detection will split each face3 quad into two triangle faces.
|
|
54
|
+
* @param {pathDetectEpsilon=0.000001} options.pathDetectEpsilon - (optional) The epsilon to tell if two points are located 'in the same place'. Used for connected path detection. If not specified the value `0.0000001` is used.
|
|
55
|
+
* @param {pointEliminationEpsilon=0.0000001} options.pointEliminationEpsilon - (optional) The epsilon for duplicate point elimination (default is 0.000001).
|
|
56
|
+
* @param {function?} onRawSegmentsDetected - (optional) Get the interim result of all detected single lines before path detection starts; DO NOT MODIFY the array.
|
|
57
|
+
* @returns {Array<GenericPath>} - A list of connected paths that resemble the contour lines of the data/terrain at the given height value.
|
|
58
|
+
*/
|
|
59
|
+
detectContourPaths(
|
|
60
|
+
criticalHeightValue: number,
|
|
61
|
+
options?: {
|
|
62
|
+
closeGapType: number;
|
|
63
|
+
useTriangles?: boolean;
|
|
64
|
+
pointEliminationEpsilon?: number;
|
|
65
|
+
pathDetectEpsilon?: number;
|
|
66
|
+
onRawSegmentsDetected?: (rawSegmentsDoNotModifiy: Array<Line>) => void;
|
|
67
|
+
}
|
|
68
|
+
): Array<GenericPath> {
|
|
69
|
+
options = options || { closeGapType: ContourLineDetection.CLOSE_GAP_TYPE_NONE };
|
|
70
|
+
|
|
71
|
+
// First: clear the buffer
|
|
72
|
+
this.rawLinearPathSegments = [];
|
|
73
|
+
|
|
74
|
+
// Imagine a face4 element like this
|
|
75
|
+
// (x,y) (x+1,y)
|
|
76
|
+
// A-----B
|
|
77
|
+
// | / |
|
|
78
|
+
// | / |
|
|
79
|
+
// D-----C
|
|
80
|
+
// (x,y+1) (x+1,y+1)
|
|
81
|
+
// then result in the buffer will be
|
|
82
|
+
// [ [A,B],
|
|
83
|
+
// [D,C] ]
|
|
84
|
+
// Note that the diagonal line (used for triangles) is optional; depends on `options.useTriangles`.
|
|
85
|
+
const heightFace: DataGridFace4<number> = [
|
|
86
|
+
[0, 0],
|
|
87
|
+
[0, 0]
|
|
88
|
+
];
|
|
89
|
+
for (var y = 0; y + 1 < this.dataGrid.ySegmentCount; y++) {
|
|
90
|
+
for (var x = 0; x + 1 < this.dataGrid.xSegmentCount; x++) {
|
|
91
|
+
this.dataGrid.getDataFace4At(x, y, heightFace);
|
|
92
|
+
this.findHeightFaceIntersectionLines(
|
|
93
|
+
x,
|
|
94
|
+
y,
|
|
95
|
+
heightFace,
|
|
96
|
+
criticalHeightValue,
|
|
97
|
+
options.pointEliminationEpsilon ?? 0.0000001,
|
|
98
|
+
options.useTriangles
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Collect value above/below on the y axis
|
|
104
|
+
if (
|
|
105
|
+
options.closeGapType == ContourLineDetection.CLOSE_GAP_TYPE_ABOVE ||
|
|
106
|
+
options.closeGapType == ContourLineDetection.CLOSE_GAP_TYPE_BELOW
|
|
107
|
+
) {
|
|
108
|
+
const xExtremes = [0, this.dataGrid.xSegmentCount - 1];
|
|
109
|
+
for (var i = 0; i < xExtremes.length; i++) {
|
|
110
|
+
const x = xExtremes[i];
|
|
111
|
+
for (var y = 0; y + 1 < this.dataGrid.ySegmentCount; y++) {
|
|
112
|
+
const nextX = x;
|
|
113
|
+
const nextY = y + 1;
|
|
114
|
+
this.detectAboveBelowLerpSegment(x, y, nextX, nextY, criticalHeightValue, options.closeGapType);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const yExtremes = [0, this.dataGrid.ySegmentCount - 1];
|
|
118
|
+
for (var j = 0; j < yExtremes.length; j++) {
|
|
119
|
+
var y = yExtremes[j];
|
|
120
|
+
for (var x = 0; x + 1 < this.dataGrid.xSegmentCount; x++) {
|
|
121
|
+
const nextX = x + 1;
|
|
122
|
+
const nextY = y;
|
|
123
|
+
this.detectAboveBelowLerpSegment(x, y, nextX, nextY, criticalHeightValue, options.closeGapType);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (options.onRawSegmentsDetected) {
|
|
129
|
+
options.onRawSegmentsDetected(this.rawLinearPathSegments);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Detect connected paths
|
|
133
|
+
let pathSegments: Array<GenericPath> = detectPaths(this.rawLinearPathSegments, options.pathDetectEpsilon ?? 0.0000001); // Epsilon
|
|
134
|
+
// Filter out segments with only a single line of length~=0
|
|
135
|
+
pathSegments = pathSegments.filter(function (pathSegment) {
|
|
136
|
+
return (
|
|
137
|
+
pathSegment.segments.length != 1 ||
|
|
138
|
+
(pathSegment.segments.length === 1 && (pathSegment.segments[0] as Line).length() > 0.1)
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
return pathSegments;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* This function will calculate a single intersecion line of the given face4 data
|
|
146
|
+
* segment. If the given face does not intersect with the plane at the given `heightValue`
|
|
147
|
+
* then no segments will be stored.
|
|
148
|
+
*
|
|
149
|
+
* @param {number} xIndex - The x position (index) of the data face.
|
|
150
|
+
* @param {number} yIndex - The y position (index) of the data face.
|
|
151
|
+
* @param {[[number,number],[number,number]]} heightFace - The data sample that composes the face4 as a two-dimensional number array.
|
|
152
|
+
* @param {number} heightValue - The height value of the intersection plane to check for.
|
|
153
|
+
* @returns {Line|null}
|
|
154
|
+
*/
|
|
155
|
+
private findHeight4FaceIntersectionLine(
|
|
156
|
+
xIndex: number,
|
|
157
|
+
yIndex: number,
|
|
158
|
+
heightFace: DataGridFace4<number>,
|
|
159
|
+
heightValue: number,
|
|
160
|
+
pointEliminationEpsilon: number
|
|
161
|
+
): Line | null {
|
|
162
|
+
const heightValueA = heightFace[0][0]; // value at (x,y)
|
|
163
|
+
const heightValueB = heightFace[1][0];
|
|
164
|
+
const heightValueC = heightFace[1][1];
|
|
165
|
+
const heightValueD = heightFace[0][1];
|
|
166
|
+
|
|
167
|
+
if (heightValueA === null || heightValueB === null || heightValueC === null || heightValueD === null) {
|
|
168
|
+
throw `[findHeightFace4IntersectionLine] Cannot extract data face at (${xIndex},${yIndex}). Some values are null.`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
let points: Array<Vertex> = [];
|
|
172
|
+
|
|
173
|
+
// Case A: use full quad face
|
|
174
|
+
if (this.isBetween(heightValueA, heightValueB, heightValue)) {
|
|
175
|
+
const lerpValueByHeight = this.getLerpRatio(heightValueA, heightValueB, heightValue);
|
|
176
|
+
points.push(new Vertex(this.lerp(xIndex, xIndex + 1, lerpValueByHeight), yIndex));
|
|
177
|
+
}
|
|
178
|
+
if (this.isBetween(heightValueB, heightValueC, heightValue)) {
|
|
179
|
+
const lerpValueByHeight = this.getLerpRatio(heightValueB, heightValueC, heightValue);
|
|
180
|
+
points.push(new Vertex(xIndex + 1, this.lerp(yIndex, yIndex + 1, lerpValueByHeight)));
|
|
181
|
+
}
|
|
182
|
+
if (this.isBetween(heightValueC, heightValueD, heightValue)) {
|
|
183
|
+
const lerpValueByHeight = this.getLerpRatio(heightValueC, heightValueD, heightValue);
|
|
184
|
+
points.push(new Vertex(this.lerp(xIndex + 1, xIndex, lerpValueByHeight), yIndex + 1));
|
|
185
|
+
}
|
|
186
|
+
if (this.isBetween(heightValueD, heightValueA, heightValue)) {
|
|
187
|
+
const lerpValueByHeight = this.getLerpRatio(heightValueD, heightValueA, heightValue);
|
|
188
|
+
points.push(new Vertex(xIndex, this.lerp(yIndex + 1, yIndex, lerpValueByHeight)));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Warning: if a plane intersection point is located EXACTLY on the corner
|
|
192
|
+
// edge of a face, then the two adjacent edges will result in 2x the same
|
|
193
|
+
// intersecion point. This must be handled as one, so filter the point list
|
|
194
|
+
// by an epsilon.
|
|
195
|
+
points = clearDuplicateVertices(points, pointEliminationEpsilon); // 0.000001);
|
|
196
|
+
|
|
197
|
+
if (points.length >= 2) {
|
|
198
|
+
const startPoint = points[0];
|
|
199
|
+
const endPoint = points[1];
|
|
200
|
+
if (this.debugMode && points.length > 2) {
|
|
201
|
+
console.warn(
|
|
202
|
+
"[findHeightFace4IntersectionLine] Detected more than 2 points on one face whre only 0 or 2 should appear. At ",
|
|
203
|
+
xIndex,
|
|
204
|
+
yIndex,
|
|
205
|
+
points
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
return new Line(startPoint, endPoint);
|
|
209
|
+
} else {
|
|
210
|
+
if (this.debugMode && points.length === 1) {
|
|
211
|
+
console.warn("[findHeightFace4IntersectionLine] Point list has only one point (should not happen).");
|
|
212
|
+
}
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* This function will calculate a single intersecion line of the given face4 data
|
|
219
|
+
* segment. If the given face does not intersect with the plane at the given `heightValue`
|
|
220
|
+
* then no segments will be stored.
|
|
221
|
+
*
|
|
222
|
+
* @param {number} xIndex - The x position (index) of the data face.
|
|
223
|
+
* @param {number} yIndex - The y position (index) of the data face.
|
|
224
|
+
* @param {[[number,number],[number,number]]} heightFace - The data sample that composes the face4 as a two-dimensional number array.
|
|
225
|
+
* @param {number} heightValue - The height value of the intersection plane to check for.
|
|
226
|
+
* @returns {Line|null}
|
|
227
|
+
*/
|
|
228
|
+
private findHeightFaceIntersectionLines(
|
|
229
|
+
xIndex: number,
|
|
230
|
+
yIndex: number,
|
|
231
|
+
heightFace: DataGridFace4<number>,
|
|
232
|
+
criticalHeightValue: number,
|
|
233
|
+
pointEliminationEpsilon: number,
|
|
234
|
+
useTriangles?: boolean
|
|
235
|
+
): void {
|
|
236
|
+
// Imagine a face4 element like this
|
|
237
|
+
// (x,y) (x+1,y)
|
|
238
|
+
// A-----B
|
|
239
|
+
// | / |
|
|
240
|
+
// | / |
|
|
241
|
+
// D-----C
|
|
242
|
+
// (x,y+1) (x+1,y+1)
|
|
243
|
+
// then result in the buffer will be
|
|
244
|
+
// [ [A,B],
|
|
245
|
+
// [D,C] ]
|
|
246
|
+
if (useTriangles) {
|
|
247
|
+
const lineA = this.findHeighteFace3IntersectionLine(
|
|
248
|
+
xIndex,
|
|
249
|
+
yIndex,
|
|
250
|
+
xIndex,
|
|
251
|
+
yIndex + 1,
|
|
252
|
+
xIndex + 1,
|
|
253
|
+
yIndex,
|
|
254
|
+
[heightFace[0][0], heightFace[0][1], heightFace[1][0]],
|
|
255
|
+
criticalHeightValue,
|
|
256
|
+
pointEliminationEpsilon
|
|
257
|
+
);
|
|
258
|
+
if (lineA) {
|
|
259
|
+
this.rawLinearPathSegments.push(lineA);
|
|
260
|
+
}
|
|
261
|
+
const lineB = this.findHeighteFace3IntersectionLine(
|
|
262
|
+
xIndex,
|
|
263
|
+
yIndex + 1,
|
|
264
|
+
xIndex + 1,
|
|
265
|
+
yIndex + 1,
|
|
266
|
+
xIndex + 1,
|
|
267
|
+
yIndex,
|
|
268
|
+
[heightFace[0][1], heightFace[1][1], heightFace[1][0]],
|
|
269
|
+
criticalHeightValue,
|
|
270
|
+
pointEliminationEpsilon
|
|
271
|
+
);
|
|
272
|
+
if (lineB) {
|
|
273
|
+
this.rawLinearPathSegments.push(lineB);
|
|
274
|
+
}
|
|
275
|
+
} else {
|
|
276
|
+
const line = this.findHeight4FaceIntersectionLine(xIndex, yIndex, heightFace, criticalHeightValue, pointEliminationEpsilon);
|
|
277
|
+
if (line) {
|
|
278
|
+
this.rawLinearPathSegments.push(line);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* This function will calculate a single intersecion line of the given face3 data
|
|
285
|
+
* segment. If the given face does not intersect with the plane at the given `heightValue`
|
|
286
|
+
* then no segments will be stored.
|
|
287
|
+
*
|
|
288
|
+
* @param {number} xIndexA - The x position (index) of the first triangle data point.
|
|
289
|
+
* @param {number} yIndexA - The y position (index) of the first triangle data point.
|
|
290
|
+
* @param {[[number,number],[number,number]]} heightFace - The data sample that composes the face4 as a two-dimensional number array.
|
|
291
|
+
* @param {number} heightValue - The height value of the intersection plane to check for.
|
|
292
|
+
* @returns {Line|null}
|
|
293
|
+
*/
|
|
294
|
+
private findHeighteFace3IntersectionLine(
|
|
295
|
+
xIndexA: number,
|
|
296
|
+
yIndexA: number,
|
|
297
|
+
xIndexB: number,
|
|
298
|
+
yIndexB: number,
|
|
299
|
+
xIndexC: number,
|
|
300
|
+
yIndexC: number,
|
|
301
|
+
heightFace: DataGridFace3<number>,
|
|
302
|
+
heightValue: number,
|
|
303
|
+
pointEliminationEpsilon: number
|
|
304
|
+
): Line | null {
|
|
305
|
+
const heightValueA = heightFace[0]; // value at (x,y)
|
|
306
|
+
const heightValueB = heightFace[1];
|
|
307
|
+
const heightValueC = heightFace[2];
|
|
308
|
+
|
|
309
|
+
if (heightValueA === null || heightValueB === null || heightValueC === null) {
|
|
310
|
+
throw `[findHeightFace3IntersectionLine] Cannot extract data face at (${xIndexA},${yIndexA}). Some values are null.`;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
let points: Array<Vertex> = [];
|
|
314
|
+
|
|
315
|
+
// Case A: use full quad face
|
|
316
|
+
if (this.isBetween(heightValueA, heightValueB, heightValue)) {
|
|
317
|
+
const lerpValueByHeight = this.getLerpRatio(heightValueA, heightValueB, heightValue);
|
|
318
|
+
points.push(new Vertex(this.lerp(xIndexA, xIndexB, lerpValueByHeight), this.lerp(yIndexA, yIndexB, lerpValueByHeight)));
|
|
319
|
+
}
|
|
320
|
+
if (this.isBetween(heightValueB, heightValueC, heightValue)) {
|
|
321
|
+
const lerpValueByHeight = this.getLerpRatio(heightValueB, heightValueC, heightValue);
|
|
322
|
+
// points.push(new Vertex(xIndex + 1, this.lerp(yIndex, yIndex + 1, lerpValueByHeight)));
|
|
323
|
+
points.push(new Vertex(this.lerp(xIndexB, xIndexC, lerpValueByHeight), this.lerp(yIndexB, yIndexC, lerpValueByHeight)));
|
|
324
|
+
}
|
|
325
|
+
if (this.isBetween(heightValueC, heightValueA, heightValue)) {
|
|
326
|
+
const lerpValueByHeight = this.getLerpRatio(heightValueC, heightValueA, heightValue);
|
|
327
|
+
// points.push(new Vertex(this.lerp(xIndex + 1, xIndex, lerpValueByHeight), yIndex + 1));
|
|
328
|
+
points.push(new Vertex(this.lerp(xIndexC, xIndexA, lerpValueByHeight), this.lerp(yIndexC, yIndexA, lerpValueByHeight)));
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Warning: if a plane intersection point is located EXACTLY on the corner
|
|
332
|
+
// edge of a face, then the two adjacent edges will result in 2x the same
|
|
333
|
+
// intersecion point. This must be handled as one, so filter the point list
|
|
334
|
+
// by an epsilon.
|
|
335
|
+
points = clearDuplicateVertices(points, pointEliminationEpsilon); // 0.0000001);
|
|
336
|
+
|
|
337
|
+
if (points.length >= 2) {
|
|
338
|
+
const startPoint = points[0];
|
|
339
|
+
const endPoint = points[1];
|
|
340
|
+
if (this.debugMode && points.length > 2) {
|
|
341
|
+
console.warn(
|
|
342
|
+
"[findHeightFace3IntersectionLine] Detected more than 2 points on one face whre only 0 or 2 should appear. At ",
|
|
343
|
+
xIndexA,
|
|
344
|
+
yIndexA,
|
|
345
|
+
points
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
return new Line(startPoint, endPoint);
|
|
349
|
+
} else {
|
|
350
|
+
if (this.debugMode && points.length === 1) {
|
|
351
|
+
console.warn("[findHeightFace3IntersectionLine] Point list has only one point (should not happen).");
|
|
352
|
+
}
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* This procedure will look at the face4 at the ((x,y),(nextX,nextY)) position – which are four values –
|
|
359
|
+
* and determines the local contour lines for these cases.
|
|
360
|
+
*
|
|
361
|
+
* This is used to detect and calculate edge cases on the borders of the underliying height data:
|
|
362
|
+
* * left and right border (x=0, x=data.xSegmentCount)
|
|
363
|
+
* * top and bottom border (x=y, x=data.ySegmentCount)
|
|
364
|
+
*
|
|
365
|
+
* Resulting path segments will be stored in the global `rawLinearPathSegments` array for further processing.
|
|
366
|
+
*
|
|
367
|
+
* @param {number} x
|
|
368
|
+
* @param {number} y
|
|
369
|
+
* @param {number} nextX
|
|
370
|
+
* @param {number} nextY
|
|
371
|
+
* @param {number} criticalHeightValue
|
|
372
|
+
* @param {number} closeGapType - CLOSE_GAP_TYPE_ABOVE or CLOSE_GAP_TYPE_BELOW.
|
|
373
|
+
* @return {void}
|
|
374
|
+
*/
|
|
375
|
+
detectAboveBelowLerpSegment(
|
|
376
|
+
x: number,
|
|
377
|
+
y: number,
|
|
378
|
+
nextX: number,
|
|
379
|
+
nextY: number,
|
|
380
|
+
criticalHeightValue: number,
|
|
381
|
+
closeGapType: number
|
|
382
|
+
): void {
|
|
383
|
+
const heightValueA = this.dataGrid.getDataValueAt(x, y);
|
|
384
|
+
const heightValueB = this.dataGrid.getDataValueAt(nextX, nextY);
|
|
385
|
+
// if (heightValueA >= criticalHeightValue && heightValueB >= criticalHeightValue) {
|
|
386
|
+
if (this.areBothValuesOnRequiredPlaneSide(heightValueA, heightValueB, criticalHeightValue, closeGapType)) {
|
|
387
|
+
// Both above
|
|
388
|
+
const line = new Line(new Vertex(x, y), new Vertex(nextX, nextY));
|
|
389
|
+
this.rawLinearPathSegments.push(line);
|
|
390
|
+
} else if (
|
|
391
|
+
(closeGapType == ContourLineDetection.CLOSE_GAP_TYPE_ABOVE &&
|
|
392
|
+
heightValueA >= criticalHeightValue &&
|
|
393
|
+
heightValueB <= criticalHeightValue) ||
|
|
394
|
+
(closeGapType == ContourLineDetection.CLOSE_GAP_TYPE_BELOW &&
|
|
395
|
+
heightValueA <= criticalHeightValue &&
|
|
396
|
+
heightValueB >= criticalHeightValue)
|
|
397
|
+
) {
|
|
398
|
+
// Only one of both (first) is above -> interpolate to find exact intersection point
|
|
399
|
+
const lerpValueByHeight = this.getLerpRatio(heightValueA, heightValueB, criticalHeightValue);
|
|
400
|
+
const interpLine = new Line(
|
|
401
|
+
new Vertex(x, y),
|
|
402
|
+
new Vertex(this.lerp(x, nextX, lerpValueByHeight), this.lerp(y, nextY, lerpValueByHeight))
|
|
403
|
+
);
|
|
404
|
+
this.rawLinearPathSegments.push(interpLine);
|
|
405
|
+
} else if (
|
|
406
|
+
(closeGapType == ContourLineDetection.CLOSE_GAP_TYPE_ABOVE &&
|
|
407
|
+
heightValueA <= criticalHeightValue &&
|
|
408
|
+
heightValueB >= criticalHeightValue) ||
|
|
409
|
+
(closeGapType == ContourLineDetection.CLOSE_GAP_TYPE_BELOW &&
|
|
410
|
+
heightValueA >= criticalHeightValue &&
|
|
411
|
+
heightValueB <= criticalHeightValue)
|
|
412
|
+
) {
|
|
413
|
+
// Only one of both (second) is above -> interpolate to find exact intersection point
|
|
414
|
+
const lerpValueByHeight = this.getLerpRatio(heightValueA, heightValueB, criticalHeightValue);
|
|
415
|
+
const interpLine = new Line(
|
|
416
|
+
new Vertex(this.lerp(x, nextX, lerpValueByHeight), this.lerp(y, nextY, lerpValueByHeight)),
|
|
417
|
+
new Vertex(nextX, nextY)
|
|
418
|
+
);
|
|
419
|
+
this.rawLinearPathSegments.push(interpLine);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Checks if both value are on the same side of the critical value (above or below). The `closeGapType`
|
|
425
|
+
* indictes if `CLOSE_GAP_TYPE_BELOW` or `CLOSE_GAP_TYPE_ABOVE` should be used as a rule.
|
|
426
|
+
*
|
|
427
|
+
* @param {number} valueA
|
|
428
|
+
* @param {number} valueB
|
|
429
|
+
* @param {number} criticalValue
|
|
430
|
+
* @param {number} closeGapType
|
|
431
|
+
* @returns {boolean}
|
|
432
|
+
*/
|
|
433
|
+
private areBothValuesOnRequiredPlaneSide(valueA: number, valueB: number, criticalValue: number, closeGapType: number): boolean {
|
|
434
|
+
return (
|
|
435
|
+
(closeGapType == ContourLineDetection.CLOSE_GAP_TYPE_BELOW && valueA <= criticalValue && valueB <= criticalValue) ||
|
|
436
|
+
(closeGapType == ContourLineDetection.CLOSE_GAP_TYPE_ABOVE && valueA >= criticalValue && valueB >= criticalValue)
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Test if a given numeric value (`curValue`) is between the given values `valA` and `valB`.
|
|
442
|
+
* Value A and B don't need to be in ascending order, so `valA <= curValue <= valB` and `valB <= curvalue <= valA`
|
|
443
|
+
* will do the job.
|
|
444
|
+
*
|
|
445
|
+
* @param {number} valA - The first of the two bounds.
|
|
446
|
+
* @param {number} valB - The second of the two bounds.
|
|
447
|
+
* @param {number} curValue - The value to check if it is between (or equal) to the given bounds.
|
|
448
|
+
* @returns {boolean}
|
|
449
|
+
*/
|
|
450
|
+
private isBetween(valA: number, valB: number, curValue: number): boolean {
|
|
451
|
+
return (valA <= curValue && curValue <= valB) || (valB <= curValue && curValue <= valA);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Get a 'lerp' value - which is some sort of percentage/ratio for the `curValue` inside the
|
|
456
|
+
* range of the given interval `[valA ... valB]`.
|
|
457
|
+
*
|
|
458
|
+
* Examples:
|
|
459
|
+
* * getLerpRatio(0,100,50) === 0.5
|
|
460
|
+
* * getLerpRatio(50,100,75) === 0.5
|
|
461
|
+
* * getLerpRatio(0,100,0) === 0.0
|
|
462
|
+
* * getLerpRatio(0,100,100) === 1.0
|
|
463
|
+
* * getLerpRatio(0,100,-50) === -0.5
|
|
464
|
+
*
|
|
465
|
+
* @param {number} valA
|
|
466
|
+
* @param {number} valB
|
|
467
|
+
* @param {number} curValue
|
|
468
|
+
* @returns
|
|
469
|
+
*/
|
|
470
|
+
private getLerpRatio(valA: number, valB: number, curValue: number): number {
|
|
471
|
+
return (curValue - valA) / (valB - valA);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Helper function to lerp a numeric value.
|
|
476
|
+
*
|
|
477
|
+
* @param {number} min - The min (start) value. Doesn't necesarily need to be the smaller one.
|
|
478
|
+
* @param {number} max - The max (end) value. Doesn't necesarily need to be the larger one.
|
|
479
|
+
* @param {number} ratio - The lerp ratio; usually a value between 0.0 and 1.0, but other values a valid for linear interpolation, too.
|
|
480
|
+
* @returns {number}
|
|
481
|
+
*/
|
|
482
|
+
private lerp(min: number, max: number, ratio: number): number {
|
|
483
|
+
return min + (max - min) * ratio;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Algorithm:
|
|
3
|
+
* Input Circles
|
|
4
|
+
* -> filter out fully contained circles
|
|
5
|
+
* -> build outer containing circles (given by detect radius)
|
|
6
|
+
* -> find radical lines
|
|
7
|
+
*
|
|
8
|
+
* This implementation currently does not detect any holes.
|
|
9
|
+
*
|
|
10
|
+
* @author Ikaros Kappler
|
|
11
|
+
* @version 1.0.0
|
|
12
|
+
* @date 2024-03-01
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { Circle } from "../../Circle";
|
|
16
|
+
import { Vertex } from "../../Vertex";
|
|
17
|
+
import { CircleIntersections } from "./CircleIntersections";
|
|
18
|
+
|
|
19
|
+
type Pair<T> = [T, T];
|
|
20
|
+
|
|
21
|
+
interface InverseCirclePair {
|
|
22
|
+
baseCircleIndexA: number;
|
|
23
|
+
baseCircleIndexB: number;
|
|
24
|
+
baseCircleA: Circle;
|
|
25
|
+
baseCircleB: Circle;
|
|
26
|
+
circleA: Circle;
|
|
27
|
+
circleB: Circle;
|
|
28
|
+
inverseCircleA: Circle;
|
|
29
|
+
inverseCircleB: Circle;
|
|
30
|
+
doIntersect: boolean;
|
|
31
|
+
circlePointsA: Pair<Vertex>;
|
|
32
|
+
circlePointsB: Pair<Vertex>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface MetaballsOptions {
|
|
36
|
+
metaRadiusAddon: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class Metaballs {
|
|
40
|
+
private circleIsFullyContained: Array<boolean> = []; // Array<boolean>
|
|
41
|
+
private inputCircles: Array<Circle> = [];
|
|
42
|
+
private circlesOfInterest: Array<Circle> = [];
|
|
43
|
+
private containingCircles: Array<Circle> = [];
|
|
44
|
+
|
|
45
|
+
// Array< { baseCircleIndexA, baseCircleIndexB,
|
|
46
|
+
// baseCircleA, baseCircleB,
|
|
47
|
+
// circleA, circleB,
|
|
48
|
+
// inverseCircleA, inverseCircleB,
|
|
49
|
+
// doIntersect, circlePointsA:[], circlePointsB:[] } >
|
|
50
|
+
private inverseCirclesPairs: Array<InverseCirclePair> = [];
|
|
51
|
+
|
|
52
|
+
constructor(inputCircles: Array<Circle>) {
|
|
53
|
+
this.inputCircles = inputCircles;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public rebuild(options: MetaballsOptions) {
|
|
57
|
+
this.detectContainmentStatus();
|
|
58
|
+
this.rebuildContainingCircles(options);
|
|
59
|
+
|
|
60
|
+
this.inverseCirclesPairs = [];
|
|
61
|
+
|
|
62
|
+
const radicalLineMatrix = CircleIntersections.buildRadicalLineMatrix(this.containingCircles);
|
|
63
|
+
for (var i = 0; i < this.containingCircles.length; i++) {
|
|
64
|
+
var circleA = this.containingCircles[i];
|
|
65
|
+
var centerCircleA = this.circlesOfInterest[i];
|
|
66
|
+
for (var j = i + 1; j < this.containingCircles.length; j++) {
|
|
67
|
+
var radicalLine = radicalLineMatrix[i][j];
|
|
68
|
+
if (radicalLine == null) {
|
|
69
|
+
// The two circles do not have an intersection.
|
|
70
|
+
// console.log("Circles", i, j, "do not have any intersections");
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
var circleB = this.containingCircles[j];
|
|
74
|
+
var centerCircleB = this.circlesOfInterest[j];
|
|
75
|
+
// But if they have -> compute. outer circle(s).
|
|
76
|
+
// They are symmetrical.
|
|
77
|
+
var inverseCircle1 = new Circle(radicalLine.a, options.metaRadiusAddon);
|
|
78
|
+
var inverseCircle2 = new Circle(radicalLine.b, options.metaRadiusAddon);
|
|
79
|
+
var doIntersect = inverseCircle1.circleIntersection(inverseCircle2) != null;
|
|
80
|
+
// console.log("doIntersect", doIntersect);
|
|
81
|
+
// Now find the intersection points between inner and outer circles.
|
|
82
|
+
// We will need them later.
|
|
83
|
+
var circlePointsA: Pair<Vertex> = [
|
|
84
|
+
centerCircleA.closestPoint(inverseCircle1.center),
|
|
85
|
+
centerCircleA.closestPoint(inverseCircle2.center)
|
|
86
|
+
];
|
|
87
|
+
var circlePointsB: Pair<Vertex> = [
|
|
88
|
+
centerCircleB.closestPoint(inverseCircle1.center),
|
|
89
|
+
centerCircleB.closestPoint(inverseCircle2.center)
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
this.inverseCirclesPairs.push({
|
|
93
|
+
baseCircleIndexA: i,
|
|
94
|
+
baseCircleIndexB: j,
|
|
95
|
+
baseCircleA: this.circlesOfInterest[i],
|
|
96
|
+
baseCircleB: this.circlesOfInterest[j],
|
|
97
|
+
circleA: circleA,
|
|
98
|
+
inverseCircleA: inverseCircle1,
|
|
99
|
+
circleB: circleB,
|
|
100
|
+
inverseCircleB: inverseCircle2,
|
|
101
|
+
doIntersect: doIntersect,
|
|
102
|
+
circlePointsA: circlePointsA,
|
|
103
|
+
circlePointsB: circlePointsB
|
|
104
|
+
});
|
|
105
|
+
} // END for j
|
|
106
|
+
} // END for i
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// public getInnerPath;
|
|
110
|
+
|
|
111
|
+
private checkCircleFullyContained(circleIndex: number): boolean {
|
|
112
|
+
for (var i = 0; i < this.inputCircles.length; i++) {
|
|
113
|
+
if (circleIndex == i) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (this.inputCircles[i].containsCircle(this.inputCircles[circleIndex])) {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private detectContainmentStatus() {
|
|
124
|
+
this.circleIsFullyContained = [];
|
|
125
|
+
this.circlesOfInterest = [];
|
|
126
|
+
for (var i = 0; i < this.inputCircles.length; i++) {
|
|
127
|
+
this.circleIsFullyContained[i] = this.checkCircleFullyContained(i);
|
|
128
|
+
if (!this.circleIsFullyContained[i]) {
|
|
129
|
+
this.circlesOfInterest.push(this.inputCircles[i]);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private rebuildContainingCircles(options: MetaballsOptions) {
|
|
135
|
+
this.containingCircles = [];
|
|
136
|
+
for (var i = 0; i < this.circlesOfInterest.length; i++) {
|
|
137
|
+
var containingCircle = new Circle(
|
|
138
|
+
this.circlesOfInterest[i].center,
|
|
139
|
+
this.circlesOfInterest[i].radius + options.metaRadiusAddon
|
|
140
|
+
);
|
|
141
|
+
this.containingCircles.push(containingCircle);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resize a given array to a desired number of elements by adding or removing some.
|
|
3
|
+
*
|
|
4
|
+
* @author Ikaros Kappler
|
|
5
|
+
* @date 2024-02-07
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const arrayResize = <T>(arr: Array<T>, desiredSize: number, factoryFn: () => T) => {
|
|
10
|
+
if (arr.length === desiredSize) {
|
|
11
|
+
// No change required
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
while (arr.length < desiredSize) {
|
|
16
|
+
arr.push(factoryFn());
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (arr.length > desiredSize) {
|
|
20
|
+
arr.splice(desiredSize);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remove duplicate vertices (2d) from an array.
|
|
3
|
+
*
|
|
4
|
+
* This method war taken and refactored from an older demo.
|
|
5
|
+
*
|
|
6
|
+
* @author Ikaros Kappler
|
|
7
|
+
* @date 2021-07-13
|
|
8
|
+
* @modified 2023-10-28 Refactored and ported to Typescript.
|
|
9
|
+
* @version 1.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { Vertex } from "../../Vertex";
|
|
13
|
+
|
|
14
|
+
const EPS: number = 0.000001;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Filter the array and clear all duplicates.
|
|
18
|
+
*
|
|
19
|
+
* The original array is left unchanged. The vertices in the array are not cloned.
|
|
20
|
+
*
|
|
21
|
+
* @param {Vertex[]} vertices
|
|
22
|
+
* @param {number=EPS} epsilon
|
|
23
|
+
* @return {Vertex[]}
|
|
24
|
+
*/
|
|
25
|
+
export const clearDuplicateVertices = (vertices: Vertex[], epsilon?: number) => {
|
|
26
|
+
if (typeof epsilon === "undefined") {
|
|
27
|
+
epsilon = EPS;
|
|
28
|
+
}
|
|
29
|
+
const result: Array<Vertex> = [];
|
|
30
|
+
for (var i = 0; i < vertices.length; i++) {
|
|
31
|
+
if (!containsElementFrom(vertices, vertices[i], i + 1, epsilon)) {
|
|
32
|
+
result.push(vertices[i]);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const isCloseTo = (vertA: Vertex, vertB: Vertex, eps: number): boolean => {
|
|
39
|
+
return vertA.distance(vertB) < eps;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const containsElementFrom = (vertices: Array<Vertex>, vertex: Vertex, fromIndex: number, epsilon: number): boolean => {
|
|
43
|
+
for (var i = fromIndex; i < vertices.length; i++) {
|
|
44
|
+
if (isCloseTo(vertices[i], vertex, epsilon)) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
};
|