pcut_uwp/PCUT/DeepNestLib.Core/GeometryUtil.cs

1448 lines
51 KiB
C#
Raw Permalink Normal View History

2024-08-21 16:02:56 +00:00
using System;
using System.Collections.Generic;
using System.Linq;
namespace DeepNestLib
{
public class GeometryUtil
{
// returns true if points are within the given distance
public static bool _withinDistance(SvgPoint p1, SvgPoint p2, double distance)
{
var dx = p1.x - p2.x;
var dy = p1.y - p2.y;
return ((dx * dx + dy * dy) < distance * distance);
}
// returns an interior NFP for the special case where A is a rectangle
public static NFP[] noFitPolygonRectangle(NFP A, NFP B)
{
var minAx = A[0].x;
var minAy = A[0].y;
var maxAx = A[0].x;
var maxAy = A[0].y;
for (var i = 1; i < A.Length; i++)
{
if (A[i].x < minAx)
{
minAx = A[i].x;
}
if (A[i].y < minAy)
{
minAy = A[i].y;
}
if (A[i].x > maxAx)
{
maxAx = A[i].x;
}
if (A[i].y > maxAy)
{
maxAy = A[i].y;
}
}
var minBx = B[0].x;
var minBy = B[0].y;
var maxBx = B[0].x;
var maxBy = B[0].y;
for (int i = 1; i < B.Length; i++)
{
if (B[i].x < minBx)
{
minBx = B[i].x;
}
if (B[i].y < minBy)
{
minBy = B[i].y;
}
if (B[i].x > maxBx)
{
maxBx = B[i].x;
}
if (B[i].y > maxBy)
{
maxBy = B[i].y;
}
}
if (maxBx - minBx > maxAx - minAx)
{
return null;
}
if (maxBy - minBy > maxAy - minAy)
{
return null;
}
var pnts = new NFP[] { new NFP() { Points=new SvgPoint[]{
new SvgPoint(minAx - minBx + B[0].x, minAy - minBy + B[0].y),
new SvgPoint(maxAx - maxBx + B[0].x, minAy - minBy + B[0].y),
new SvgPoint( maxAx - maxBx + B[0].x, maxAy - maxBy + B[0].y),
new SvgPoint( minAx - minBx + B[0].x, maxAy - maxBy + B[0].y)
} } };
return pnts;
}
// returns the rectangular bounding box of the given polygon
public static PolygonBounds getPolygonBounds(NFP _polygon)
{
return getPolygonBounds(_polygon.Points);
}
public static PolygonBounds getPolygonBounds(List<SvgPoint
> polygon)
{
return getPolygonBounds(polygon.ToArray());
}
public static PolygonBounds getPolygonBounds(SvgPoint[] polygon)
{
if (polygon == null || polygon.Count() < 3)
{
throw new ArgumentException("null");
}
var xmin = polygon[0].x;
var xmax = polygon[0].x;
var ymin = polygon[0].y;
var ymax = polygon[0].y;
for (var i = 1; i < polygon.Length; i++)
{
if (polygon[i].x > xmax)
{
xmax = polygon[i].x;
}
else if (polygon[i].x < xmin)
{
xmin = polygon[i].x;
}
if (polygon[i].y > ymax)
{
ymax = polygon[i].y;
}
else if (polygon[i].y < ymin)
{
ymin = polygon[i].y;
}
}
var w = xmax - xmin;
var h = ymax - ymin;
//return new rectanglef(xmin, ymin, xmax - xmin, ymax - ymin);
return new PolygonBounds(xmin, ymin, w, h);
}
public static bool isRectangle(NFP poly, double? tolerance = null)
{
var bb = getPolygonBounds(poly);
if (tolerance == null)
{
tolerance = TOL;
}
for (var i = 0; i < poly.Points.Length; i++)
{
if (!_almostEqual(poly.Points[i].x, bb.x) && !_almostEqual(poly.Points[i].x, bb.x + bb.width))
{
return false;
}
if (!_almostEqual(poly.Points[i].y, bb.y) && !_almostEqual(poly.Points[i].y, bb.y + bb.height))
{
return false;
}
}
return true;
}
public static PolygonWithBounds rotatePolygon(NFP polygon, float angle)
{
List<SvgPoint> rotated = new List<SvgPoint>();
angle = (float)(angle * Math.PI / 180.0f);
for (var i = 0; i < polygon.Points.Length; i++)
{
var x = polygon.Points[i].x;
var y = polygon.Points[i].y;
var x1 = (float)(x * Math.Cos(angle) - y * Math.Sin(angle));
var y1 = (float)(x * Math.Sin(angle) + y * Math.Cos(angle));
rotated.Add(new SvgPoint(x1, y1));
}
// reset bounding box
//RectangleF rr = new RectangleF();
var ret = new PolygonWithBounds()
{
Points = rotated.ToArray()
};
var bounds = GeometryUtil.getPolygonBounds(ret);
ret.x = bounds.x;
ret.y = bounds.y;
ret.width = bounds.width;
ret.height = bounds.height;
return ret;
}
public class PolygonWithBounds : NFP
{
public double x;
public double y;
public double width;
public double height;
}
public static bool _almostEqual(double a, double b, double? tolerance = null)
{
if (tolerance == null)
{
tolerance = TOL;
}
return Math.Abs(a - b) < tolerance;
}
public static bool _almostEqual(double? a, double? b, double? tolerance = null)
{
return _almostEqual(a.Value, b.Value, tolerance);
}
// returns true if point already exists in the given nfp
public static bool inNfp(SvgPoint p, NFP[] nfp)
{
if (nfp == null || nfp.Length == 0)
{
return false;
}
for (var i = 0; i < nfp.Length; i++)
{
for (var j = 0; j < nfp[i].length; j++)
{
if (_almostEqual(p.x, nfp[i][j].x) && _almostEqual(p.y, nfp[i][j].y))
{
return true;
}
}
}
return false;
}
// normalize vector into a unit vector
public static SvgPoint _normalizeVector(SvgPoint v)
{
if (_almostEqual(v.x * v.x + v.y * v.y, 1))
{
return v; // given vector was already a unit vector
}
var len = Math.Sqrt(v.x * v.x + v.y * v.y);
var inverse = (float)(1 / len);
return new SvgPoint(v.x * inverse, v.y * inverse
);
}
public static double? pointDistance(SvgPoint p, SvgPoint s1, SvgPoint s2, SvgPoint normal, bool infinite = false)
{
normal = _normalizeVector(normal);
var dir = new SvgPoint(normal.y, -normal.x);
var pdot = p.x * dir.x + p.y * dir.y;
var s1dot = s1.x * dir.x + s1.y * dir.y;
var s2dot = s2.x * dir.x + s2.y * dir.y;
var pdotnorm = p.x * normal.x + p.y * normal.y;
var s1dotnorm = s1.x * normal.x + s1.y * normal.y;
var s2dotnorm = s2.x * normal.x + s2.y * normal.y;
if (!infinite)
{
if (((pdot < s1dot || _almostEqual(pdot, s1dot)) && (pdot < s2dot || _almostEqual(pdot, s2dot))) || ((pdot > s1dot || _almostEqual(pdot, s1dot)) && (pdot > s2dot || _almostEqual(pdot, s2dot))))
{
return null; // dot doesn't collide with segment, or lies directly on the vertex
}
if ((_almostEqual(pdot, s1dot) && _almostEqual(pdot, s2dot)) && (pdotnorm > s1dotnorm && pdotnorm > s2dotnorm))
{
return Math.Min(pdotnorm - s1dotnorm, pdotnorm - s2dotnorm);
}
if ((_almostEqual(pdot, s1dot) && _almostEqual(pdot, s2dot)) && (pdotnorm < s1dotnorm && pdotnorm < s2dotnorm))
{
return -Math.Min(s1dotnorm - pdotnorm, s2dotnorm - pdotnorm);
}
}
return -(pdotnorm - s1dotnorm + (s1dotnorm - s2dotnorm) * (s1dot - pdot) / (s1dot - s2dot));
}
static double TOL = (float)Math.Pow(10, -9); // Floating point error is likely to be above 1 epsilon
// returns true if p lies on the line segment defined by AB, but not at any endpoints
// may need work!
public static bool _onSegment(SvgPoint A, SvgPoint B, SvgPoint p)
{
// vertical line
if (_almostEqual(A.x, B.x) && _almostEqual(p.x, A.x))
{
if (!_almostEqual(p.y, B.y) && !_almostEqual(p.y, A.y) && p.y < Math.Max(B.y, A.y) && p.y > Math.Min(B.y, A.y))
{
return true;
}
else
{
return false;
}
}
// horizontal line
if (_almostEqual(A.y, B.y) && _almostEqual(p.y, A.y))
{
if (!_almostEqual(p.x, B.x) && !_almostEqual(p.x, A.x) && p.x < Math.Max(B.x, A.x) && p.x > Math.Min(B.x, A.x))
{
return true;
}
else
{
return false;
}
}
//range check
if ((p.x < A.x && p.x < B.x) || (p.x > A.x && p.x > B.x) || (p.y < A.y && p.y < B.y) || (p.y > A.y && p.y > B.y))
{
return false;
}
// exclude end points
if ((_almostEqual(p.x, A.x) && _almostEqual(p.y, A.y)) || (_almostEqual(p.x, B.x) && _almostEqual(p.y, B.y)))
{
return false;
}
var cross = (p.y - A.y) * (B.x - A.x) - (p.x - A.x) * (B.y - A.y);
if (Math.Abs(cross) > TOL)
{
return false;
}
var dot = (p.x - A.x) * (B.x - A.x) + (p.y - A.y) * (B.y - A.y);
if (dot < 0 || _almostEqual(dot, 0))
{
return false;
}
var len2 = (B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y);
if (dot > len2 || _almostEqual(dot, len2))
{
return false;
}
return true;
}
// project each point of B onto A in the given direction, and return the
public static double? polygonProjectionDistance(NFP A, NFP B, SvgPoint direction)
{
var Boffsetx = B.offsetx ?? 0;
var Boffsety = B.offsety ?? 0;
var Aoffsetx = A.offsetx ?? 0;
var Aoffsety = A.offsety ?? 0;
A = A.slice(0);
B = B.slice(0);
// close the loop for polygons
if (A[0] != A[A.length - 1])
{
A.push(A[0]);
}
if (B[0] != B[B.length - 1])
{
B.push(B[0]);
}
var edgeA = A;
var edgeB = B;
double? distance = null;
SvgPoint p, s1, s2;
double? d;
for (var i = 0; i < edgeB.length; i++)
{
// the shortest/most negative projection of B onto A
double? minprojection = null;
SvgPoint minp = null;
for (var j = 0; j < edgeA.length - 1; j++)
{
p = new SvgPoint(edgeB[i].x + Boffsetx, edgeB[i].y + Boffsety);
s1 = new SvgPoint(edgeA[j].x + Aoffsetx, edgeA[j].y + Aoffsety);
s2 = new SvgPoint(edgeA[j + 1].x + Aoffsetx, edgeA[j + 1].y + Aoffsety);
if (Math.Abs((s2.y - s1.y) * direction.x - (s2.x - s1.x) * direction.y) < TOL)
{
continue;
}
// project point, ignore edge boundaries
d = pointDistance(p, s1, s2, direction);
if (d != null && (minprojection == null || d < minprojection))
{
minprojection = d;
minp = p;
}
}
if (minprojection != null && (distance == null || minprojection > distance))
{
distance = minprojection;
}
}
return distance;
}
public static double polygonArea(NFP polygon)
{
double area = 0;
int i, j;
for (i = 0, j = polygon.Points.Length - 1; i < polygon.Points.Length; j = i++)
{
area += (polygon.Points[j].x + polygon.Points[i].x) * (polygon.Points[j].y
- polygon.Points[i].y);
}
return 0.5f * area;
}
// return true if point is in the polygon, false if outside, and null if exactly on a point or edge
public static bool? pointInPolygon(SvgPoint point, NFP polygon)
{
if (polygon == null || polygon.Points.Length < 3)
{
throw new ArgumentException();
}
var inside = false;
//var offsetx = polygon.offsetx || 0;
//var offsety = polygon.offsety || 0;
double offsetx = polygon.offsetx == null ? 0 : polygon.offsetx.Value;
double offsety = polygon.offsety == null ? 0 : polygon.offsety.Value;
int i, j;
for (i = 0, j = polygon.Points.Count() - 1; i < polygon.Points.Length; j = i++)
{
var xi = polygon.Points[i].x + offsetx;
var yi = polygon.Points[i].y + offsety;
var xj = polygon.Points[j].x + offsetx;
var yj = polygon.Points[j].y + offsety;
if (_almostEqual(xi, point.x) && _almostEqual(yi, point.y))
{
return null; // no result
}
if (_onSegment(new SvgPoint(xi, yi), new SvgPoint(xj, yj), point))
{
return null; // exactly on the segment
}
if (_almostEqual(xi, xj) && _almostEqual(yi, yj))
{ // ignore very small lines
continue;
}
var intersect = ((yi > point.y) != (yj > point.y)) && (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi);
if (intersect) inside = !inside;
}
return inside;
}
// todo: swap this for a more efficient sweep-line implementation
// returnEdges: if set, return all edges on A that have intersections
public static bool intersect(NFP A, NFP B)
{
var Aoffsetx = A.offsetx ?? 0;
var Aoffsety = A.offsety ?? 0;
var Boffsetx = B.offsetx ?? 0;
var Boffsety = B.offsety ?? 0;
A = A.slice(0);
B = B.slice(0);
for (var i = 0; i < A.length - 1; i++)
{
for (var j = 0; j < B.length - 1; j++)
{
var a1 = new SvgPoint(A[i].x + Aoffsetx, A[i].y + Aoffsety);
var a2 = new SvgPoint(A[i + 1].x + Aoffsetx, A[i + 1].y + Aoffsety);
var b1 = new SvgPoint(B[j].x + Boffsetx, B[j].y + Boffsety);
var b2 = new SvgPoint(B[j + 1].x + Boffsetx, B[j + 1].y + Boffsety);
var prevbindex = (j == 0) ? B.length - 1 : j - 1;
var prevaindex = (i == 0) ? A.length - 1 : i - 1;
var nextbindex = (j + 1 == B.length - 1) ? 0 : j + 2;
var nextaindex = (i + 1 == A.length - 1) ? 0 : i + 2;
// go even further back if we happen to hit on a loop end point
if (B[prevbindex] == B[j] || (_almostEqual(B[prevbindex].x, B[j].x) && _almostEqual(B[prevbindex].y, B[j].y)))
{
prevbindex = (prevbindex == 0) ? B.length - 1 : prevbindex - 1;
}
if (A[prevaindex] == A[i] || (_almostEqual(A[prevaindex].x, A[i].x) && _almostEqual(A[prevaindex].y, A[i].y)))
{
prevaindex = (prevaindex == 0) ? A.length - 1 : prevaindex - 1;
}
// go even further forward if we happen to hit on a loop end point
if (B[nextbindex] == B[j + 1] || (_almostEqual(B[nextbindex].x, B[j + 1].x) && _almostEqual(B[nextbindex].y, B[j + 1].y)))
{
nextbindex = (nextbindex == B.length - 1) ? 0 : nextbindex + 1;
}
if (A[nextaindex] == A[i + 1] || (_almostEqual(A[nextaindex].x, A[i + 1].x) && _almostEqual(A[nextaindex].y, A[i + 1].y)))
{
nextaindex = (nextaindex == A.length - 1) ? 0 : nextaindex + 1;
}
var a0 = new SvgPoint(A[prevaindex].x + Aoffsetx, A[prevaindex].y + Aoffsety);
var b0 = new SvgPoint(B[prevbindex].x + Boffsetx, B[prevbindex].y + Boffsety);
var a3 = new SvgPoint(A[nextaindex].x + Aoffsetx, A[nextaindex].y + Aoffsety);
var b3 = new SvgPoint(B[nextbindex].x + Boffsetx, B[nextbindex].y + Boffsety);
if (_onSegment(a1, a2, b1) || (_almostEqual(a1.x, b1.x) && _almostEqual(a1.y, b1.y)))
{
// if a point is on a segment, it could intersect or it could not. Check via the neighboring points
var b0in = pointInPolygon(b0, A);
var b2in = pointInPolygon(b2, A);
if ((b0in == true && b2in == false) || (b0in == false && b2in == true))
{
return true;
}
else
{
continue;
}
}
if (_onSegment(a1, a2, b2) || (_almostEqual(a2.x, b2.x) && _almostEqual(a2.y, b2.y)))
{
// if a point is on a segment, it could intersect or it could not. Check via the neighboring points
var b1in = pointInPolygon(b1, A);
var b3in = pointInPolygon(b3, A);
if ((b1in == true && b3in == false) || (b1in == false && b3in == true))
{
return true;
}
else
{
continue;
}
}
if (_onSegment(b1, b2, a1) || (_almostEqual(a1.x, b2.x) && _almostEqual(a1.y, b2.y)))
{
// if a point is on a segment, it could intersect or it could not. Check via the neighboring points
var a0in = pointInPolygon(a0, B);
var a2in = pointInPolygon(a2, B);
if ((a0in == true && a2in == false) || (a0in == false && a2in == true))
{
return true;
}
else
{
continue;
}
}
if (_onSegment(b1, b2, a2) || (_almostEqual(a2.x, b1.x) && _almostEqual(a2.y, b1.y)))
{
// if a point is on a segment, it could intersect or it could not. Check via the neighboring points
var a1in = pointInPolygon(a1, B);
var a3in = pointInPolygon(a3, B);
if ((a1in == true && a3in == false) || (a1in == false && a3in == true))
{
return true;
}
else
{
continue;
}
}
var p = _lineIntersect(b1, b2, a1, a2);
if (p != null)
{
return true;
}
}
}
return false;
}
public static bool isFinite(object obj)
{
return true;
}
// returns the intersection of AB and EF
// or null if there are no intersections or other numerical error
// if the infinite flag is set, AE and EF describe infinite lines without endpoints, they are finite line segments otherwise
public static SvgPoint _lineIntersect(SvgPoint A, SvgPoint B, SvgPoint E, SvgPoint F, bool infinite = false)
{
double a1, a2, b1, b2, c1, c2, x, y;
a1 = B.y - A.y;
b1 = A.x - B.x;
c1 = B.x * A.y - A.x * B.y;
a2 = F.y - E.y;
b2 = E.x - F.x;
c2 = F.x * E.y - E.x * F.y;
var denom = a1 * b2 - a2 * b1;
x = (b1 * c2 - b2 * c1) / denom;
y = (a2 * c1 - a1 * c2) / denom;
if (!isFinite(x) || !isFinite(y))
{
return null;
}
// lines are colinear
/*var crossABE = (E.y - A.y) * (B.x - A.x) - (E.x - A.x) * (B.y - A.y);
var crossABF = (F.y - A.y) * (B.x - A.x) - (F.x - A.x) * (B.y - A.y);
if(_almostEqual(crossABE,0) && _almostEqual(crossABF,0)){
return null;
}*/
if (!infinite)
{
// coincident points do not count as intersecting
if (Math.Abs(A.x - B.x) > TOL && ((A.x < B.x) ? x < A.x || x > B.x : x > A.x || x < B.x)) return null;
if (Math.Abs(A.y - B.y) > TOL && ((A.y < B.y) ? y < A.y || y > B.y : y > A.y || y < B.y)) return null;
if (Math.Abs(E.x - F.x) > TOL && ((E.x < F.x) ? x < E.x || x > F.x : x > E.x || x < F.x)) return null;
if (Math.Abs(E.y - F.y) > TOL && ((E.y < F.y) ? y < E.y || y > F.y : y > E.y || y < F.y)) return null;
}
return new SvgPoint(x, y);
}
// searches for an arrangement of A and B such that they do not overlap
// if an NFP is given, only search for startpoints that have not already been traversed in the given NFP
public static SvgPoint searchStartPoint(NFP A, NFP B, bool inside, NFP[] NFP = null)
{
// clone arrays
A = A.slice(0);
B = B.slice(0);
// close the loop for polygons
if (A[0] != A[A.length - 1])
{
A.push(A[0]);
}
if (B[0] != B[B.length - 1])
{
B.push(B[0]);
}
for (var i = 0; i < A.length - 1; i++)
{
if (!A[i].marked)
{
A[i].marked = true;
for (var j = 0; j < B.length; j++)
{
B.offsetx = A[i].x - B[j].x;
B.offsety = A[i].y - B[j].y;
bool? Binside = null;
for (var k = 0; k < B.length; k++)
{
var inpoly = pointInPolygon(new SvgPoint(B[k].x + B.offsetx.Value,
B[k].y + B.offsety.Value), A);
if (inpoly != null)
{
Binside = inpoly;
break;
}
}
if (Binside == null)
{ // A and B are the same
return null;
}
var startPoint = new SvgPoint(B.offsetx.Value, B.offsety.Value);
if (((Binside.Value && inside) || (!Binside.Value && !inside)) &&
!intersect(A, B) && !inNfp(startPoint, NFP))
{
return startPoint;
}
// slide B along vector
var vx = A[i + 1].x - A[i].x;
var vy = A[i + 1].y - A[i].y;
var d1 = polygonProjectionDistance(A, B, new SvgPoint(vx, vy));
var d2 = polygonProjectionDistance(B, A, new SvgPoint(-vx, -vy));
double? d = null;
// todo: clean this up
if (d1 == null && d2 == null)
{
// nothin
}
else if (d1 == null)
{
d = d2;
}
else if (d2 == null)
{
d = d1;
}
else
{
d = Math.Min(d1.Value, d2.Value);
}
// only slide until no longer negative
// todo: clean this up
if (d != null && !_almostEqual(d, 0) && d > 0)
{
}
else
{
continue;
}
var vd2 = vx * vx + vy * vy;
if (d * d < vd2 && !_almostEqual(d * d, vd2))
{
var vd = (float)Math.Sqrt(vx * vx + vy * vy);
vx *= d.Value / vd;
vy *= d.Value / vd;
}
B.offsetx += vx;
B.offsety += vy;
for (var k = 0; k < B.length; k++)
{
var inpoly = pointInPolygon(
new SvgPoint(
B[k].x + B.offsetx.Value, B[k].y + B.offsety.Value), A);
if (inpoly != null)
{
Binside = inpoly;
break;
}
}
startPoint =
new SvgPoint(B.offsetx.Value, B.offsety.Value);
if (((Binside.Value && inside) || (!Binside.Value && !inside)) &&
!intersect(A, B) && !inNfp(startPoint, NFP))
{
return startPoint;
}
}
}
}
return null;
}
public class TouchingItem
{
public TouchingItem(int _type, int _a, int _b)
{
A = _a;
B = _b;
type = _type;
}
public int A;
public int B;
public int type;
}
public static double? segmentDistance(SvgPoint A, SvgPoint B, SvgPoint E, SvgPoint F, SvgPoint direction)
{
var normal = new SvgPoint(
direction.y,
-direction.x
);
var reverse = new SvgPoint(
-direction.x,
-direction.y
);
var dotA = A.x * normal.x + A.y * normal.y;
var dotB = B.x * normal.x + B.y * normal.y;
var dotE = E.x * normal.x + E.y * normal.y;
var dotF = F.x * normal.x + F.y * normal.y;
var crossA = A.x * direction.x + A.y * direction.y;
var crossB = B.x * direction.x + B.y * direction.y;
var crossE = E.x * direction.x + E.y * direction.y;
var crossF = F.x * direction.x + F.y * direction.y;
var crossABmin = Math.Min(crossA, crossB);
var crossABmax = Math.Max(crossA, crossB);
var crossEFmax = Math.Max(crossE, crossF);
var crossEFmin = Math.Min(crossE, crossF);
var ABmin = Math.Min(dotA, dotB);
var ABmax = Math.Max(dotA, dotB);
var EFmax = Math.Max(dotE, dotF);
var EFmin = Math.Min(dotE, dotF);
// segments that will merely touch at one point
if (_almostEqual(ABmax, EFmin, TOL) || _almostEqual(ABmin, EFmax, TOL))
{
return null;
}
// segments miss eachother completely
if (ABmax < EFmin || ABmin > EFmax)
{
return null;
}
double overlap;
if ((ABmax > EFmax && ABmin < EFmin) || (EFmax > ABmax && EFmin < ABmin))
{
overlap = 1;
}
else
{
var minMax = Math.Min(ABmax, EFmax);
var maxMin = Math.Max(ABmin, EFmin);
var maxMax = Math.Max(ABmax, EFmax);
var minMin = Math.Min(ABmin, EFmin);
overlap = (minMax - maxMin) / (maxMax - minMin);
}
var crossABE = (E.y - A.y) * (B.x - A.x) - (E.x - A.x) * (B.y - A.y);
var crossABF = (F.y - A.y) * (B.x - A.x) - (F.x - A.x) * (B.y - A.y);
// lines are colinear
if (_almostEqual(crossABE, 0) && _almostEqual(crossABF, 0))
{
var ABnorm = new SvgPoint(B.y - A.y, A.x - B.x);
var EFnorm = new SvgPoint(F.y - E.y, E.x - F.x);
var ABnormlength = (float)Math.Sqrt(ABnorm.x * ABnorm.x + ABnorm.y * ABnorm.y);
ABnorm.x /= ABnormlength;
ABnorm.y /= ABnormlength;
var EFnormlength = (float)Math.Sqrt(EFnorm.x * EFnorm.x + EFnorm.y * EFnorm.y);
EFnorm.x /= EFnormlength;
EFnorm.y /= EFnormlength;
// segment normals must point in opposite directions
if (Math.Abs(ABnorm.y * EFnorm.x - ABnorm.x * EFnorm.y) < TOL && ABnorm.y * EFnorm.y + ABnorm.x * EFnorm.x < 0)
{
// normal of AB segment must point in same direction as given direction vector
var normdot = ABnorm.y * direction.y + ABnorm.x * direction.x;
// the segments merely slide along eachother
if (_almostEqual(normdot, 0, TOL))
{
return null;
}
if (normdot < 0)
{
return 0;
}
}
return null;
}
var distances = new List<double>();
// coincident points
if (_almostEqual(dotA, dotE))
{
distances.Add(crossA - crossE);
}
else if (_almostEqual(dotA, dotF))
{
distances.Add(crossA - crossF);
}
else if (dotA > EFmin && dotA < EFmax)
{
var d = pointDistance(A, E, F, reverse);
if (d != null && _almostEqual(d, 0))
{ // A currently touches EF, but AB is moving away from EF
var dB = pointDistance(B, E, F, reverse, true);
if (dB < 0 || _almostEqual(dB * overlap, 0))
{
d = null;
}
}
if (d != null)
{
distances.Add(d.Value);
}
}
if (_almostEqual(dotB, dotE))
{
distances.Add(crossB - crossE);
}
else if (_almostEqual(dotB, dotF))
{
distances.Add(crossB - crossF);
}
else if (dotB > EFmin && dotB < EFmax)
{
var d = pointDistance(B, E, F, reverse);
if (d != null && _almostEqual(d, 0))
{ // crossA>crossB A currently touches EF, but AB is moving away from EF
var dA = pointDistance(A, E, F, reverse, true);
if (dA < 0 || _almostEqual(dA * overlap, 0))
{
d = null;
}
}
if (d != null)
{
distances.Add(d.Value);
}
}
if (dotE > ABmin && dotE < ABmax)
{
var d = pointDistance(E, A, B, direction);
if (d != null && _almostEqual(d, 0))
{ // crossF<crossE A currently touches EF, but AB is moving away from EF
var dF = pointDistance(F, A, B, direction, true);
if (dF < 0 || _almostEqual(dF * overlap, 0))
{
d = null;
}
}
if (d != null)
{
distances.Add(d.Value);
}
}
if (dotF > ABmin && dotF < ABmax)
{
var d = pointDistance(F, A, B, direction);
if (d != null && _almostEqual(d, 0))
{ // && crossE<crossF A currently touches EF, but AB is moving away from EF
var dE = pointDistance(E, A, B, direction, true);
if (dE < 0 || _almostEqual(dE * overlap, 0))
{
d = null;
}
}
if (d != null)
{
distances.Add(d.Value);
}
}
if (distances.Count == 0)
{
return null;
}
//return Math.min.apply(Math, distances);
return distances.Min();
}
public static double? polygonSlideDistance(NFP A, NFP B, nVector direction, bool ignoreNegative)
{
SvgPoint A1, A2, B1, B2;
double Aoffsetx, Aoffsety, Boffsetx, Boffsety;
Aoffsetx = A.offsetx ?? 0;
Aoffsety = A.offsety ?? 0;
Boffsetx = B.offsetx ?? 0;
Boffsety = B.offsety ?? 0;
A = A.slice(0);
B = B.slice(0);
// close the loop for polygons
if (A[0] != A[A.length - 1])
{
A.push(A[0]);
}
if (B[0] != B[B.length - 1])
{
B.push(B[0]);
}
var edgeA = A;
var edgeB = B;
double? distance = null;
//var p, s1, s2;
double? d;
var dir = _normalizeVector(new SvgPoint(direction.x, direction.y));
var normal = new SvgPoint(
dir.y,
-dir.x
);
var reverse = new SvgPoint(-dir.x, -dir.y);
for (var i = 0; i < edgeB.length - 1; i++)
{
//var mind = null;
for (var j = 0; j < edgeA.length - 1; j++)
{
A1 = new SvgPoint(
edgeA[j].x + Aoffsetx, edgeA[j].y + Aoffsety
);
A2 = new SvgPoint(
edgeA[j + 1].x + Aoffsetx, edgeA[j + 1].y + Aoffsety
);
B1 = new SvgPoint(edgeB[i].x + Boffsetx, edgeB[i].y + Boffsety);
B2 = new SvgPoint(edgeB[i + 1].x + Boffsetx, edgeB[i + 1].y + Boffsety);
if ((_almostEqual(A1.x, A2.x) && _almostEqual(A1.y, A2.y)) || (_almostEqual(B1.x, B2.x) && _almostEqual(B1.y, B2.y)))
{
continue; // ignore extremely small lines
}
d = segmentDistance(A1, A2, B1, B2, dir);
if (d != null && (distance == null || d < distance))
{
if (!ignoreNegative || d > 0 || _almostEqual(d, 0))
{
distance = d;
}
}
}
}
return distance;
}
public class nVector
{
public SvgPoint start;
public SvgPoint end;
public double x;
public double y;
public nVector(double v1, double v2, SvgPoint _start, SvgPoint _end)
{
this.x = v1;
this.y = v2;
this.start = _start;
this.end = _end;
}
}
// given a static polygon A and a movable polygon B, compute a no fit polygon by orbiting B about A
// if the inside flag is set, B is orbited inside of A rather than outside
// if the searchEdges flag is set, all edges of A are explored for NFPs - multiple
public static NFP[] noFitPolygon(NFP A, NFP B, bool inside, bool searchEdges)
{
if (A == null || A.length < 3 || B == null || B.length < 3)
{
return null;
}
A.offsetx = 0;
A.offsety = 0;
int i = 0, j = 0;
var minA = A[0].y;
var minAindex = 0;
var maxB = B[0].y;
var maxBindex = 0;
for (i = 1; i < A.length; i++)
{
A[i].marked = false;
if (A[i].y < minA)
{
minA = A[i].y;
minAindex = i;
}
}
for (i = 1; i < B.length; i++)
{
B[i].marked = false;
if (B[i].y > maxB)
{
maxB = B[i].y;
maxBindex = i;
}
}
SvgPoint startpoint;
if (!inside)
{
// shift B such that the bottom-most point of B is at the top-most point of A. This guarantees an initial placement with no intersections
startpoint = new SvgPoint(
A[minAindex].x - B[maxBindex].x,
A[minAindex].y - B[maxBindex].y);
}
else
{
// no reliable heuristic for inside
startpoint = searchStartPoint(A, B, true);
}
List<NFP> NFPlist = new List<NFP>();
while (startpoint != null)
{
B.offsetx = startpoint.x;
B.offsety = startpoint.y;
// maintain a list of touching points/edges
List<TouchingItem> touching = null;
nVector prevvector = null; // keep track of previous vector
NFP NFP = new NFP();
/*var NFP = [{
x: B[0].x + B.offsetx,
y: B[0].y + B.offsety
}];*/
NFP.push(new SvgPoint(B[0].x + B.offsetx.Value, B[0].y + B.offsety.Value));
double referencex = B[0].x + B.offsetx.Value;
double referencey = B[0].y + B.offsety.Value;
var startx = referencex;
var starty = referencey;
var counter = 0;
while (counter < 10 * (A.length + B.length))
{ // sanity check, prevent infinite loop
touching = new List<GeometryUtil.TouchingItem>();
// find touching vertices/edges
for (i = 0; i < A.length; i++)
{
var nexti = (i == A.length - 1) ? 0 : i + 1;
for (j = 0; j < B.length; j++)
{
var nextj = (j == B.length - 1) ? 0 : j + 1;
if (_almostEqual(A[i].x, B[j].x + B.offsetx) && _almostEqual(A[i].y, B[j].y + B.offsety))
{
touching.Add(new TouchingItem(0, i, j));
}
else if (_onSegment(A[i], A[nexti],
new SvgPoint(B[j].x + B.offsetx.Value, B[j].y + B.offsety.Value)))
{
touching.Add(new TouchingItem(1, nexti, j));
}
else if (_onSegment(
new SvgPoint(
B[j].x + B.offsetx.Value, B[j].y + B.offsety.Value),
new SvgPoint(
B[nextj].x + B.offsetx.Value, B[nextj].y + B.offsety.Value), A[i]))
{
touching.Add(new TouchingItem(2, i, nextj));
}
}
}
// generate translation vectors from touching vertices/edges
var vectors = new List<nVector>();
for (i = 0; i < touching.Count; i++)
{
var vertexA = A[touching[i].A];
vertexA.marked = true;
// adjacent A vertices
var prevAindex = touching[i].A - 1;
var nextAindex = touching[i].A + 1;
prevAindex = (prevAindex < 0) ? A.length - 1 : prevAindex; // loop
nextAindex = (nextAindex >= A.length) ? 0 : nextAindex; // loop
var prevA = A[prevAindex];
var nextA = A[nextAindex];
// adjacent B vertices
var vertexB = B[touching[i].B];
var prevBindex = touching[i].B - 1;
var nextBindex = touching[i].B + 1;
prevBindex = (prevBindex < 0) ? B.length - 1 : prevBindex; // loop
nextBindex = (nextBindex >= B.length) ? 0 : nextBindex; // loop
var prevB = B[prevBindex];
var nextB = B[nextBindex];
if (touching[i].type == 0)
{
var vA1 = new nVector(
prevA.x - vertexA.x,
prevA.y - vertexA.y,
vertexA,
prevA
);
var vA2 = new nVector(
nextA.x - vertexA.x,
nextA.y - vertexA.y,
vertexA,
nextA
);
// B vectors need to be inverted
var vB1 = new nVector(
vertexB.x - prevB.x,
vertexB.y - prevB.y,
prevB,
vertexB
);
var vB2 = new nVector(
vertexB.x - nextB.x,
vertexB.y - nextB.y,
nextB,
vertexB
);
vectors.Add(vA1);
vectors.Add(vA2);
vectors.Add(vB1);
vectors.Add(vB2);
}
else if (touching[i].type == 1)
{
vectors.Add(new nVector(
vertexA.x - (vertexB.x + B.offsetx.Value),
vertexA.y - (vertexB.y + B.offsety.Value),
prevA,
vertexA
));
vectors.Add(new nVector(
prevA.x - (vertexB.x + B.offsetx.Value),
prevA.y - (vertexB.y + B.offsety.Value),
vertexA,
prevA
));
}
else if (touching[i].type == 2)
{
vectors.Add(new nVector(
vertexA.x - (vertexB.x + B.offsetx.Value),
vertexA.y - (vertexB.y + B.offsety.Value),
prevB,
vertexB
));
vectors.Add(new nVector(
vertexA.x - (prevB.x + B.offsetx.Value),
vertexA.y - (prevB.y + B.offsety.Value),
vertexB,
prevB
));
}
}
// todo: there should be a faster way to reject vectors that will cause immediate intersection. For now just check them all
nVector translate = null;
double maxd = 0;
for (i = 0; i < vectors.Count; i++)
{
if (vectors[i].x == 0 && vectors[i].y == 0)
{
continue;
}
// if this vector points us back to where we came from, ignore it.
// ie cross product = 0, dot product < 0
if (prevvector != null &&
vectors[i].y * prevvector.y + vectors[i].x * prevvector.x < 0)
{
// compare magnitude with unit vectors
var vectorlength = (float)Math.Sqrt(vectors[i].x * vectors[i].x + vectors[i].y * vectors[i].y);
var unitv = new SvgPoint(vectors[i].x / vectorlength, vectors[i].y / vectorlength);
var prevlength = (float)Math.Sqrt(prevvector.x * prevvector.x + prevvector.y * prevvector.y);
var prevunit = new SvgPoint(prevvector.x / prevlength, prevvector.y / prevlength);
// we need to scale down to unit vectors to normalize vector length. Could also just do a tan here
if (Math.Abs(unitv.y * prevunit.x - unitv.x * prevunit.y) < 0.0001)
{
continue;
}
}
var d = polygonSlideDistance(A, B, vectors[i], true);
var vecd2 = vectors[i].x * vectors[i].x + vectors[i].y * vectors[i].y;
if (d == null || d * d > vecd2)
{
var vecd = (float)Math.Sqrt(vectors[i].x * vectors[i].x + vectors[i].y * vectors[i].y);
d = vecd;
}
if (d != null && d > maxd)
{
maxd = d.Value;
translate = vectors[i];
}
}
if (translate == null || _almostEqual(maxd, 0))
{
// didn't close the loop, something went wrong here
NFP = null;
break;
}
translate.start.marked = true;
translate.end.marked = true;
prevvector = translate;
// trim
var vlength2 = translate.x * translate.x + translate.y * translate.y;
if (maxd * maxd < vlength2 && !_almostEqual(maxd * maxd, vlength2))
{
var scale = (float)Math.Sqrt((maxd * maxd) / vlength2);
translate.x *= scale;
translate.y *= scale;
}
referencex += translate.x;
referencey += translate.y;
if (_almostEqual(referencex, startx) && _almostEqual(referencey, starty))
{
// we've made a full loop
break;
}
// if A and B start on a touching horizontal line, the end point may not be the start point
var looped = false;
if (NFP.length > 0)
{
for (i = 0; i < NFP.length - 1; i++)
{
if (_almostEqual(referencex, NFP[i].x) && _almostEqual(referencey, NFP[i].y))
{
looped = true;
}
}
}
if (looped)
{
// we've made a full loop
break;
}
NFP.push(new SvgPoint(
referencex, referencey
));
B.offsetx += translate.x;
B.offsety += translate.y;
counter++;
}
if (NFP != null && NFP.length > 0)
{
NFPlist.Add(NFP);
}
if (!searchEdges)
{
// only get outer NFP or first inner NFP
break;
}
startpoint = searchStartPoint(A, B, inside, NFPlist.ToArray());
}
return NFPlist.ToArray();
}
public static bool pnpoly((double X, double Y)[] verts, double testx, double testy)
{
int nvert = verts.Length;
int i, j;
bool c = false;
for (i = 0, j = nvert - 1; i < nvert; j = i++)
{
if (((verts[i].Y > testy) != (verts[j].Y > testy)) &&
(testx < (verts[j].X - verts[i].X) * (testy - verts[i].Y) / (verts[j].Y - verts[i].Y) + verts[i].X))
c = !c;
}
return c;
}
}
}