使用PointBase代替Point

This commit is contained in:
艾竹
2023-01-08 09:22:37 +08:00
parent 8fc69bc96d
commit 5d7717cc2b
65 changed files with 4317 additions and 403 deletions

View File

@@ -0,0 +1,206 @@
using System;
namespace AIStudio.Wpf.DiagramDesigner.Geometry
{
/// <summary>
/// Bezier Spline methods
/// </summary>
/// <remarks>
/// Modified: Peter Lee (peterlee.com.cn < at > gmail.com)
/// Update: 2009-03-16
///
/// see also:
/// Draw a smooth curve through a set of 2D points with Bezier primitives
/// http://www.codeproject.com/KB/graphics/BezierSpline.aspx
/// By Oleg V. Polikarpotchkin
///
/// Algorithm Descripition:
///
/// To make a sequence of individual Bezier curves to be a spline, we
/// should calculate Bezier control points so that the spline curve
/// has two continuous derivatives at knot points.
///
/// Note: `[]` denotes subscript
/// `^` denotes supscript
/// `'` denotes first derivative
/// `''` denotes second derivative
///
/// A Bezier curve on a single interval can be expressed as:
///
/// B(t) = (1-t)^3 P0 + 3(1-t)^2 t P1 + 3(1-t)t^2 P2 + t^3 P3 (*)
///
/// where t is in [0,1], and
/// 1. P0 - first knot point
/// 2. P1 - first control point (close to P0)
/// 3. P2 - second control point (close to P3)
/// 4. P3 - second knot point
///
/// The first derivative of (*) is:
///
/// B'(t) = -3(1-t)^2 P0 + 3(3t^24t+1) P1 + 3(23t)t P2 + 3t^2 P3
///
/// The second derivative of (*) is:
///
/// B''(t) = 6(1-t) P0 + 6(3t-2) P1 + 6(13t) P2 + 6t P3
///
/// Considering a set of piecewise Bezier curves with n+1 points
/// (Q[0..n]) and n subintervals, the (i-1)-th curve should connect
/// to the i-th one:
///
/// Q[0] = P0[1],
/// Q[1] = P0[2] = P3[1], ... , Q[i-1] = P0[i] = P3[i-1] (i = 1..n) (@)
///
/// At the i-th subinterval, the Bezier curve is:
///
/// B[i](t) = (1-t)^3 P0[i] + 3(1-t)^2 t P1[i] +
/// 3(1-t)t^2 P2[i] + t^3 P3[i] (i = 1..n)
///
/// applying (@):
///
/// B[i](t) = (1-t)^3 Q[i-1] + 3(1-t)^2 t P1[i] +
/// 3(1-t)t^2 P2[i] + t^3 Q[i] (i = 1..n) (i)
///
/// From (i), the first derivative at the i-th subinterval is:
///
/// B'[i](t) = -3(1-t)^2 Q[i-1] + 3(3t^24t+1) P1[i] +
/// 3(23t)t P2[i] + 3t^2 Q[i] (i = 1..n)
///
/// Using the first derivative continuity condition:
///
/// B'[i-1](1) = B'[i](0)
///
/// we get:
///
/// P1[i] + P2[i-1] = 2Q[i-1] (i = 2..n) (1)
///
/// From (i), the second derivative at the i-th subinterval is:
///
/// B''[i](t) = 6(1-t) Q[i-1] + 6(3t-2) P1[i] +
/// 6(1-3t) P2[i] + 6t Q[i] (i = 1..n)
///
/// Using the second derivative continuity condition:
///
/// B''[i-1](1) = B''[i](0)
///
/// we get:
///
/// P1[i-1] + 2P1[i] = P2[i] + 2P2[i-1] (i = 2..n) (2)
///
/// Then, using the so-called "natural conditions":
///
/// B''[1](0) = 0
///
/// B''[n](1) = 0
///
/// to the second derivative equations, and we get:
///
/// 2P1[1] - P2[1] = Q[0] (3)
///
/// 2P2[n] - P1[n] = Q[n] (4)
///
/// From (1)(2)(3)(4), we have 2n conditions for n first control points
/// P1[1..n], and n second control points P2[1..n].
///
/// Eliminating P2[1..n], we get (be patient to get :-) a set of n
/// equations for solving P1[1..n]:
///
/// 2P1[1] + P1[2] + = Q[0] + 2Q[1]
/// P1[1] + 4P1[2] + P1[3] = 4Q[1] + 2Q[2]
/// ...
/// P1[i-1] + 4P1[i] + P1[i+1] = 4Q[i-1] + 2Q[i]
/// ...
/// P1[n-2] + 4P1[n-1] + P1[n] = 4Q[n-2] + 2Q[n-1]
/// P1[n-1] + 3.5P1[n] = (8Q[n-1] + Q[n]) / 2
///
/// From this set of equations, P1[1..n] are easy but tedious to solve.
/// </remarks>
public static class BezierSpline
{
/// <summary>
/// Get open-ended Bezier Spline Control Points.
/// </summary>
/// <param name="knots">Input Knot Bezier spline points.</param>
/// <param name="firstControlPoints">Output First Control points array of knots.Length - 1 length.</param>
/// <param name="secondControlPoints">Output Second Control points array of knots.Length - 1 length.</param>
/// <exception cref="ArgumentNullException"><paramref name="knots"/> parameter must be not null.</exception>
/// <exception cref="ArgumentException"><paramref name="knots"/> array must containg at least two points.</exception>
public static void GetCurveControlPoints(PointBase[] knots, out PointBase[] firstControlPoints, out PointBase[] secondControlPoints)
{
if (knots == null)
throw new ArgumentNullException("knots");
int n = knots.Length - 1;
if (n < 1)
throw new ArgumentException("At least two knot points required", "knots");
if (n == 1)
{ // Special case: Bezier curve should be a straight line.
firstControlPoints = new PointBase[1];
// 3P1 = 2P0 + P3
firstControlPoints[0] = new PointBase((2 * knots[0].X + knots[1].X) / 3, (2 * knots[0].Y + knots[1].Y) / 3);
secondControlPoints = new PointBase[1];
// P2 = 2P1 P0
secondControlPoints[0] = new PointBase(2 * firstControlPoints[0].X - knots[0].X, 2 * firstControlPoints[0].Y - knots[0].Y);
return;
}
// Calculate first Bezier control points
// Right hand side vector
double[] rhs = new double[n];
// Set right hand side X values
for (int i = 1; i < n - 1; ++i)
rhs[i] = 4 * knots[i].X + 2 * knots[i + 1].X;
rhs[0] = knots[0].X + 2 * knots[1].X;
rhs[n - 1] = (8 * knots[n - 1].X + knots[n].X) / 2.0;
// Get first control points X-values
double[] x = GetFirstControlPoints(rhs);
// Set right hand side Y values
for (int i = 1; i < n - 1; ++i)
rhs[i] = 4 * knots[i].Y + 2 * knots[i + 1].Y;
rhs[0] = knots[0].Y + 2 * knots[1].Y;
rhs[n - 1] = (8 * knots[n - 1].Y + knots[n].Y) / 2.0;
// Get first control points Y-values
double[] y = GetFirstControlPoints(rhs);
// Fill output arrays.
firstControlPoints = new PointBase[n];
secondControlPoints = new PointBase[n];
for (int i = 0; i < n; ++i)
{
// First control point
firstControlPoints[i] = new PointBase(x[i], y[i]);
// Second control point
if (i < n - 1)
secondControlPoints[i] = new PointBase(2 * knots[i + 1].X - x[i + 1], 2 * knots[i + 1].Y - y[i + 1]);
else
secondControlPoints[i] = new PointBase((knots[n].X + x[n - 1]) / 2, (knots[n].Y + y[n - 1]) / 2);
}
}
/// <summary>
/// Solves a tridiagonal system for one of coordinates (x or y) of first Bezier control points.
/// </summary>
/// <param name="rhs">Right hand side vector.</param>
/// <returns>Solution vector.</returns>
private static double[] GetFirstControlPoints(double[] rhs)
{
int n = rhs.Length;
double[] x = new double[n]; // Solution vector.
double[] tmp = new double[n]; // Temp workspace.
double b = 2.0;
x[0] = rhs[0] / b;
for (int i = 1; i < n; i++) // Decomposition and forward substitution.
{
tmp[i] = 1 / b;
b = (i < n - 1 ? 4.0 : 3.5) - tmp[i];
x[i] = (rhs[i] - x[i - 1]) / b;
}
for (int i = 1; i < n; i++)
x[n - i - 1] -= tmp[n - i] * x[n - i]; // Backsubstitution.
return x;
}
}
}

