@itwin/core-geometry 4.0.0-dev.22 → 4.0.0-dev.24
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/lib/cjs/Geometry.d.ts +23 -0
- package/lib/cjs/Geometry.d.ts.map +1 -1
- package/lib/cjs/Geometry.js +25 -1
- package/lib/cjs/Geometry.js.map +1 -1
- package/lib/cjs/geometry3d/BarycentricTriangle.d.ts +195 -8
- package/lib/cjs/geometry3d/BarycentricTriangle.d.ts.map +1 -1
- package/lib/cjs/geometry3d/BarycentricTriangle.js +459 -11
- package/lib/cjs/geometry3d/BarycentricTriangle.js.map +1 -1
- package/lib/cjs/geometry3d/GrowableXYArray.d.ts +1 -1
- package/lib/cjs/geometry3d/GrowableXYArray.d.ts.map +1 -1
- package/lib/cjs/geometry3d/GrowableXYArray.js +1 -1
- package/lib/cjs/geometry3d/GrowableXYArray.js.map +1 -1
- package/lib/cjs/geometry3d/IndexedXYCollection.d.ts +22 -7
- package/lib/cjs/geometry3d/IndexedXYCollection.d.ts.map +1 -1
- package/lib/cjs/geometry3d/IndexedXYCollection.js +41 -5
- package/lib/cjs/geometry3d/IndexedXYCollection.js.map +1 -1
- package/lib/cjs/geometry3d/IndexedXYZCollection.d.ts +58 -4
- package/lib/cjs/geometry3d/IndexedXYZCollection.d.ts.map +1 -1
- package/lib/cjs/geometry3d/IndexedXYZCollection.js +102 -4
- package/lib/cjs/geometry3d/IndexedXYZCollection.js.map +1 -1
- package/lib/cjs/geometry3d/Point2dArrayCarrier.d.ts +10 -0
- package/lib/cjs/geometry3d/Point2dArrayCarrier.d.ts.map +1 -1
- package/lib/cjs/geometry3d/Point2dArrayCarrier.js +14 -0
- package/lib/cjs/geometry3d/Point2dArrayCarrier.js.map +1 -1
- package/lib/cjs/geometry3d/Point3dArrayCarrier.d.ts +0 -6
- package/lib/cjs/geometry3d/Point3dArrayCarrier.d.ts.map +1 -1
- package/lib/cjs/geometry3d/Point3dArrayCarrier.js +0 -6
- package/lib/cjs/geometry3d/Point3dArrayCarrier.js.map +1 -1
- package/lib/cjs/geometry3d/Point3dVector3d.d.ts +1 -1
- package/lib/cjs/geometry3d/Point3dVector3d.js +2 -2
- package/lib/cjs/geometry3d/Point3dVector3d.js.map +1 -1
- package/lib/cjs/geometry3d/PointHelpers.d.ts +14 -1
- package/lib/cjs/geometry3d/PointHelpers.d.ts.map +1 -1
- package/lib/cjs/geometry3d/PointHelpers.js +33 -1
- package/lib/cjs/geometry3d/PointHelpers.js.map +1 -1
- package/lib/cjs/geometry3d/PolygonOps.d.ts +120 -10
- package/lib/cjs/geometry3d/PolygonOps.d.ts.map +1 -1
- package/lib/cjs/geometry3d/PolygonOps.js +412 -12
- package/lib/cjs/geometry3d/PolygonOps.js.map +1 -1
- package/lib/cjs/polyface/FacetLocationDetail.d.ts +264 -0
- package/lib/cjs/polyface/FacetLocationDetail.d.ts.map +1 -0
- package/lib/cjs/polyface/FacetLocationDetail.js +378 -0
- package/lib/cjs/polyface/FacetLocationDetail.js.map +1 -0
- package/lib/cjs/polyface/IndexedPolyfaceVisitor.d.ts +2 -5
- package/lib/cjs/polyface/IndexedPolyfaceVisitor.d.ts.map +1 -1
- package/lib/cjs/polyface/IndexedPolyfaceVisitor.js +5 -2
- package/lib/cjs/polyface/IndexedPolyfaceVisitor.js.map +1 -1
- package/lib/cjs/polyface/PolyfaceBuilder.d.ts +20 -14
- package/lib/cjs/polyface/PolyfaceBuilder.d.ts.map +1 -1
- package/lib/cjs/polyface/PolyfaceBuilder.js +21 -17
- package/lib/cjs/polyface/PolyfaceBuilder.js.map +1 -1
- package/lib/cjs/polyface/PolyfaceData.d.ts +1 -1
- package/lib/cjs/polyface/PolyfaceData.d.ts.map +1 -1
- package/lib/cjs/polyface/PolyfaceData.js.map +1 -1
- package/lib/cjs/polyface/PolyfaceQuery.d.ts +22 -1
- package/lib/cjs/polyface/PolyfaceQuery.d.ts.map +1 -1
- package/lib/cjs/polyface/PolyfaceQuery.js +52 -2
- package/lib/cjs/polyface/PolyfaceQuery.js.map +1 -1
- package/lib/cjs/solid/Sphere.d.ts +5 -5
- package/lib/cjs/solid/Sphere.js +5 -5
- package/lib/cjs/solid/Sphere.js.map +1 -1
- package/lib/esm/Geometry.d.ts +23 -0
- package/lib/esm/Geometry.d.ts.map +1 -1
- package/lib/esm/Geometry.js +24 -0
- package/lib/esm/Geometry.js.map +1 -1
- package/lib/esm/geometry3d/BarycentricTriangle.d.ts +195 -8
- package/lib/esm/geometry3d/BarycentricTriangle.d.ts.map +1 -1
- package/lib/esm/geometry3d/BarycentricTriangle.js +459 -12
- package/lib/esm/geometry3d/BarycentricTriangle.js.map +1 -1
- package/lib/esm/geometry3d/GrowableXYArray.d.ts +1 -1
- package/lib/esm/geometry3d/GrowableXYArray.d.ts.map +1 -1
- package/lib/esm/geometry3d/GrowableXYArray.js +1 -1
- package/lib/esm/geometry3d/GrowableXYArray.js.map +1 -1
- package/lib/esm/geometry3d/IndexedXYCollection.d.ts +22 -7
- package/lib/esm/geometry3d/IndexedXYCollection.d.ts.map +1 -1
- package/lib/esm/geometry3d/IndexedXYCollection.js +41 -5
- package/lib/esm/geometry3d/IndexedXYCollection.js.map +1 -1
- package/lib/esm/geometry3d/IndexedXYZCollection.d.ts +58 -4
- package/lib/esm/geometry3d/IndexedXYZCollection.d.ts.map +1 -1
- package/lib/esm/geometry3d/IndexedXYZCollection.js +103 -5
- package/lib/esm/geometry3d/IndexedXYZCollection.js.map +1 -1
- package/lib/esm/geometry3d/Point2dArrayCarrier.d.ts +10 -0
- package/lib/esm/geometry3d/Point2dArrayCarrier.d.ts.map +1 -1
- package/lib/esm/geometry3d/Point2dArrayCarrier.js +14 -0
- package/lib/esm/geometry3d/Point2dArrayCarrier.js.map +1 -1
- package/lib/esm/geometry3d/Point3dArrayCarrier.d.ts +0 -6
- package/lib/esm/geometry3d/Point3dArrayCarrier.d.ts.map +1 -1
- package/lib/esm/geometry3d/Point3dArrayCarrier.js +0 -6
- package/lib/esm/geometry3d/Point3dArrayCarrier.js.map +1 -1
- package/lib/esm/geometry3d/Point3dVector3d.d.ts +1 -1
- package/lib/esm/geometry3d/Point3dVector3d.js +2 -2
- package/lib/esm/geometry3d/Point3dVector3d.js.map +1 -1
- package/lib/esm/geometry3d/PointHelpers.d.ts +14 -1
- package/lib/esm/geometry3d/PointHelpers.d.ts.map +1 -1
- package/lib/esm/geometry3d/PointHelpers.js +33 -1
- package/lib/esm/geometry3d/PointHelpers.js.map +1 -1
- package/lib/esm/geometry3d/PolygonOps.d.ts +120 -10
- package/lib/esm/geometry3d/PolygonOps.d.ts.map +1 -1
- package/lib/esm/geometry3d/PolygonOps.js +411 -12
- package/lib/esm/geometry3d/PolygonOps.js.map +1 -1
- package/lib/esm/polyface/FacetLocationDetail.d.ts +264 -0
- package/lib/esm/polyface/FacetLocationDetail.d.ts.map +1 -0
- package/lib/esm/polyface/FacetLocationDetail.js +371 -0
- package/lib/esm/polyface/FacetLocationDetail.js.map +1 -0
- package/lib/esm/polyface/IndexedPolyfaceVisitor.d.ts +2 -5
- package/lib/esm/polyface/IndexedPolyfaceVisitor.d.ts.map +1 -1
- package/lib/esm/polyface/IndexedPolyfaceVisitor.js +5 -2
- package/lib/esm/polyface/IndexedPolyfaceVisitor.js.map +1 -1
- package/lib/esm/polyface/PolyfaceBuilder.d.ts +20 -14
- package/lib/esm/polyface/PolyfaceBuilder.d.ts.map +1 -1
- package/lib/esm/polyface/PolyfaceBuilder.js +21 -17
- package/lib/esm/polyface/PolyfaceBuilder.js.map +1 -1
- package/lib/esm/polyface/PolyfaceData.d.ts +1 -1
- package/lib/esm/polyface/PolyfaceData.d.ts.map +1 -1
- package/lib/esm/polyface/PolyfaceData.js.map +1 -1
- package/lib/esm/polyface/PolyfaceQuery.d.ts +22 -1
- package/lib/esm/polyface/PolyfaceQuery.d.ts.map +1 -1
- package/lib/esm/polyface/PolyfaceQuery.js +52 -2
- package/lib/esm/polyface/PolyfaceQuery.js.map +1 -1
- package/lib/esm/solid/Sphere.d.ts +5 -5
- package/lib/esm/solid/Sphere.js +5 -5
- package/lib/esm/solid/Sphere.js.map +1 -1
- package/package.json +4 -4
|
@@ -5,18 +5,75 @@
|
|
|
5
5
|
/** @packageDocumentation
|
|
6
6
|
* @module CartesianGeometry
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
8
|
+
import { assert } from "@itwin/core-bentley";
|
|
9
|
+
import { AxisOrder, Geometry, PolygonLocation } from "../Geometry";
|
|
9
10
|
import { Matrix4d } from "../geometry4d/Matrix4d";
|
|
10
11
|
import { Point4d } from "../geometry4d/Point4d";
|
|
11
12
|
import { XYParitySearchContext } from "../topology/XYParitySearchContext";
|
|
12
13
|
import { FrameBuilder } from "./FrameBuilder";
|
|
13
14
|
import { GrowableXYZArray } from "./GrowableXYZArray";
|
|
14
15
|
import { IndexedXYZCollection } from "./IndexedXYZCollection";
|
|
16
|
+
import { Matrix3d } from "./Matrix3d";
|
|
17
|
+
import { Plane3dByOriginAndUnitNormal } from "./Plane3dByOriginAndUnitNormal";
|
|
15
18
|
import { Point2d, Vector2d } from "./Point2dVector2d";
|
|
16
19
|
import { Point3dArrayCarrier } from "./Point3dArrayCarrier";
|
|
17
20
|
import { Point3d, Vector3d } from "./Point3dVector3d";
|
|
18
21
|
import { Ray3d } from "./Ray3d";
|
|
19
22
|
import { SortablePolygon } from "./SortablePolygon";
|
|
23
|
+
/**
|
|
24
|
+
* Carries data about a point in the plane of a polygon.
|
|
25
|
+
* @public
|
|
26
|
+
*/
|
|
27
|
+
export class PolygonLocationDetail {
|
|
28
|
+
constructor() {
|
|
29
|
+
this.point = new Point3d();
|
|
30
|
+
this.a = 0.0;
|
|
31
|
+
this.v = new Vector3d();
|
|
32
|
+
this.code = PolygonLocation.Unknown;
|
|
33
|
+
this.closestEdgeIndex = 0;
|
|
34
|
+
this.closestEdgeParam = 0.0;
|
|
35
|
+
}
|
|
36
|
+
/** Invalidate this detail. */
|
|
37
|
+
invalidate() {
|
|
38
|
+
this.point.setZero();
|
|
39
|
+
this.a = 0.0;
|
|
40
|
+
this.v.setZero();
|
|
41
|
+
this.code = PolygonLocation.Unknown;
|
|
42
|
+
this.closestEdgeIndex = 0;
|
|
43
|
+
this.closestEdgeParam = 0.0;
|
|
44
|
+
}
|
|
45
|
+
/** Create an invalid detail.
|
|
46
|
+
* @param result optional pre-allocated object to fill and return
|
|
47
|
+
*/
|
|
48
|
+
static create(result) {
|
|
49
|
+
if (undefined === result)
|
|
50
|
+
result = new PolygonLocationDetail();
|
|
51
|
+
else
|
|
52
|
+
result.invalidate();
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
/** Set the instance contents from the other detail.
|
|
56
|
+
* @param other detail to clone
|
|
57
|
+
*/
|
|
58
|
+
copyContentsFrom(other) {
|
|
59
|
+
this.point.setFrom(other.point);
|
|
60
|
+
this.a = other.a;
|
|
61
|
+
this.v.setFrom(other.v);
|
|
62
|
+
this.code = other.code;
|
|
63
|
+
this.closestEdgeIndex = other.closestEdgeIndex;
|
|
64
|
+
this.closestEdgeParam = other.closestEdgeParam;
|
|
65
|
+
}
|
|
66
|
+
/** Whether this detail is valid. */
|
|
67
|
+
get isValid() {
|
|
68
|
+
return this.code !== PolygonLocation.Unknown;
|
|
69
|
+
}
|
|
70
|
+
/** Whether this instance specifies a location inside or on the polygon. */
|
|
71
|
+
get isInsideOrOn() {
|
|
72
|
+
return this.code === PolygonLocation.InsidePolygon ||
|
|
73
|
+
this.code === PolygonLocation.OnPolygonVertex || this.code === PolygonLocation.OnPolygonEdgeInterior ||
|
|
74
|
+
this.code === PolygonLocation.InsidePolygonProjectsToVertex || this.code === PolygonLocation.InsidePolygonProjectsToEdgeInterior;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
20
77
|
/**
|
|
21
78
|
* Carrier for a loop extracted from clip operation, annotated for sorting
|
|
22
79
|
* @internal
|
|
@@ -533,29 +590,27 @@ export class PolygonOps {
|
|
|
533
590
|
}
|
|
534
591
|
}
|
|
535
592
|
/** Test the direction of turn at the vertices of the polygon, ignoring z-coordinates.
|
|
536
|
-
*
|
|
537
|
-
* *
|
|
538
|
-
* all
|
|
539
|
-
* * Beware that a polygon which turns through more than a full turn can cross itself and close, but is not convex
|
|
540
|
-
* * Returns 1 if all turns are to the left, -1 if all to the right, and 0 if there are any zero or reverse turns
|
|
593
|
+
* * For a polygon without self-intersections and successive colinear edges, this is a convexity and orientation test: all positive is convex and counterclockwise, all negative is convex and clockwise.
|
|
594
|
+
* * Beware that a polygon which turns through more than a full turn can cross itself and close, but is not convex.
|
|
595
|
+
* @returns 1 if all turns are to the left, -1 if all to the right, and 0 if there are any zero or reverse turns
|
|
541
596
|
*/
|
|
542
|
-
static testXYPolygonTurningDirections(
|
|
597
|
+
static testXYPolygonTurningDirections(points) {
|
|
543
598
|
// Reduce count by trailing duplicates; leaves iLast at final index
|
|
544
|
-
let numPoint =
|
|
599
|
+
let numPoint = points.length;
|
|
545
600
|
let iLast = numPoint - 1;
|
|
546
|
-
while (iLast > 1 &&
|
|
601
|
+
while (iLast > 1 && points[iLast].x === points[0].x && points[iLast].y === points[0].y) {
|
|
547
602
|
numPoint = iLast--;
|
|
548
603
|
}
|
|
549
604
|
if (numPoint > 2) {
|
|
550
|
-
let vector0 = Point2d.create(
|
|
551
|
-
const vector1 = Point2d.create(
|
|
605
|
+
let vector0 = Point2d.create(points[iLast].x - points[iLast - 1].x, points[iLast].y - points[iLast - 1].y);
|
|
606
|
+
const vector1 = Point2d.create(points[0].x - points[iLast].x, points[0].y - points[iLast].y);
|
|
552
607
|
const baseArea = vector0.x * vector1.y - vector0.y * vector1.x;
|
|
553
608
|
// In a convex polygon, all successive-vector cross products will
|
|
554
609
|
// have the same sign as the base area, hence all products will be
|
|
555
610
|
// positive.
|
|
556
611
|
for (let i1 = 1; i1 < numPoint; i1++) {
|
|
557
612
|
vector0 = vector1.clone();
|
|
558
|
-
Point2d.create(
|
|
613
|
+
Point2d.create(points[i1].x - points[i1 - 1].x, points[i1].y - points[i1 - 1].y, vector1);
|
|
559
614
|
const currArea = vector0.x * vector1.y - vector0.y * vector1.x;
|
|
560
615
|
if (currArea * baseArea <= 0.0)
|
|
561
616
|
return 0;
|
|
@@ -565,6 +620,36 @@ export class PolygonOps {
|
|
|
565
620
|
}
|
|
566
621
|
return 0;
|
|
567
622
|
}
|
|
623
|
+
/**
|
|
624
|
+
* Determine whether the polygon is convex.
|
|
625
|
+
* @param polygon vertices, closure point optional
|
|
626
|
+
* @returns whether the polygon is convex.
|
|
627
|
+
*/
|
|
628
|
+
static isConvex(polygon) {
|
|
629
|
+
if (!(polygon instanceof IndexedXYZCollection))
|
|
630
|
+
return this.isConvex(new Point3dArrayCarrier(polygon));
|
|
631
|
+
let n = polygon.length;
|
|
632
|
+
if (n > 1 && polygon.getPoint3dAtUncheckedPointIndex(0).isExactEqual(polygon.getPoint3dAtUncheckedPointIndex(n - 1)))
|
|
633
|
+
--n; // ignore closure point
|
|
634
|
+
const normal = Vector3d.create();
|
|
635
|
+
if (!this.unitNormal(polygon, normal))
|
|
636
|
+
return false;
|
|
637
|
+
let positiveArea = 0.0;
|
|
638
|
+
let negativeArea = 0.0;
|
|
639
|
+
const vecA = this._vector0;
|
|
640
|
+
let vecB = Vector3d.createStartEnd(polygon.getPoint3dAtUncheckedPointIndex(n - 1), polygon.getPoint3dAtUncheckedPointIndex(0), this._vector1);
|
|
641
|
+
for (let i = 1; i <= n; i++) {
|
|
642
|
+
// check turn through vertices i-1,i,i+1
|
|
643
|
+
vecA.setFromVector3d(vecB);
|
|
644
|
+
vecB = Vector3d.createStartEnd(polygon.getPoint3dAtUncheckedPointIndex(i - 1), polygon.getPoint3dAtUncheckedPointIndex(i % n), vecB);
|
|
645
|
+
const signedArea = normal.tripleProduct(vecA, vecB);
|
|
646
|
+
if (signedArea >= 0.0)
|
|
647
|
+
positiveArea += signedArea;
|
|
648
|
+
else
|
|
649
|
+
negativeArea += signedArea;
|
|
650
|
+
}
|
|
651
|
+
return Math.abs(negativeArea) < Geometry.smallMetricDistanceSquared * positiveArea;
|
|
652
|
+
}
|
|
568
653
|
/**
|
|
569
654
|
* Test if point (x,y) is IN, OUT or ON a polygon.
|
|
570
655
|
* @return (1) for in, (-1) for OUT, (0) for ON
|
|
@@ -712,6 +797,320 @@ export class PolygonOps {
|
|
|
712
797
|
}
|
|
713
798
|
return sortedLoopsArray;
|
|
714
799
|
}
|
|
800
|
+
/** Compute the closest point on the polygon boundary to the given point.
|
|
801
|
+
* @param polygon points of the polygon, closure point optional
|
|
802
|
+
* @param testPoint point p to project onto the polygon edges. Works best when p is in the plane of the polygon.
|
|
803
|
+
* @param tolerance optional distance tolerance to determine point-vertex and point-edge coincidence.
|
|
804
|
+
* @param result optional pre-allocated object to fill and return
|
|
805
|
+
* @returns details d of the closest point `d.point`:
|
|
806
|
+
* * `d.isValid()` returns true if and only if the polygon is nontrivial.
|
|
807
|
+
* * `d.edgeIndex` and `d.edgeParam` specify the location of the closest point, within `distTol`.
|
|
808
|
+
* * `d.code` classifies the closest point as a vertex (`PolygonLocation.OnPolygonVertex`) or as a point on an edge (`PolygonLocation.OnPolygonEdgeInterior`).
|
|
809
|
+
* * `d.a` is the distance from testPoint to the closest point.
|
|
810
|
+
* * `d.v` can be used to classify p (if p and polygon are coplanar): if n is the polygon normal then `d.v.dotProduct(n)` is +/-/0 if and only if p is inside/outside/on the polygon.
|
|
811
|
+
*/
|
|
812
|
+
static closestPointOnBoundary(polygon, testPoint, tolerance = Geometry.smallMetricDistance, result) {
|
|
813
|
+
if (!(polygon instanceof IndexedXYZCollection))
|
|
814
|
+
return this.closestPointOnBoundary(new Point3dArrayCarrier(polygon), testPoint, tolerance, result);
|
|
815
|
+
const distTol2 = tolerance * tolerance;
|
|
816
|
+
let numPoints = polygon.length;
|
|
817
|
+
while (numPoints > 1) {
|
|
818
|
+
if (polygon.distanceSquaredIndexIndex(0, numPoints - 1) > distTol2)
|
|
819
|
+
break;
|
|
820
|
+
--numPoints; // ignore closure point
|
|
821
|
+
}
|
|
822
|
+
result = PolygonLocationDetail.create(result);
|
|
823
|
+
if (0 === numPoints)
|
|
824
|
+
return result; // invalid
|
|
825
|
+
if (1 === numPoints) {
|
|
826
|
+
polygon.getPoint3dAtUncheckedPointIndex(0, result.point);
|
|
827
|
+
result.a = result.point.distance(testPoint);
|
|
828
|
+
result.v.setZero();
|
|
829
|
+
result.code = PolygonLocation.OnPolygonVertex;
|
|
830
|
+
result.closestEdgeIndex = 0;
|
|
831
|
+
result.closestEdgeParam = 0.0;
|
|
832
|
+
return result;
|
|
833
|
+
}
|
|
834
|
+
let iPrev = numPoints - 1;
|
|
835
|
+
let minDist2 = Geometry.largeCoordinateResult;
|
|
836
|
+
for (let iBase = 0; iBase < numPoints; ++iBase) {
|
|
837
|
+
let iNext = iBase + 1;
|
|
838
|
+
if (iNext === numPoints)
|
|
839
|
+
iNext = 0;
|
|
840
|
+
const uDotU = polygon.distanceSquaredIndexIndex(iBase, iNext);
|
|
841
|
+
if (uDotU <= distTol2)
|
|
842
|
+
continue; // ignore trivial polygon edge (keep iPrev)
|
|
843
|
+
const vDotV = polygon.distanceSquaredIndexXYAndZ(iBase, testPoint);
|
|
844
|
+
const uDotV = polygon.dotProductIndexIndexXYAndZ(iBase, iNext, testPoint);
|
|
845
|
+
const edgeParam = uDotV / uDotU; // param of projection of testPoint onto this edge
|
|
846
|
+
if (edgeParam <= 0.0) { // testPoint projects to/before edge start
|
|
847
|
+
const distToStart2 = vDotV;
|
|
848
|
+
if (distToStart2 <= distTol2) {
|
|
849
|
+
// testPoint is at edge start; we are done
|
|
850
|
+
polygon.getPoint3dAtUncheckedPointIndex(iBase, result.point);
|
|
851
|
+
result.a = Math.sqrt(distToStart2);
|
|
852
|
+
result.v.setZero();
|
|
853
|
+
result.code = PolygonLocation.OnPolygonVertex;
|
|
854
|
+
result.closestEdgeIndex = iBase;
|
|
855
|
+
result.closestEdgeParam = 0.0;
|
|
856
|
+
return result;
|
|
857
|
+
}
|
|
858
|
+
if (distToStart2 < minDist2) {
|
|
859
|
+
if (polygon.dotProductIndexIndexXYAndZ(iBase, iPrev, testPoint) <= 0.0) {
|
|
860
|
+
// update candidate (to edge start) only if previous edge was NOOP
|
|
861
|
+
polygon.getPoint3dAtUncheckedPointIndex(iBase, result.point);
|
|
862
|
+
result.a = Math.sqrt(distToStart2);
|
|
863
|
+
polygon.crossProductIndexIndexIndex(iBase, iPrev, iNext, result.v);
|
|
864
|
+
result.code = PolygonLocation.OnPolygonVertex;
|
|
865
|
+
result.closestEdgeIndex = iBase;
|
|
866
|
+
result.closestEdgeParam = 0.0;
|
|
867
|
+
minDist2 = distToStart2;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
else if (edgeParam <= 1.0) { // testPoint projects inside edge, or to edge end
|
|
872
|
+
const projDist2 = vDotV - edgeParam * edgeParam * uDotU;
|
|
873
|
+
if (projDist2 <= distTol2) {
|
|
874
|
+
// testPoint is on edge; we are done
|
|
875
|
+
const distToStart2 = vDotV;
|
|
876
|
+
if (edgeParam <= 0.5 && distToStart2 <= distTol2) {
|
|
877
|
+
// testPoint is at edge start
|
|
878
|
+
polygon.getPoint3dAtUncheckedPointIndex(iBase, result.point);
|
|
879
|
+
result.a = Math.sqrt(distToStart2);
|
|
880
|
+
result.v.setZero();
|
|
881
|
+
result.code = PolygonLocation.OnPolygonVertex;
|
|
882
|
+
result.closestEdgeIndex = iBase;
|
|
883
|
+
result.closestEdgeParam = 0.0;
|
|
884
|
+
return result;
|
|
885
|
+
}
|
|
886
|
+
const distToEnd2 = projDist2 + (1.0 - edgeParam) * (1.0 - edgeParam) * uDotU;
|
|
887
|
+
if (edgeParam > 0.5 && distToEnd2 <= distTol2) {
|
|
888
|
+
// testPoint is at edge end
|
|
889
|
+
polygon.getPoint3dAtUncheckedPointIndex(iNext, result.point);
|
|
890
|
+
result.a = Math.sqrt(distToEnd2);
|
|
891
|
+
result.v.setZero();
|
|
892
|
+
result.code = PolygonLocation.OnPolygonVertex;
|
|
893
|
+
result.closestEdgeIndex = iNext;
|
|
894
|
+
result.closestEdgeParam = 0.0;
|
|
895
|
+
return result;
|
|
896
|
+
}
|
|
897
|
+
// testPoint is on edge interior
|
|
898
|
+
polygon.interpolateIndexIndex(iBase, edgeParam, iNext, result.point);
|
|
899
|
+
result.a = Math.sqrt(projDist2);
|
|
900
|
+
result.v.setZero();
|
|
901
|
+
result.code = PolygonLocation.OnPolygonEdgeInterior;
|
|
902
|
+
result.closestEdgeIndex = iBase;
|
|
903
|
+
result.closestEdgeParam = edgeParam;
|
|
904
|
+
return result;
|
|
905
|
+
}
|
|
906
|
+
if (projDist2 < minDist2) {
|
|
907
|
+
// update candidate (to edge interior)
|
|
908
|
+
polygon.interpolateIndexIndex(iBase, edgeParam, iNext, result.point);
|
|
909
|
+
result.a = Math.sqrt(projDist2);
|
|
910
|
+
polygon.crossProductIndexIndexXYAndZ(iBase, iNext, testPoint, result.v);
|
|
911
|
+
result.code = PolygonLocation.OnPolygonEdgeInterior;
|
|
912
|
+
result.closestEdgeIndex = iBase;
|
|
913
|
+
result.closestEdgeParam = edgeParam;
|
|
914
|
+
minDist2 = projDist2;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
else { // edgeParam > 1.0
|
|
918
|
+
// NOOP: testPoint projects beyond edge end, handled by next edge
|
|
919
|
+
}
|
|
920
|
+
iPrev = iBase;
|
|
921
|
+
}
|
|
922
|
+
return result;
|
|
923
|
+
}
|
|
924
|
+
/** Compute the intersection of a line (parameterized as a ray) with the plane of this polygon.
|
|
925
|
+
* @param polygon points of the polygon, closure point optional
|
|
926
|
+
* @param ray infinite line to intersect, as a ray
|
|
927
|
+
* @param tolerance optional distance tolerance to determine point-vertex and point-edge coincidence.
|
|
928
|
+
* @param result optional pre-allocated object to fill and return
|
|
929
|
+
* @returns details d of the line-plane intersection `d.point`:
|
|
930
|
+
* * `d.isValid()` returns true if and only if the line intersects the plane.
|
|
931
|
+
* * `d.code` indicates where the intersection lies with respect to the polygon.
|
|
932
|
+
* * `d.a` is the ray intersection parameter. If `d.a` >= 0, the ray intersects the plane of the polygon.
|
|
933
|
+
* * `d.edgeIndex` and `d.edgeParam` specify the location of the closest point on the polygon to the intersection, within `distTol`.
|
|
934
|
+
*/
|
|
935
|
+
static intersectRay3d(polygon, ray, tolerance = Geometry.smallMetricDistance, result) {
|
|
936
|
+
if (!(polygon instanceof IndexedXYZCollection))
|
|
937
|
+
return this.intersectRay3d(new Point3dArrayCarrier(polygon), ray, tolerance, result);
|
|
938
|
+
if (!this.unitNormal(polygon, this._normal))
|
|
939
|
+
return PolygonLocationDetail.create(result); // invalid
|
|
940
|
+
this._workPlane = Plane3dByOriginAndUnitNormal.createXYZUVW(polygon.getXAtUncheckedPointIndex(0), polygon.getYAtUncheckedPointIndex(0), polygon.getZAtUncheckedPointIndex(0), this._normal.x, this._normal.y, this._normal.z, this._workPlane);
|
|
941
|
+
const intersectionPoint = Point3d.createZero(this._workXYZ);
|
|
942
|
+
const rayParam = ray.intersectionWithPlane(this._workPlane, intersectionPoint);
|
|
943
|
+
if (undefined === rayParam)
|
|
944
|
+
return PolygonLocationDetail.create(result);
|
|
945
|
+
result = this.closestPointOnBoundary(polygon, intersectionPoint, tolerance, result);
|
|
946
|
+
if (result.isValid) {
|
|
947
|
+
result.point.setFrom(intersectionPoint);
|
|
948
|
+
result.a = rayParam;
|
|
949
|
+
const dot = result.v.dotProduct(this._normal);
|
|
950
|
+
if (dot === 0.0) {
|
|
951
|
+
// NOOP: intersectionPoint is on the polygon, so result.code already classifies it
|
|
952
|
+
}
|
|
953
|
+
else {
|
|
954
|
+
// intersectionPoint is not on polygon, so result.code refers to the closest point. Update it to refer to intersectionPoint.
|
|
955
|
+
if (PolygonLocation.OnPolygonVertex === result.code)
|
|
956
|
+
result.code = (dot > 0.0) ? PolygonLocation.InsidePolygonProjectsToVertex : PolygonLocation.OutsidePolygonProjectsToVertex;
|
|
957
|
+
else if (PolygonLocation.OnPolygonEdgeInterior === result.code)
|
|
958
|
+
result.code = (dot > 0.0) ? PolygonLocation.InsidePolygonProjectsToEdgeInterior : PolygonLocation.OutsidePolygonProjectsToEdgeInterior;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
return result;
|
|
962
|
+
}
|
|
963
|
+
/** Compute the intersection of a line (parameterized as a line segment) with the plane of this polygon.
|
|
964
|
+
* @param polygon points of the polygon, closure point optional
|
|
965
|
+
* @param point0 start point of segment on line to intersect
|
|
966
|
+
* @param point1 end point of segment on line to intersect
|
|
967
|
+
* @param tolerance optional distance tolerance to determine point-vertex and point-edge coincidence.
|
|
968
|
+
* @param result optional pre-allocated object to fill and return
|
|
969
|
+
* @returns details d of the line-plane intersection `d.point`:
|
|
970
|
+
* * `d.isValid()` returns true if and only if the line intersects the plane.
|
|
971
|
+
* * `d.code` indicates where the intersection lies with respect to the polygon.
|
|
972
|
+
* * `d.a` is the segment intersection parameter. If `d.a` is in [0,1], the segment intersects the plane of the polygon.
|
|
973
|
+
* * `d.edgeIndex` and `d.edgeParam` specify the location of the closest point on the polygon to the intersection, within `distTol`.
|
|
974
|
+
* @see intersectRay3d
|
|
975
|
+
*/
|
|
976
|
+
static intersectSegment(polygon, point0, point1, tolerance = Geometry.smallMetricDistance, result) {
|
|
977
|
+
this._workRay = Ray3d.createStartEnd(point0, point1, this._workRay);
|
|
978
|
+
return this.intersectRay3d(polygon, this._workRay, tolerance, result);
|
|
979
|
+
}
|
|
980
|
+
/** Compute edge data for the barycentric coordinate computation, ignoring all z-coordinates.
|
|
981
|
+
* @param polygon points of the polygon (without closure point)
|
|
982
|
+
* @param edgeStartVertexIndex index of start vertex of the edge (unchecked)
|
|
983
|
+
* @param point point to project to the edge
|
|
984
|
+
* @param edgeOutwardUnitNormal pre-allocated vector to be populated on return with the unit perpendicular to the edge, facing outward, in xy-plane
|
|
985
|
+
* @param tolerance used to clamp outputs
|
|
986
|
+
* @param result optional pre-allocated result
|
|
987
|
+
* @returns x: signed projection distance of `point` to the edge, y: edge parameter of the projection
|
|
988
|
+
*/
|
|
989
|
+
static computeEdgeDataXY(polygon, edgeStartVertexIndex, point, edgeOutwardUnitNormal, tolerance = Geometry.smallMetricDistance, result) {
|
|
990
|
+
const i0 = edgeStartVertexIndex % polygon.length;
|
|
991
|
+
const i1 = (i0 + 1) % polygon.length;
|
|
992
|
+
polygon.vectorIndexIndex(i0, i1, edgeOutwardUnitNormal).unitPerpendicularXY(edgeOutwardUnitNormal).negate(edgeOutwardUnitNormal); // z is zero
|
|
993
|
+
const hypDeltaX = polygon.getXAtUncheckedPointIndex(i0) - point.x;
|
|
994
|
+
const hypDeltaY = polygon.getYAtUncheckedPointIndex(i0) - point.y;
|
|
995
|
+
let projDist = Geometry.dotProductXYXY(hypDeltaX, hypDeltaY, edgeOutwardUnitNormal.x, edgeOutwardUnitNormal.y);
|
|
996
|
+
const edgeDist = Geometry.crossProductXYXY(hypDeltaX, hypDeltaY, edgeOutwardUnitNormal.x, edgeOutwardUnitNormal.y);
|
|
997
|
+
const edgeLength = Geometry.distanceXYXY(polygon.getXAtUncheckedPointIndex(i0), polygon.getYAtUncheckedPointIndex(i0), polygon.getXAtUncheckedPointIndex(i1), polygon.getYAtUncheckedPointIndex(i1));
|
|
998
|
+
let edgeParam = Geometry.safeDivideFraction(edgeDist, edgeLength, 0.0);
|
|
999
|
+
if (Geometry.isSameCoordinate(0.0, projDist, tolerance))
|
|
1000
|
+
projDist = 0.0;
|
|
1001
|
+
if (Geometry.isSameCoordinate(0.0, edgeParam, tolerance))
|
|
1002
|
+
edgeParam = 0.0;
|
|
1003
|
+
else if (Geometry.isSameCoordinate(1.0, edgeParam, tolerance))
|
|
1004
|
+
edgeParam = 1.0;
|
|
1005
|
+
return Point2d.create(projDist, edgeParam, result);
|
|
1006
|
+
}
|
|
1007
|
+
/** Compute the barycentric coordinates for a point on either of a pair of adjacent edges of a convex polygon.
|
|
1008
|
+
* @param polygon points of the polygon, assumed to be convex. Assumed to have no closure point.
|
|
1009
|
+
* @param iPrev start index of previous edge
|
|
1010
|
+
* @param prevNormal outward unit normal of previous edge
|
|
1011
|
+
* @param prevProj x = signed distance from point to previous edge; y = edge parameter of this projection in [0,1]
|
|
1012
|
+
* @param i start index of current edge
|
|
1013
|
+
* @param normal outward unit normal of current edge
|
|
1014
|
+
* @param proj x = signed distance from point to current edge; y = edge parameter of this projection in [0,1]
|
|
1015
|
+
* @param coords pre-allocated barycentric coordinate array to return, assumed to have length at least `polygon.length`
|
|
1016
|
+
* @returns barycentric coordinates, or undefined if not on either edge
|
|
1017
|
+
*/
|
|
1018
|
+
static convexBarycentricCoordinatesOnEdge(polygon, iPrev, prevNormal, prevProj, i, normal, proj, coords) {
|
|
1019
|
+
// ignore degenerate edges
|
|
1020
|
+
const pointIsOnPrevEdge = !prevNormal.isZero && (0.0 === prevProj.x) && Geometry.isIn01(prevProj.y);
|
|
1021
|
+
const pointIsOnEdge = !normal.isZero && (0.0 === proj.x) && Geometry.isIn01(proj.y);
|
|
1022
|
+
if (pointIsOnPrevEdge && pointIsOnEdge) { // the point is at vertex i
|
|
1023
|
+
coords.fill(0);
|
|
1024
|
+
coords[i] = 1.0;
|
|
1025
|
+
return coords;
|
|
1026
|
+
}
|
|
1027
|
+
const n = polygon.length;
|
|
1028
|
+
if (pointIsOnPrevEdge) { // the point is on the previous edge
|
|
1029
|
+
coords.fill(0);
|
|
1030
|
+
const i0 = iPrev;
|
|
1031
|
+
const i1 = i;
|
|
1032
|
+
const edgeParam = prevProj.y;
|
|
1033
|
+
coords[i0] = 1.0 - edgeParam;
|
|
1034
|
+
coords[i1] = edgeParam;
|
|
1035
|
+
return coords;
|
|
1036
|
+
}
|
|
1037
|
+
if (pointIsOnEdge) { // the point is on the edge starting at the i_th vertex
|
|
1038
|
+
coords.fill(0);
|
|
1039
|
+
const i0 = i;
|
|
1040
|
+
const i1 = (i + 1) % n;
|
|
1041
|
+
const edgeParam = proj.y;
|
|
1042
|
+
coords[i0] = 1.0 - edgeParam;
|
|
1043
|
+
coords[i1] = edgeParam;
|
|
1044
|
+
return coords;
|
|
1045
|
+
}
|
|
1046
|
+
return undefined; // not on edge
|
|
1047
|
+
}
|
|
1048
|
+
// cspell:word CAGD
|
|
1049
|
+
/** Compute the barycentric coordinates for a point inside a convex polygon.
|
|
1050
|
+
* @param polygon points of the polygon, assumed to be convex. Closure point optional.
|
|
1051
|
+
* @param point point assumed to be inside or on polygon
|
|
1052
|
+
* @param tolerance distance tolerance for point to be considered on a polygon edge
|
|
1053
|
+
* @return barycentric coordinates of the interior point, or undefined if invalid polygon or exterior point. Length is same as `polygon.length`.
|
|
1054
|
+
* @see BarycentricTriangle.pointToFraction
|
|
1055
|
+
*/
|
|
1056
|
+
static convexBarycentricCoordinates(polygon, point, tolerance = Geometry.smallMetricDistance) {
|
|
1057
|
+
// cf. "Barycentric Coordinates for Convex Sets", by Warren et al., CAGD (2003)
|
|
1058
|
+
if (Array.isArray(polygon))
|
|
1059
|
+
return this.convexBarycentricCoordinates(new Point3dArrayCarrier(polygon), point);
|
|
1060
|
+
let n = polygon.length;
|
|
1061
|
+
while (n > 1 && polygon.getPoint3dAtUncheckedPointIndex(0).isExactEqual(polygon.getPoint3dAtUncheckedPointIndex(n - 1)))
|
|
1062
|
+
--n; // ignore closure point(s)
|
|
1063
|
+
if (n < 3 || !PolygonOps.unitNormal(polygon, this._normal))
|
|
1064
|
+
return undefined;
|
|
1065
|
+
const localToWorld = this._workMatrix3d = Matrix3d.createRigidHeadsUp(this._normal, AxisOrder.ZXY, this._workMatrix3d);
|
|
1066
|
+
const polygonXY = new GrowableXYZArray(n);
|
|
1067
|
+
for (let i = 0; i < n; ++i)
|
|
1068
|
+
polygonXY.push(localToWorld.multiplyInverseXYZAsPoint3d(polygon.getXAtUncheckedPointIndex(i), polygon.getYAtUncheckedPointIndex(i), polygon.getZAtUncheckedPointIndex(i), this._workXYZ));
|
|
1069
|
+
const pointXY = this._workXYZ = localToWorld.multiplyInverseXYZAsPoint3d(point.x, point.y, point.z, this._workXYZ);
|
|
1070
|
+
// now we know polygon orientation is ccw, its last edge has positive length, and we can ignore z-coords
|
|
1071
|
+
let iPrev = n - 1;
|
|
1072
|
+
const outwardUnitNormalOfLastEdge = this._vector0;
|
|
1073
|
+
const projToLastEdge = this._workXY0 = this.computeEdgeDataXY(polygonXY, iPrev, pointXY, outwardUnitNormalOfLastEdge, tolerance, this._workXY0);
|
|
1074
|
+
// we can compare to exact zero because computeEdgeDataXY has chopped small distances to zero
|
|
1075
|
+
if (projToLastEdge.x < 0.0)
|
|
1076
|
+
return undefined; // point is outside polygon, or polygon is nonconvex
|
|
1077
|
+
const outwardUnitNormalOfPrevEdge = Vector3d.createFrom(outwardUnitNormalOfLastEdge, this._vector1);
|
|
1078
|
+
const projToPrevEdge = this._workXY1 = Point2d.createFrom(projToLastEdge, this._workXY1);
|
|
1079
|
+
const coords = Array(polygon.length).fill(0); // use original length
|
|
1080
|
+
const largestResult = (tolerance > 0.0) ? 1.0 / (tolerance * tolerance) : Geometry.largeCoordinateResult;
|
|
1081
|
+
let coordSum = 0.0;
|
|
1082
|
+
for (let i = 0; i < n; ++i) {
|
|
1083
|
+
const outwardUnitNormalOfEdge = Vector3d.createFrom(outwardUnitNormalOfLastEdge, this._vector2);
|
|
1084
|
+
const projToEdge = this._workXY2 = (i < n - 1) ? this.computeEdgeDataXY(polygonXY, i, pointXY, outwardUnitNormalOfEdge, tolerance, this._workXY2) : Point2d.createFrom(projToLastEdge, this._workXY2);
|
|
1085
|
+
if (projToEdge.x < 0.0)
|
|
1086
|
+
return undefined; // point is outside polygon, or polygon is nonconvex
|
|
1087
|
+
if (undefined !== this.convexBarycentricCoordinatesOnEdge(polygonXY, iPrev, outwardUnitNormalOfPrevEdge, projToPrevEdge, i, outwardUnitNormalOfEdge, projToEdge, coords))
|
|
1088
|
+
return coords; // point is on vertex or edge; we are done
|
|
1089
|
+
if (outwardUnitNormalOfEdge.x === 0.0 && outwardUnitNormalOfEdge.y === 0.0)
|
|
1090
|
+
continue; // edge is degenerate; coords[i] = 0; keep previous edge data
|
|
1091
|
+
if (0.0 === projToPrevEdge.x || 0.0 === projToEdge.x)
|
|
1092
|
+
continue; // point is on subsequent colinear edge (ASSUMING interior point, convex polygon!); coords[i] = 0; keep previous edge data
|
|
1093
|
+
const areaOfNormalParallelogram = Math.abs(outwardUnitNormalOfPrevEdge.crossProductXY(outwardUnitNormalOfEdge));
|
|
1094
|
+
const coord = Geometry.conditionalDivideCoordinate(areaOfNormalParallelogram, projToPrevEdge.x * projToEdge.x, largestResult);
|
|
1095
|
+
if (undefined === coord) {
|
|
1096
|
+
assert(!"unexpectedly small projection distance to an edge");
|
|
1097
|
+
return undefined; // shouldn't happen due to chopping in computeEdgeDataXY: area/(dist*dist) <= 1/tol^2 = largestResult
|
|
1098
|
+
}
|
|
1099
|
+
coords[i] = coord;
|
|
1100
|
+
coordSum += coord;
|
|
1101
|
+
outwardUnitNormalOfPrevEdge.setFrom(outwardUnitNormalOfEdge);
|
|
1102
|
+
projToPrevEdge.setFrom(projToEdge);
|
|
1103
|
+
iPrev = i;
|
|
1104
|
+
}
|
|
1105
|
+
const scale = Geometry.conditionalDivideCoordinate(1.0, coordSum);
|
|
1106
|
+
if (undefined === scale) {
|
|
1107
|
+
assert(!"unexpected zero barycentric coordinate sum");
|
|
1108
|
+
return undefined;
|
|
1109
|
+
}
|
|
1110
|
+
for (let i = 0; i < n; ++i)
|
|
1111
|
+
coords[i] *= scale; // normalized
|
|
1112
|
+
return coords;
|
|
1113
|
+
}
|
|
715
1114
|
}
|
|
716
1115
|
/** These values are the integrated area moment products [xx,xy,xz, x]
|
|
717
1116
|
* for a right triangle in the first quadrant at the origin -- (0,0),(1,0),(0,1)
|