Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@itwin/core-geometry",
"comment": "",
"type": "none"
}
],
"packageName": "@itwin/core-geometry"
}
95 changes: 61 additions & 34 deletions core/geometry/src/test/topology/Voronoi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,20 @@ function visualizeGraphEdges(allGeometry: GeometryQuery[], graph: HalfEdgeGraph,
* @param showDelaunay whether to visualize the Delaunay triangulation.
* @param showClippers whether to visualize the curve-based Voronoi super faces as UnionRegions derived from the clippers instead.
*/
function visualizeAndVerifyCurveBasedVoronoiDiagram(ck: Checker, allGeometry: GeometryQuery[], path: CurveChain, strokeOptions?: StrokeOptions, distanceTol?: number, bbox?: LowAndHighXY, showDelaunay?: boolean, showClippers?: boolean): void {
function visualizeAndVerifyCurveBasedVoronoiDiagram(
ck: Checker,
allGeometry: GeometryQuery[],
path: CurveChain,
strokeOptions?: StrokeOptions,
distanceTol?: number,
bbox?: LowAndHighXY,
showDelaunay?: boolean,
showClippers?: boolean,
testName?: string,
): void {
const voronoi = Voronoi.createFromCurveChain(path, strokeOptions, distanceTol, bbox);
if (ck.testDefined(voronoi, "created Voronoi instance")) {
verifyVoronoiTopology(ck, voronoi);
verifyVoronoiTopology(ck, voronoi, testName);
ck.testTrue(voronoi.isCurveBased, "Voronoi instance is curve-based");
if (showDelaunay)
GeometryCoreTestIO.captureCloneGeometry(allGeometry, PolyfaceBuilder.graphToPolyface(voronoi.getInputGraph, undefined, undefined, () => true));
Expand Down Expand Up @@ -98,24 +108,41 @@ function visualizeAndVerifyCurveBasedVoronoiDiagram(ck: Checker, allGeometry: Ge
}

/** Verify the faces of the Voronoi graph consist of points closest to the expected Delaunay vertex. */
function verifyVoronoiTopology(ck: Checker, v: Voronoi): void {
function verifyVoronoiTopology(ck: Checker, v: Voronoi, testName?: string): void {
const dVertices = v.getInputGraph.collectVertexLoops();
const searcher = Point3dArrayRangeTreeContext.createCapture(dVertices, undefined, undefined, true);
if (ck.testDefined(searcher, "searcher for Delaunay vertices is defined")) {
const vRange = HalfEdgeGraphOps.graphRangeXY(v.getVoronoiGraph);
const dRange = v.getInputGraphRange;
const testPoints: Point2d[] = [];
for (let i = 0; i < 50; i++) { // concentrate test points in the input graph range
testPoints.push(Point2d.create(getRandomNumber(vRange.low.x, vRange.high.x), getRandomNumber(vRange.low.y, vRange.high.y)));
testPoints.push(Point2d.create(getRandomNumber(dRange.low.x, dRange.high.x), getRandomNumber(dRange.low.y, dRange.high.y)));
if (GeometryCoreTestIO.enableLongTests) {
for (let i = 0; i < 50; i++) { // concentrate random test points in the input graph range
testPoints.push(
Point2d.create(getRandomNumber(vRange.low.x, vRange.high.x), getRandomNumber(vRange.low.y, vRange.high.y)),
);
testPoints.push(
Point2d.create(getRandomNumber(dRange.low.x, dRange.high.x), getRandomNumber(dRange.low.y, dRange.high.y)),
);
}
} else { // concentrate fixed test points in the input graph range
for (let i = 0.1; i <= 0.9; i += 0.1) {
for (let j = 0.1; j <= 0.9; j += 0.1) {
testPoints.push(vRange.fractionToPoint(i, j));
testPoints.push(dRange.fractionToPoint(i, j));
}
}
}
// verify the Voronoi condition via Monte Carlo method
const vFaces = HalfEdgeGraphSearch.findContainingFaceXY(v.getVoronoiGraph, testPoints) as (HalfEdge | undefined)[] | undefined;
if (ck.testDefined(vFaces, "findContainingFaceXY succeeded")) {
if (ck.testExactNumber(vFaces.length, testPoints.length, "findContainingFaceXY returned one entry per point")) {
for (let i = 0; i < vFaces.length; i++) {
const vFace = vFaces[i];
if (ck.testDefined(vFace, "interior point containing face found")) {
const errorMessage =
testName ?
`expect point ${JSON.stringify(testPoints[i].toJSON())} to be found in an interior Voronoi face for test ${testName}` :
`expect point ${JSON.stringify(testPoints[i].toJSON())} to be found in an interior Voronoi face`;
if (ck.testDefined(vFace, errorMessage)) {
const dGenerator = v.getInputGraph.allHalfEdges[vFace.faceTag];
let closestPoints = searcher.searchForClosestPoint(testPoints[i], Number.POSITIVE_INFINITY) as CurveLocationDetail[] | undefined;
if (ck.testDefined(closestPoints, "closest Delaunay vertex found")) {
Expand Down Expand Up @@ -212,7 +239,7 @@ describe("Voronoi", () => {
visualizeGraphEdges(allGeometry, voronoiGraph);
ck.testExactNumber(voronoiGraph.collectFaceLoops().length, 3, "voronoiGraph should have 3 faces");
ck.testExactNumber(voronoiGraph.allHalfEdges.length / 2, 7, "voronoiGraph should have 7 edges");
verifyVoronoiTopology(ck, voronoi);
verifyVoronoiTopology(ck, voronoi, "ColinearPoints0");
}

let dx = 6;
Expand All @@ -229,7 +256,7 @@ describe("Voronoi", () => {
visualizeGraphEdges(allGeometry, voronoiGraph, dx);
ck.testExactNumber(voronoiGraph.collectFaceLoops().length, 3, "voronoiGraph should have 3 faces");
ck.testExactNumber(voronoiGraph.allHalfEdges.length / 2, 7, "voronoiGraph should have 7 edges");
verifyVoronoiTopology(ck, voronoi);
verifyVoronoiTopology(ck, voronoi, "ColinearPoints1");
}

dx += 5;
Expand All @@ -246,7 +273,7 @@ describe("Voronoi", () => {
visualizeGraphEdges(allGeometry, voronoiGraph, dx);
ck.testExactNumber(voronoiGraph.collectFaceLoops().length, 3, "voronoiGraph should have 3 faces");
ck.testExactNumber(voronoiGraph.allHalfEdges.length / 2, 5, "voronoiGraph should have 5 edges");
verifyVoronoiTopology(ck, voronoi);
verifyVoronoiTopology(ck, voronoi, "ColinearPoints2");
}

dx += 6;
Expand All @@ -263,7 +290,7 @@ describe("Voronoi", () => {
visualizeGraphEdges(allGeometry, voronoiGraph, dx);
ck.testExactNumber(voronoiGraph.collectFaceLoops().length, 3, "voronoiGraph should have 3 faces");
ck.testExactNumber(voronoiGraph.allHalfEdges.length / 2, 7, "voronoiGraph should have 7 edges");
verifyVoronoiTopology(ck, voronoi);
verifyVoronoiTopology(ck, voronoi, "ColinearPoints3");
}

// 3d single edge graph
Expand All @@ -281,7 +308,7 @@ describe("Voronoi", () => {
visualizeGraphEdges(allGeometry, voronoiGraph, dx);
ck.testExactNumber(voronoiGraph.collectFaceLoops().length, 3, "voronoiGraph should have 3 faces");
ck.testExactNumber(voronoiGraph.allHalfEdges.length / 2, 7, "voronoiGraph should have 7 edges");
verifyVoronoiTopology(ck, voronoi);
verifyVoronoiTopology(ck, voronoi, "ColinearPoints4");
}

dx += 9;
Expand All @@ -300,7 +327,7 @@ describe("Voronoi", () => {
visualizeGraphEdges(allGeometry, voronoiGraph, dx);
ck.testExactNumber(voronoiGraph.collectFaceLoops().length, 4, "voronoiGraph should have 4 faces");
ck.testExactNumber(voronoiGraph.allHalfEdges.length / 2, 10, "voronoiGraph should have 10 edges");
verifyVoronoiTopology(ck, voronoi);
verifyVoronoiTopology(ck, voronoi, "ColinearPoints5");
}
dx += 11;
points = [[-1, -1], [1, 1], [4, 4]];
Expand All @@ -318,7 +345,7 @@ describe("Voronoi", () => {
visualizeGraphEdges(allGeometry, voronoiGraph, dx);
ck.testExactNumber(voronoiGraph.collectFaceLoops().length, 4, "voronoiGraph should have 4 faces");
ck.testExactNumber(voronoiGraph.allHalfEdges.length / 2, 10, "voronoiGraph should have 10 edges");
verifyVoronoiTopology(ck, voronoi);
verifyVoronoiTopology(ck, voronoi, "ColinearPoints6");
}

dx += 9;
Expand All @@ -339,7 +366,7 @@ describe("Voronoi", () => {
visualizeGraphEdges(allGeometry, voronoiGraph, dx);
ck.testExactNumber(voronoiGraph.collectFaceLoops().length, 5, "voronoiGraph should have 5 faces");
ck.testExactNumber(voronoiGraph.allHalfEdges.length / 2, 13, "voronoiGraph should have 13 edges");
verifyVoronoiTopology(ck, voronoi);
verifyVoronoiTopology(ck, voronoi, "ColinearPoints7");
}

GeometryCoreTestIO.saveGeometry(allGeometry, "Voronoi", "ColinearPoints");
Expand Down Expand Up @@ -372,7 +399,7 @@ describe("Voronoi", () => {
visualizeGraphEdges(allGeometry, voronoiGraph);
ck.testExactNumber(voronoiGraph.collectFaceLoops().length, 4, "voronoiGraph should have 4 faces");
ck.testExactNumber(voronoiGraph.allHalfEdges.length / 2, 10, "voronoiGraph should have 10 edges");
verifyVoronoiTopology(ck, voronoi);
verifyVoronoiTopology(ck, voronoi, "GraphWith1Triangle0");
}

const dy = 8;
Expand All @@ -399,7 +426,7 @@ describe("Voronoi", () => {
visualizeGraphEdges(allGeometry, voronoiGraph, dx);
ck.testExactNumber(voronoiGraph.collectFaceLoops().length, 4, "voronoiGraph from points should have 4 faces");
ck.testExactNumber(voronoiGraph.allHalfEdges.length / 2, 10, "voronoiGraph from points should have 10 edges");
verifyVoronoiTopology(ck, voronoi);
verifyVoronoiTopology(ck, voronoi, "GraphWith1Triangle1");
}
}

Expand All @@ -416,7 +443,7 @@ describe("Voronoi", () => {
visualizeGraphEdges(allGeometry, voronoiGraph, dx);
ck.testExactNumber(voronoiGraph.collectFaceLoops().length, 4, "voronoiGraph for 3d should have 4 faces");
ck.testExactNumber(voronoiGraph.allHalfEdges.length / 2, 10, "voronoiGraph for 3d should have 10 edges");
verifyVoronoiTopology(ck, voronoi);
verifyVoronoiTopology(ck, voronoi, "GraphWith1Triangle2");
}
}

Expand All @@ -433,7 +460,7 @@ describe("Voronoi", () => {
visualizeGraphEdges(allGeometry, voronoiGraph, dx);
ck.testExactNumber(voronoiGraph.collectFaceLoops().length, 4, "voronoiGraph from points should have 4 faces");
ck.testExactNumber(voronoiGraph.allHalfEdges.length / 2, 10, "voronoiGraph from points should have 10 edges");
verifyVoronoiTopology(ck, voronoi);
verifyVoronoiTopology(ck, voronoi, "GraphWith1Triangle3");
}
}

Expand Down Expand Up @@ -475,7 +502,7 @@ describe("Voronoi", () => {
visualizeGraphEdges(allGeometry, voronoiGraph);
ck.testExactNumber(voronoiGraph.collectFaceLoops().length, 5, "voronoiGraph should have 5 faces");
ck.testExactNumber(voronoiGraph.allHalfEdges.length / 2, 13, "voronoiGraph should have 13 edges");
verifyVoronoiTopology(ck, voronoi);
verifyVoronoiTopology(ck, voronoi, "GraphWith2Triangles0");
}

const dy = 11;
Expand All @@ -502,7 +529,7 @@ describe("Voronoi", () => {
visualizeGraphEdges(allGeometry, voronoiGraph, dx);
ck.testExactNumber(voronoiGraph.collectFaceLoops().length, 5, "voronoiGraph should have 5 faces");
ck.testExactNumber(voronoiGraph.allHalfEdges.length / 2, 13, "voronoiGraph should have 13 edges");
verifyVoronoiTopology(ck, voronoi);
verifyVoronoiTopology(ck, voronoi, "GraphWith2Triangles1");
}
}

Expand All @@ -518,7 +545,7 @@ describe("Voronoi", () => {
visualizeGraphEdges(allGeometry, voronoiGraph, dx);
ck.testExactNumber(voronoiGraph.collectFaceLoops().length, 5, "voronoiGraph should have 5 faces");
ck.testExactNumber(voronoiGraph.allHalfEdges.length / 2, 12, "voronoiGraph should have 12 edges");
verifyVoronoiTopology(ck, voronoi);
verifyVoronoiTopology(ck, voronoi, "GraphWith2Triangles2");
}
}

Expand All @@ -535,7 +562,7 @@ describe("Voronoi", () => {
visualizeGraphEdges(allGeometry, voronoiGraph, dx);
ck.testExactNumber(voronoiGraph.collectFaceLoops().length, 5, "voronoiGraph for 3d should have 5 faces");
ck.testExactNumber(voronoiGraph.allHalfEdges.length / 2, 12, "voronoiGraph for 3d should have 12 edges");
verifyVoronoiTopology(ck, voronoi);
verifyVoronoiTopology(ck, voronoi, "GraphWith2Triangles3");
}
}

Expand All @@ -552,7 +579,7 @@ describe("Voronoi", () => {
visualizeGraphEdges(allGeometry, voronoiGraph, dx);
ck.testExactNumber(voronoiGraph.collectFaceLoops().length, 5, "voronoiGraph from points should have 5 faces");
ck.testExactNumber(voronoiGraph.allHalfEdges.length / 2, 13, "voronoiGraph from points should have 13 edges");
verifyVoronoiTopology(ck, voronoi);
verifyVoronoiTopology(ck, voronoi, "GraphWith2Triangles4");
}
}

Expand All @@ -569,7 +596,7 @@ describe("Voronoi", () => {
visualizeGraphEdges(allGeometry, voronoiGraph, dx);
ck.testExactNumber(voronoiGraph.collectFaceLoops().length, 5, "voronoiGraph from points should have 5 faces");
ck.testExactNumber(voronoiGraph.allHalfEdges.length / 2, 13, "voronoiGraph from points should have 13 edges");
verifyVoronoiTopology(ck, voronoi);
verifyVoronoiTopology(ck, voronoi, "GraphWith2Triangles5");
}
}

Expand All @@ -595,7 +622,7 @@ describe("Voronoi", () => {
visualizeGraphEdges(allGeometry, voronoiGraph);
ck.testExactNumber(voronoiGraph.collectFaceLoops().length, 6, "voronoiGraph should have 6 faces");
ck.testExactNumber(voronoiGraph.allHalfEdges.length / 2, 16, "voronoiGraph should have 16 edges");
verifyVoronoiTopology(ck, voronoi);
verifyVoronoiTopology(ck, voronoi, "GraphWith3Triangles0");
}
}

Expand All @@ -612,7 +639,7 @@ describe("Voronoi", () => {
visualizeGraphEdges(allGeometry, voronoiGraph, dx);
ck.testExactNumber(voronoiGraph.collectFaceLoops().length, 6, "voronoiGraph for 3d should have 6 faces");
ck.testExactNumber(voronoiGraph.allHalfEdges.length / 2, 16, "voronoiGraph for 3d should have 16 edges");
verifyVoronoiTopology(ck, voronoi);
verifyVoronoiTopology(ck, voronoi, "GraphWith3Triangles1");
}
}

Expand All @@ -629,7 +656,7 @@ describe("Voronoi", () => {
visualizeGraphEdges(allGeometry, voronoiGraph, dx);
ck.testExactNumber(voronoiGraph.collectFaceLoops().length, 6, "voronoiGraph should have 6 faces");
ck.testExactNumber(voronoiGraph.allHalfEdges.length / 2, 16, "voronoiGraph should have 16 edges");
verifyVoronoiTopology(ck, voronoi);
verifyVoronoiTopology(ck, voronoi, "GraphWith3Triangles2");
}
}

Expand All @@ -655,7 +682,7 @@ describe("Voronoi", () => {
visualizeGraphEdges(allGeometry, voronoiGraph);
ck.testExactNumber(voronoiGraph.collectFaceLoops().length, 6, "voronoiGraph should have 6 faces");
ck.testExactNumber(voronoiGraph.allHalfEdges.length / 2, 16, "voronoiGraph should have 16 edges");
verifyVoronoiTopology(ck, voronoi);
verifyVoronoiTopology(ck, voronoi, "GraphWith4Triangles0");
}
}

Expand All @@ -672,7 +699,7 @@ describe("Voronoi", () => {
visualizeGraphEdges(allGeometry, voronoiGraph, dx);
ck.testExactNumber(voronoiGraph.collectFaceLoops().length, 6, "voronoiGraph for 3d should have 6 faces");
ck.testExactNumber(voronoiGraph.allHalfEdges.length / 2, 16, "voronoiGraph for 3d should have 16 edges");
verifyVoronoiTopology(ck, voronoi);
verifyVoronoiTopology(ck, voronoi, "GraphWith4Triangles1");
}
}

Expand All @@ -689,7 +716,7 @@ describe("Voronoi", () => {
visualizeGraphEdges(allGeometry, voronoiGraph, dx);
ck.testExactNumber(voronoiGraph.collectFaceLoops().length, 6, "voronoiGraph should have 6 faces");
ck.testExactNumber(voronoiGraph.allHalfEdges.length / 2, 16, "voronoiGraph should have 16 edges");
verifyVoronoiTopology(ck, voronoi);
verifyVoronoiTopology(ck, voronoi, "GraphWith4Triangles2");
}
}

Expand All @@ -715,7 +742,7 @@ describe("Voronoi", () => {
visualizeGraphEdges(allGeometry, voronoiGraph);
ck.testExactNumber(voronoiGraph.collectFaceLoops().length, 11, "voronoiGraph should have 11 faces");
ck.testExactNumber(voronoiGraph.allHalfEdges.length / 2, 31, "voronoiGraph should have 31 edges");
verifyVoronoiTopology(ck, voronoi);
verifyVoronoiTopology(ck, voronoi, "GraphWithManyTriangles0");
}
}

Expand All @@ -735,7 +762,7 @@ describe("Voronoi", () => {
visualizeGraphEdges(allGeometry, voronoiGraph, dx);
ck.testExactNumber(voronoiGraph.collectFaceLoops().length, 11, "voronoiGraph for 3d should have 11 faces");
ck.testExactNumber(voronoiGraph.allHalfEdges.length / 2, 31, "voronoiGraph for 3d should have 31 edges");
verifyVoronoiTopology(ck, voronoi);
verifyVoronoiTopology(ck, voronoi, "GraphWithManyTriangles1");
}
}

Expand Down Expand Up @@ -926,7 +953,7 @@ describe("Voronoi", () => {
const graph = PolyfaceQuery.convertToHalfEdgeGraph(mesh);
const testPoints: Point3d[] = [];
for (let i = 0; i < 100; i++) // sample some interior points
testPoints.push(Point3d.create(getRandomNumberScaled(side, 1/side), getRandomNumberScaled(side, 1/side)));
testPoints.push(Point3d.create(getRandomNumberScaled(side, 1 / side), getRandomNumberScaled(side, 1 / side)));
testPoints.push(Point3d.create(-1, -1)); // and one exterior point
GeometryCoreTestIO.createAndCaptureXYCircle(allGeometry, testPoints, 0.1);
const containingFaces = HalfEdgeGraphSearch.findContainingFaceXY(graph, testPoints) as (HalfEdge | undefined)[] | undefined;
Expand Down