View File

@@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
namespace AIStudio.Wpf.DiagramDesigner.Geometry
{
public class EllipseBase : IShape
{
public EllipseBase(double cx, double cy, double rx, double ry)
{
Cx = cx;
Cy = cy;
Rx = rx;
Ry = ry;
}
public double Cx { get; }
public double Cy { get; }
public double Rx { get; }
public double Ry { get; }
public IEnumerable<PointBase> GetIntersectionsWithLine(LineBase line)
{
var a1 = line.Start;
var a2 = line.End;
var dir = new PointBase(line.End.X - line.Start.X, line.End.Y - line.Start.Y);
var diff = a1.Substract(Cx, Cy);
var mDir = new PointBase(dir.X / (Rx * Rx), dir.Y / (Ry * Ry));
var mDiff = new PointBase(diff.X / (Rx * Rx), diff.Y / (Ry * Ry));
var a = dir.Dot(mDir);
var b = dir.Dot(mDiff);
var c = diff.Dot(mDiff) - 1.0;
var d = b * b - a * c;
if (d > 0)
{
var root = Math.Sqrt(d);
var ta = (-b - root) / a;
var tb = (-b + root) / a;
if (ta >= 0 && 1 >= ta || tb >= 0 && 1 >= tb)
{
if (0 <= ta && ta <= 1)
yield return a1.Lerp(a2, ta);
if (0 <= tb && tb <= 1)
yield return a1.Lerp(a2, tb);
}
}
else
{
var t = -b / a;
if (0 <= t && t <= 1)
{
yield return a1.Lerp(a2, t);
}
}
}
}
}

View File

@@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace AIStudio.Wpf.DiagramDesigner.Geometry
{
public interface IShape
{
IEnumerable<PointBase> GetIntersectionsWithLine(LineBase line);
}
}

View File

@@ -0,0 +1,43 @@
namespace AIStudio.Wpf.DiagramDesigner.Geometry
{
public struct LineBase
{
public LineBase(PointBase start, PointBase end)
{
Start = start;
End = end;
}
public PointBase Start { get; }
public PointBase End { get; }
public PointBase GetIntersection(LineBase line)
{
var pt1Dir = new PointBase(End.X - Start.X, End.Y - Start.Y);
var pt2Dir = new PointBase(line.End.X - line.Start.X, line.End.Y - line.Start.Y);
var det = (pt1Dir.X * pt2Dir.Y) - (pt1Dir.Y * pt2Dir.X);
var deltaPt = new PointBase(line.Start.X - Start.X, line.Start.Y - Start.Y);
var alpha = (deltaPt.X * pt2Dir.Y) - (deltaPt.Y * pt2Dir.X);
var beta = (deltaPt.X * pt1Dir.Y) - (deltaPt.Y * pt1Dir.X);
if (det == 0 || alpha * det < 0 || beta * det < 0)
return PointBase.Empty;
if (det > 0)
{
if (alpha > det || beta > det)
return PointBase.Empty;
}
else
{
if (alpha < det || beta < det)
return PointBase.Empty;
}
return new PointBase(Start.X + (alpha * pt1Dir.X / det), Start.Y + (alpha * pt1Dir.Y / det));
}
public override string ToString() => $"Line from {Start} to {End}";
}
}

View File

