첨부 소스 코드는 나눔고딕코딩 폰트를 사용합니다.
728x90
반응형
728x170

TestProject.zip
다운로드

▶ MainForm.cs

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

namespace TestProject
{
    /// <summary>
    /// 메인 폼
    /// </summary>
    public partial class MainForm : Form
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// SMALL
        /// </summary>
        private const float SMALL = 0.1f;


        /// <summary>
        /// 타원 완성 여부 1
        /// </summary>
        private bool gotEllipse1 = false;
        
        /// <summary>
        /// 타원 완성 여부 2
        /// </summary>
        private bool gotEllipse2 = false;

        /// <summary>
        /// 타원 사각형 1
        /// </summary>
        private Rectangle ellipseRectangle1;
        
        /// <summary>
        /// 타원 사각형 2
        /// </summary>
        private Rectangle ellipseRectangle2;


        /// <summary>
        /// DX1
        /// </summary>
        private float dx1;

        /// <summary>
        /// DY1
        /// </summary>
        private float dy1;

        /// <summary>
        /// RX1
        /// </summary>
        private float rx1;

        /// <summary>
        /// RY1
        /// </summary>
        private float ry1;

        /// <summary>
        /// A1
        /// </summary>
        private float a1;

        /// <summary>
        /// B1
        /// </summary>
        private float b1;

        /// <summary>
        /// C1
        /// </summary>
        private float c1;

        /// <summary>
        /// D1
        /// </summary>
        private float d1;

        /// <summary>
        /// E1
        /// </summary>
        private float e1;

        /// <summary>
        /// F1
        /// </summary>
        private float f1;


        /// <summary>
        /// DX2
        /// </summary>
        private float dx2;

        /// <summary>
        /// DY2
        /// </summary>
        private float dy2;

        /// <summary>
        /// RX2
        /// </summary>
        private float rx2;

        /// <summary>
        /// RY2
        /// </summary>
        private float ry2;

        /// <summary>
        /// A2
        /// </summary>
        private float a2;

        /// <summary>
        /// B2
        /// </summary>
        private float b2;

        /// <summary>
        /// C2
        /// </summary>
        private float c2;

        /// <summary>
        /// D2
        /// </summary>
        private float d2;

        /// <summary>
        /// E2
        /// </summary>
        private float e2;

        /// <summary>
        /// F2
        /// </summary>
        private float f2;


        /// <summary>
        /// 루트 포인트 리스트
        /// </summary>
        private List<PointF> rootPointList = new List<PointF>();

        /// <summary>
        /// 루트 부호 리스트 1
        /// </summary>
        private List<float> rootSignList1 = new List<float>();

        /// <summary>
        /// 루트 부호 리스트 2
        /// </summary>
        private List<float> rootSignList2 = new List<float>();

        /// <summary>
        /// 교차점 리스트
        /// </summary>
        private List<PointF> intersectionPointList = new List<PointF>();

        /// <summary>
        /// 그리기 타원 카운트
        /// </summary>
        private int drawingEllipseCount = 0;

        /// <summary>
        /// 시작 X
        /// </summary>
        private int startX;
        
        /// <summary>
        /// 시작 Y
        /// </summary>
        private int startY;
        
        /// <summary>
        /// 종료 X
        /// </summary>
        private int endX;
        
        /// <summary>
        /// 종료 Y
        /// </summary>
        private int endY;


        /// <summary>
        /// 탄젠트 X
        /// </summary>
        private float tangentX = 0;

        /// <summary>
        /// 탄젠트 중심 포인트 리스트
        /// </summary>
        private List<PointF> tangentCenterPointList = null;

        /// <summary>
        /// 탄젠트 P1
        /// </summary>
        private List<PointF> tangentP1 = null;

        /// <summary>
        /// 탄젠트 P2
        /// </summary>
        private List<PointF> tangentP2 = null;

        /// <summary>
        /// 탄젠트 펜 리스트
        /// </summary>
        private List<Pen> tangentPenList = null;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - MainForm()

