using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Numerics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace CommonUsage.Mathematics { using T3 = Tuple; using D3 = Tuple; public class CommonMath { public class PrimeEnumerator { public PrimeEnumerator(List items, Func process) { _n = items.Count; _items = items; _process = process; foreach (var pNum in _primes) { if (_n % pNum != 0) { _a = pNum; _b = 11; break; } } } public void Enumerate() { using (var enumerator = Get().GetEnumerator()) { while (enumerator.MoveNext()) { } } } private readonly int _n, _a, _b; private readonly int[] _primes = new[] { 29, 23, 19, 17, 13 }; private List _items; private readonly Func _process; private IEnumerable Get() { for (var i = 0; i < _n; ++i) { var id = (i * _a + _b) % _n; yield return _process(_items[id]); } } } private static IEnumerable> GetPermutationsInternal(IEnumerable list, int length) { if (length == 1) return list.Select(t => new T[] { t }); return GetPermutationsInternal(list, length - 1) .SelectMany(t => list.Where(e => !t.Contains(e)), (t1, t2) => t1.Concat(new T[] { t2 })); } /// /// 得到一组数据的所有排列。 /// /// 元素数据类型 /// 所有待选元素 /// 所选出的元素数量 /// public static List> GetPermutations(List list, int selectNum) { return GetPermutationsInternal(list, selectNum).Select(ll => ll.ToList()).ToList(); } public static (float bias, Vector2 hPnt, float d) Project2DLine(Vector2 pnt, Vector2 segSt, Vector2 segEnd) { var dir = Vector2.Normalize(segEnd - segSt); var fd = Vector2.Dot(pnt - segSt, dir); var hPnt = segSt + fd * dir; var bias = dir.X * (pnt.Y-segSt.Y) - (pnt.X-segSt.X) * dir.Y; return (bias, hPnt, fd); } public static (float bias, Vector2 hPnt, float fd) Project2DLine(Vector2 pnt, Vector2 segSt, float tangent) { var dir = new Vector2((float)System.Math.Cos(tangent / 180 * System.Math.PI), (float)System.Math.Sin(tangent / 180 * System.Math.PI)); var fd = Vector2.Dot(pnt - segSt, dir); var hPnt = segSt + fd * dir; var bias = dir.X * (pnt.Y - segSt.Y) - (pnt.X - segSt.X) * dir.Y; return (bias, hPnt, fd); } public class LineEqu { public double A, B, C, ln, dAB; public double px1, px2, py1, py2; public float midX; public float midY; } // Fit line with PCA. public LineEqu CalcLine(IEnumerable tls) { var lidarPoint2Ds = tls as Vector2[] ?? tls.ToArray(); float fx = lidarPoint2Ds.Average(f => f.X); float fy = lidarPoint2Ds.Average(f => f.Y); float fxx = lidarPoint2Ds.Average(f => f.X * f.X); float fxy = lidarPoint2Ds.Average(f => f.X * f.Y); float fyy = lidarPoint2Ds.Average(f => f.Y * f.Y); float a = fxx - fx * fx, b = fxy - fx * fy, c = fyy - fy * fy; double sqt = System.Math.Sqrt((a - c) * (a - c) + 4 * b * b); double l1 = a + c + sqt; double l2 = a + c - sqt; double dx, dy; if (System.Math.Abs(a - l1 / 2) > System.Math.Abs(c - l1 / 2)) { dy = l1 / 2 - a; dx = b; } else { dx = l1 / 2 - c; dy = b; } double norm = System.Math.Sqrt(dx * dx + dy * dy); dx /= norm; dy /= norm; double A = dy, B = -dx, C = dx * fy - dy * fx; double dAB = System.Math.Sqrt(A * A + B * B); return new CommonMath.LineEqu { A = A, B = B, C = C, ln = lidarPoint2Ds.Average(p => System.Math.Abs(p.X * A + p.Y * B + C) / dAB), midX = fx, midY = fy }; } public static double QuadInterp3(double[] confsF) { if (confsF[0] > confsF[1] && confsF[0] > confsF[2]) { //printf("left overflow...\n"); return -1; } if (confsF[1] > confsF[0] && confsF[1] > confsF[2]) { return (-(confsF[2] - confsF[0]) / 2.0f / (confsF[0] + confsF[2] - 2.0f * confsF[1] + 0.0001f)); } if (confsF[2] > confsF[0] && confsF[2] > confsF[1]) { //printf("right overflow...\n"); return 1; } return 0; } public static double cross(PointF O, PointF A, PointF B) { return (A.X - O.X) * (B.Y - O.Y) - (A.Y - O.Y) * (B.X - O.X); } public static List GetConvexHull(List points) { if (points == null) return null; if (points.Count() <= 1) return points; int n = points.Count(), k = 0; List H = new List(new PointF[2 * n]); points.Sort((a, b) => a.X == b.X ? a.Y.CompareTo(b.Y) : a.X.CompareTo(b.X)); // Build lower hull for (int i = 0; i < n; ++i) { while (k >= 2 && cross(H[k - 2], H[k - 1], points[i]) <= 0) k--; H[k++] = points[i]; } // Build upper hull for (int i = n - 2, t = k + 1; i >= 0; i--) { while (k >= t && cross(H[k - 2], H[k - 1], points[i]) <= 0) k--; H[k++] = points[i]; } return H.Take(k - 1).ToList(); } public static bool IsPointInPolygon4(PointF[] polygon, PointF testPoint) { // ray casting odd even test. bool result = false; int j = polygon.Count() - 1; for (int i = 0; i < polygon.Count(); i++) { if (polygon[i].Y < testPoint.Y && polygon[j].Y >= testPoint.Y || polygon[j].Y < testPoint.Y && polygon[i].Y >= testPoint.Y) { if (polygon[i].X + (testPoint.Y - polygon[i].Y) / (polygon[j].Y - polygon[i].Y) * (polygon[j].X - polygon[i].X) < testPoint.X) { result = !result; } } j = i; } return result; } public static double Exp(double val) { if (val < -20) return 0.0000001; if (val > 20) return 99999999999999; long tmp = (long)(1512775 * val + 1072632447); return BitConverter.Int64BitsToDouble(tmp << 32); } public static double gaussmf(double x, double sig, double c) { return Exp(-(x - c) * (x - c) / (2 * sig * sig)); } public static float Exp(float x) { if (x < -10) return 0; if (x > 10) return 99999999999999; x = 1.0f + x / 64f; x *= x; x *= x; x *= x; x *= x; x *= x; x *= x; return x; } public static float gaussmf(float x, float sig, float c) { return Exp(-(x - c) * (x - c) / (2 * sig * sig)); } public static D3 Transform2D(D3 src, D3 t) { var rth = src.Item3 / 180.0 * System.Math.PI; var p1dtx = (src.Item1 + System.Math.Cos(rth) * t.Item1 - System.Math.Sin(rth) * t.Item2); var p1dty = (src.Item2 + System.Math.Sin(rth) * t.Item1 + System.Math.Cos(rth) * t.Item2); var p1dtth = src.Item3 + t.Item3; return Tuple.Create(p1dtx, p1dty, p1dtth); } public struct LngLatToXY { public double scale; public double rad; public double biasX, biasY; } public static LngLatToXY GetTransformLngLatToXY(Vector2 lnglat1, Vector2 xy1, Vector2 lnglat2, Vector2 xy2) { var scale = (xy1 - xy2).Length() / (lnglat1 - lnglat2).Length(); var dxy = (xy1 - xy2); var dlnglat = lnglat1 - lnglat2; var rad = System.Math.Atan2(dxy.X, dxy.Y) - System.Math.Atan2(dlnglat.X, dlnglat.Y); var intm = lnglat1 * scale; var biasX = xy1.X - (intm.X * System.Math.Cos(rad) - intm.Y * System.Math.Sin(rad)); var biasY = xy1.Y - (intm.X * System.Math.Sin(rad) + intm.Y * System.Math.Cos(rad)); return new LngLatToXY {rad = rad, biasX = biasX, biasY = biasY, scale = scale}; } public Vector2 TransformLngLatToXY(Vector2 lnglat, LngLatToXY t) { var intm = lnglat * (float) t.scale; return new Vector2((float) (intm.X * System.Math.Cos(t.rad) - intm.Y * System.Math.Sin(t.rad) + t.biasX), (float) (intm.X * System.Math.Sin(t.rad) + intm.Y * System.Math.Cos(t.rad) + t.biasY)); } public static D3 ReverseTransform(D3 dest, D3 t) { var rth = (dest.Item3 - t.Item3) / 180.0 * System.Math.PI; var nxT = (dest.Item1 - System.Math.Cos(rth) * t.Item1 + System.Math.Sin(rth) * t.Item2); var nyT = (dest.Item2 - System.Math.Sin(rth) * t.Item1 - System.Math.Cos(rth) * t.Item2); var pth = dest.Item3 - t.Item3; return Tuple.Create(nxT, nyT, pth); } public static D3 SolveTransform2D(D3 src, D3 dest) { var th = dest.Item3 - src.Item3; th = (th - System.Math.Round((th) / 360.0f) * 360); var rth = src.Item3 / 180.0 * System.Math.PI; var x = ((dest.Item1 - src.Item1) * System.Math.Cos(rth) + (dest.Item2 - src.Item2) * System.Math.Sin(rth)); var y = (-(dest.Item1 - src.Item1) * System.Math.Sin(rth) + (dest.Item2 - src.Item2) * System.Math.Cos(rth)); return Tuple.Create(x, y, th); } public static T3 Transform2D(T3 src, T3 t) { var rth = src.Item3 / 180.0 * System.Math.PI; var p1dtx = (float)(src.Item1 + System.Math.Cos(rth) * t.Item1 - System.Math.Sin(rth) * t.Item2); var p1dty = (float)(src.Item2 + System.Math.Sin(rth) * t.Item1 + System.Math.Cos(rth) * t.Item2); var p1dtth = src.Item3 + t.Item3; return Tuple.Create(p1dtx, p1dty, p1dtth); } public static Vector2 Transform2D(Vector3 src, Vector3 t) { var tup = Transform2D(Tuple.Create(src.X, src.Y, src.Z), Tuple.Create(t.X, t.Y, t.Z)); return new Vector2(tup.Item1, tup.Item2); } public static Vector2 Transform2D(Vector2 srcPos, float srcTh, Vector2 dt, float dth = 0) { var tup = Transform2D(Tuple.Create(srcPos.X, srcPos.Y, srcTh), Tuple.Create(dt.X, dt.Y, dth)); return new Vector2(tup.Item1, tup.Item2); } public static T3 ReverseTransform(T3 dest, T3 t) { var rth = (dest.Item3 - t.Item3) / 180.0 * System.Math.PI; var nxT = (float)(dest.Item1 - System.Math.Cos(rth) * t.Item1 + System.Math.Sin(rth) * t.Item2); var nyT = (float)(dest.Item2 - System.Math.Sin(rth) * t.Item1 - System.Math.Cos(rth) * t.Item2); var pth = dest.Item3 - t.Item3; return Tuple.Create(nxT, nyT, pth); } public static T3 SolveTransform2D(T3 src, T3 dest) { var th = dest.Item3 - src.Item3; th = (float)(th - System.Math.Round((th) / 360.0f) * 360); var rth = src.Item3 / 180.0 * System.Math.PI; var x = (float)((dest.Item1 - src.Item1) * System.Math.Cos(rth) + (dest.Item2 - src.Item2) * System.Math.Sin(rth)); var y = (float)(-(dest.Item1 - src.Item1) * System.Math.Sin(rth) + (dest.Item2 - src.Item2) * System.Math.Cos(rth)); return Tuple.Create(x, y, th); } public static Vector2 SolveTransform2D(Vector2 srcPos, float srcTh, Vector2 dt, float dth = 0) { var tup = SolveTransform2D(Tuple.Create(srcPos.X, srcPos.Y, srcTh), Tuple.Create(dt.X, dt.Y, dth)); return new Vector2(tup.Item1, tup.Item2); } public static double dist(double x1, double y1, double x2, double y2) { return System.Math.Sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); } [StructLayout(LayoutKind.Explicit)] private struct FloatIntUnion { [FieldOffset(0)] public float f; [FieldOffset(0)] public int tmp; } public static float Sqrt(float z) { FloatIntUnion u; u.tmp = 0; u.f = z; u.tmp -= 1 << 23; /* Subtract 2^m. */ u.tmp >>= 1; /* Divide by 2. */ u.tmp += 1 << 29; /* Add ((b + 1) / 2) * 2^m. */ return u.f; } public static float dist2(float x1, float y1, float x2, float y2) { return ((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); } public static float d2(float x1, float y1, float x2, float y2) { return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2); } public static float ThAverage(List angles) { var anchor = angles[0]; var diff = 0f; foreach (var angle in angles) diff += ThDiff(angle, anchor); return RoundTh(anchor + diff / angles.Count); } public static float ThDiff(float th1, float th2) { return (float)(th1 - th2 - System.Math.Round((th1 - th2) / 360.0f) * 360); } public static double ThDiff(double th1, double th2) { return th1 - th2 - System.Math.Round((th1 - th2) / 360.0f) * 360; } public static double refine(double x) { if (x < 1 && x > -1) return x; if (x > 1) return (2 / (1 + System.Math.Exp(-((x - 1) * 2)))); return (2 / (1 + System.Math.Exp(-((x + 1) * 2)))) - 2; } /// /// 求点p到两点式直线p1p2的距离 /// /// 点p的x坐标 /// 点p的y坐标 /// 直线点p1的x坐标 /// 直线点p1的y坐标 /// 直线点p2的x坐标 /// 直线点p2的y坐标 /// public static double Point2LineDist(double x, double y, double x1, double y1, double x2, double y2) { double a1 = -(y1 - y2) / 10; double b1 = (x1 - x2) / 10; double c1 = (x1 * (y1 - y2) - y1 * (x1 - x2)) / 10; return System.Math.Abs(a1 * x + b1 * y + c1) / System.Math.Sqrt(a1 * a1 + b1 * b1); } public static double Point2LineDist(Vector2 p, LineSegment ll) { double a1 = -(ll.Src.Y - ll.Dst.Y) / 10; double b1 = (ll.Src.X - ll.Dst.X) / 10; double c1 = (ll.Src.X * (ll.Src.Y - ll.Dst.Y) - ll.Src.Y * (ll.Src.X - ll.Dst.X)) / 10; return System.Math.Abs(a1 * p.X + b1 * p.Y + c1) / System.Math.Sqrt(a1 * a1 + b1 * b1); } /// /// 两条两点式直线间的夹角 /// /// /// /// /// /// /// /// /// /// 角度制 public static double AngleBetweenLines(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4) { var vec1 = new Vector2((float)(x2 - x1), (float)(y2 - y1)); var vec2 = new Vector2((float)(x4 - x3), (float)(y4 - y3)); return System.Math.Acos(System.Math.Abs(Vector2.Dot(vec1, vec2) / vec1.Length() / vec2.Length())) / System.Math.PI * 180; } public static double AngleBetweenLines(LineSegment ls1, LineSegment ls2) { return AngleBetweenLines(ls1.Src.X, ls1.Src.Y, ls1.Dst.X, ls1.Dst.Y, ls2.Src.X, ls2.Src.Y, ls2.Dst.X, ls2.Dst.Y); } /// /// 两向量间夹角 /// /// /// /// /// /// /// /// /// /// 角度制 public static double AngleBetweenVectors(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4) { var vec1 = new Vector2((float)(x2 - x1), (float)(y2 - y1)); var vec2 = new Vector2((float)(x4 - x3), (float)(y4 - y3)); return System.Math.Acos(Vector2.Dot(vec1, vec2) / vec1.Length() / vec2.Length()) / System.Math.PI * 180; } /// /// 两向量间夹角. /// /// /// /// 角度制 public static double AngleBetweenVectors(Vector2 vec1, Vector2 vec2) { return System.Math.Acos(Vector2.Dot(vec1, vec2) / vec1.Length() / vec2.Length()) / System.Math.PI * 180; } public static double AngleBetweenVectors(Vector3 vector1, Vector3 vector2) { float dotProduct = Vector3.Dot(vector1, vector2); float magnitude1 = vector1.Length(); float magnitude2 = vector2.Length(); float cosine = dotProduct / (magnitude1 * magnitude2); return System.Math.Acos(cosine) / System.Math.PI * 180; } /// /// 求点到直线的垂足 /// /// /// /// /// /// /// /// public static (double, double) PerpendicularPoint(double x, double y, double x1, double y1, double x2, double y2) { double lx = x2 - x1, ly = y2 - y1, dAB = lx * lx + ly * ly; var u = ((x - x1) * lx + (y - y1) * ly) / dAB; return new(x1 + u * lx, y1 + u * ly); } public static Vector2 PerpendicularPoint(Vector2 p, LineSegment ls) { double lx = ls.Dst.X - ls.Src.X, ly = ls.Dst.Y - ls.Src.Y, dAB = lx * lx + ly * ly; var u = ((p.X - ls.Src.X) * lx + (p.Y - ls.Src.Y) * ly) / dAB; return new Vector2((float)(ls.Src.X + u * lx), (float)(ls.Src.Y + u * ly)); } public static double PerpendicularPosition(double x, double y, double x1, double y1, double x2, double y2) { double lx = x2 - x1, ly = y2 - y1; var dAB = CommonMath.Sqrt((float)(lx * lx + ly * ly)); lx /= dAB; ly /= dAB; return (x - x1) * lx + (y - y1) * ly; } /// /// 最小二乘法拟合直线,得到两点式。 /// /// 待拟合的点集,应至少有2个点。 /// 检查是否所有点距直线的距离均小于maxDist2Line,若为-1则不检查。 /// 返回两点式的两个端点坐标。若坐标为全0,则拟合失败。 public static (bool, Vector2, Vector2) FitLineSegment(List pts, double maxDist2Line = -1) { if (pts.Count < 2) { Console.WriteLine($"Points too Few! {pts.Count}! Cannot perform line fitting!", MethodBase.GetCurrentMethod()?.Name ?? "FitLine"); return (false, Vector2.Zero, Vector2.Zero); }; // y = kx + b double A = 0, B = 0, C = 0, D = 0; foreach (var p in pts) { A += p.X * p.X; B += p.X; C += p.X * p.Y; D += p.Y; } var tmp = A * pts.Count - B * B; var k = (C * pts.Count - B * D) / tmp; var b = (A * D - C * B) / tmp; double x1 = 0, y1 = k * x1 + b, x2 = 1000, y2 = k * x2 + b; double CalcDist(ref bool fail, ref Vector2 endP, ref Vector2 endQ) { double distSum = 0; double lx = x2 - x1, ly = y2 - y1, dAB = lx * lx + ly * ly; double minU = double.MaxValue, maxU = double.MinValue; foreach (var p in pts) { var u = ((p.X - x1) * lx + (p.Y - y1) * ly) / dAB; var perp = new Vector2((float)(x1 + u * lx), (float)(y1 + u * ly)); if (u < minU) { endP = perp; minU = u; } if (u > maxU) { endQ = perp; maxU = u; } var curDist = dist(perp.X, perp.Y, p.X, p.Y); if (maxDist2Line > -1 && curDist > maxDist2Line) fail = true; distSum += curDist; } return distSum; } var kbFail = false; Vector2 endP1 = new Vector2(), endQ1 = new Vector2(); double kbDist = CalcDist(ref kbFail, ref endP1, ref endQ1); // x = my + n A = 0; B = 0; C = 0; D = 0; foreach (var p in pts) { A += p.X * p.Y; B += p.Y * p.Y; C += p.Y; D += p.X; } tmp = C * C - B * pts.Count; var m = (C * D - A * pts.Count) / tmp; var n = (A * C - B * D) / tmp; y1 = 0; x1 = m * y1 + n; y2 = 1000; x2 = m * y2 + n; var mnFail = false; Vector2 endP2 = new Vector2(), endQ2 = new Vector2(); double mnDist = CalcDist(ref mnFail, ref endP2, ref endQ2); Vector2 endP = endP1, endQ = endQ1; if (mnDist < kbDist) { if (mnFail) return (false, new Vector2(), new Vector2()); endP = endP2; endQ = endQ2; } else if (kbFail) return (false, new Vector2(), new Vector2()); return (true, endP, endQ); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int toId(int x, int y, int z) { return (x * 1140671485 + 12820163) ^ (y * 134775813 + 1) ^ (z * 1103515245 + 12345); } public class Clustering { public int numIteration = 3; public int itemNumThreshold = 10; public Func inRange; public Func, T> average; private readonly List _inputData; private Dictionary> _clusters = new Dictionary>(); public Clustering(List data, Func inRange, Func, T> average) { _inputData = data; this.inRange = inRange; this.average = average; } public Dictionary> GetClusters() { var tmp = new List<(T center, List items)>(); for (var iter = 0; iter < numIteration; iter++) { tmp = tmp.Where(cluster => cluster.items.Count > itemNumThreshold) .Select(cluster => (average(cluster.items), new List())).ToList(); foreach (var data in _inputData) { var added = false; foreach (var cluster in tmp) { if (inRange(cluster.center, data)) { cluster.items.Add(data); added = true; break; } } if (!added) tmp.Add((data, new List() { data })); } } _clusters = tmp.Where(cluster => cluster.items.Count > itemNumThreshold) .ToDictionary(cluster => cluster.center, cluster => cluster.items); return _clusters; } } public static (bool, Vector2) TwoLinesIntersection(Vector2 A, Vector2 B, Vector2 C, Vector2 D) { // Line AB represented as a1x + b1y = c1 double a1 = B.Y - A.Y; double b1 = A.X - B.X; double c1 = a1 * (A.X) + b1 * (A.Y); // Line CD represented as a2x + b2y = c2 double a2 = D.Y - C.Y; double b2 = C.X - D.X; double c2 = a2 * (C.X) + b2 * (C.Y); double determinant = a1 * b2 - a2 * b1; if (determinant == 0) { // The lines are parallel. This is simplified // by returning a pair of FLT_MAX return new(false, new Vector2()); } else { double x = (b2 * c1 - b1 * c2) / determinant; double y = (a1 * c2 - a2 * c1) / determinant; return (true, new Vector2((float)x, (float)y)); } } public static bool IsAtLeft(Vector2 anchor, Vector2 dest, Vector2 p) { var v1 = new Vector3(anchor - p, 0); var v2 = new Vector3(dest - p, 0); return Vector3.Cross(v1, v2).Z > 0; } /// /// 将角度转化至-180到180度的范围内。 /// /// /// public static double RoundTh(double th) { return th - System.Math.Round(th / 360) * 360; } /// /// 将角度转化至-180到180度的范围内。 /// /// /// public static float RoundTh(float th) { return th - (float)System.Math.Round(th / 360f) * 360f; } } }