@@ -0,0 +1,466 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
namespace AIStudio.Wpf.DiagramDesigner.Geometry
{
[Serializable]
public struct PointBase : IFormattable
{
public static PointBase Zero { get; } = new PointBase(0, 0);
public PointBase(double x, double y)
{
_x = x;
_y = y;
}
/// <summary>
/// 中间X
/// </summary>
internal double _x;
public double X
{
get
{
return _x;
}
set
{
_x = value;
}
}
/// <summary>
/// 中间Y
/// </summary>
internal double _y;
public double Y
{
get
{
return _y;
}
set
{
_y = value;
}
}
public double Dot(PointBase other) => X * other.X + Y * other.Y;
public PointBase Lerp(PointBase other, double t)
=> new PointBase(X * (1.0 - t) + other.X * t, Y * (1.0 - t) + other.Y * t);
// Maybe just make Points mutable?
public PointBase Add(double value) => new PointBase(X + value, Y + value);
public PointBase Add(double x, double y) => new PointBase(X + x, Y + y);
public PointBase Substract(double value) => new PointBase(X - value, Y - value);
public PointBase Substract(double x, double y) => new PointBase(X - x, Y - y);
public double DistanceTo(PointBase other)
=> Math.Sqrt(Math.Pow(X - other.X, 2) + Math.Pow(Y - other.Y, 2));
public void Deconstruct(out double x, out double y)
{
x = X;
y = Y;
}
//public static PointBase operator -(PointBase a, PointBase b)
//{
// return new PointBase(a.X - b.X, a.Y - b.Y);
//}
//public static PointBase operator +(PointBase a, PointBase b)
//{
// return new PointBase(a.X + b.X, a.Y + b.Y);
//}
public static implicit operator PointBase(Point point)
{
return new PointBase(point.X, point.Y);
}
public static implicit operator Point(PointBase pointInfoBase)
{
return new Point(pointInfoBase.X, pointInfoBase.Y);
}
//public override string ToString() => $"PointBase(x={X}, y={Y})";
#region Statics
/// <summary>
/// Empty - a static property which provides an Empty rectangle. X and Y are positive-infinity
/// and Width and Height are negative infinity. This is the only situation where Width or
/// Height can be negative.
/// </summary>
public static PointBase Empty
{
get
{
return s_empty;
}
}
#endregion Statics
#region Private Methods
static private PointBase CreateEmptyRect()
{
PointBase point = new PointBase();
// We can't set these via the property setters because negatives widths
// are rejected in those APIs.
point._x = Double.PositiveInfinity;
point._y = Double.PositiveInfinity;
return point;
}
#endregion Private Methods
#region Private Fields
private readonly static PointBase s_empty = CreateEmptyRect();
#endregion Private Fields
#region Public Methods
/// <summary>
/// Compares two PointBase instances for exact equality.
/// Note that double values can acquire error when operated upon, such that
/// an exact comparison between two values which are logically equal may fail.
/// Furthermore, using this equality operator, Double.NaN is not equal to itself.
/// </summary>
/// <returns>
/// bool - true if the two PointBase instances are exactly equal, false otherwise
/// </returns>
/// <param name='point1'>The first PointBase to compare</param>
/// <param name='point2'>The second PointBase to compare</param>
public static bool operator ==(PointBase point1, PointBase point2)
{
return point1.X == point2.X &&
point1.Y == point2.Y;
}
/// <summary>
/// Compares two PointBase instances for exact inequality.
/// Note that double values can acquire error when operated upon, such that
/// an exact comparison between two values which are logically equal may fail.
/// Furthermore, using this equality operator, Double.NaN is not equal to itself.
/// </summary>
/// <returns>
/// bool - true if the two PointBase instances are exactly unequal, false otherwise
/// </returns>
/// <param name='point1'>The first PointBase to compare</param>
/// <param name='point2'>The second PointBase to compare</param>
public static bool operator !=(PointBase point1, PointBase point2)
{
return !(point1 == point2);
}
/// <summary>
/// Compares two PointBase instances for object equality. In this equality
/// Double.NaN is equal to itself, unlike in numeric equality.
/// Note that double values can acquire error when operated upon, such that
/// an exact comparison between two values which
/// are logically equal may fail.
/// </summary>
/// <returns>
/// bool - true if the two PointBase instances are exactly equal, false otherwise
/// </returns>
/// <param name='point1'>The first PointBase to compare</param>
/// <param name='point2'>The second PointBase to compare</param>
public static bool Equals(PointBase point1, PointBase point2)
{
return point1.X.Equals(point2.X) &&
point1.Y.Equals(point2.Y);
}
/// <summary>
/// Equals - compares this PointBase with the passed in object. In this equality
/// Double.NaN is equal to itself, unlike in numeric equality.
/// Note that double values can acquire error when operated upon, such that
/// an exact comparison between two values which
/// are logically equal may fail.
/// </summary>
/// <returns>
/// bool - true if the object is an instance of PointBase and if it's equal to "this".
/// </returns>
/// <param name='o'>The object to compare to "this"</param>
public override bool Equals(object o)
{
if ((null == o) || !(o is PointBase))
{
return false;
}
PointBase value = (PointBase)o;
return PointBase.Equals(this, value);
}
/// <summary>
/// Equals - compares this PointBase with the passed in object. In this equality
/// Double.NaN is equal to itself, unlike in numeric equality.
/// Note that double values can acquire error when operated upon, such that
/// an exact comparison between two values which
/// are logically equal may fail.
/// </summary>
/// <returns>
/// bool - true if "value" is equal to "this".
/// </returns>
/// <param name='value'>The PointBase to compare to "this"</param>
public bool Equals(PointBase value)
{
return PointBase.Equals(this, value);
}
/// <summary>
/// Returns the HashCode for this PointBase
/// </summary>
/// <returns>
/// int - the HashCode for this PointBase
/// </returns>
public override int GetHashCode()
{
// Perform field-by-field XOR of HashCodes
return X.GetHashCode() ^
Y.GetHashCode();
}
///// <summary>
///// Parse - returns an instance converted from the provided string using
///// the culture "en-US"
///// <param name="source"> string with PointBase data </param>
///// </summary>
//public static PointBase Parse(string source)
//{
// IFormatProvider formatProvider = System.Windows.Markup.TypeConverterHelper.InvariantEnglishUS;
// TokenizerHelper th = new TokenizerHelper(source, formatProvider);
// PointBase value;
// String firstToken = th.NextTokenRequired();
// value = new PointBase(
// Convert.ToDouble(firstToken, formatProvider),
// Convert.ToDouble(th.NextTokenRequired(), formatProvider));
// // There should be no more tokens in this string.
// th.LastTokenRequired();
// return value;
//}
#endregion Public Methods
#region Internal Properties
/// <summary>
/// Creates a string representation of this object based on the current culture.
/// </summary>
/// <returns>
/// A string representation of this object.
/// </returns>
public override string ToString()
{
// Delegate to the internal method which implements all ToString calls.
return ConvertToString(null /* format string */, null /* format provider */);
}
/// <summary>
/// Creates a string representation of this object based on the IFormatProvider
/// passed in. If the provider is null, the CurrentCulture is used.
/// </summary>
/// <returns>
/// A string representation of this object.
/// </returns>
public string ToString(IFormatProvider provider)
{
// Delegate to the internal method which implements all ToString calls.
return ConvertToString(null /* format string */, provider);
}
/// <summary>
/// Creates a string representation of this object based on the format string
/// and IFormatProvider passed in.
/// If the provider is null, the CurrentCulture is used.
/// See the documentation for IFormattable for more information.
/// </summary>
/// <returns>
/// A string representation of this object.
/// </returns>
string IFormattable.ToString(string format, IFormatProvider provider)
{
// Delegate to the internal method which implements all ToString calls.
return ConvertToString(format, provider);
}
/// <summary>
/// Creates a string representation of this object based on the format string
/// and IFormatProvider passed in.
/// If the provider is null, the CurrentCulture is used.
/// See the documentation for IFormattable for more information.
/// </summary>
/// <returns>
/// A string representation of this object.
/// </returns>
internal string ConvertToString(string format, IFormatProvider provider)
{
// Helper to get the numeric list separator for a given culture.
char separator = ',';
return String.Format(provider,
"{1:" + format + "}{0}{2:" + format + "}",
separator,
_x,
_y);
}
#endregion Internal Properties
#region Public Methods
/// <summary>
/// Offset - update the location by adding offsetX to X and offsetY to Y
/// </summary>
/// <param name="offsetX"> The offset in the x dimension </param>
/// <param name="offsetY"> The offset in the y dimension </param>
public void Offset(double offsetX, double offsetY)
{
_x += offsetX;
_y += offsetY;
}
/// <summary>
/// Operator PointBase + Vector
/// </summary>
/// <returns>
/// PointBase - The result of the addition
/// </returns>
/// <param name="point"> The PointBase to be added to the Vector </param>
/// <param name="vector"> The Vectr to be added to the PointBase </param>
public static PointBase operator +(PointBase point, VectorBase vector)
{
return new PointBase(point._x + vector._x, point._y + vector._y);
}
/// <summary>
/// Add: PointBase + Vector
/// </summary>
/// <returns>
/// PointBase - The result of the addition
/// </returns>
/// <param name="point"> The PointBase to be added to the Vector </param>
/// <param name="vector"> The Vector to be added to the PointBase </param>
public static PointBase Add(PointBase point, VectorBase vector)
{
return new PointBase(point._x + vector._x, point._y + vector._y);
}
/// <summary>
/// Operator PointBase - Vector
/// </summary>
/// <returns>
/// PointBase - The result of the subtraction
/// </returns>
/// <param name="point"> The PointBase from which the Vector is subtracted </param>
/// <param name="vector"> The Vector which is subtracted from the PointBase </param>
public static PointBase operator -(PointBase point, VectorBase vector)
{
return new PointBase(point._x - vector._x, point._y - vector._y);
}
/// <summary>
/// Subtract: PointBase - Vector
/// </summary>
/// <returns>
/// PointBase - The result of the subtraction
/// </returns>
/// <param name="point"> The PointBase from which the Vector is subtracted </param>
/// <param name="vector"> The Vector which is subtracted from the PointBase </param>
public static PointBase Subtract(PointBase point, VectorBase vector)
{
return new PointBase(point._x - vector._x, point._y - vector._y);
}
/// <summary>
/// Operator PointBase - PointBase
/// </summary>
/// <returns>
/// Vector - The result of the subtraction
/// </returns>
/// <param name="point1"> The PointBase from which point2 is subtracted </param>
/// <param name="point2"> The PointBase subtracted from point1 </param>
public static VectorBase operator -(PointBase point1, PointBase point2)
{
return new VectorBase(point1._x - point2._x, point1._y - point2._y);
}
/// <summary>
/// Subtract: PointBase - PointBase
/// </summary>
/// <returns>
/// Vector - The result of the subtraction
/// </returns>
/// <param name="point1"> The PointBase from which point2 is subtracted </param>
/// <param name="point2"> The PointBase subtracted from point1 </param>
public static VectorBase Subtract(PointBase point1, PointBase point2)
{
return new VectorBase(point1._x - point2._x, point1._y - point2._y);
}
/// <summary>
/// Operator PointBase * Matrix
/// </summary>
//public static PointBase operator *(PointBase point, Matrix matrix)
//{
// return matrix.Transform(point);
//}
/// <summary>
/// Multiply: PointBase * Matrix
/// </summary>
//public static PointBase Multiply(PointBase point, Matrix matrix)
//{
// return matrix.Transform(point);
//}
/// <summary>
/// Explicit conversion to Size. Note that since Size cannot contain negative values,
/// the resulting size will contains the absolute values of X and Y
/// </summary>
/// <returns>
/// Size - A Size equal to this PointBase
/// </returns>
/// <param name="point"> PointBase - the PointBase to convert to a Size </param>
public static explicit operator SizeBase(PointBase point)
{
return new SizeBase(Math.Abs(point._x), Math.Abs(point._y));
}
/// <summary>
/// Explicit conversion to Vector
/// </summary>
/// <returns>
/// Vector - A Vector equal to this PointBase
/// </returns>
/// <param name="point"> PointBase - the PointBase to convert to a Vector </param>
public static explicit operator VectorBase(PointBase point)
{
return new VectorBase(point._x, point._y);
}
#endregion Public Methods
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,28 @@
using AIStudio.Wpf.DiagramDesigner.Models;
namespace AIStudio.Wpf.DiagramDesigner.Geometry
{
public delegate IShape ShapeDefiner(DesignerItemViewModelBase node);
public static class Shapes
{
public static IShape Rectangle(DesignerItemViewModelBase node) => new RectangleBase(node.Position, node.Size);
public static IShape Circle(DesignerItemViewModelBase node)
{
var halfWidth = node.Size.Width / 2;
var centerX = node.Position.X + halfWidth;
var centerY = node.Position.Y + node.Size.Height / 2;
return new EllipseBase(centerX, centerY, halfWidth, halfWidth);
}
public static IShape Ellipse(DesignerItemViewModelBase node)
{
var halfWidth = node.Size.Width / 2;
var halfHeight = node.Size.Height / 2;
var centerX = node.Position.X + halfWidth;
var centerY = node.Position.Y + halfHeight;
return new EllipseBase(centerX, centerY, halfWidth, halfHeight);
}
}
}

View File

@@ -0,0 +1,385 @@
using System;
using System.Windows;
namespace AIStudio.Wpf.DiagramDesigner.Geometry
{
[Serializable]
public struct SizeBase : IFormattable
{
public static SizeBase Zero { get; } = new SizeBase(0, 0);
public SizeBase(double width, double height)
{
if (width < 0 || height < 0)
{
throw new System.ArgumentException("Size_WidthAndHeightCannotBeNegative");
}
_width = width;
_height = height;
}
public bool IsEmpty
{
get
{
return _width < 0;
}
}
internal double _width;
public double Width
{
get
{
return _width;
}
set
{
if (IsEmpty)
{
throw new System.InvalidOperationException("Size_CannotModifyEmptySize");
}
if (value < 0)
{
throw new System.ArgumentException("Size_WidthCannotBeNegative");
}
_width = value;
}
}
internal double _height;
public double Height
{
get
{
return _height;
}
set
{
if (IsEmpty)
{
throw new System.InvalidOperationException("Size_CannotModifyEmptySize");
}
if (value < 0)
{
throw new System.ArgumentException("Size_HeightCannotBeNegative");
}
_height = value;
}
}
public SizeBase Add(double value) => new SizeBase(Width + value, Height + value);
//public bool Equals(Size size) => size != null && Width == size.Width && Height == size.Height;
//public override string ToString() => $"Size(width={Width}, height={Height})";
public static implicit operator SizeBase(Size size)
{
return new SizeBase(size.Width, size.Height);
}
public static implicit operator Size(SizeBase sizebase)
{
return new Size(sizebase.Width, sizebase.Height);
}
#region Statics
/// <summary>
/// Empty - a static property which provides an Empty size. Width and Height are
/// negative-infinity. This is the only situation
/// where size can be negative.
/// </summary>
public static SizeBase Empty
{
get
{
return s_empty;
}
}
#endregion Statics
#region Public Methods
/// <summary>
/// Compares two Size instances for exact equality.
/// Note that double values can acquire error when operated upon, such that
/// an exact comparison between two values which are logically equal may fail.
/// Furthermore, using this equality operator, Double.NaN is not equal to itself.
/// </summary>
/// <returns>
/// bool - true if the two Size instances are exactly equal, false otherwise
/// </returns>
/// <param name='size1'>The first Size to compare</param>
/// <param name='size2'>The second Size to compare</param>
public static bool operator ==(SizeBase size1, SizeBase size2)
{
return size1.Width == size2.Width &&
size1.Height == size2.Height;
}
/// <summary>
/// Compares two Size instances for exact inequality.
/// Note that double values can acquire error when operated upon, such that
/// an exact comparison between two values which are logically equal may fail.
/// Furthermore, using this equality operator, Double.NaN is not equal to itself.
/// </summary>
/// <returns>
/// bool - true if the two Size instances are exactly unequal, false otherwise
/// </returns>
/// <param name='size1'>The first Size to compare</param>
/// <param name='size2'>The second Size to compare</param>
public static bool operator !=(SizeBase size1, SizeBase size2)
{
return !(size1 == size2);
}
/// <summary>
/// Compares two Size instances for object equality. In this equality
/// Double.NaN is equal to itself, unlike in numeric equality.
/// Note that double values can acquire error when operated upon, such that
/// an exact comparison between two values which
/// are logically equal may fail.
/// </summary>
/// <returns>
/// bool - true if the two Size instances are exactly equal, false otherwise
/// </returns>
/// <param name='size1'>The first Size to compare</param>
/// <param name='size2'>The second Size to compare</param>
public static bool Equals(SizeBase size1, SizeBase size2)
{
if (size1.IsEmpty)
{
return size2.IsEmpty;
}
else
{
return size1.Width.Equals(size2.Width) &&
size1.Height.Equals(size2.Height);
}
}
/// <summary>
/// Equals - compares this Size with the passed in object. In this equality
/// Double.NaN is equal to itself, unlike in numeric equality.
/// Note that double values can acquire error when operated upon, such that
/// an exact comparison between two values which
/// are logically equal may fail.
/// </summary>
/// <returns>
/// bool - true if the object is an instance of Size and if it's equal to "this".
/// </returns>
/// <param name='o'>The object to compare to "this"</param>
public override bool Equals(object o)
{
if ((null == o) || !(o is SizeBase))
{
return false;
}
SizeBase value = (SizeBase)o;
return SizeBase.Equals(this, value);
}
/// <summary>
/// Equals - compares this Size with the passed in object. In this equality
/// Double.NaN is equal to itself, unlike in numeric equality.
/// Note that double values can acquire error when operated upon, such that
/// an exact comparison between two values which
/// are logically equal may fail.
/// </summary>
/// <returns>
/// bool - true if "value" is equal to "this".
/// </returns>
/// <param name='value'>The Size to compare to "this"</param>
public bool Equals(SizeBase value)
{
return SizeBase.Equals(this, value);
}
/// <summary>
/// Returns the HashCode for this Size
/// </summary>
/// <returns>
/// int - the HashCode for this Size
/// </returns>
public override int GetHashCode()
{
if (IsEmpty)
{
return 0;
}
else
{
// Perform field-by-field XOR of HashCodes
return Width.GetHashCode() ^
Height.GetHashCode();
}
}
/// <summary>
/// Parse - returns an instance converted from the provided string using
/// the culture "en-US"
/// <param name="source"> string with Size data </param>
/// </summary>
//public static Size Parse(string source)
//{
// IFormatProvider formatProvider = System.Windows.Markup.TypeConverterHelper.InvariantEnglishUS;
// TokenizerHelper th = new TokenizerHelper(source, formatProvider);
// Size value;
// String firstToken = th.NextTokenRequired();
// // The token will already have had whitespace trimmed so we can do a
// // simple string compare.
// if (firstToken == "Empty")
// {
// value = Empty;
// }
// else
// {
// value = new Size(
// Convert.ToDouble(firstToken, formatProvider),
// Convert.ToDouble(th.NextTokenRequired(), formatProvider));
// }
// // There should be no more tokens in this string.
// th.LastTokenRequired();
// return value;
//}
#endregion Public Methods
#region Public Operators
/// <summary>
/// Explicit conversion to Vector.
/// </summary>
/// <returns>
/// Vector - A Vector equal to this Size
/// </returns>
/// <param name="size"> Size - the Size to convert to a Vector </param>
public static explicit operator VectorBase(SizeBase size)
{
return new VectorBase(size._width, size._height);
}
/// <summary>
/// Explicit conversion to PointBase
/// </summary>
/// <returns>
/// PointBase - A PointBase equal to this Size
/// </returns>
/// <param name="size"> Size - the Size to convert to a PointBase </param>
public static explicit operator PointBase(SizeBase size)
{
return new PointBase(size._width, size._height);
}
#endregion Public Operators
#region Private Methods
static private SizeBase CreateEmptySize()
{
SizeBase size = new SizeBase();
// We can't set these via the property setters because negatives widths
// are rejected in those APIs.
size._width = Double.NegativeInfinity;
size._height = Double.NegativeInfinity;
return size;
}
#endregion Private Methods
#region Private Fields
private readonly static SizeBase s_empty = CreateEmptySize();
#endregion Private Fields
#region Internal Properties
/// <summary>
/// Creates a string representation of this object based on the current culture.
/// </summary>
/// <returns>
/// A string representation of this object.
/// </returns>
public override string ToString()
{
// Delegate to the internal method which implements all ToString calls.
return ConvertToString(null /* format string */, null /* format provider */);
}
/// <summary>
/// Creates a string representation of this object based on the IFormatProvider
/// passed in. If the provider is null, the CurrentCulture is used.
/// </summary>
/// <returns>
/// A string representation of this object.
/// </returns>
public string ToString(IFormatProvider provider)
{
// Delegate to the internal method which implements all ToString calls.
return ConvertToString(null /* format string */, provider);
}
/// <summary>
/// Creates a string representation of this object based on the format string
/// and IFormatProvider passed in.
/// If the provider is null, the CurrentCulture is used.
/// See the documentation for IFormattable for more information.
/// </summary>
/// <returns>
/// A string representation of this object.
/// </returns>
string IFormattable.ToString(string format, IFormatProvider provider)
{
// Delegate to the internal method which implements all ToString calls.
return ConvertToString(format, provider);
}
/// <summary>
/// Creates a string representation of this object based on the format string
/// and IFormatProvider passed in.
/// If the provider is null, the CurrentCulture is used.
/// See the documentation for IFormattable for more information.
/// </summary>
/// <returns>
/// A string representation of this object.
/// </returns>
internal string ConvertToString(string format, IFormatProvider provider)
{
if (IsEmpty)
{
return "Empty";
}
// Helper to get the numeric list separator for a given culture.
char separator = ',';
return String.Format(provider,
"{1:" + format + "}{0}{2:" + format + "}",
separator,
_width,
_height);
}
#endregion Internal Properties
}
}

View File

@@ -0,0 +1,618 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace AIStudio.Wpf.DiagramDesigner.Geometry
{
[Serializable]
public struct VectorBase : IFormattable
{
#region Constructors
/// <summary>
/// Constructor which sets the vector's initial values
/// </summary>
/// <param name="x"> double - The initial X </param>
/// <param name="y"> double - THe initial Y </param>
public VectorBase(double x, double y)
{
_x = x;
_y = y;
}
#endregion Constructors
#region Public Methods
/// <summary>
/// Length Property - the length of this Vector
/// </summary>
public double Length
{
get
{
return Math.Sqrt(_x * _x + _y * _y);
}
}
/// <summary>
/// LengthSquared Property - the squared length of this Vector
/// </summary>
public double LengthSquared
{
get
{
return _x * _x + _y * _y;
}
}
/// <summary>
/// Normalize - Updates this Vector to maintain its direction, but to have a length
/// of 1. This is equivalent to dividing this Vector by Length
/// </summary>
public void Normalize()
{
// Avoid overflow
this /= Math.Max(Math.Abs(_x), Math.Abs(_y));
this /= Length;
}
/// <summary>
/// CrossProduct - Returns the cross product: vector1.X*vector2.Y - vector1.Y*vector2.X
/// </summary>
/// <returns>
/// Returns the cross product: vector1.X*vector2.Y - vector1.Y*vector2.X
/// </returns>
/// <param name="vector1"> The first Vector </param>
/// <param name="vector2"> The second Vector </param>
public static double CrossProduct(VectorBase vector1, VectorBase vector2)
{
return vector1._x * vector2._y - vector1._y * vector2._x;
}
/// <summary>
/// AngleBetween - the angle between 2 vectors
/// </summary>
/// <returns>
/// Returns the the angle in degrees between vector1 and vector2
/// </returns>
/// <param name="vector1"> The first Vector </param>
/// <param name="vector2"> The second Vector </param>
public static double AngleBetween(VectorBase vector1, VectorBase vector2)
{
double sin = vector1._x * vector2._y - vector2._x * vector1._y;
double cos = vector1._x * vector2._x + vector1._y * vector2._y;
return Math.Atan2(sin, cos) * (180 / Math.PI);
}
#endregion Public Methods
#region Public Operators
/// <summary>
/// Operator -Vector (unary negation)
/// </summary>
public static VectorBase operator -(VectorBase vector)
{
return new VectorBase(-vector._x, -vector._y);
}
/// <summary>
/// Negates the values of X and Y on this Vector
/// </summary>
public void Negate()
{
_x = -_x;
_y = -_y;
}
/// <summary>
/// Operator Vector + Vector
/// </summary>
public static VectorBase operator +(VectorBase vector1, VectorBase vector2)
{
return new VectorBase(vector1._x + vector2._x,
vector1._y + vector2._y);
}
/// <summary>
/// Add: Vector + Vector
/// </summary>
public static VectorBase Add(VectorBase vector1, VectorBase vector2)
{
return new VectorBase(vector1._x + vector2._x,
vector1._y + vector2._y);
}
/// <summary>
/// Operator Vector - Vector
/// </summary>
public static VectorBase operator -(VectorBase vector1, VectorBase vector2)
{
return new VectorBase(vector1._x - vector2._x,
vector1._y - vector2._y);
}
/// <summary>
/// Subtract: Vector - Vector
/// </summary>
public static VectorBase Subtract(VectorBase vector1, VectorBase vector2)
{
return new VectorBase(vector1._x - vector2._x,
vector1._y - vector2._y);
}
/// <summary>
/// Operator Vector + PointBase
/// </summary>
public static PointBase operator +(VectorBase vector, PointBase point)
{
return new PointBase(point._x + vector._x, point._y + vector._y);
}
/// <summary>
/// Add: Vector + PointBase
/// </summary>
public static PointBase Add(VectorBase vector, PointBase point)
{
return new PointBase(point._x + vector._x, point._y + vector._y);
}
/// <summary>
/// Operator Vector * double
/// </summary>
public static VectorBase operator *(VectorBase vector, double scalar)
{
return new VectorBase(vector._x * scalar,
vector._y * scalar);
}
/// <summary>
/// Multiply: Vector * double
/// </summary>
public static VectorBase Multiply(VectorBase vector, double scalar)
{
return new VectorBase(vector._x * scalar,
vector._y * scalar);
}
/// <summary>
/// Operator double * Vector
/// </summary>
public static VectorBase operator *(double scalar, VectorBase vector)
{
return new VectorBase(vector._x * scalar,
vector._y * scalar);
}
/// <summary>
/// Multiply: double * Vector
/// </summary>
public static VectorBase Multiply(double scalar, VectorBase vector)
{
return new VectorBase(vector._x * scalar,
vector._y * scalar);
}
/// <summary>
/// Operator Vector / double
/// </summary>
public static VectorBase operator /(VectorBase vector, double scalar)
{
return vector * (1.0 / scalar);
}
/// <summary>
/// Multiply: Vector / double
/// </summary>
public static VectorBase Divide(VectorBase vector, double scalar)
{
return vector * (1.0 / scalar);
}
/// <summary>
/// Operator Vector * Matrix
/// </summary>
//public static Vector operator *(Vector vector, Matrix matrix)
//{
// return matrix.Transform(vector);
//}
/// <summary>
/// Multiply: Vector * Matrix
/// </summary>
//public static Vector Multiply(Vector vector, Matrix matrix)
//{
// return matrix.Transform(vector);
//}
/// <summary>
/// Operator Vector * Vector, interpreted as their dot product
/// </summary>
public static double operator *(VectorBase vector1, VectorBase vector2)
{
return vector1._x * vector2._x + vector1._y * vector2._y;
}
/// <summary>
/// Multiply - Returns the dot product: vector1.X*vector2.X + vector1.Y*vector2.Y
/// </summary>
/// <returns>
/// Returns the dot product: vector1.X*vector2.X + vector1.Y*vector2.Y
/// </returns>
/// <param name="vector1"> The first Vector </param>
/// <param name="vector2"> The second Vector </param>
public static double Multiply(VectorBase vector1, VectorBase vector2)
{
return vector1._x * vector2._x + vector1._y * vector2._y;
}
/// <summary>
/// Determinant - Returns the determinant det(vector1, vector2)
/// </summary>
/// <returns>
/// Returns the determinant: vector1.X*vector2.Y - vector1.Y*vector2.X
/// </returns>
/// <param name="vector1"> The first Vector </param>
/// <param name="vector2"> The second Vector </param>
public static double Determinant(VectorBase vector1, VectorBase vector2)
{
return vector1._x * vector2._y - vector1._y * vector2._x;
}
/// <summary>
/// Explicit conversion to Size. Note that since Size cannot contain negative values,
/// the resulting size will contains the absolute values of X and Y
/// </summary>
/// <returns>
/// Size - A Size equal to this Vector
/// </returns>
/// <param name="vector"> Vector - the Vector to convert to a Size </param>
public static explicit operator SizeBase(VectorBase vector)
{
return new SizeBase(Math.Abs(vector._x), Math.Abs(vector._y));
}
/// <summary>
/// Explicit conversion to PointBase
/// </summary>
/// <returns>
/// PointBase - A PointBase equal to this Vector
/// </returns>
/// <param name="vector"> Vector - the Vector to convert to a PointBase </param>
public static explicit operator PointBase(VectorBase vector)
{
return new PointBase(vector._x, vector._y);
}
#endregion Public Operators
//------------------------------------------------------
//
// Public Methods
//
//------------------------------------------------------
#region Public Methods
/// <summary>
/// Compares two Vector instances for exact equality.
/// Note that double values can acquire error when operated upon, such that
/// an exact comparison between two values which are logically equal may fail.
/// Furthermore, using this equality operator, Double.NaN is not equal to itself.
/// </summary>
/// <returns>
/// bool - true if the two Vector instances are exactly equal, false otherwise
/// </returns>
/// <param name='vector1'>The first Vector to compare</param>
/// <param name='vector2'>The second Vector to compare</param>
public static bool operator ==(VectorBase vector1, VectorBase vector2)
{
return vector1.X == vector2.X &&
vector1.Y == vector2.Y;
}
/// <summary>
/// Compares two Vector instances for exact inequality.
/// Note that double values can acquire error when operated upon, such that
/// an exact comparison between two values which are logically equal may fail.
/// Furthermore, using this equality operator, Double.NaN is not equal to itself.
/// </summary>
/// <returns>
/// bool - true if the two Vector instances are exactly unequal, false otherwise
/// </returns>
/// <param name='vector1'>The first Vector to compare</param>
/// <param name='vector2'>The second Vector to compare</param>
public static bool operator !=(VectorBase vector1, VectorBase vector2)
{
return !(vector1 == vector2);
}
/// <summary>
/// Compares two Vector instances for object equality. In this equality
/// Double.NaN is equal to itself, unlike in numeric equality.
/// Note that double values can acquire error when operated upon, such that
/// an exact comparison between two values which
/// are logically equal may fail.
/// </summary>
/// <returns>
/// bool - true if the two Vector instances are exactly equal, false otherwise
/// </returns>
/// <param name='vector1'>The first Vector to compare</param>
/// <param name='vector2'>The second Vector to compare</param>
public static bool Equals(VectorBase vector1, VectorBase vector2)
{
return vector1.X.Equals(vector2.X) &&
vector1.Y.Equals(vector2.Y);
}
/// <summary>
/// Equals - compares this Vector with the passed in object. In this equality
/// Double.NaN is equal to itself, unlike in numeric equality.
/// Note that double values can acquire error when operated upon, such that
/// an exact comparison between two values which
/// are logically equal may fail.
/// </summary>
/// <returns>
/// bool - true if the object is an instance of Vector and if it's equal to "this".
/// </returns>
/// <param name='o'>The object to compare to "this"</param>
public override bool Equals(object o)
{
if ((null == o) || !(o is VectorBase))
{
return false;
}
VectorBase value = (VectorBase)o;
return VectorBase.Equals(this, value);
}
/// <summary>
/// Equals - compares this Vector with the passed in object. In this equality
/// Double.NaN is equal to itself, unlike in numeric equality.
/// Note that double values can acquire error when operated upon, such that
/// an exact comparison between two values which
/// are logically equal may fail.
/// </summary>
/// <returns>
/// bool - true if "value" is equal to "this".
/// </returns>
/// <param name='value'>The Vector to compare to "this"</param>
public bool Equals(VectorBase value)
{
return VectorBase.Equals(this, value);
}
/// <summary>
/// Returns the HashCode for this Vector
/// </summary>
/// <returns>
/// int - the HashCode for this Vector
/// </returns>
public override int GetHashCode()
{
// Perform field-by-field XOR of HashCodes
return X.GetHashCode() ^
Y.GetHashCode();
}
/// <summary>
/// Parse - returns an instance converted from the provided string using
/// the culture "en-US"
/// <param name="source"> string with Vector data </param>
/// </summary>
//public static Vector Parse(string source)
//{
// IFormatProvider formatProvider = System.Windows.Markup.TypeConverterHelper.InvariantEnglishUS;
// TokenizerHelper th = new TokenizerHelper(source, formatProvider);
// Vector value;
// String firstToken = th.NextTokenRequired();
// value = new Vector(
// Convert.ToDouble(firstToken, formatProvider),
// Convert.ToDouble(th.NextTokenRequired(), formatProvider));
// // There should be no more tokens in this string.
// th.LastTokenRequired();
// return value;
//}
#endregion Public Methods
//------------------------------------------------------
//
// Public Properties
//
//------------------------------------------------------
#region Public Properties
/// <summary>
/// X - double. Default value is 0.
/// </summary>
public double X
{
get
{
return _x;
}
set
{
_x = value;
}
}
/// <summary>
/// Y - double. Default value is 0.
/// </summary>
public double Y
{
get
{
return _y;
}
set
{
_y = value;
}
}
#endregion Public Properties
//------------------------------------------------------
//
// Protected Methods
//
//------------------------------------------------------
#region Protected Methods
#endregion ProtectedMethods
//------------------------------------------------------
//
// Internal Methods
//
//------------------------------------------------------
#region Internal Methods
#endregion Internal Methods
//------------------------------------------------------
//
// Internal Properties
//
//------------------------------------------------------
#region Internal Properties
/// <summary>
/// Creates a string representation of this object based on the current culture.
/// </summary>
/// <returns>
/// A string representation of this object.
/// </returns>
public override string ToString()
{
// Delegate to the internal method which implements all ToString calls.
return ConvertToString(null /* format string */, null /* format provider */);
}
/// <summary>
/// Creates a string representation of this object based on the IFormatProvider
/// passed in. If the provider is null, the CurrentCulture is used.
/// </summary>
/// <returns>
/// A string representation of this object.
/// </returns>
public string ToString(IFormatProvider provider)
{
// Delegate to the internal method which implements all ToString calls.
return ConvertToString(null /* format string */, provider);
}
/// <summary>
/// Creates a string representation of this object based on the format string
/// and IFormatProvider passed in.
/// If the provider is null, the CurrentCulture is used.
/// See the documentation for IFormattable for more information.
/// </summary>
/// <returns>
/// A string representation of this object.
/// </returns>
string IFormattable.ToString(string format, IFormatProvider provider)
{
// Delegate to the internal method which implements all ToString calls.
return ConvertToString(format, provider);
}
/// <summary>
/// Creates a string representation of this object based on the format string
/// and IFormatProvider passed in.
/// If the provider is null, the CurrentCulture is used.
/// See the documentation for IFormattable for more information.
/// </summary>
/// <returns>
/// A string representation of this object.
/// </returns>
internal string ConvertToString(string format, IFormatProvider provider)
{
// Helper to get the numeric list separator for a given culture.
char separator = ',';
return String.Format(provider,
"{1:" + format + "}{0}{2:" + format + "}",
separator,
_x,
_y);
}
#endregion Internal Properties
//------------------------------------------------------
//
// Dependency Properties
//
//------------------------------------------------------
#region Dependency Properties
#endregion Dependency Properties
//------------------------------------------------------
//
// Internal Fields
//
//------------------------------------------------------
#region Internal Fields
internal double _x;
internal double _y;
#endregion Internal Fields
#region Constructors
//------------------------------------------------------
//
// Constructors
//
//------------------------------------------------------
#endregion Constructors
}
}