        /// <summary>
        /// 생성자
        /// </summary>
        public MainForm()
        {
            InitializeComponent();

            #region 이벤트를 설정한다.

            Load                               += Form_Load;
            this.canvasPictureBox.Resize       += canvasPictureBox_Resize;
            this.canvasPictureBox.MouseDown    += canvasPictureBox_MouseDown;
            this.canvasPictureBox.MouseMove    += canvasPictureBox_MouseMove;
            this.canvasPictureBox.MouseUp      += canvasPictureBox_MouseUp;
            this.canvasPictureBox.Paint        += canvasPictureBox_Paint;
            this.equationPictureBox.Resize     += equationPictureBox_Resize;
            this.equationPictureBox.MouseClick += equationPictureBox_MouseClick;
            this.equationPictureBox.Paint      += equationPictureBox_Paint;

            #endregion
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Private
        //////////////////////////////////////////////////////////////////////////////// Event

        #region 폼 로드시 처리하기 - Form_Load(sender, e)

        /// <summary>
        /// 폼 로드시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void Form_Load(object sender, EventArgs e)
        {
            DoubleBuffered = true;

            this.tangentX = 184;

            int deltaX = this.canvasPictureBox.ClientSize.Width  / 4;
            int deltaY = this.canvasPictureBox.ClientSize.Height / 4;

            this.ellipseRectangle1 = new Rectangle(deltaX / 2, deltaY / 2, deltaX    , deltaY * 2);
            this.ellipseRectangle2 = new Rectangle(deltaX    , deltaY    , deltaX * 2, deltaY * 2);

            this.gotEllipse1 = true;
            this.gotEllipse2 = true;

            Calculate();
        }

        #endregion
        #region 캔버스 픽처 박스 크기 조정시 처리하기 - canvasPictureBox_Resize(sender, e)

        /// <summary>
        /// 캔버스 픽처 박스 크기 조정시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void canvasPictureBox_Resize(object sender, EventArgs e)
        {
            int deltaX = this.canvasPictureBox.ClientSize.Width  / 4;
            int deltaY = this.canvasPictureBox.ClientSize.Height / 4;

            this.ellipseRectangle1 = new Rectangle(deltaX / 2, deltaY / 2, deltaX    , deltaY * 2);
            this.ellipseRectangle2 = new Rectangle(deltaX    , deltaY    , deltaX * 2, deltaY * 2);

            this.gotEllipse1 = true;
            this.gotEllipse2 = true;

            Calculate();

            this.canvasPictureBox.Refresh();
        }

        #endregion
        #region 캔버스 픽처 박스 마우스 DOWN 처리하기 - canvasPictureBox_MouseDown(sender, e)

        /// <summary>
        /// 캔버스 픽처 박스 마우스 DOWN 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void canvasPictureBox_MouseDown(object sender, MouseEventArgs e)
        {
            this.rootPointList          = new List<PointF>();
            this.rootSignList1          = new List<float>();
            this.rootSignList2          = new List<float>();
            this.intersectionPointList  = new List<PointF>();
            this.tangentCenterPointList = new List<PointF>();
            this.tangentP1              = new List<PointF>();
            this.tangentP2              = new List<PointF>();
            this.tangentPenList         = new List<Pen>();

            if(this.drawingEllipseCount > 0)
            {
                this.drawingEllipseCount = 0;

                this.canvasPictureBox.Refresh();

                this.equationPictureBox.Refresh();

                return;
            }

            if(e.Button == MouseButtons.Left)
            {
                this.drawingEllipseCount = 1;

                this.gotEllipse1 = false;
            }
            else if(e.Button == MouseButtons.Right)
            {
                this.drawingEllipseCount = 2;

                this.gotEllipse2 = false;
            }

            this.startX = e.X;
            this.startY = e.Y;
            this.endX   = e.X;
            this.endY   = e.Y;
        }

        #endregion
        #region 캔버스 픽처 박스 마우스 이동시 처리하기 - canvasPictureBox_MouseMove(sender, e)

        /// <summary>
        /// 캔버스 픽처 박스 마우스 이동시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void canvasPictureBox_MouseMove(object sender, MouseEventArgs e)
        {
            if(this.drawingEllipseCount == 0)
            {
                return;
            }

            this.endX = e.X;
            this.endY = e.Y;

            this.canvasPictureBox.Refresh();
        }

        #endregion
        #region 캔버스 픽처 박스 마우스 UP 처리하기 - canvasPictureBox_MouseUp(sender, e)

        /// <summary>
        /// 캔버스 픽처 박스 마우스 UP 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void canvasPictureBox_MouseUp(object sender, MouseEventArgs e)
        {
            if(this.drawingEllipseCount == 0)
            {
                return;
            }

            this.endX = e.X;
            this.endY = e.Y;

            if((this.startX != this.endX) && (this.startY != this.endY))
            {
                if(this.drawingEllipseCount == 1)
                {
                    this.ellipseRectangle1 = new Rectangle
                    (
                        Math.Min(this.startX, this.endX),
                        Math.Min(this.startY, this.endY),
                        Math.Abs(this.startX - this.endX),
                        Math.Abs(this.startY - this.endY)
                    );

                    this.gotEllipse1 = true;
                }
                else if(this.drawingEllipseCount == 2)
                {
                    this.ellipseRectangle2 = new Rectangle
                    (
                        Math.Min(this.startX, this.endX),
                        Math.Min(this.startY, this.endY),
                        Math.Abs(this.startX - this.endX),
                        Math.Abs(this.startY - this.endY)
                    );

                    this.gotEllipse2 = true;
                }
            }

            Calculate();

            if(this.gotEllipse1 || this.gotEllipse2)
            {
                this.equationPictureBox.Refresh();
            }

            this.drawingEllipseCount = 0;

            this.canvasPictureBox.Refresh();

            this.equationPictureBox.Refresh();
        }

        #endregion
        #region 캔버스 픽처 박스 페인트시 처리하기 - canvasPictureBox_Paint(sender, e)

        /// <summary>
        /// 캔버스 픽처 박스 페인트시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void canvasPictureBox_Paint(object sender, PaintEventArgs e)
        {
            e.Graphics.Clear(this.canvasPictureBox.BackColor);

            e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;

            if(this.gotEllipse1)
            {
                using(Brush brush = new SolidBrush(Color.FromArgb(128, Color.Blue)))
                {
                    e.Graphics.FillEllipse(brush, this.ellipseRectangle1);
                }

                e.Graphics.DrawEllipse(Pens.Blue, this.ellipseRectangle1);
            }

            if(this.gotEllipse2)
            {
                using(Brush brush = new SolidBrush(Color.FromArgb(128, Color.Red)))
                {
                    e.Graphics.FillEllipse(brush, this.ellipseRectangle2);
                }

                e.Graphics.DrawEllipse(Pens.Red, this.ellipseRectangle2);
            }

            if(this.drawingEllipseCount == 1)
            {
                e.Graphics.DrawEllipse
                (
                    Pens.Blue,
                    Math.Min(this.startX, this.endX),
                    Math.Min(this.startY, this.endY),
                    Math.Abs(this.startX - this.endX),
                    Math.Abs(this.startY - this.endY)
                );
            }
            else if(this.drawingEllipseCount == 2)
            {
                e.Graphics.DrawEllipse
                (
                    Pens.Red,
                    Math.Min(this.startX, this.endX),
                    Math.Min(this.startY, this.endY),
                    Math.Abs(this.startX - this.endX),
                    Math.Abs(this.startY - this.endY)
                );
            }

            const int RADIUS = 4;

            foreach(PointF point in this.intersectionPointList)
            {
                RectangleF rectangle = new RectangleF
                (
                    point.X - RADIUS,
                    point.Y - RADIUS,
                    2 * RADIUS,
                    2 * RADIUS
                );

                e.Graphics.DrawEllipse(Pens.Green, rectangle);
            }
        }

        #endregion
        #region 방정식 픽처 박스 크기 조정시 처리하기 - equationPictureBox_Resize(sender, e)

        /// <summary>
        /// 방정식 픽처 박스 크기 조정시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void equationPictureBox_Resize(object sender, EventArgs e)
        {
            this.equationPictureBox.Refresh();
        }

        #endregion
        #region 방정식 픽처 박스 마우스 클릭시 처리하기 - equationPictureBox_MouseClick(sender, e)

        /// <summary>
        /// 방정식 픽처 박스 마우스 클릭시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void equationPictureBox_MouseClick(object sender, MouseEventArgs e)
        {
            this.tangentX = e.X;

            FindDifferenceTangents();

            this.equationPictureBox.Refresh();
        }

        #endregion
        #region 방정식 픽처 박스 페인트시 처리하기 - equationPictureBox_Paint(sender, e)

        /// <summary>
        /// 방정식 픽처 박스 페인트시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void equationPictureBox_Paint(object sender, PaintEventArgs e)
        {
            e.Graphics.Clear(this.equationPictureBox.BackColor);

            e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;

            if(this.gotEllipse1 && this.gotEllipse2)
            {
                float dy = equationPictureBox.ClientSize.Height / 2f;

                e.Graphics.TranslateTransform(0, dy);

                e.Graphics.DrawLine(Pens.Black, 0, 0, equationPictureBox.ClientSize.Width, 0);

                float xMinimum = Math.Max(this.ellipseRectangle1.Left , this.ellipseRectangle2.Left );
                float xMaximum = Math.Min(this.ellipseRectangle1.Right, this.ellipseRectangle2.Right);

                DrawDifferenceCurves(e.Graphics, xMinimum, xMaximum);

                DrawTangents(e.Graphics);

                const float RADIUS = 4;

                foreach(PointF point in this.rootPointList)
                {
                    e.Graphics.DrawEllipse
                    (
                        Pens.Red,
                        point.X - RADIUS,
                        point.Y - RADIUS,
                        2 * RADIUS,
                        2 * RADIUS
                    );
                }
            }
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////// Function

        #region 방정식 확인하기 - VerifyEquation(a, b, c, d, e, f, x, y)

        /// <summary>
        /// 방정식 확인하기
        /// </summary>
        /// <param name="a">A</param>
        /// <param name="b">B</param>
        /// <param name="c">C</param>
        /// <param name="d">D</param>
        /// <param name="e">E</param>
        /// <param name="f">F</param>
        /// <param name="x">X</param>
        /// <param name="y">Y</param>
        private void VerifyEquation(float a, float b, float c, float d, float e, float f, float x, float y)
        {
            float total = a * x * x + b * x * y + c * y * y + d * x + e * y + f;

            Debug.Assert(Math.Abs(total) < 0.001f);
        }

        #endregion
        #region 타원 공식 설정하기 - SetEllipseFormula(rectangle, dx, dy, rx, ry, a, b, c, d, e, f)

        /// <summary>
        /// 타원 공식 설정하기
        /// </summary>
        /// <param name="rectangle">사각형</param>
        /// <param name="dx">dx</param>
        /// <param name="dy">dy</param>
        /// <param name="rx">rx</param>
        /// <param name="ry">ry</param>
        /// <param name="a">a</param>
        /// <param name="b">b</param>
        /// <param name="c">c</param>
        /// <param name="d">d</param>
        /// <param name="e">e</param>
        /// <param name="f">f</param>
        private void SetEllipseFormula
        (
            RectangleF rectangle,
            out float dx,
            out float dy,
            out float rx,
            out float ry,
            out float a,
            out float b,
            out float c,
            out float d,
            out float e,
            out float f
        )
        {
            dx = rectangle.X + rectangle.Width  / 2f;
            dy = rectangle.Y + rectangle.Height / 2f;

            rx = rectangle.Width  / 2f;
            ry = rectangle.Height / 2f;

            a = 1f / rx / rx;
            b = 0;
            c = 1f / ry / ry;
            d = -2f * dx / rx / rx;
            e = -2f * dy / ry / ry;
            f = dx * dx / rx / rx + dy * dy / ry / ry - 1;

            float xMiddle = rectangle.Left + rectangle.Width / 2f;
            float yMiddle = rectangle.Top + rectangle.Height / 2f;

            VerifyEquation(a, b, c, d, e, f, rectangle.Left , yMiddle         );
            VerifyEquation(a, b, c, d, e, f, rectangle.Right, yMiddle         );
            VerifyEquation(a, b, c, d, e, f, xMiddle        , rectangle.Top   );
            VerifyEquation(a, b, c, d, e, f, xMiddle        , rectangle.Bottom);
        }

        #endregion
        #region 숫자 여부 구하기 - IsNumber(number)

        /// <summary>
        /// 숫자 여부 구하기
        /// </summary>
        /// <param name="number">숫자</param>
        /// <returns>숫자 여부</returns>
        private bool IsNumber(float number)
        {
            return !(float.IsNaN(number) || float.IsInfinity(number));
        }

        #endregion
        #region G1 계산하기 - CalculateG1(x, a, b, c, d, e, f, rootSign)

        /// <summary>
        /// G1 계산하기
        /// </summary>
        /// <param name="x">X</param>
        /// <param name="a">A</param>
        /// <param name="b">B</param>
        /// <param name="c">C</param>
        /// <param name="d">D</param>
        /// <param name="e">E</param>
        /// <param name="f">F</param>
        /// <param name="rootSign">루트 부호</param>
        /// <returns>G1</returns>
        private float CalculateG1(float x, float a, float b, float c, float d, float e, float f, float rootSign)
        {
            float result = b * x + e;

            result = result * result;
            result = result - 4 * c * (a * x * x + d * x + f);
            result = rootSign * (float)Math.Sqrt(result);
            result = -(b * x + e) + result;
            result = result / 2 / c;

            return result;
        }

        #endregion
        #region G 계산하기 - CalculateG(x, a1, b1, c1, d1, e1, f1, sign1, a2, b2, c2, d2, e2, f2, sign2)

        /// <summary>
        /// G 계산하기
        /// </summary>
        /// <param name="x">X</param>
        /// <param name="a1">A1</param>
        /// <param name="b1">B1</param>
        /// <param name="c1">C1</param>
        /// <param name="d1">D1</param>
        /// <param name="e1">E1</param>
        /// <param name="f1">F1</param>
        /// <param name="sign1">부호 1</param>
        /// <param name="a2">A2</param>
        /// <param name="b2">B2</param>
        /// <param name="c2">C2</param>
        /// <param name="d2">D2</param>
        /// <param name="e2">E2</param>
        /// <param name="f2">F2</param>
        /// <param name="sign2">부호 2</param>
        /// <returns>G</returns>
        private float CalculateG
        (
            float x,
            float a1,
            float b1,
            float c1,
            float d1,
            float e1,
            float f1,
            float sign1,
            float a2,
            float b2,
            float c2,
            float d2,
            float e2,
            float f2,
            float sign2
        )
        {
            return CalculateG1(x, a1, b1, c1, d1, e1, f1, sign1) -
                   CalculateG1(x, a2, b2, c2, d2, e2, f2, sign2);
        }

        #endregion
        #region G1 프라임 계산하기 - CalculateG1Prime(x, a, b, c, d, e, f, rootSign)

        /// <summary>
        /// G1 프라임 계산하기
        /// </summary>
        /// <param name="x">X</param>
        /// <param name="a">A</param>
        /// <param name="b">B</param>
        /// <param name="c">C</param>
        /// <param name="d">D</param>
        /// <param name="e">E</param>
        /// <param name="f">F</param>
        /// <param name="rootSign">루프 부호</param>
        /// <returns>G1 프라임</returns>
        private float CalculateG1Prime(float x, float a, float b, float c, float d, float e, float f, float rootSign)
        {
            float numerator = 2 * (b * x + e) * b - 4 * c * (2 * a * x + d);

            float denominator = 2 * (float)Math.Sqrt((b * x + e) * (b * x + e) - 4 * c * (a * x * x + d * x + f));

            float result = -b + rootSign * numerator / denominator;

            result = result / 2 / c;

            return result;
        }

        #endregion
        #region 차분 탄젠트 찾기 - FindDifferenceTangents()

        /// <summary>
        /// 차분 탄젠트 찾기
        /// </summary>
        private void FindDifferenceTangents()
        {
            this.tangentCenterPointList = new List<PointF>();
            this.tangentP1              = new List<PointF>();
            this.tangentP2              = new List<PointF>();
            this.tangentPenList         = new List<Pen>();

            if(!this.gotEllipse1 || !this.gotEllipse2)
            {
                return;
            }

            const float TANGENT_LENGTH = 50;

            float tangentY = CalculateG
            (
                this.tangentX,
                this.a1,
                this.b1,
                this.c1,
                this.d1,
                this.e1,
                this.f1,
                +1f,
                this.a2,
                this.b2,
                this.c2,
                this.d2,
                this.e2,
                this.f2,
                +1f
            );

            if(IsNumber(tangentY))
            {
                float slope = CalculateG1Prime(this.tangentX, this.a1, this.b1, this.c1, this.d1, this.e1, this.f1, +1f) -
                              CalculateG1Prime(this.tangentX, this.a2, this.b2, this.c2, this.d2, this.e2, this.f2, +1f);

                if(IsNumber(slope))
                {
                    float deltaX = (float)Math.Sqrt(TANGENT_LENGTH * TANGENT_LENGTH / (1 + slope * slope)) / 2;

                    this.tangentCenterPointList.Add(new PointF(this.tangentX, tangentY));

                    this.tangentP1.Add(new PointF(this.tangentX - deltaX, tangentY - slope * deltaX));
                    this.tangentP2.Add(new PointF(this.tangentX + deltaX, tangentY + slope * deltaX));

                    this.tangentPenList.Add(Pens.Red);
                }
            }

            tangentY = CalculateG
            (
                this.tangentX,
                this.a1,
                this.b1,
                this.c1,
                this.d1,
                this.e1,
                this.f1,
                +1f,
                this.a2,
                this.b2,
                this.c2,
                this.d2,
                this.e2,
                this.f2,
                -1f
            );

            if(IsNumber(tangentY))
            {
                float slope = CalculateG1Prime(this.tangentX, this.a1, this.b1, this.c1, this.d1, this.e1, this.f1, +1f) -
                              CalculateG1Prime(this.tangentX, this.a2, this.b2, this.c2, this.d2, this.e2, this.f2, -1f);

                if(IsNumber(slope))
                {
                    float deltaX = (float)Math.Sqrt(TANGENT_LENGTH * TANGENT_LENGTH / (1 + slope * slope)) / 2;

                    this.tangentCenterPointList.Add(new PointF(this.tangentX, tangentY));

                    this.tangentP1.Add(new PointF(this.tangentX - deltaX, tangentY - slope * deltaX));
                    this.tangentP2.Add(new PointF(this.tangentX + deltaX, tangentY + slope * deltaX));

                    this.tangentPenList.Add(Pens.Green);
                }
            }

            tangentY = CalculateG
            (
                this.tangentX,
                this.a1,
                this.b1,
                this.c1,
                this.d1,
                this.e1,
                this.f1,
                -1f,
                this.a2,
                this.b2,
                this.c2,
                this.d2,
                this.e2,
                this.f2,
                +1f
            );

            if(IsNumber(tangentY))
            {
                float slope = CalculateG1Prime(this.tangentX, this.a1, this.b1, this.c1, this.d1, this.e1, this.f1, -1f) -
                              CalculateG1Prime(this.tangentX, this.a2, this.b2, this.c2, this.d2, this.e2, this.f2, +1f);

                if(IsNumber(slope))
                {
                    float deltaX = (float)Math.Sqrt(TANGENT_LENGTH * TANGENT_LENGTH / (1 + slope * slope)) / 2;

                    this.tangentCenterPointList.Add(new PointF(this.tangentX, tangentY));

                    this.tangentP1.Add(new PointF(this.tangentX - deltaX, tangentY - slope * deltaX));
                    this.tangentP2.Add(new PointF(this.tangentX + deltaX, tangentY + slope * deltaX));

                    this.tangentPenList.Add(Pens.Blue);
                }
            }

            tangentY = CalculateG
            (
                this.tangentX,
                this.a1,
                this.b1,
                this.c1,
                this.d1,
                this.e1,
                this.f1,
                -1f,
                this.a2,
                this.b2,
                this.c2,
                this.d2,
                this.e2,
                this.f2,
                -1f
            );

            if(IsNumber(tangentY))
            {
                float slope = CalculateG1Prime(this.tangentX, this.a1, this.b1, this.c1, this.d1, this.e1, this.f1, -1f) -
                              CalculateG1Prime(this.tangentX, this.a2, this.b2, this.c2, this.d2, this.e2, this.f2, -1f);

                if(IsNumber(slope))
                {
                    float deltaX = (float)Math.Sqrt(TANGENT_LENGTH * TANGENT_LENGTH / (1 + slope * slope)) / 2;

                    this.tangentCenterPointList.Add(new PointF(this.tangentX, tangentY));

                    this.tangentP1.Add(new PointF(this.tangentX - deltaX, tangentY - slope * deltaX));
                    this.tangentP2.Add(new PointF(this.tangentX + deltaX, tangentY + slope * deltaX));

                    this.tangentPenList.Add(Pens.Orange);
                }
            }
        }

        #endregion
        #region 이진 분할을 사용해 루트 포인트 설정하기 - SetRootPointUsingBinaryDivision(x0, deltaX, x, y, a1, b1, c1, d1, e1, f1, sign1, a2, b2, c2, d2, e2, f2, sign2)

        /// <summary>
        /// 이진 분할을 사용해 루트 포인트 설정하기
        /// </summary>
        /// <param name="x0">X0</param>
        /// <param name="deltaX">델타 X</param>
        /// <param name="x">X</param>
        /// <param name="y">Y</param>
        /// <param name="a1">A1</param>
        /// <param name="b1">B1</param>
        /// <param name="c1">C1</param>
        /// <param name="d1">D1</param>
        /// <param name="e1">E1</param>
        /// <param name="f1">F1</param>
        /// <param name="sign1">부호 1</param>
        /// <param name="a2">A2</param>
        /// <param name="b2">B2</param>
        /// <param name="c2">C2</param>
        /// <param name="d2">D2</param>
        /// <param name="e2">E2</param>
        /// <param name="f2">F2</param>
        /// <param name="sign2">부호 2</param>
        private void SetRootPointUsingBinaryDivision
        (
            float     x0,
            float     deltaX,
            out float x,
            out float y,
            float     a1,
            float     b1,
            float     c1,
            float     d1,
            float     e1,
            float     f1,
            float     sign1,
            float     a2,
            float     b2,
            float     c2,
            float     d2,
            float     e2,
            float     f2,
            float     sign2
        )
        {
            const int trialCount = 200;
            const int signNaN    = -2;

            float xMinimum  = x0;
            float gXMinimum = CalculateG(xMinimum, a1, b1, c1, d1, e1, f1, sign1, a2, b2, c2, d2, e2, f2, sign2);

            if(Math.Abs(gXMinimum) < SMALL)
            {
                x = xMinimum;
                y = gXMinimum;

                return;
            }

            float xMaximum  = xMinimum + deltaX;
            float gXMaximum = CalculateG(xMaximum, a1, b1, c1, d1, e1, f1, sign1, a2, b2, c2, d2, e2, f2, sign2);

            if(Math.Abs(gXMaximum) < SMALL)
            {
                x = xMaximum;
                y = gXMaximum;

                return;
            }

            int signMinimum;
            int sigmMaximum;

            if(IsNumber(gXMinimum))
            {
                signMinimum = Math.Sign(gXMinimum);
            }
            else
            {
                signMinimum = signNaN;
            }

            if(IsNumber(gXMaximum))
            {
                sigmMaximum = Math.Sign(gXMaximum);
            }
            else
            {
                sigmMaximum = signNaN;
            }

            if(signMinimum == sigmMaximum)
            {
                x = 1;
                y = float.NaN;

                return;
            }

            float xMiddle    = 0;
            float gXMiddle   = 0;
            int   signMiddle = 0;

            for(int i = 0; i < trialCount; i++)
            {
                xMiddle  = (xMinimum + xMaximum) / 2;
                gXMiddle = CalculateG(xMiddle, a1, b1, c1, d1, e1, f1, sign1, a2, b2, c2, d2, e2, f2, sign2);

                if(IsNumber(gXMiddle))
                {
                    signMiddle = Math.Sign(gXMiddle);
                }
                else
                {
                    signMiddle = signNaN;
                }

                if(signMiddle == 0)
                {
                    break;
                }

                if(signMiddle == signMinimum)
                {
                    xMinimum  = xMiddle;
                    gXMinimum = gXMiddle;
                }
                else if(signMiddle == sigmMaximum)
                {
                    xMaximum  = xMiddle;
                    gXMaximum = gXMiddle;
                }
                else
                {
                    if(signMinimum == signNaN)
                    {
                        xMinimum  = xMiddle;
                        gXMinimum = gXMiddle;
                    }
                    else if(sigmMaximum == signNaN)
                    {
                        xMaximum  = xMiddle;
                        gXMaximum = gXMiddle;
                    }
                    else
                    {
                        throw new InvalidOperationException
                        (
                            string.Format
                            (
                                "Unexpected difference curve. Cannot find a root between X = {0} and X = {1}",
                                xMinimum,
                                xMaximum
                            )
                        );
                    }
                }
            }

            if(IsNumber(gXMiddle) && (Math.Abs(gXMiddle) < SMALL))
            {
                x = xMiddle;
                y = gXMiddle;
            }
            else if(IsNumber(gXMinimum) && (Math.Abs(gXMinimum) < SMALL))
            {
                x = xMinimum;
                y = gXMinimum;
            }
            else if(IsNumber(gXMaximum) && (Math.Abs(gXMaximum) < SMALL))
            {
                x = xMaximum;
                y = gXMaximum;
            }
            else
            {
                x = xMiddle;
                y = float.NaN;
            }
        }

        #endregion
        #region 이진 분할을 사용해 루트 포인트 리스트 구하기 - GetRootPointListUsingBinaryDivision(xMinimum, xMaximum, a1, b1, c1, d1, e1, f1, sign1, a2, b2, c2, d2, e2, f2, sign2)

        /// <summary>
        /// 이진 분할을 사용해 루트 포인트 리스트 구하기
        /// </summary>
        /// <param name="xMinimum">X 최소값</param>
        /// <param name="xMaximum">X 최대값</param>
        /// <param name="a1">A1</param>
        /// <param name="b1">B1</param>
        /// <param name="c1">C1</param>
        /// <param name="d1">D1</param>
        /// <param name="e1">E1</param>
        /// <param name="f1">F1</param>
        /// <param name="sign1">부호 1</param>
        /// <param name="a2">A2</param>
        /// <param name="b2">B2</param>
        /// <param name="c2">C2</param>
        /// <param name="d2">D2</param>
        /// <param name="e2">E2</param>
        /// <param name="f2">F2</param>
        /// <param name="sign2">부호 2</param>
        /// <returns>루트 포인트 리스트</returns>
        private List<PointF> GetRootPointListUsingBinaryDivision
        (
            float xMinimum,
            float xMaximum,
            float a1,
            float b1,
            float c1,
            float d1,
            float e1,
            float f1,
            float sign1,
            float a2,
            float b2,
            float c2,
            float d2,
            float e2,
            float f2,
            float sign2
        )
        {
            List<PointF> rootPointList = new List<PointF>();

            const int testCount = 10;

            float deltaX = (xMaximum - xMinimum) / (testCount - 1);

            float x0 = xMinimum;

            float x;
            float y;

            for(int i = 0; i < testCount; i++)
            {
                SetRootPointUsingBinaryDivision
                (
                    x0,
                    deltaX,
                    out x,
                    out y,
                    a1,
                    b1,
                    c1,
                    d1,
                    e1,
                    f1,
                    sign1,
                    a2,
                    b2,
                    c2,
                    d2,
                    e2,
                    f2,
                    sign2
                );

                if(IsNumber(y))
                {
                    bool isNew = true;

                    foreach(PointF point in rootPointList)
                    {
                        if(Math.Abs(point.X - x) < SMALL)
                        {
                            isNew = false;

                            break;
                        }
                    }

                    if(isNew)
                    {
                        rootPointList.Add(new PointF(x, y));

                        if(rootPointList.Count > 1)
                        {
                            return rootPointList;
                        }
                    }
                }

                x0 += deltaX;
            }

            return rootPointList;
        }

        #endregion
        #region 교차점 찾기 - FindIntersectionPoint(xMinimum, xMaximum)

        /// <summary>
        /// 교차점 찾기
        /// </summary>
        /// <param name="xMinimum">X 최소값</param>
        /// <param name="xMaximum">X 최대값</param>
        private void FindIntersectionPoint(float xMinimum, float xMaximum)
        {
            this.rootPointList = new List<PointF>();
            this.rootSignList1 = new List<float>();
            this.rootSignList2 = new List<float>();

            if(!this.gotEllipse1 || !this.gotEllipse2)
            {
                return;
            }

            float[] signArray = { +1f, -1f };

            foreach(float sign1 in signArray)
            {
                foreach(float sign2 in signArray)
                {
                    List<PointF> pointList = GetRootPointListUsingBinaryDivision
                    (
                        xMinimum,
                        xMaximum,
                        this.a1,
                        this.b1,
                        this.c1,
                        this.d1,
                        this.e1,
                        this.f1,
                        sign1,
                        this.a2,
                        this.b2,
                        this.c2,
                        this.d2,
                        this.e2,
                        this.f2,
                        sign2
                    );

                    if(pointList.Count > 0)
                    {
                        this.rootPointList.AddRange(pointList);

                        for(int i = 0; i < pointList.Count; i++)
                        {
                            this.rootSignList1.Add(sign1);
                            this.rootSignList2.Add(sign2);
                        }
                    }
                }
            }

            this.intersectionPointList = new List<PointF>();

            for(int i = 0; i < this.rootPointList.Count; i++)
            {
                float y1 = CalculateG1
                (
                    this.rootPointList[i].X,
                    this.a1,
                    this.b1,
                    this.c1,
                    this.d1,
                    this.e1,
                    this.f1,
                    this.rootSignList1[i]
                );

                float y2 = CalculateG1
                (
                    this.rootPointList[i].X,
                    this.a2,
                    this.b2,
                    this.c2,
                    this.d2,
                    this.e2,
                    this.f2,
                    this.rootSignList2[i]
                );

                this.intersectionPointList.Add(new PointF(this.rootPointList[i].X, y1));

                Debug.Assert(Math.Abs(y1 - y2) < SMALL);
            }
        }

        #endregion
        #region 계산하기 - Calculate()

        /// <summary>
        /// 계산하기
        /// </summary>
        private void Calculate()
        {
            if(this.gotEllipse1)
            {
                SetEllipseFormula
                (
                    this.ellipseRectangle1,
                    out this.dx1,
                    out this.dy1,
                    out this.rx1,
                    out this.ry1,
                    out this.a1,
                    out this.b1,
                    out this.c1,
                    out this.d1,
                    out this.e1,
                    out this.f1
                );

                this.parameterListBox1.Items.Clear();

                this.parameterListBox1.Items.Add("A = " + this.a1.ToString());
                this.parameterListBox1.Items.Add("B = " + this.b1.ToString());
                this.parameterListBox1.Items.Add("C = " + this.c1.ToString());
                this.parameterListBox1.Items.Add("D = " + this.d1.ToString());
                this.parameterListBox1.Items.Add("E = " + this.e1.ToString());
                this.parameterListBox1.Items.Add("F = " + this.f1.ToString());
            }

            if(this.gotEllipse2)
            {
                SetEllipseFormula
                (
                    this.ellipseRectangle2,
                    out this.dx2,
                    out this.dy2,
                    out this.rx2,
                    out this.ry2,
                    out this.a2,
                    out this.b2,
                    out this.c2,
                    out this.d2,
                    out this.e2,
                    out this.f2
                );

                this.parameterListBox2.Items.Clear();

                this.parameterListBox2.Items.Add("A = " + this.a2.ToString());
                this.parameterListBox2.Items.Add("B = " + this.b2.ToString());
                this.parameterListBox2.Items.Add("C = " + this.c2.ToString());
                this.parameterListBox2.Items.Add("D = " + this.d2.ToString());
                this.parameterListBox2.Items.Add("E = " + this.e2.ToString());
                this.parameterListBox2.Items.Add("F = " + this.f2.ToString());
            }

            FindDifferenceTangents();

            float xMinimum = Math.Max(this.ellipseRectangle1.Left , this.ellipseRectangle2.Left );
            float xMaxumum = Math.Min(this.ellipseRectangle1.Right, this.ellipseRectangle2.Right);

            FindIntersectionPoint(xMinimum, xMaxumum);
        }

        #endregion

        #region 차분 곡선 그리기 - DrawDifferenceCurves(graphics, xMinimum, xMaximum)

        /// <summary>
        /// 차분 곡선 그리기
        /// </summary>
        /// <param name="graphics">그래픽스</param>
        /// <param name="xMinimum">X 최소값</param>
        /// <param name="xMaximum">X 최대값</param>
        private void DrawDifferenceCurves(Graphics graphics, float xMinimum, float xMaximum)
        {
            const float dx = 0.01f;

            using(Pen pen = new Pen(Color.Red, 3))
            {
                List<PointF> pointList = new List<PointF>();

                for(float x = xMinimum; x <= xMaximum; x += dx)
                {
                    float y1 = CalculateG1(x, this.a1, this.b1, this.c1, this.d1, this.e1, this.f1, +1f);

                    if(IsNumber(y1))
                    {
                        float y2 = CalculateG1(x, this.a2, this.b2, this.c2, this.d2, this.e2, this.f2, +1f);

                        if(IsNumber(y2))
                        {
                            pointList.Add(new PointF(x, y1 - y2));
                        }
                    }
                }

                if(pointList.Count > 1)
                {
                    pen.Color = Color.Red;

                    graphics.DrawLines(pen, pointList.ToArray());
                }

                pointList = new List<PointF>();

                for(float x = xMaximum; x >= xMinimum; x -= dx)
                {
                    float y1 = CalculateG1(x, this.a1, this.b1, this.c1, this.d1, this.e1, this.f1, +1f);

                    if(IsNumber(y1))
                    {
                        float y2 = CalculateG1(x, this.a2, this.b2, this.c2, this.d2, this.e2, this.f2, -1f);

                        if(IsNumber(y2))
                        {
                            pointList.Add(new PointF(x, y1 - y2));
                        }
                    }
                }

                if(pointList.Count > 1)
                {
                    pen.Color = Color.Green;

                    graphics.DrawLines(pen, pointList.ToArray());
                }

                pointList = new List<PointF>();

                for(float x = xMinimum; x <= xMaximum; x += dx)
                {
                    float y1 = CalculateG1(x, this.a1, this.b1, this.c1, this.d1, this.e1, this.f1, -1f);

                    if(IsNumber(y1))
                    {
                        float y2 = CalculateG1(x, this.a2, this.b2, this.c2, this.d2, this.e2, this.f2, +1f);

                        if(IsNumber(y2))
                        {
                            pointList.Add(new PointF(x, y1 - y2));
                        }
                    }
                }

                if(pointList.Count > 1)
                {
                    pen.Color = Color.Blue;

                    graphics.DrawLines(pen, pointList.ToArray());
                }

                pointList = new List<PointF>();

                for(float x = xMaximum; x >= xMinimum; x -= dx)
                {
                    float y1 = CalculateG1(x, this.a1, this.b1, this.c1, this.d1, this.e1, this.f1, -1f);

                    if(IsNumber(y1))
                    {
                        float y2 = CalculateG1(x, this.a2, this.b2, this.c2, this.d2, this.e2, this.f2, -1f);

                        if(IsNumber(y2))
                        {
                            pointList.Add(new PointF(x, y1 - y2));
                        }
                    }
                }

                if(pointList.Count > 1)
                {
                    pen.Color = Color.Orange;

                    graphics.DrawLines(pen, pointList.ToArray());
                }
            }
        }

        #endregion
        #region 탄젠트 그리기 - DrawTangents(graphics)

        /// <summary>
        /// 탄젠트 그리기
        /// </summary>
        /// <param name="graphics">그래픽스</param>
        private void DrawTangents(Graphics graphics)
        {
            const float RADIUS = 3;

            for(int i = 0; i < this.tangentP1.Count; i++)
            {
                graphics.FillEllipse
                (
                    Brushes.Black,
                    this.tangentCenterPointList[i].X - RADIUS,
                    this.tangentCenterPointList[i].Y - RADIUS,
                    2 * RADIUS,
                    2 * RADIUS
                );

                graphics.DrawLine(this.tangentPenList[i], this.tangentP1[i], this.tangentP2[i]);
            }
        }

        #endregion
    }
}
728x90
반응형
그리드형(광고전용)
Posted by icodebroker

댓글을 달아 주세요