dhq_boiler
@dhq_boiler

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

[C#]45°回転した楕円の接線の交点を求めたい

解決したいこと

今、boiler's Graphicsで、描画した楕円の接線の交点をスナップするように実装しました。

intersection_of_ellipse_and_tangent.gif

この記事、Calculate where a line segment and an ellipse intersect in C#を参考に、以下のように楕円の接線の交点を求めるコードを書きました。記事のコードを少しアレンジしており、戻り値をTupleにしています。TupleのItem1は交点の配列、Item2は判別式の値です。パラメータはellipseが楕円のビューモデルで、pt1が線分の開始点、pt2が線分の終了点、segment_onlyは理解できていません!

Helpers/Intersection.cs
using boilersGraphics.ViewModels;
using NLog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;

namespace boilersGraphics.Helpers
{
    public static class Intersection
    {
        /// <summary>
        /// http://csharphelper.com/blog/2017/08/calculate-where-a-line-segment-and-an-ellipse-intersect-in-c/
        /// </summary>
        /// <param name="ellipse">ellipse</param>
        /// <param name="pt1">beginPoint of tangent</param>
        /// <param name="pt2">endPoint of tangent</param>
        /// <param name="segment_only"></param>
        /// <returns></returns>
        public static Tuple<Point[], double> FindEllipseSegmentIntersections(NEllipseViewModel ellipse, Point pt1, Point pt2, bool segment_only)
        {
            var clone = ellipse.Clone() as NEllipseViewModel;
            // If the ellipse or line segment are empty, return no intersections.
            if ((clone.Width.Value == 0) || (clone.Height.Value == 0) ||
                ((pt1.X == pt2.X) && (pt1.Y == pt2.Y)))
                return new Tuple<Point[], double>(new Point[] { }, double.NaN);

            // Make sure the rectangle has non-negative width and height.
            if (clone.Width.Value < 0)
            {
                clone.Left.Value = clone.Right.Value;
                clone.Width.Value = -clone.Width.Value;
            }
            if (clone.Height.Value < 0)
            {
                clone.Top.Value = clone.Bottom.Value;
                clone.Height.Value = -clone.Height.Value;
            }

            // Translate so the ellipse is centered at the origin.
            double cx = clone.CenterX.Value;
            double cy = clone.CenterY.Value;
            clone.Left.Value -= cx;
            clone.Top.Value -= cy;
            pt1.X -= cx;
            pt1.Y -= cy;
            pt2.X -= cx;
            pt2.Y -= cy;

            // Get the semimajor and semiminor axes.
            double a = clone.Width.Value / 2;
            double b = clone.Height.Value / 2;

            // Calculate the quadratic parameters.
            double A = (pt2.X - pt1.X) * (pt2.X - pt1.X) / a / a +
                       (pt2.Y - pt1.Y) * (pt2.Y - pt1.Y) / b / b;
            double B = 2 * pt1.X * (pt2.X - pt1.X) / a / a +
                       2 * pt1.Y * (pt2.Y - pt1.Y) / b / b;
            double C = pt1.X * pt1.X / a / a + pt1.Y * pt1.Y / b / b - 1;

            // Make a list of t values.
            List<double> t_values = new List<double>();

            // Calculate the discriminant.
            double discriminant = B * B - 4 * A * C;
            LogManager.GetCurrentClassLogger().Debug($"discriminant:{discriminant}");
            if (Math.Abs(discriminant) < 0.1)
            {
                // One real solution.
                t_values.Add(-B / 2 / A);
            }
            else if (discriminant > 0)
            {
                // Two real solutions.
                t_values.Add((double)((-B + Math.Sqrt(discriminant)) / 2 / A));
                t_values.Add((double)((-B - Math.Sqrt(discriminant)) / 2 / A));
            }

            // Convert the t values into points.
            List<Point> points = new List<Point>();
            foreach (double t in t_values)
            {
                // If the points are on the segment (or we
                // don't care if they are), add them to the list.
                if (!segment_only || ((t >= 0f) && (t <= 1f)))
                {
                    double x = pt1.X + (pt2.X - pt1.X) * t + cx;
                    double y = pt1.Y + (pt2.Y - pt1.Y) * t + cy;
                    points.Add(new Point(x, y));
                }
            }

            // Return the points.
            return new Tuple<Point[], double>(points.ToArray(), discriminant);
        }
    }
}

これで普通の回転していない楕円の接線の交点は求めることができたのですが、回転した楕円にはまだ対応できていません。
例えば、45°回転した楕円の接線の交点を求めるにはどのようにしたらいいでしょうか?

高校数学なのか大学数学なのか、その区別もついておりませんが、わかる方いらっしゃいましたら、是非回答お願いします!!!

該当するソースコード

ブランチ:feature/intersection

0

6Answer

45°回転体との交点を求めるのであれば、

  1. 座標系を-45°回転させる。
  2. その座標系で交点を求める。
  3. その後座標系を45°回転させる。

でいけるんじゃないのかな?なんとなく。

座標系の回転は、以下の内容がわかりやすかったかな

0Like

理屈としては @ktz_alias さんと同じことですが

  1. 「楕円の中心が(0,0)になるよう平行移動」&「楕円の長軸がx軸となるよう回転」を行う座標変換マトリクスを作成
  2. 楕円の接線を計算(中心が0,0固定なので式がシンプル)
  3. 2で得られた接線に対して1で作ったマトリクスの逆マトリクスを適用して元の座標系に変換

とするとわかりやすいコードになると思います。
1のときにたとえば「長軸半径が1.0になるようにスケーリング」も行うとさらにすっきりするかもしれませんね。

0Like

ちょっと計算してみた。

x(t) = x1 + (x2-x1)t
y(t) = y1 + (y2-y1)t

回転
x' = cosθx - sinθy
y' = sinθx + cosθy

楕円の公式
x^2 / a^2 + y^2 / b^2 = 1

1/a^2*(cosθ^2*(x1+(x2-x1)t)^2-2cosθsinθ(x1+(x2-x1)t)(y1+(y2-y1)t)+sinθ^2*(y1+(y2-y1)t)^2) + 
1/b^2*(sinθ^2*(x1+(x2-x1)t)^2+2sinθcosθ(x1+(x2-x1)t)(y1+(y2-y1)t)+cosθ^2*(y1+(y2-y1)t)^2) = 1


(x1+(x2-x1)t)^2 = x1^2 + 2(x2-x1)t + (x2-x1)^2 * t^2

(y1+(y2-y1)t)^2 = y1^2 + 2(y2-y1)t + (y2-y1)^2 * t^2

(x1+(x2-x1)t)(y1+(y2-y1)t) = x1y1+y1(x2-x1)t+x1(y2-y1)t+(x2-x1)(y2-y1)t^2

1/a^2*(cosθ^2*(x1^2 + 2(x2-x1)t + (x2-x1)^2 * t^2)-2cosθsinθ(x1y1 + y1(x2-x1)t + x1(y2-y1)t + (x2-x1)(y2-y1)t^2)+sinθ^2*(y1^2 + 2(y2-y1)t + (y2-y1)^2 * t^2)) + 

1/b^2*(sinθ^2*(x1^2 + 2(x2-x1)t + (x2-x1)^2 * t^2)+2sinθcosθ(x1y1 + y1(x2-x1)t + x1(y2-y1)t + (x2-x1)(y2-y1)t^2)+cosθ^2*(y1^2 + 2(y2-y1)t + (y2-y1)^2 * t^2)) = 1


1/a^2 * cosθ^2 * (x2-x1)^2 * t^2 + 1/a^2 * (-2cosθsinθ)(x2-x1)(y2-y1) * t^2 + 1/a^2 * sinθ^2(y2-y1)^2 * t^2 + 
1/b^2 * sinθ^2 * (x2-x1)^2 * t^2 + 1/b^2 * 2sinθcosθ(x2-x1)(y2-y1) * t^2 + 1/b^2 * cosθ^2(y2-y1)^2 * t^2 +
1/a^2 * cosθ^2 * 2(x2-x1)t + 1/a^2 * (-2cosθsinθ) * (y1(x2-x1) + x1(y2-y1))t + 1/a^2 * sinθ^2 * 2(y2-y1)t +
1/b^2 * sinθ^2 * 2(x2-x1)t + 1/b^2 * (2sinθcosθ) * (y1(x2-x1) + x1(y2-y1))t + 1/b^2 * cosθ^2 * 2(y2-y1)t +
1/a^2 * cosθ^2 * x1^2 + 1/a^2 * (-2cosθsinθ) * x1y1 + 1/a^2 * sinθ^2 * y1^2 +
1/b^2 * sinθ^2 * x1^2 + 1/b^2 * 2sinθcosθ * x1y1 + 1/b^2 * cosθ^2 * y1^2 = 1

tについて整理する

(1/a^2 * (cosθ^2 * (x2-x1)^2 + (-2cosθsinθ)(x2-x1)(y2-y1) + sinθ^2(y2-y1)^2) + 1/b^2 * (sinθ^2 * (x2-x1)^2 + 2sinθcosθ(x2-x1)(y2-y1) + cosθ^2(y2-y1)^2)) * t^2
(1/a^2 * (cosθ^2 * 2(x2-x1) - 2cosθsinθ * (y1(x2-x1) + x1(y2-y1)) + sinθ^2 * 2(y2-y1)) + 1/b^2 * (sinθ^2 * 2(x2-x1) + 2sinθcosθ * (y1(x2-x1) + x1(y2-y1)) + cosθ^2 * 2(y2-y1))) * t
(1/a^2 * cosθ^2 * x1^2 + 1/a^2 * (-2cosθsinθ) * x1y1 + 1/a^2 * sinθ^2 * y1^2 + 1/b^2 * sinθ^2 * x1^2 + 1/b^2 * 2sinθcosθ * x1y1 + 1/b^2 * cosθ^2 * y1^2) = 1

で、テストを回してみました。
案の定、回転0度の円_回転対応版のテストが失敗しました。
θ=0で検算してみたのですがこれはおかしい。

using boilersGraphics.Helpers;
using boilersGraphics.ViewModels;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;

namespace boilersGraphics.Test
{

    /// <summary>
    /// http://csharphelper.com/blog/2017/08/calculate-where-a-line-segment-and-an-ellipse-intersect-in-c/
    /// </summary>
    [TestFixture]
    public class IntersectionTest
    {
        [Test]
        public void Basic()
        {
            boilersGraphics.App.IsTest = true;
            var mainWindowViewModel = new MainWindowViewModel(null);
            var diagramViewModel = new DiagramViewModel(mainWindowViewModel, 1000, 1000);
            var ellipse = new NEllipseViewModel(-4, 3, 8, 6);

            var intersections = Intersection.FindEllipseSegmentIntersections(ellipse, new Point(-4, -1), new Point(-4, 3), false);

            Assert.That(intersections.Item1.Count(), Is.EqualTo(1));
            Assert.That(intersections.Item1.First().X, Is.EqualTo(-4).Within(0.00001));
            Assert.That(intersections.Item1.First().Y, Is.EqualTo(6).Within(0.00001));
        }


        [Test]
        public void 回転0度の円()
        {
            boilersGraphics.App.IsTest = true;
            var mainWindowViewModel = new MainWindowViewModel(null);
            var diagramViewModel = new DiagramViewModel(mainWindowViewModel, 1000, 1000);
            var ellipse = new NEllipseViewModel(-4, -4, 8, 8);

            Assert.That(ellipse.CenterX.Value, Is.EqualTo(0));
            Assert.That(ellipse.CenterY.Value, Is.EqualTo(0));

            var intersections = Intersection.FindEllipseSegmentIntersections(ellipse, new Point(-4, -10), new Point(-4, 10), false);

            Assert.That(intersections.Item1.Count(), Is.EqualTo(1));
            Assert.That(intersections.Item1.First().X, Is.EqualTo(-4).Within(0.00001));
            Assert.That(intersections.Item1.First().Y, Is.EqualTo(0).Within(0.00001));
        }


        ///テスト失敗
        [Test]
        public void 回転0度の円_回転対応版()
        {
            boilersGraphics.App.IsTest = true;
            var mainWindowViewModel = new MainWindowViewModel(null);
            var diagramViewModel = new DiagramViewModel(mainWindowViewModel, 1000, 1000);
            var ellipse = new NEllipseViewModel(-4, -4, 8, 8);

            Assert.That(ellipse.CenterX.Value, Is.EqualTo(0));
            Assert.That(ellipse.CenterY.Value, Is.EqualTo(0));

            var intersections = Intersection.FindEllipseSegmentIntersectionsSupportRotation(ellipse, new Point(-4, -10), new Point(-4, 10), false);

            Assert.That(intersections.Item1.Count(), Is.EqualTo(1));
            Assert.That(intersections.Item1.First().X, Is.EqualTo(-4).Within(0.00001));
            Assert.That(intersections.Item1.First().Y, Is.EqualTo(0).Within(0.00001));
        }
    }
}

テスト失敗です。

 回転0度の円_回転対応版
 ソース: IntersectionTest.cs 行 56
 期間: 44 ミリ秒

メッセージ: 
Expected: 1
But was: 0

スタック トレース: 
IntersectionTest.回転0度の円_回転対応版() 行 68

標準出力: 
2021-11-20 16:36:14.6532|INFO|boilersGraphics.ViewModels.MainWindowViewModel|Heavy Modifying AppDB Count : 0
A:25 B:2.5 C:6.25
Ba:0
Bb:2.5
Bb_1:0 Bb_2:0 Bb_3:2.5

正常に動いているIntersection.FindEllipseSegmentIntersectionsメソッドの中の判別式の構成要素のA, B, Cと比較してみました。

正常な方
A:25 B:-25 C:6.25
Ba:0
Bb:-25

異常な方
A:25 B:2.5 C:6.25
Ba:0
Bb:2.5
Bb_1:0 Bb_2:0 Bb_3:2.5

Bの値がおかしい。-25のはずが2.5になっている。
Bbの値がなにかおかしい。
Bbとは判別式= B * B - 4 * A * CのBの一部です。
B = (Pow(Cos(θ), 2) * 2 * (x2 - x1) - 2 * Cos(θ) * Sin(θ) * (y1 * (x2 - x1) + x1 * (y2 - y1)) + Pow(Sin(θ), 2) * 2 * (y2 - y1)) / a / a
+ (Pow(Sin(θ), 2) * 2 * (x2 - x1) + 2 * Sin(θ) * Cos(θ) * (y1 * (x2 - x1) + x1 * (y2 - y1)) + Pow(Cos(θ), 2) * 2 * (y2 - y1)) / b / b
ですが、Bbはb^2で割っている値を示します。すなわちBb=(Pow(Sin(θ), 2) * 2 * (x2 - x1) + 2 * Sin(θ) * Cos(θ) * (y1 * (x2 - x1) + x1 * (y2 - y1)) + Pow(Cos(θ), 2) * 2 * (y2 - y1)) / b / b

惜しいところまで行っているような気がするのですが...。

0Like

計算が間違ってました。
正しくは以下の通りです。

(x1+(x2-x1)t)^2 = x1^2 + 2x1(x2-x1)t + (x2-x1)^2 * t^2
             ^^
(y1+(y2-y1)t)^2 = y1^2 + 2y1(y2-y1)t + (y2-y1)^2 * t^2
                          ^^

上記のようになるので、結局


(1/a^2 * (cosθ^2 * (x2-x1)^2 + (-2cosθsinθ)(x2-x1)(y2-y1) + sinθ^2(y2-y1)^2) + 1/b^2 * (sinθ^2 * (x2-x1)^2 + 2sinθcosθ(x2-x1)(y2-y1) + cosθ^2(y2-y1)^2)) * t^2
(1/a^2 * (cosθ^2 * 2x1(x2-x1) - 2cosθsinθ * (y1(x2-x1) + x1(y2-y1)) + sinθ^2 * 2y1(y2-y1)) + 1/b^2 * (sinθ^2 * 2x1(x2-x1) + 2sinθcosθ * (y1(x2-x1) + x1(y2-y1)) + cosθ^2 * 2y1(y2-y1))) * t
(1/a^2 * cosθ^2 * x1^2 + 1/a^2 * (-2cosθsinθ) * x1y1 + 1/a^2 * sinθ^2 * y1^2 + 1/b^2 * sinθ^2 * x1^2 + 1/b^2 * 2sinθcosθ * x1y1 + 1/b^2 * cosθ^2 * y1^2) = 1

となります。

0Like

その後、色々と手を加えました。

単純な円を回転させた場合は、正しく接線の交点が求められているようなのですが、
楕円を回転させた場合は、接線の交点を求められていません。
(正しく接線の交点がもとめられていると、図形の中心から交点へ青い補助線が引かれます)

bug_verification.gif

これはどういうことでしょう。検討もつきません。

以下に今のコードを掲載します。

        public static Tuple<Point[], double> FindEllipseSegmentIntersectionsSupportRotation(NEllipseViewModel ellipse, Point pt1, Point pt2, bool segment_only)
        {
            var clone = ellipse.Clone() as NEllipseViewModel;
            // If the ellipse or line segment are empty, return no intersections.
            if ((clone.Width.Value == 0) || (clone.Height.Value == 0) ||
                ((pt1.X == pt2.X) && (pt1.Y == pt2.Y)))
                return new Tuple<Point[], double>(new Point[] { }, double.NaN);

            // Make sure the rectangle has non-negative width and height.
            if (clone.Width.Value < 0)
            {
                clone.Left.Value = clone.Right.Value;
                clone.Width.Value = -clone.Width.Value;
            }
            if (clone.Height.Value < 0)
            {
                clone.Top.Value = clone.Bottom.Value;
                clone.Height.Value = -clone.Height.Value;
            }

            // Translate so the ellipse is centered at the origin.
            double cx = clone.CenterX.Value;
            double cy = clone.CenterY.Value;
            clone.Left.Value -= cx;
            clone.Top.Value -= cy;
            pt1.X -= cx;
            pt1.Y -= cy;
            pt2.X -= cx;
            pt2.Y -= cy;

            var x1 = pt1.X;
            var y1 = pt1.Y;
            var x2 = pt2.X;
            var y2 = pt2.Y;

            var θ = (ellipse.RotationAngle.Value) * PI / 180.0;

            // Get the semimajor and semiminor axes.
            double a = clone.Width.Value / 2;
            double b = clone.Height.Value / 2;

            // Calculate the quadratic parameters.
            double A = (Pow(Cos(θ) * (x2 - x1), 2) - 2 * Cos(θ) * Sin(θ) * (x2 - x1) * (y2 - y1) + Pow(Sin(θ) * (y2 - y1), 2)) / a / a +
                       (Pow(Sin(θ) * (x2 - x1), 2) + 2 * Sin(θ) * Cos(θ) * (x2 - x1) * (y2 - y1) + Pow(Cos(θ) * (y2 - y1), 2)) / b / b;
            double B = (Pow(Cos(θ), 2) * 2 * x1 * (x2 - x1) - 2 * Cos(θ) * Sin(θ) * (y1 * (x2 - x1) + x1 * (y2 - y1)) + Pow(Sin(θ), 2) * 2 * y1 * (y2 - y1)) / a / a
                     + (Pow(Sin(θ), 2) * 2 * x1 * (x2 - x1) + 2 * Sin(θ) * Cos(θ) * (y1 * (x2 - x1) + x1 * (y2 - y1)) + Pow(Cos(θ), 2) * 2 * y1 * (y2 - y1)) / b / b;
            double C = (Pow(Cos(θ) * x1, 2) - 2 * Cos(θ) * Sin(θ) * x1 * y1 + Pow(Sin(θ) * y1, 2)) / a / a
                     + (Pow(Sin(θ) * x1, 2) + 2 * Sin(θ) * Cos(θ) * x1 * y1 + Pow(Cos(θ) * y1, 2)) / b / b - 1;

            // Make a list of t values.
            List<double> t_values = new List<double>();

            // Calculate the discriminant.
            double discriminant = B * B - 4 * A * C;
            LogManager.GetCurrentClassLogger().Debug($"discriminant:{discriminant}");
            if (Abs(discriminant) < 0.1)
            {
                // One real solution.
                t_values.Add(-B / 2 / A);
            }
            else if (discriminant > 0)
            {
                // Two real solutions.
                t_values.Add((double)((-B + Sqrt(discriminant)) / 2 / A));
                t_values.Add((double)((-B - Sqrt(discriminant)) / 2 / A));
            }

            // Convert the t values into points.
            List<Point> points = new List<Point>();
            foreach (double t in t_values)
            {
                // If the points are on the segment (or we
                // don't care if they are), add them to the list.
                if (!segment_only || ((t >= 0f) && (t <= 1f)))
                {
                    double x = pt1.X + (pt2.X - pt1.X) * t + cx;
                    double y = pt1.Y + (pt2.Y - pt1.Y) * t + cy;
                    points.Add(new Point(x, y));
                }
            }

            // Return the points.
            return new Tuple<Point[], double>(points.ToArray(), discriminant);
        }
0Like

バグ治りました!!!

var θ = (-ellipse.RotationAngle.Value) * PI / 180.0;

度をラジアンに変換する際、楕円の回転角度を指定しますが、-1を掛ける必要があったみたいです。

コードの全文は以下の通りです。

Intersection.cs
        public static Tuple<Point[], double> FindEllipseSegmentIntersectionsSupportRotation(NEllipseViewModel ellipse, Point pt1, Point pt2, bool segment_only)
        {
            var clone = ellipse.Clone() as NEllipseViewModel;
            // If the ellipse or line segment are empty, return no intersections.
            if ((clone.Width.Value == 0) || (clone.Height.Value == 0) ||
                ((pt1.X == pt2.X) && (pt1.Y == pt2.Y)))
                return new Tuple<Point[], double>(new Point[] { }, double.NaN);

            // Make sure the rectangle has non-negative width and height.
            if (clone.Width.Value < 0)
            {
                clone.Left.Value = clone.Right.Value;
                clone.Width.Value = -clone.Width.Value;
            }
            if (clone.Height.Value < 0)
            {
                clone.Top.Value = clone.Bottom.Value;
                clone.Height.Value = -clone.Height.Value;
            }

            // Translate so the ellipse is centered at the origin.
            double cx = clone.CenterX.Value;
            double cy = clone.CenterY.Value;
            pt1.X -= cx;
            pt1.Y -= cy;
            pt2.X -= cx;
            pt2.Y -= cy;

            var x1 = pt1.X;
            var y1 = pt1.Y;
            var x2 = pt2.X;
            var y2 = pt2.Y;

            var θ = (-ellipse.RotationAngle.Value) * PI / 180.0;

            // Get the semimajor and semiminor axes.
            double a = clone.Width.Value / 2;
            double b = clone.Height.Value / 2;

            var cosθ = Cos(θ);
            var sinθ = Sin(θ);

            // Calculate the quadratic parameters.
            double A = (Pow(cosθ * (x2 - x1), 2) - 2 * cosθ * sinθ * (x2 - x1) * (y2 - y1) + Pow(sinθ * (y2 - y1), 2)) / a / a +
                       (Pow(sinθ * (x2 - x1), 2) + 2 * sinθ * cosθ * (x2 - x1) * (y2 - y1) + Pow(cosθ * (y2 - y1), 2)) / b / b;
            double B = (Pow(cosθ, 2) * 2 * x1 * (x2 - x1) - 2 * cosθ * sinθ * (y1 * (x2 - x1) + x1 * (y2 - y1)) + Pow(sinθ, 2) * 2 * y1 * (y2 - y1)) / a / a
                     + (Pow(sinθ, 2) * 2 * x1 * (x2 - x1) + 2 * sinθ * cosθ * (y1 * (x2 - x1) + x1 * (y2 - y1)) + Pow(cosθ, 2) * 2 * y1 * (y2 - y1)) / b / b;
            double C = (Pow(cosθ * x1, 2) - 2 * cosθ * sinθ * x1 * y1 + Pow(sinθ * y1, 2)) / a / a
                     + (Pow(sinθ * x1, 2) + 2 * sinθ * cosθ * x1 * y1 + Pow(cosθ * y1, 2)) / b / b - 1;

            // Make a list of t values.
            List<double> t_values = new List<double>();

            // Calculate the discriminant.
            double discriminant = B * B - 4 * A * C;
            LogManager.GetCurrentClassLogger().Debug($"discriminant:{discriminant}");
            if (Abs(discriminant) < 0.1)
            {
                // One real solution.
                t_values.Add(-B / 2 / A);
            }
            else if (discriminant > 0)
            {
                // Two real solutions.
                t_values.Add((double)((-B + Sqrt(discriminant)) / 2 / A));
                t_values.Add((double)((-B - Sqrt(discriminant)) / 2 / A));
            }

            // Convert the t values into points.
            List<Point> points = new List<Point>();
            foreach (double t in t_values)
            {
                // If the points are on the segment (or we
                // don't care if they are), add them to the list.
                if (!segment_only || ((t >= 0f) && (t <= 1f)))
                {
                    double x = pt1.X + (pt2.X - pt1.X) * t + cx;
                    double y = pt1.Y + (pt2.Y - pt1.Y) * t + cy;
                    points.Add(new Point(x, y));
                }
            }

            // Return the points.
            return new Tuple<Point[], double>(points.ToArray(), discriminant);
        }

bugfixed.gif

あとは、スナップする時に出てくる補助線を法線ベクトルにしたいのですが
これはまた別の話題ということで、後ほど新たに質問を投げたいと思います。

0Like

Your answer might help someone💌