|
@@ -16,13 +16,17 @@ internal static class VectorMath
|
|
|
case PathVerb.Line:
|
|
|
return ClosestPointOnLine((VecD)verb.From, (VecD)verb.To, point);
|
|
|
case PathVerb.Quad:
|
|
|
- break;
|
|
|
+ return GetClosestPointOnQuad(point, (VecD)verb.From, (VecD)(verb.ControlPoint1 ?? verb.From),
|
|
|
+ (VecD)verb.To);
|
|
|
case PathVerb.Conic:
|
|
|
- break;
|
|
|
+ return GetClosestPointOnConic(point, (VecD)verb.From, (VecD)(verb.ControlPoint1 ?? verb.From),
|
|
|
+ (VecD)verb.To,
|
|
|
+ verb.ConicWeight);
|
|
|
case PathVerb.Cubic:
|
|
|
- break;
|
|
|
+ return GetClosestPointOnCubic(point, (VecD)verb.From, (VecD)(verb.ControlPoint1 ?? verb.From),
|
|
|
+ (VecD)(verb.ControlPoint2 ?? verb.To), (VecD)verb.To);
|
|
|
case PathVerb.Close:
|
|
|
- break;
|
|
|
+ return (VecD)verb.From;
|
|
|
case PathVerb.Done:
|
|
|
break;
|
|
|
case null:
|
|
@@ -30,26 +34,29 @@ internal static class VectorMath
|
|
|
default:
|
|
|
throw new ArgumentOutOfRangeException();
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
return null;
|
|
|
}
|
|
|
-
|
|
|
- public static bool IsPointOnSegment(VecF point, Verb shapePointVerb)
|
|
|
+
|
|
|
+ public static bool IsPointOnSegment(VecD point, Verb shapePointVerb)
|
|
|
{
|
|
|
if (shapePointVerb.IsEmptyVerb()) return false;
|
|
|
|
|
|
switch (shapePointVerb.VerbType)
|
|
|
{
|
|
|
case PathVerb.Move:
|
|
|
- return point == shapePointVerb.From;
|
|
|
+ return Math.Abs(point.X - shapePointVerb.From.X) < 0.0001 && Math.Abs(point.Y - shapePointVerb.From.Y) < 0.0001;
|
|
|
case PathVerb.Line:
|
|
|
- return IsPointOnLine(point, shapePointVerb.From, shapePointVerb.To);
|
|
|
+ return IsPointOnLine(point, (VecD)shapePointVerb.From, (VecD)shapePointVerb.To);
|
|
|
case PathVerb.Quad:
|
|
|
- break;
|
|
|
+ return IsPointOnQuad(point, (VecD)shapePointVerb.From, (VecD)(shapePointVerb.ControlPoint1 ?? shapePointVerb.From),
|
|
|
+ (VecD)shapePointVerb.To);
|
|
|
case PathVerb.Conic:
|
|
|
- break;
|
|
|
+ return IsPointOnConic(point, (VecD)shapePointVerb.From, (VecD)(shapePointVerb.ControlPoint1 ?? shapePointVerb.From),
|
|
|
+ (VecD)shapePointVerb.To, shapePointVerb.ConicWeight);
|
|
|
case PathVerb.Cubic:
|
|
|
- break;
|
|
|
+ return IsPointOnCubic(point, (VecD)shapePointVerb.From, (VecD)(shapePointVerb.ControlPoint1 ?? shapePointVerb.From),
|
|
|
+ (VecD)(shapePointVerb.ControlPoint2 ?? shapePointVerb.To), (VecD)shapePointVerb.To);
|
|
|
case PathVerb.Close:
|
|
|
break;
|
|
|
case PathVerb.Done:
|
|
@@ -67,28 +74,113 @@ internal static class VectorMath
|
|
|
{
|
|
|
VecD startToPoint = point - start;
|
|
|
VecD startToEnd = end - start;
|
|
|
-
|
|
|
+
|
|
|
double sqrtMagnitudeToEnd = Math.Pow(startToEnd.X, 2) + Math.Pow(startToEnd.Y, 2);
|
|
|
-
|
|
|
+
|
|
|
double dot = startToPoint.X * startToEnd.X + startToPoint.Y * startToEnd.Y;
|
|
|
var t = dot / sqrtMagnitudeToEnd;
|
|
|
-
|
|
|
+
|
|
|
if (t < 0) return start;
|
|
|
if (t > 1) return end;
|
|
|
-
|
|
|
+
|
|
|
return start + startToEnd * t;
|
|
|
}
|
|
|
-
|
|
|
- public static bool IsPointOnLine(VecF point, VecF start, VecF end)
|
|
|
+
|
|
|
+ public static bool IsPointOnLine(VecD point, VecD start, VecD end)
|
|
|
{
|
|
|
- VecF startToPoint = point - start;
|
|
|
- VecF startToEnd = end - start;
|
|
|
-
|
|
|
+ VecD startToPoint = point - start;
|
|
|
+ VecD startToEnd = end - start;
|
|
|
+
|
|
|
double sqrtMagnitudeToEnd = Math.Pow(startToEnd.X, 2) + Math.Pow(startToEnd.Y, 2);
|
|
|
-
|
|
|
+
|
|
|
double dot = startToPoint.X * startToEnd.X + startToPoint.Y * startToEnd.Y;
|
|
|
var t = dot / sqrtMagnitudeToEnd;
|
|
|
-
|
|
|
+
|
|
|
return t is >= 0 and <= 1;
|
|
|
}
|
|
|
+
|
|
|
+ public static VecD GetClosestPointOnQuad(VecD point, VecD start, VecD controlPoint, VecD end)
|
|
|
+ {
|
|
|
+ return FindClosestPointBruteForce(point, (t) => QuadraticBezier(start, controlPoint, end, t));
|
|
|
+ }
|
|
|
+
|
|
|
+ public static VecD GetClosestPointOnCubic(VecD point, VecD start, VecD controlPoint1, VecD controlPoint2, VecD end)
|
|
|
+ {
|
|
|
+ return FindClosestPointBruteForce(point, (t) => CubicBezier(start, controlPoint1, controlPoint2, end, t));
|
|
|
+ }
|
|
|
+
|
|
|
+ public static VecD GetClosestPointOnConic(VecD point, VecD start, VecD controlPoint, VecD end, float weight)
|
|
|
+ {
|
|
|
+ return FindClosestPointBruteForce(point, (t) => ConicBezier(start, controlPoint, end, weight, t));
|
|
|
+ }
|
|
|
+
|
|
|
+ public static bool IsPointOnQuad(VecD point, VecD start, VecD controlPoint, VecD end)
|
|
|
+ {
|
|
|
+ return IsPointOnPath(point, (t) => QuadraticBezier(start, controlPoint, end, t));
|
|
|
+ }
|
|
|
+
|
|
|
+ public static bool IsPointOnCubic(VecD point, VecD start, VecD controlPoint1, VecD controlPoint2, VecD end)
|
|
|
+ {
|
|
|
+ return IsPointOnPath(point, (t) => CubicBezier(start, controlPoint1, controlPoint2, end, t));
|
|
|
+ }
|
|
|
+
|
|
|
+ public static bool IsPointOnConic(VecD point, VecD start, VecD controlPoint, VecD end, float weight)
|
|
|
+ {
|
|
|
+ return IsPointOnPath(point, (t) => ConicBezier(start, controlPoint, end, weight, t));
|
|
|
+ }
|
|
|
+
|
|
|
+ private static VecD FindClosestPointBruteForce(VecD point, Func<double, VecD> func, double step = 0.001)
|
|
|
+ {
|
|
|
+ double minDistance = double.MaxValue;
|
|
|
+ VecD closestPoint = new VecD();
|
|
|
+ for (double t = 0; t <= 1; t += step)
|
|
|
+ {
|
|
|
+ VecD currentPoint = func(t);
|
|
|
+ double distance = VecD.Distance(point, currentPoint);
|
|
|
+ if (distance < minDistance)
|
|
|
+ {
|
|
|
+ minDistance = distance;
|
|
|
+ closestPoint = currentPoint;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return closestPoint;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static bool IsPointOnPath(VecD point, Func<double, VecD> func, double step = 0.001)
|
|
|
+ {
|
|
|
+ for (double t = 0; t <= 1; t += step)
|
|
|
+ {
|
|
|
+ VecD currentPoint = func(t);
|
|
|
+ if (VecD.Distance(point, currentPoint) < 0.1)
|
|
|
+ {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static VecD QuadraticBezier(VecD start, VecD control, VecD end, double t)
|
|
|
+ {
|
|
|
+ double x = Math.Pow(1 - t, 2) * start.X + 2 * (1 - t) * t * control.X + Math.Pow(t, 2) * end.X;
|
|
|
+ double y = Math.Pow(1 - t, 2) * start.Y + 2 * (1 - t) * t * control.Y + Math.Pow(t, 2) * end.Y;
|
|
|
+ return new VecD(x, y);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static VecD CubicBezier(VecD start, VecD control1, VecD control2, VecD end, double t)
|
|
|
+ {
|
|
|
+ double x = Math.Pow(1 - t, 3) * start.X + 3 * Math.Pow(1 - t, 2) * t * control1.X +
|
|
|
+ 3 * (1 - t) * Math.Pow(t, 2) * control2.X + Math.Pow(t, 3) * end.X;
|
|
|
+ double y = Math.Pow(1 - t, 3) * start.Y + 3 * Math.Pow(1 - t, 2) * t * control1.Y +
|
|
|
+ 3 * (1 - t) * Math.Pow(t, 2) * control2.Y + Math.Pow(t, 3) * end.Y;
|
|
|
+ return new VecD(x, y);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static VecD ConicBezier(VecD start, VecD control, VecD end, float weight, double t)
|
|
|
+ {
|
|
|
+ double x = Math.Pow(1 - t, 2) * start.X + 2 * (1 - t) * t * control.X + Math.Pow(t, 2) * end.X;
|
|
|
+ double y = Math.Pow(1 - t, 2) * start.Y + 2 * (1 - t) * t * control.Y + Math.Pow(t, 2) * end.Y;
|
|
|
+ return new VecD(x, y);
|
|
|
+ }
|
|
|
}
|