Compare commits
3 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
425e4dae22 | 8 years ago |
|
|
34b6c232d3 | 8 years ago |
|
|
05407a8e86 | 8 years ago |
63 changed files with 16919 additions and 68 deletions
@ -0,0 +1,338 @@ |
|||
using System; |
|||
using System.Globalization; |
|||
using System.IO; |
|||
using System.Xml; |
|||
using System.Xml.Serialization; |
|||
using MathNet.Numerics.Spatial; |
|||
using NUnit.Framework; |
|||
|
|||
namespace MathNet.Numerics.Tests.Spatial |
|||
{ |
|||
[TestFixture] |
|||
public class AngleTests |
|||
{ |
|||
private const double Tolerance = 1e-6; |
|||
private const double DegToRad = Math.PI / 180; |
|||
|
|||
[Test] |
|||
public void OperatorCompare() |
|||
{ |
|||
var one = Angle.FromRadians(1); |
|||
var two = Angle.FromRadians(2); |
|||
Assert.AreEqual(true, one < two); |
|||
Assert.AreEqual(true, one <= two); |
|||
Assert.AreEqual(true, one <= Angle.FromRadians(1)); |
|||
Assert.AreEqual(false, one < Angle.FromRadians(1)); |
|||
Assert.AreEqual(false, one > Angle.FromRadians(1)); |
|||
Assert.AreEqual(true, one >= Angle.FromRadians(1)); |
|||
} |
|||
|
|||
[TestCase("1.5707 rad", "1.5707 rad", 1.5707 + 1.5707)] |
|||
[TestCase("1.5707 rad", "2 °", 1.5707 + (2 * DegToRad))] |
|||
public void OperatorAdd(string lvs, string rvs, double ev) |
|||
{ |
|||
var lv = Angle.Parse(lvs); |
|||
var rv = Angle.Parse(rvs); |
|||
var sum = lv + rv; |
|||
Assert.AreEqual(ev, sum.Radians, Tolerance); |
|||
Assert.IsInstanceOf<Angle>(sum); |
|||
} |
|||
|
|||
[TestCase("1.5707 rad", "1.5706 rad", 1.5707 - 1.5706)] |
|||
[TestCase("1.5707 rad", "2 °", 1.5707 - (2 * DegToRad))] |
|||
public void OperatorSubtract(string lvs, string rvs, double ev) |
|||
{ |
|||
var lv = Angle.Parse(lvs); |
|||
var rv = Angle.Parse(rvs); |
|||
var diff = lv - rv; |
|||
Assert.AreEqual(ev, diff.Radians, Tolerance); |
|||
Assert.IsInstanceOf<Angle>(diff); |
|||
} |
|||
|
|||
[TestCase("15 °", 5, 15 * 5 * DegToRad)] |
|||
[TestCase("-10 °", 0, 0)] |
|||
[TestCase("-10 °", 2, -10 * 2 * DegToRad)] |
|||
[TestCase("1 rad", 2, 2)] |
|||
public void OperatorMultiply(string lvs, double rv, double ev) |
|||
{ |
|||
var lv = Angle.Parse(lvs); |
|||
var prods = new[] { lv * rv, rv * lv }; |
|||
foreach (var prod in prods) |
|||
{ |
|||
Assert.AreEqual(ev, prod.Radians, 1e-3); |
|||
Assert.IsInstanceOf<Angle>(prod); |
|||
} |
|||
} |
|||
|
|||
[Test] |
|||
public void OperatorNegate() |
|||
{ |
|||
Assert.AreEqual(-1, (-Angle.FromRadians(1)).Radians); |
|||
} |
|||
|
|||
[TestCase("3.141596 rad", 2, 1.570797999)] |
|||
public void DivisionTest(string s, double rv, double expected) |
|||
{ |
|||
var angle = Angle.Parse(s); |
|||
var actual = angle / rv; |
|||
Assert.AreEqual(expected, actual.Radians, Tolerance); |
|||
Assert.IsInstanceOf<Angle>(actual); |
|||
} |
|||
|
|||
[TestCase("90 °", 90, Math.PI / 2, true)] |
|||
[TestCase("1 rad", 1 * 180 / Math.PI, 1, true)] |
|||
[TestCase("1.1 rad", 1 * 180 / Math.PI, Math.PI / 2, false)] |
|||
public void Equals(string s, double degrees, double radians, bool expected) |
|||
{ |
|||
var a = Angle.Parse(s); |
|||
var deg = Angle.FromDegrees(degrees); |
|||
Assert.AreEqual(expected, deg.Equals(a)); |
|||
Assert.AreEqual(expected, deg.Equals(a, Tolerance)); |
|||
Assert.AreEqual(expected, deg == a); |
|||
Assert.AreEqual(!expected, deg != a); |
|||
|
|||
var rad = Angle.FromRadians(radians); |
|||
Assert.AreEqual(expected, rad.Equals(a)); |
|||
Assert.AreEqual(expected, rad.Equals(a, Tolerance)); |
|||
Assert.AreEqual(expected, rad == a); |
|||
Assert.AreEqual(!expected, rad != a); |
|||
} |
|||
|
|||
[Test] |
|||
public void EqualsWithTolerance() |
|||
{ |
|||
var one = Angle.FromRadians(1); |
|||
var two = Angle.FromRadians(2); |
|||
Assert.AreEqual(true, one.Equals(two, 2)); |
|||
Assert.AreEqual(false, one.Equals(two, 0.1)); |
|||
Assert.AreEqual(true, one.Equals(two, Angle.FromRadians(2))); |
|||
Assert.AreEqual(false, one.Equals(two, Angle.FromRadians(0.1))); |
|||
} |
|||
|
|||
[TestCase(90, 1.5707963267948966)] |
|||
public void FromDegrees(double degrees, double expected) |
|||
{ |
|||
Assert.AreEqual(expected, Angle.FromDegrees(degrees).Radians); |
|||
Assert.AreEqual(degrees, Angle.FromDegrees(degrees).Degrees, 1E-6); |
|||
} |
|||
|
|||
[TestCase(1, 1)] |
|||
public void FromRadians(double radians, double expected) |
|||
{ |
|||
Assert.AreEqual(expected, Angle.FromRadians(radians).Radians); |
|||
} |
|||
|
|||
[TestCase(20, 33, 49, 0.35890271998857842)] |
|||
public void FromSexagesimal(int degrees, int minutes, double seconds, double expected) |
|||
{ |
|||
Assert.AreEqual(expected, Angle.FromSexagesimal(degrees, minutes, seconds).Radians, 1E-6); |
|||
} |
|||
|
|||
[TestCase("5 °", 5 * DegToRad)] |
|||
[TestCase("5°", 5 * DegToRad)] |
|||
[TestCase("-5.34 rad", -5.34)] |
|||
[TestCase("-5,34 rad", -5.34)] |
|||
[TestCase("1e-4 rad", 0.0001)] |
|||
[TestCase("1e-4 °", 0.0001 * DegToRad)] |
|||
public void Parse(string s, double expected) |
|||
{ |
|||
Assert.AreEqual(true, Angle.TryParse(s, out var angle)); |
|||
Assert.AreEqual(expected, angle.Radians, Tolerance); |
|||
angle = Angle.Parse(s); |
|||
Assert.AreEqual(expected, angle.Radians, Tolerance); |
|||
Assert.IsInstanceOf<Angle>(angle); |
|||
|
|||
// ReSharper disable once RedundantToStringCall
|
|||
angle = Angle.Parse(s.ToString()); |
|||
Assert.AreEqual(expected, angle.Radians, Tolerance); |
|||
Assert.IsInstanceOf<Angle>(angle); |
|||
} |
|||
|
|||
[Test] |
|||
public void FailParse() |
|||
{ |
|||
bool result = Angle.TryParse("test", out var angle); |
|||
Assert.AreEqual(default(Angle), angle); |
|||
Assert.IsFalse(result); |
|||
} |
|||
|
|||
[Test] |
|||
public void FailParseDirect() |
|||
{ |
|||
Assert.Throws<FormatException>(() => Angle.Parse("Test"), "Expected FormatException", null); |
|||
} |
|||
|
|||
[TestCase(".1 rad", 0.1)] |
|||
[TestCase("1.2 rad", 1.2)] |
|||
[TestCase("1.2\u00A0rad", 1.2)] |
|||
[TestCase("1.2radians", 1.2)] |
|||
[TestCase("1.2 radians", 1.2)] |
|||
[TestCase("1.2\u00A0radians", 1.2)] |
|||
[TestCase("1.2\u00A0Radians", 1.2)] |
|||
public void ParseRadians(string text, double expected) |
|||
{ |
|||
Assert.AreEqual(true, Angle.TryParse(text, out var angle)); |
|||
Assert.AreEqual(expected, angle.Radians); |
|||
Assert.AreEqual(expected, Angle.Parse(text).Radians); |
|||
} |
|||
|
|||
[TestCase("1°", 1)] |
|||
[TestCase("1 °", 1)] |
|||
[TestCase("1deg", 1)] |
|||
[TestCase("1 deg", 1)] |
|||
[TestCase("1\u00A0deg", 1)] |
|||
[TestCase("1\u00A0DEG", 1)] |
|||
[TestCase("1degrees", 1)] |
|||
[TestCase("1 degrees", 1)] |
|||
[TestCase("1\u00A0degrees", 1)] |
|||
[TestCase("1\u00A0Degrees", 1)] |
|||
public void ParseDegrees(string text, double expected) |
|||
{ |
|||
Assert.AreEqual(true, Angle.TryParse(text, out var angle)); |
|||
Assert.AreEqual(expected, angle.Degrees); |
|||
Assert.AreEqual(expected, Angle.Parse(text).Degrees); |
|||
} |
|||
|
|||
[TestCase(@"<Angle Value=""1"" />")] |
|||
[TestCase(@"<Angle><Value>1</Value></Angle>")] |
|||
public void ReadFrom(string xml) |
|||
{ |
|||
var v = Angle.FromRadians(1); |
|||
Assert.AreEqual(v, Angle.ReadFrom(XmlReader.Create(new StringReader(xml)))); |
|||
} |
|||
|
|||
[Test] |
|||
public void Compare() |
|||
{ |
|||
var small = Angle.FromDegrees(1); |
|||
var big = Angle.FromDegrees(2); |
|||
Assert.IsTrue(small < big); |
|||
Assert.IsTrue(small <= big); |
|||
Assert.IsFalse(small > big); |
|||
Assert.IsFalse(small >= big); |
|||
Assert.AreEqual(-1, small.CompareTo(big)); |
|||
Assert.AreEqual(0, small.CompareTo(small)); |
|||
Assert.AreEqual(1, big.CompareTo(small)); |
|||
} |
|||
|
|||
[TestCase("15 °", "0.261799387799149\u00A0rad")] |
|||
public void ToString(string s, string expected) |
|||
{ |
|||
var angle = Angle.Parse(s); |
|||
var toString = angle.ToString(CultureInfo.InvariantCulture); |
|||
Assert.AreEqual(expected, toString); |
|||
Assert.IsTrue(angle.Equals(Angle.Parse(toString), Tolerance)); |
|||
Assert.IsTrue(angle.Equals(Angle.Parse(toString), Angle.FromRadians(Tolerance))); |
|||
} |
|||
|
|||
//[TestCase("15°", "F2", "15.00°")]
|
|||
//public void ToString(string s, string format, string expected)
|
|||
//{
|
|||
// var angle = Angle.Parse(s);
|
|||
// var toString = angle.ToString(format, CultureInfo.InvariantCulture, AngleUnit.Degrees);
|
|||
// Assert.AreEqual(expected, toString);
|
|||
// Assert.AreEqual(angle.Radians, Angle.Parse(angle.ToString(format)).Radians, 1E-2);
|
|||
// Assert.IsTrue(angle.Equals(Angle.Parse(toString), Tolerance));
|
|||
//}
|
|||
|
|||
[TestCase("15°", @"<Angle Value=""0.26179938779914941"" />")] |
|||
public void XmlRoundTrips(string vs, string xml) |
|||
{ |
|||
var angle = Angle.Parse(vs); |
|||
AssertXml.XmlRoundTrips(angle, xml, (e, a) => |
|||
{ |
|||
Assert.AreEqual(e.Radians, a.Radians, Tolerance); |
|||
}); |
|||
} |
|||
|
|||
[Test] |
|||
public void XmlContainerRoundtrip() |
|||
{ |
|||
var container = new AssertXml.Container<Angle> |
|||
{ |
|||
Value1 = Angle.FromRadians(1), |
|||
Value2 = Angle.FromRadians(2), |
|||
}; |
|||
var expected = "<ContainerOfAngle>\r\n" + |
|||
" <Value1 Value=\"1\"></Value1>\r\n" + |
|||
" <Value2 Value=\"2\"></Value2>\r\n" + |
|||
"</ContainerOfAngle>"; |
|||
var roundTrip = AssertXml.XmlSerializerRoundTrip(container, expected); |
|||
Assert.AreEqual(container.Value1, roundTrip.Value1); |
|||
Assert.AreEqual(container.Value2, roundTrip.Value2); |
|||
} |
|||
|
|||
[Test] |
|||
public void ReadXmlContainerElementValues() |
|||
{ |
|||
var container = new AssertXml.Container<Angle> |
|||
{ |
|||
Value1 = Angle.FromRadians(1), |
|||
Value2 = Angle.FromRadians(2), |
|||
}; |
|||
var xml = "<ContainerOfAngle>\r\n" + |
|||
" <Value1>1</Value1>\r\n" + |
|||
" <Value2>2</Value2>\r\n" + |
|||
"</ContainerOfAngle>"; |
|||
var serializer = new XmlSerializer(typeof(AssertXml.Container<Angle>)); |
|||
using (var reader = new StringReader(xml)) |
|||
{ |
|||
var deserialized = (AssertXml.Container<Angle>)serializer.Deserialize(reader); |
|||
Assert.AreEqual(container.Value1, deserialized.Value1); |
|||
Assert.AreEqual(container.Value2, deserialized.Value2); |
|||
} |
|||
} |
|||
|
|||
[TestCase("15°", @"<Angle><Value>0.261799387799149</Value></Angle>")] |
|||
[TestCase("15°", @"<Angle><Radians>0.261799387799149</Radians></Angle>")] |
|||
[TestCase("15°", @"<Angle><Degrees>15</Degrees></Angle>")] |
|||
[TestCase("15°", @"<Angle Radians=""0.26179938779914941"" />")] |
|||
[TestCase("180°", @"<Angle Degrees=""180"" />")] |
|||
public void XmlElement(string vs, string xml) |
|||
{ |
|||
var angle = Angle.Parse(vs); |
|||
var serializer = new XmlSerializer(typeof(Angle)); |
|||
using (var reader = new StringReader(xml)) |
|||
{ |
|||
var fromElements = (Angle)serializer.Deserialize(reader); |
|||
Assert.AreEqual(angle.Radians, fromElements.Radians, 1e-6); |
|||
} |
|||
} |
|||
|
|||
[Test] |
|||
public void ToStringTest() |
|||
{ |
|||
int number = 1; |
|||
var angle = Angle.FromRadians(number); |
|||
string expected = number + " rad"; |
|||
Assert.AreEqual(expected, angle.ToString()); |
|||
} |
|||
|
|||
[Test] |
|||
public void ObjectEqualsTest() |
|||
{ |
|||
var angle = Angle.FromRadians(1); |
|||
Assert.IsTrue(angle.Equals((object)angle)); |
|||
} |
|||
|
|||
[Test] |
|||
public void ObjectNullTest() |
|||
{ |
|||
var angle = Angle.FromRadians(1); |
|||
Assert.IsFalse(angle.Equals(null)); |
|||
} |
|||
|
|||
[Test] |
|||
public void HashCodeTest() |
|||
{ |
|||
string test = "test"; |
|||
var angle = Angle.FromRadians(1); |
|||
var lookup = new System.Collections.Generic.Dictionary<Angle, string> |
|||
{ |
|||
{ angle, test } |
|||
}; |
|||
|
|||
Assert.AreEqual(test, lookup[angle]); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,164 @@ |
|||
using MathNet.Numerics.LinearAlgebra; |
|||
using MathNet.Numerics.Spatial.Euclidean2D; |
|||
using MathNet.Numerics.Spatial.Euclidean3D; |
|||
using NUnit.Framework; |
|||
|
|||
namespace MathNet.Numerics.Tests.Spatial |
|||
{ |
|||
public static class AssertGeometry |
|||
{ |
|||
public static void AreEqual(CoordinateSystem3D coordinateSystem, Point3D origin, Vector3D xAxis, Vector3D yAxis, Vector3D zAxis, double tolerance = 1e-6) |
|||
{ |
|||
AreEqual(xAxis, coordinateSystem.XAxis, tolerance); |
|||
AreEqual(yAxis, coordinateSystem.YAxis, tolerance); |
|||
AreEqual(zAxis, coordinateSystem.ZAxis, tolerance); |
|||
AreEqual(origin, coordinateSystem.Origin, tolerance); |
|||
|
|||
AreEqual(new double[] { xAxis.X, xAxis.Y, xAxis.Z, 0 }, coordinateSystem.Column(0).ToArray(), tolerance); |
|||
AreEqual(new double[] { yAxis.X, yAxis.Y, yAxis.Z, 0 }, coordinateSystem.Column(1).ToArray(), tolerance); |
|||
AreEqual(new double[] { zAxis.X, zAxis.Y, zAxis.Z, 0 }, coordinateSystem.Column(2).ToArray(), tolerance); |
|||
AreEqual(new double[] { origin.X, origin.Y, origin.Z, 1 }, coordinateSystem.Column(3).ToArray(), tolerance); |
|||
} |
|||
|
|||
public static void AreEqual(UnitVector3D expected, UnitVector3D actual, double tolerance = 1e-6, string message = "") |
|||
{ |
|||
if (string.IsNullOrEmpty(message)) |
|||
{ |
|||
message = string.Format("Expected {0} but was {1}", expected, actual); |
|||
} |
|||
|
|||
Assert.AreEqual(expected.X, actual.X, tolerance, message); |
|||
Assert.AreEqual(expected.Y, actual.Y, tolerance, message); |
|||
Assert.AreEqual(expected.Z, actual.Z, tolerance, message); |
|||
} |
|||
|
|||
public static void AreEqual(Vector3D expected, Vector3D actual, double tolerance = 1e-6, string message = "") |
|||
{ |
|||
if (string.IsNullOrEmpty(message)) |
|||
{ |
|||
message = string.Format("Expected {0} but was {1}", expected, actual); |
|||
} |
|||
|
|||
Assert.AreEqual(expected.X, actual.X, tolerance, message); |
|||
Assert.AreEqual(expected.Y, actual.Y, tolerance, message); |
|||
Assert.AreEqual(expected.Z, actual.Z, tolerance, message); |
|||
} |
|||
|
|||
public static void AreEqual(UnitVector3D expected, Vector3D actual, double tolerance = 1e-6, string message = "") |
|||
{ |
|||
AreEqual(expected.ToVector3D(), actual, tolerance, message); |
|||
} |
|||
|
|||
public static void AreEqual(Vector3D expected, UnitVector3D actual, double tolerance = 1e-6, string message = "") |
|||
{ |
|||
AreEqual(expected, actual.ToVector3D(), tolerance, message); |
|||
} |
|||
|
|||
public static void AreEqual(Vector2D expected, Vector2D actual, double tolerance = 1e-6, string message = "") |
|||
{ |
|||
if (string.IsNullOrEmpty(message)) |
|||
{ |
|||
message = string.Format("Expected {0} but was {1}", expected, actual); |
|||
} |
|||
|
|||
Assert.AreEqual(expected.X, actual.X, tolerance, message); |
|||
Assert.AreEqual(expected.Y, actual.Y, tolerance, message); |
|||
} |
|||
|
|||
public static void AreEqual(Point3D expected, Point3D actual, double tolerance = 1e-6, string message = "") |
|||
{ |
|||
if (string.IsNullOrEmpty(message)) |
|||
{ |
|||
message = string.Format("Expected {0} but was {1}", expected, actual); |
|||
} |
|||
|
|||
Assert.AreEqual(expected.X, actual.X, tolerance, message); |
|||
Assert.AreEqual(expected.Y, actual.Y, tolerance, message); |
|||
Assert.AreEqual(expected.Z, actual.Z, tolerance, message); |
|||
} |
|||
|
|||
public static void AreEqual(CoordinateSystem3D expected, CoordinateSystem3D actual, double tolerance = 1e-6, string message = "") |
|||
{ |
|||
if (string.IsNullOrEmpty(message)) |
|||
{ |
|||
message = string.Format("Expected {0} but was {1}", expected, actual); |
|||
} |
|||
|
|||
if (expected.Values.Length != actual.Values.Length) |
|||
{ |
|||
Assert.Fail(); |
|||
} |
|||
|
|||
for (var i = 0; i < expected.Values.Length; i++) |
|||
{ |
|||
Assert.AreEqual(expected.Values[i], actual.Values[i], tolerance); |
|||
} |
|||
} |
|||
|
|||
public static void AreEqual(double[] expected, double[] actual, double tolerance = 1e-6, string message = "") |
|||
{ |
|||
if (string.IsNullOrEmpty(message)) |
|||
{ |
|||
message = string.Format("Expected {0} but was {1}", "{" + string.Join(",", expected) + "}", "{" + string.Join(",", actual) + "}"); |
|||
} |
|||
|
|||
if (expected.Length != actual.Length) |
|||
{ |
|||
Assert.Fail(); |
|||
} |
|||
|
|||
for (var i = 0; i < expected.Length; i++) |
|||
{ |
|||
Assert.AreEqual(expected[i], actual[i], tolerance); |
|||
} |
|||
} |
|||
|
|||
public static void AreEqual(Line3D expected, Line3D actual, double tolerance = 1e-6) |
|||
{ |
|||
AreEqual(expected.StartPoint, actual.StartPoint, tolerance); |
|||
AreEqual(expected.EndPoint, actual.EndPoint, tolerance); |
|||
} |
|||
|
|||
public static void AreEqual(LineSegment3D expected, LineSegment3D actual, double tolerance = 1e-6) |
|||
{ |
|||
AreEqual(expected.StartPoint, actual.StartPoint, tolerance); |
|||
AreEqual(expected.EndPoint, actual.EndPoint, tolerance); |
|||
} |
|||
|
|||
public static void AreEqual(Ray3D expected, Ray3D actual, double tolerance = 1e-6, string message = "") |
|||
{ |
|||
AreEqual(expected.ThroughPoint, actual.ThroughPoint, tolerance, message); |
|||
AreEqual(expected.Direction, actual.Direction, tolerance, message); |
|||
} |
|||
|
|||
public static void AreEqual(Plane3D expected, Plane3D actual, double tolerance = 1e-6, string message = "") |
|||
{ |
|||
AreEqual(expected.Normal, actual.Normal, tolerance, message); |
|||
AreEqual(expected.RootPoint, actual.RootPoint, tolerance, message); |
|||
Assert.AreEqual(expected.D, actual.D, tolerance, message); |
|||
} |
|||
|
|||
public static void AreEqual(Matrix<double> expected, Matrix<double> actual, double tolerance = 1e-6) |
|||
{ |
|||
Assert.AreEqual(expected.RowCount, actual.RowCount); |
|||
Assert.AreEqual(expected.ColumnCount, actual.ColumnCount); |
|||
var expectedRowWiseArray = expected.ToRowMajorArray(); |
|||
var actualRowWiseArray = actual.ToRowMajorArray(); |
|||
for (var i = 0; i < expectedRowWiseArray.Length; i++) |
|||
{ |
|||
Assert.AreEqual(expectedRowWiseArray[i], actualRowWiseArray[i], tolerance); |
|||
} |
|||
} |
|||
|
|||
public static void AreEqual(Point2D expected, Point2D actual, double tolerance = 1e-6, string message = "") |
|||
{ |
|||
if (string.IsNullOrEmpty(message)) |
|||
{ |
|||
message = string.Format("Expected {0} but was {1}", expected, actual); |
|||
} |
|||
|
|||
Assert.AreEqual(expected.X, actual.X, tolerance, message); |
|||
Assert.AreEqual(expected.Y, actual.Y, tolerance, message); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,117 @@ |
|||
using System; |
|||
using System.Diagnostics; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Xml; |
|||
using System.Xml.Linq; |
|||
using System.Xml.Serialization; |
|||
using NUnit.Framework; |
|||
|
|||
namespace MathNet.Numerics.Tests.Spatial |
|||
{ |
|||
public static class AssertXml |
|||
{ |
|||
public static XmlWriterSettings Settings |
|||
{ |
|||
get |
|||
{ |
|||
var settings = new XmlWriterSettings |
|||
{ |
|||
Indent = true, |
|||
NewLineHandling = NewLineHandling.Entitize, |
|||
OmitXmlDeclaration = true, |
|||
////NamespaceHandling = NamespaceHandling.Default
|
|||
}; |
|||
return settings; |
|||
} |
|||
} |
|||
|
|||
public static void AreEqual(string first, string other) |
|||
{ |
|||
var x1 = CleanupXml(first); |
|||
var x2 = CleanupXml(other); |
|||
Assert.AreEqual(x1, x2); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Serializes using XmlSerializer & DataContractSerializer
|
|||
/// Compares the generated xml
|
|||
/// Then asserts that the deserialized is the same as input (item)
|
|||
/// </summary>
|
|||
/// <typeparam name="T"></typeparam>
|
|||
/// <param name="item"></param>
|
|||
/// <param name="expectedXml"></param>
|
|||
/// <param name="assert"></param>
|
|||
public static void XmlRoundTrips<T>(T item, string expectedXml, Action<T, T> assert) |
|||
{ |
|||
var roundtrips = new[] |
|||
{ |
|||
XmlSerializerRoundTrip(item, expectedXml) |
|||
}; |
|||
foreach (var roundtrip in roundtrips) |
|||
{ |
|||
assert(item, roundtrip); |
|||
} |
|||
} |
|||
|
|||
public static T XmlSerializerRoundTrip<T>(T item, string expected) |
|||
{ |
|||
var serializer = new XmlSerializer(item.GetType()); |
|||
string xml; |
|||
|
|||
using (var sw = new StringWriter()) |
|||
using (var writer = XmlWriter.Create(sw, Settings)) |
|||
{ |
|||
serializer.Serialize(writer, item); |
|||
xml = sw.ToString(); |
|||
Debug.WriteLine("XmlSerializer"); |
|||
Debug.Write(xml); |
|||
Debug.WriteLine(string.Empty); |
|||
AreEqual(expected, xml); |
|||
} |
|||
|
|||
using (var reader = new StringReader(xml)) |
|||
{ |
|||
return (T)serializer.Deserialize(reader); |
|||
} |
|||
} |
|||
|
|||
private static string Normalize(XElement e) |
|||
{ |
|||
using (var sw = new StringWriter()) |
|||
using (var writer = XmlWriter.Create(sw, Settings)) |
|||
{ |
|||
e.WriteTo(writer); |
|||
writer.Flush(); |
|||
return sw.ToString(); |
|||
} |
|||
} |
|||
|
|||
private static string CleanupXml(string xml) |
|||
{ |
|||
var e = XElement.Parse(xml); |
|||
var clean = RemoveAllNamespaces(e); |
|||
return Normalize(clean); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Core recursion function
|
|||
/// </summary>
|
|||
/// <param name="e"></param>
|
|||
/// <returns></returns>
|
|||
private static XElement RemoveAllNamespaces(XElement e) |
|||
{ |
|||
var ne = new XElement(e.Name.LocalName, e.HasElements ? null : e.Value); |
|||
ne.Add(e.Attributes().Where(a => !a.IsNamespaceDeclaration)); |
|||
ne.Add(e.Elements().Select(RemoveAllNamespaces)); |
|||
return ne; |
|||
} |
|||
|
|||
public class Container<T> |
|||
{ |
|||
public T Value1 { get; set; } |
|||
|
|||
public T Value2 { get; set; } |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,111 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Globalization; |
|||
using System.Linq; |
|||
using System.Linq.Expressions; |
|||
using System.Reflection; |
|||
using System.Xml; |
|||
using System.Xml.Linq; |
|||
using System.Xml.Schema; |
|||
using System.Xml.Serialization; |
|||
using NUnit.Framework; |
|||
|
|||
namespace MathNet.Numerics.Tests.Spatial |
|||
{ |
|||
|
|||
#if !NETCOREAPP1_1
|
|||
|
|||
public class AssertXmlTests |
|||
{ |
|||
[Test] |
|||
public void XmlSerializerRoundTripTest() |
|||
{ |
|||
var dummy = new XmlSerializableDummy("Meh", 14); |
|||
var roundTrip = AssertXml.XmlSerializerRoundTrip(dummy, @"<XmlSerializableDummy Age=""14""><Name>Meh</Name></XmlSerializableDummy>"); |
|||
Assert.AreEqual(dummy.Name, roundTrip.Name); |
|||
Assert.AreEqual(dummy.Age, roundTrip.Age); |
|||
} |
|||
|
|||
public class XmlSerializableDummy : IXmlSerializable |
|||
{ |
|||
private readonly string name; |
|||
|
|||
public XmlSerializableDummy(string name, int age) |
|||
{ |
|||
this.Age = age; |
|||
this.name = name; |
|||
} |
|||
|
|||
// ReSharper disable once UnusedMember.Local
|
|||
private XmlSerializableDummy() |
|||
{ |
|||
} |
|||
|
|||
public string Name => this.name; |
|||
|
|||
public int Age { get; set; } |
|||
|
|||
public XmlSchema GetSchema() => null; |
|||
|
|||
public void ReadXml(XmlReader reader) |
|||
{ |
|||
var e = (XElement)XNode.ReadFrom(reader); |
|||
this.Age = XmlConvert.ToInt32(e.Attribute("Age").Value); |
|||
var name = ReadAttributeOrElement(e, "Name"); |
|||
WriteValueToReadonlyField(this, name, () => this.name); |
|||
} |
|||
|
|||
public void WriteXml(XmlWriter writer) |
|||
{ |
|||
writer.WriteAttributeString("Age", this.Age.ToString(CultureInfo.InvariantCulture)); |
|||
writer.WriteElementString("Name", this.Name); |
|||
} |
|||
|
|||
private static string ReadAttributeOrElement(XElement e, string localName) |
|||
{ |
|||
XAttribute xattribute = e.Attributes() |
|||
.SingleOrDefault(x => x.Name.LocalName == localName); |
|||
if (xattribute != null) |
|||
{ |
|||
return xattribute.Value; |
|||
} |
|||
|
|||
XElement xelement = e.Elements() |
|||
.SingleOrDefault(x => x.Name.LocalName == localName); |
|||
if (xelement != null) |
|||
{ |
|||
return xelement.Value; |
|||
} |
|||
|
|||
throw new XmlException($"Attribute or element {localName} not found"); |
|||
} |
|||
|
|||
private static void WriteValueToReadonlyField<TClass, TProperty>( |
|||
TClass item, |
|||
TProperty value, |
|||
Expression<Func<TProperty>> fieldExpression) |
|||
{ |
|||
string name = ((MemberExpression)fieldExpression.Body).Member.Name; |
|||
GetAllFields(item.GetType()) |
|||
.Single(x => x.Name == name) |
|||
.SetValue(item, value); |
|||
} |
|||
|
|||
private static IEnumerable<FieldInfo> GetAllFields(Type t) |
|||
{ |
|||
if (t == null) |
|||
{ |
|||
return Enumerable.Empty<FieldInfo>(); |
|||
} |
|||
|
|||
BindingFlags bindingAttr = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | |
|||
BindingFlags.Public | BindingFlags.NonPublic; |
|||
return t.GetFields(bindingAttr) |
|||
.Concat(GetAllFields(t.BaseType)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
#endif
|
|||
|
|||
} |
|||
@ -0,0 +1,58 @@ |
|||
using System; |
|||
using MathNet.Numerics.Spatial.Euclidean2D; |
|||
using NUnit.Framework; |
|||
|
|||
namespace MathNet.Numerics.Tests.Spatial.Euclidean2D |
|||
{ |
|||
[TestFixture] |
|||
public class Circle2DTests |
|||
{ |
|||
[TestCase("0, 0", 2.5)] |
|||
[TestCase("2, -4", 4.7)] |
|||
public void CircleCenterRadius(string p1s, double radius) |
|||
{ |
|||
var center = Point2D.Parse(p1s); |
|||
var circle = new Circle2D(center, radius); |
|||
Assert.AreEqual(2 * radius, circle.Diameter, double.Epsilon); |
|||
Assert.AreEqual(2 * Math.PI * radius, circle.Circumference, double.Epsilon); |
|||
Assert.AreEqual(Math.PI * radius * radius, circle.Area, double.Epsilon); |
|||
} |
|||
|
|||
[TestCase("0, 0", 1)] |
|||
[TestCase("2, -4", 4.7)] |
|||
public void CircleEquality(string center, double radius) |
|||
{ |
|||
var cp = Point2D.Parse(center); |
|||
var c = new Circle2D(cp, radius); |
|||
var c2 = new Circle2D(cp, radius); |
|||
Assert.True(c == c2); |
|||
Assert.True(c.Equals(c2)); |
|||
} |
|||
|
|||
[TestCase("-7,4", "-4,5", "0,3", "-4,0", 5)] |
|||
[TestCase("1,1", "2,4", "5,3", "3,2", 2.2360679775)] |
|||
[TestCase("-1,0", "0,1", "1,0", "0,0", 1)] |
|||
public void CircleFromThreePoints(string p1s, string p2s, string p3s, string centers, double radius) |
|||
{ |
|||
var p1 = Point2D.Parse(p1s); |
|||
var p2 = Point2D.Parse(p2s); |
|||
var p3 = Point2D.Parse(p3s); |
|||
var center = Point2D.Parse(centers); |
|||
|
|||
var circle = Circle2D.FromPoints(p1, p2, p3); |
|||
|
|||
AssertGeometry.AreEqual(center, circle.Center); |
|||
Assert.AreEqual(radius, circle.Radius, 1e-6); |
|||
} |
|||
|
|||
[Test] |
|||
public void CircleFromThreePointsArgumentException() |
|||
{ |
|||
var p1 = new Point2D(0, 0); |
|||
var p2 = new Point2D(-1, 0); |
|||
var p3 = new Point2D(1, 0); |
|||
|
|||
Assert.Throws<ArgumentException>(() => { Circle2D.FromPoints(p1, p2, p3); }); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,214 @@ |
|||
using System; |
|||
using MathNet.Numerics.Spatial; |
|||
using MathNet.Numerics.Spatial.Euclidean2D; |
|||
using NUnit.Framework; |
|||
|
|||
namespace MathNet.Numerics.Tests.Spatial.Euclidean2D |
|||
{ |
|||
[TestFixture] |
|||
public class Line2DTests |
|||
{ |
|||
[Test] |
|||
public void Constructor() |
|||
{ |
|||
var p1 = new Point2D(0, 0); |
|||
var p2 = new Point2D(1, 1); |
|||
var line = new Line2D(p1, p2); |
|||
|
|||
AssertGeometry.AreEqual(p1, line.StartPoint); |
|||
AssertGeometry.AreEqual(p2, line.EndPoint); |
|||
} |
|||
|
|||
[Test] |
|||
public void ConstructorThrowsErrorOnSamePoint() |
|||
{ |
|||
var p1 = new Point2D(1, -1); |
|||
var p2 = new Point2D(1, -1); |
|||
Assert.Throws<ArgumentException>(() => new Line2D(p1, p2)); |
|||
} |
|||
|
|||
[TestCase("0,0", "1,0", 1)] |
|||
[TestCase("0,0", "0,1", 1)] |
|||
[TestCase("0,0", "-1,0", 1)] |
|||
[TestCase("0,-1", "0,1", 2)] |
|||
[TestCase("-1,-1", "2,2", 4.24264068711)] |
|||
public void LineLength(string p1s, string p2s, double expected) |
|||
{ |
|||
var p1 = Point2D.Parse(p1s); |
|||
var p2 = Point2D.Parse(p2s); |
|||
var line = new Line2D(p1, p2); |
|||
var len = line.Length; |
|||
|
|||
Assert.AreEqual(expected, len, 1e-7); |
|||
} |
|||
|
|||
[TestCase("0,0", "4,0", "1,0")] |
|||
[TestCase("3,0", "0,0", "-1,0")] |
|||
[TestCase("2.7,-2.7", "0,0", "-0.707106781,0.707106781")] |
|||
[TestCase("11,-1", "11,1", "0,1")] |
|||
public void LineDirection(string p1s, string p2s, string exs) |
|||
{ |
|||
var p1 = Point2D.Parse(p1s); |
|||
var p2 = Point2D.Parse(p2s); |
|||
var ex = Vector2D.Parse(exs); |
|||
var line = new Line2D(p1, p2); |
|||
|
|||
AssertGeometry.AreEqual(ex, line.Direction); |
|||
} |
|||
|
|||
[TestCase("0,0", "10,10", "0,0", "10,10", true)] |
|||
[TestCase("0,0", "10,10", "0,0", "10,11", false)] |
|||
public void EqualityOperator(string p1s, string p2s, string p3s, string p4s, bool expected) |
|||
{ |
|||
var l1 = Line2D.Parse(p1s, p2s); |
|||
var l2 = Line2D.Parse(p3s, p4s); |
|||
|
|||
Assert.AreEqual(expected, l1 == l2); |
|||
} |
|||
|
|||
[TestCase("0,0", "10,10", "0,0", "10,10", false)] |
|||
[TestCase("0,0", "10,10", "0,0", "10,11", true)] |
|||
public void InequalityOperator(string p1s, string p2s, string p3s, string p4s, bool expected) |
|||
{ |
|||
var l1 = new Line2D(Point2D.Parse(p1s), Point2D.Parse(p2s)); |
|||
var l2 = new Line2D(Point2D.Parse(p3s), Point2D.Parse(p4s)); |
|||
|
|||
Assert.AreEqual(expected, l1 != l2); |
|||
} |
|||
|
|||
[Test] |
|||
public void EqualityComparisonFalseAgainstNull() |
|||
{ |
|||
var line = new Line2D(default(Point2D), new Point2D(1, 1)); |
|||
Assert.IsFalse(line.Equals(null)); |
|||
} |
|||
|
|||
[Test] |
|||
public void AdditionOperator() |
|||
{ |
|||
var l1 = Line2D.Parse("0,0", "1,1"); |
|||
var ex = Line2D.Parse("-1,-1", "0,0"); |
|||
|
|||
Assert.AreEqual(ex, l1 + new Vector2D(-1, -1)); |
|||
} |
|||
|
|||
[Test] |
|||
public void SubtractionOperator() |
|||
{ |
|||
var l1 = Line2D.Parse("0,0", "1,1"); |
|||
var ex = Line2D.Parse("-1,-1", "0,0"); |
|||
|
|||
Assert.AreEqual(ex, l1 - new Vector2D(1, 1)); |
|||
} |
|||
|
|||
[TestCase("0,0", "1,-1", Description = "Check start point")] |
|||
[TestCase("1,0", "1,-1")] |
|||
[TestCase("1,-2", "1,-1")] |
|||
[TestCase("4,0", "3,-1", Description = "Check end point")] |
|||
[TestCase("3,0", "3,-1")] |
|||
[TestCase("3,-3", "3,-1")] |
|||
[TestCase("1.5,0", "1.5,-1", Description = "Check near middle")] |
|||
[TestCase("1.5,-2", "1.5,-1")] |
|||
public void LineToBetweenEndPoints(string ptest, string exs) |
|||
{ |
|||
var line = Line2D.Parse("1,-1", "3,-1"); |
|||
var point = Point2D.Parse(ptest); |
|||
var expPoint = Point2D.Parse(exs); |
|||
var expLine = new Line2D(expPoint, point); |
|||
|
|||
Assert.AreEqual(expLine, line.LineTo(point, true)); |
|||
} |
|||
|
|||
[TestCase("0,0", "0,-1", Description = "Check start point")] |
|||
[TestCase("1,0", "1,-1")] |
|||
[TestCase("1,-2", "1,-1")] |
|||
[TestCase("4,0", "4,-1", Description = "Check end pointt")] |
|||
[TestCase("3,0", "3,-1")] |
|||
[TestCase("3,-3", "3,-1")] |
|||
[TestCase("1.5,0", "1.5,-1", Description = "Check near middle")] |
|||
[TestCase("1.5,-2", "1.5,-1")] |
|||
public void LineToIgnoreEndPoints(string ptest, string exs) |
|||
{ |
|||
var line = Line2D.Parse("1,-1", "3,-1"); |
|||
var point = Point2D.Parse(ptest); |
|||
var expPoint = Point2D.Parse(exs); |
|||
var expLine = new Line2D(expPoint, point); |
|||
|
|||
Assert.AreEqual(expLine, line.LineTo(point, false)); |
|||
} |
|||
|
|||
[TestCase("0,0", "1,0", "0,0", "0,0")] |
|||
[TestCase("0,0", "1,0", "1,0", "1,0")] |
|||
[TestCase("0,0", "1,0", ".25,1", ".25,0")] |
|||
[TestCase("0,0", "1,0", "-1,0", "0,0")] |
|||
[TestCase("0,0", "1,0", "3,0", "1,0")] |
|||
public void ClosestPointToWithinSegment(string start, string end, string point, string expected) |
|||
{ |
|||
var line = Line2D.Parse(start, end); |
|||
var p = Point2D.Parse(point); |
|||
var e = Point2D.Parse(expected); |
|||
|
|||
Assert.AreEqual(e, line.ClosestPointTo(p, true)); |
|||
} |
|||
|
|||
[TestCase("0,0", "1,0", "0,0", "0,0")] |
|||
[TestCase("0,0", "1,0", "1,0", "1,0")] |
|||
[TestCase("0,0", "1,0", ".25,1", ".25,0")] |
|||
[TestCase("0,0", "1,0", "-1,1", "-1,0")] |
|||
[TestCase("0,0", "1,0", "3,0", "3,0")] |
|||
public void ClosestPointToOutsideSegment(string start, string end, string point, string expected) |
|||
{ |
|||
var line = Line2D.Parse(start, end); |
|||
var p = Point2D.Parse(point); |
|||
var e = Point2D.Parse(expected); |
|||
|
|||
Assert.AreEqual(e, line.ClosestPointTo(p, false)); |
|||
} |
|||
|
|||
[TestCase("0,0", "2,2", "1,0", "1,2", "1,1")] |
|||
[TestCase("0,0", "2,2", "0,1", "2,1", "1,1")] |
|||
[TestCase("0,0", "2,2", "-1,-5", "-1,0", "-1,-1")] |
|||
[TestCase("0,0", "2,2", "0,1", "1,2", null)] |
|||
public void IntersectWithTest(string s1, string e1, string s2, string e2, string expected) |
|||
{ |
|||
var line1 = Line2D.Parse(s1, e1); |
|||
var line2 = Line2D.Parse(s2, e2); |
|||
var e = string.IsNullOrEmpty(expected) ? (Point2D?)null : Point2D.Parse(expected); |
|||
Assert.AreEqual(e, line1.IntersectWith(line2)); |
|||
Assert.AreEqual(e, line1.IntersectWith(line2, Angle.FromRadians(0.001))); |
|||
} |
|||
|
|||
[TestCase("0,0", "0,1", "1,1", "1,2", true)] |
|||
[TestCase("0,0", "0,-1", "1,1", "1,2", true)] |
|||
[TestCase("0,0", "0.5,-1", "1,1", "1,2", false)] |
|||
[TestCase("0,0", "0.00001,-1.0000", "1,1", "1,2", false)] |
|||
public void IsParallelToWithinDoubleTol(string s1, string e1, string s2, string e2, bool expected) |
|||
{ |
|||
var line1 = Line2D.Parse(s1, e1); |
|||
var line2 = Line2D.Parse(s2, e2); |
|||
|
|||
Assert.AreEqual(expected, line1.IsParallelTo(line2)); |
|||
} |
|||
|
|||
[TestCase("0,0", "0,1", "1,1", "1,2", 0.01, true)] |
|||
[TestCase("0,0", "0,-1", "1,1", "1,2", 0.01, true)] |
|||
[TestCase("0,0", "0.5,-1", "1,1", "1,2", 0.01, false)] |
|||
[TestCase("0,0", "0.001,-1.0000", "1,1", "1,2", 0.05, false)] |
|||
[TestCase("0,0", "0.001,-1.0000", "1,1", "1,2", 0.06, true)] |
|||
public void IsParallelToWithinAngleTol(string s1, string e1, string s2, string e2, double degreesTol, bool expected) |
|||
{ |
|||
var line1 = Line2D.Parse(s1, e1); |
|||
var line2 = Line2D.Parse(s2, e2); |
|||
|
|||
Assert.AreEqual(expected, line1.IsParallelTo(line2, Angle.FromDegrees(degreesTol))); |
|||
} |
|||
|
|||
[Test] |
|||
public void ToStringCheck() |
|||
{ |
|||
var check = Line2D.Parse("0,0", "1,1").ToString(); |
|||
|
|||
Assert.AreEqual("StartPoint: (0,\u00A00), EndPoint: (1,\u00A01)", check); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,167 @@ |
|||
using System; |
|||
using MathNet.Numerics.Spatial; |
|||
using MathNet.Numerics.Spatial.Euclidean2D; |
|||
using NUnit.Framework; |
|||
|
|||
namespace MathNet.Numerics.Tests.Spatial.Euclidean2D |
|||
{ |
|||
[TestFixture] |
|||
public class LineSegment2DTests |
|||
{ |
|||
[Test] |
|||
public void Constructor() |
|||
{ |
|||
var p1 = new Point2D(0, 0); |
|||
var p2 = new Point2D(1, 1); |
|||
var line = new LineSegment2D(p1, p2); |
|||
|
|||
AssertGeometry.AreEqual(p1, line.StartPoint); |
|||
AssertGeometry.AreEqual(p2, line.EndPoint); |
|||
} |
|||
|
|||
[Test] |
|||
public void ConstructorThrowsErrorOnSamePoint() |
|||
{ |
|||
var p1 = new Point2D(1, -1); |
|||
var p2 = new Point2D(1, -1); |
|||
Assert.Throws<ArgumentException>(() => new LineSegment2D(p1, p2)); |
|||
} |
|||
|
|||
[TestCase("0,0", "1,0", 1)] |
|||
[TestCase("0,0", "0,1", 1)] |
|||
[TestCase("0,0", "-1,0", 1)] |
|||
[TestCase("0,-1", "0,1", 2)] |
|||
[TestCase("-1,-1", "2,2", 4.24264068711)] |
|||
public void LineLength(string p1s, string p2s, double expected) |
|||
{ |
|||
var p1 = Point2D.Parse(p1s); |
|||
var p2 = Point2D.Parse(p2s); |
|||
var line = new LineSegment2D(p1, p2); |
|||
var len = line.Length; |
|||
|
|||
Assert.AreEqual(expected, len, 1e-7); |
|||
} |
|||
|
|||
[TestCase("0,0", "4,0", "1,0")] |
|||
[TestCase("3,0", "0,0", "-1,0")] |
|||
[TestCase("2.7,-2.7", "0,0", "-0.707106781,0.707106781")] |
|||
[TestCase("11,-1", "11,1", "0,1")] |
|||
public void LineDirection(string p1s, string p2s, string exs) |
|||
{ |
|||
var p1 = Point2D.Parse(p1s); |
|||
var p2 = Point2D.Parse(p2s); |
|||
var ex = Vector2D.Parse(exs); |
|||
var line = new LineSegment2D(p1, p2); |
|||
|
|||
AssertGeometry.AreEqual(ex, line.Direction); |
|||
} |
|||
|
|||
[TestCase("0,0", "10,10", "0,0", "10,10", true)] |
|||
[TestCase("0,0", "10,10", "0,0", "10,11", false)] |
|||
public void EqualityOperator(string p1s, string p2s, string p3s, string p4s, bool expected) |
|||
{ |
|||
var l1 = LineSegment2D.Parse(p1s, p2s); |
|||
var l2 = LineSegment2D.Parse(p3s, p4s); |
|||
|
|||
Assert.AreEqual(expected, l1 == l2); |
|||
} |
|||
|
|||
[TestCase("0,0", "10,10", "0,0", "10,10", false)] |
|||
[TestCase("0,0", "10,10", "0,0", "10,11", true)] |
|||
public void InequalityOperator(string p1s, string p2s, string p3s, string p4s, bool expected) |
|||
{ |
|||
var l1 = new LineSegment2D(Point2D.Parse(p1s), Point2D.Parse(p2s)); |
|||
var l2 = new LineSegment2D(Point2D.Parse(p3s), Point2D.Parse(p4s)); |
|||
|
|||
Assert.AreEqual(expected, l1 != l2); |
|||
} |
|||
|
|||
[Test] |
|||
public void EqualityComparisonFalseAgainstNull() |
|||
{ |
|||
var line = new LineSegment2D(default(Point2D), new Point2D(1, 1)); |
|||
Assert.IsFalse(line.Equals(null)); |
|||
} |
|||
|
|||
[TestCase("0,0", "1,-1", Description = "Check start point")] |
|||
[TestCase("1,0", "1,-1")] |
|||
[TestCase("1,-2", "1,-1")] |
|||
[TestCase("4,0", "3,-1", Description = "Check end point")] |
|||
[TestCase("3,0", "3,-1")] |
|||
[TestCase("3,-3", "3,-1")] |
|||
[TestCase("1.5,0", "1.5,-1", Description = "Check near middle")] |
|||
[TestCase("1.5,-2", "1.5,-1")] |
|||
public void LineToBetweenEndPoints(string ptest, string exs) |
|||
{ |
|||
var line = LineSegment2D.Parse("1,-1", "3,-1"); |
|||
var point = Point2D.Parse(ptest); |
|||
var expPoint = Point2D.Parse(exs); |
|||
var expLine = new LineSegment2D(expPoint, point); |
|||
|
|||
Assert.AreEqual(expLine, line.LineTo(point)); |
|||
} |
|||
|
|||
[TestCase("1,1", "3,1", "1,1", "2,2", "4,2")] |
|||
[TestCase("1,1", "3,1", "-1,-1", "0,0", "2,0")] |
|||
public void TranslateBy(string spoint1, string spoint2, string svector, string spoint3, string spoint4) |
|||
{ |
|||
var line = LineSegment2D.Parse(spoint1, spoint2); |
|||
var expected = LineSegment2D.Parse(spoint3, spoint4); |
|||
var vector = Vector2D.Parse(svector); |
|||
Assert.AreEqual(expected.Length, line.Length); |
|||
Assert.AreEqual(expected, line.TranslateBy(vector)); |
|||
} |
|||
|
|||
[TestCase("0,0", "1,0", "0,0", "0,0")] |
|||
[TestCase("0,0", "1,0", "1,0", "1,0")] |
|||
[TestCase("0,0", "1,0", ".25,1", ".25,0")] |
|||
[TestCase("0,0", "1,0", "-1,0", "0,0")] |
|||
[TestCase("0,0", "1,0", "3,0", "1,0")] |
|||
public void ClosestPointToWithinSegment(string start, string end, string point, string expected) |
|||
{ |
|||
var line = LineSegment2D.Parse(start, end); |
|||
var p = Point2D.Parse(point); |
|||
var e = Point2D.Parse(expected); |
|||
|
|||
Assert.AreEqual(e, line.ClosestPointTo(p)); |
|||
} |
|||
|
|||
[TestCase("0,0", "2,2", "1,0", "1,2", "1,1")] |
|||
[TestCase("0,0", "2,2", "0,1", "2,1", "1,1")] |
|||
[TestCase("0,0", "2,2", "-1,-5", "-1,0", "-1,-1")] |
|||
public void IntersectWithTest(string s1, string e1, string s2, string e2, string expected) |
|||
{ |
|||
var line1 = LineSegment2D.Parse(s1, e1); |
|||
var line2 = LineSegment2D.Parse(s2, e2); |
|||
var e = string.IsNullOrEmpty(expected) ? (Point2D?)null : Point2D.Parse(expected); |
|||
bool success = line1.TryIntersect(line2, out var result, Angle.FromRadians(0.001)); |
|||
Assert.IsTrue(success); |
|||
Assert.AreEqual(e, result); |
|||
} |
|||
|
|||
[TestCase("0,0", "0,1", "1,1", "1,2", 0.0001, true)] |
|||
[TestCase("0,0", "0,-1", "1,1", "1,2", 0.0001, true)] |
|||
[TestCase("0,0", "0.5,-1", "1,1", "1,2", 0.0001, false)] |
|||
[TestCase("0,0", "0.00001,-1.0000", "1,1", "1,2", 0.0001, false)] |
|||
[TestCase("0,0", "0,1", "1,1", "1,2", 0.01, true)] |
|||
[TestCase("0,0", "0,-1", "1,1", "1,2", 0.01, true)] |
|||
[TestCase("0,0", "0.5,-1", "1,1", "1,2", 0.01, false)] |
|||
[TestCase("0,0", "0.001,-1.0000", "1,1", "1,2", 0.05, false)] |
|||
[TestCase("0,0", "0.001,-1.0000", "1,1", "1,2", 0.06, true)] |
|||
public void IsParallelToWithinAngleTol(string s1, string e1, string s2, string e2, double degreesTol, bool expected) |
|||
{ |
|||
var line1 = LineSegment2D.Parse(s1, e1); |
|||
var line2 = LineSegment2D.Parse(s2, e2); |
|||
|
|||
Assert.AreEqual(expected, line1.IsParallelTo(line2, Angle.FromDegrees(degreesTol))); |
|||
} |
|||
|
|||
[Test] |
|||
public void ToStringCheck() |
|||
{ |
|||
var check = LineSegment2D.Parse("0,0", "1,1").ToString(); |
|||
|
|||
Assert.AreEqual("StartPoint: (0,\u00A00), EndPoint: (1,\u00A01)", check); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,283 @@ |
|||
// ReSharper disable InconsistentNaming
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
using System.Xml; |
|||
using System.Xml.Serialization; |
|||
using MathNet.Numerics.LinearAlgebra.Double; |
|||
using MathNet.Numerics.Spatial; |
|||
using MathNet.Numerics.Spatial.Euclidean2D; |
|||
using NUnit.Framework; |
|||
|
|||
namespace MathNet.Numerics.Tests.Spatial.Euclidean2D |
|||
{ |
|||
[TestFixture] |
|||
public class Point2DTests |
|||
{ |
|||
[Test] |
|||
public void Ctor() |
|||
{ |
|||
var p = new Point2D(1, 2); |
|||
Assert.AreEqual(1, p.X); |
|||
Assert.AreEqual(2, p.Y); |
|||
} |
|||
|
|||
[TestCase(5, "90 °", "0, 5")] |
|||
[TestCase(3, "-90 °", "0, -3")] |
|||
[TestCase(1, "45 °", "0.71, 0.71")] |
|||
[TestCase(1, "-45 °", "0.71, -0.71")] |
|||
[TestCase(1, "0 °", "1, 0")] |
|||
[TestCase(1, "180 °", "-1, 0")] |
|||
public void FromPolar(int radius, string avs, string eps) |
|||
{ |
|||
var angle = Angle.Parse(avs); |
|||
var p = Point2D.FromPolar(radius, angle); |
|||
var ep = Point2D.Parse(eps); |
|||
AssertGeometry.AreEqual(ep, p, 1e-2); |
|||
} |
|||
|
|||
[Test] |
|||
public void FromPolarFailsWhenNegativeRadius() |
|||
{ |
|||
Assert.Throws<ArgumentOutOfRangeException>(() => Point2D.FromPolar(-1.0, Angle.FromRadians(0))); |
|||
} |
|||
|
|||
[TestCase("-1, -2", "1, 2", "0, 0")] |
|||
public void OperatorAddVector2D(string ps, string vs, string eps) |
|||
{ |
|||
var p = Point2D.Parse(ps); |
|||
var v = Vector2D.Parse(vs); |
|||
var actual = p + v; |
|||
var expected = Point2D.Parse(eps); |
|||
Assert.AreEqual(expected, actual); |
|||
} |
|||
|
|||
[TestCase("-1, -2", "1, 2", "-2, -4")] |
|||
public void OperatorSubtractVector2D(string ps, string vs, string eps) |
|||
{ |
|||
var p = Point2D.Parse(ps); |
|||
var v = Vector2D.Parse(vs); |
|||
var actual = p - v; |
|||
var expected = Point2D.Parse(eps); |
|||
Assert.AreEqual(expected, actual); |
|||
} |
|||
|
|||
[TestCase("-1, -2", "1, 2", "-2, -4")] |
|||
public void OperatorSubtractPoint2D(string p1s, string p2s, string eps) |
|||
{ |
|||
var p1 = Point2D.Parse(p1s); |
|||
var p2 = Point2D.Parse(p2s); |
|||
var actual = p1 - p2; |
|||
var expected = Vector2D.Parse(eps); |
|||
Assert.AreEqual(expected, actual); |
|||
} |
|||
|
|||
[TestCase("-1,1", -1, 1)] |
|||
[TestCase("-1,-1", -1, -1)] |
|||
[TestCase("1, 2", 1, 2)] |
|||
[TestCase("1.2; 3.4", 1.2, 3.4)] |
|||
[TestCase("1.2;3.4", 1.2, 3.4)] |
|||
[TestCase("1,2; 3,4", 1.2, 3.4)] |
|||
[TestCase("1.2, 3.4", 1.2, 3.4)] |
|||
[TestCase("1.2 3.4", 1.2, 3.4)] |
|||
[TestCase("1.2,\u00A03.4", 1.2, 3.4)] |
|||
[TestCase("1.2\u00A03.4", 1.2, 3.4)] |
|||
[TestCase("(1.2, 3.4)", 1.2, 3.4)] |
|||
[TestCase("(.1, 2.3e-4)", 0.1, 0.00023000000000000001)] |
|||
public void Parse(string text, double expectedX, double expectedY) |
|||
{ |
|||
Assert.AreEqual(true, Point2D.TryParse(text, out var p)); |
|||
Assert.AreEqual(expectedX, p.X); |
|||
Assert.AreEqual(expectedY, p.Y); |
|||
|
|||
p = Point2D.Parse(text); |
|||
Assert.AreEqual(expectedX, p.X); |
|||
Assert.AreEqual(expectedY, p.Y); |
|||
|
|||
p = Point2D.Parse(p.ToString()); |
|||
Assert.AreEqual(expectedX, p.X); |
|||
Assert.AreEqual(expectedY, p.Y); |
|||
} |
|||
|
|||
[TestCase("1.2")] |
|||
[TestCase("1; 2; 3")] |
|||
public void ParseFails(string text) |
|||
{ |
|||
Assert.AreEqual(false, Point2D.TryParse(text, out _)); |
|||
Assert.Throws<FormatException>(() => Point2D.Parse(text)); |
|||
} |
|||
|
|||
[TestCase(@"<Point2D X=""1"" Y=""2"" />")] |
|||
[TestCase(@"<Point2D><X>1</X><Y>2</Y></Point2D>")] |
|||
public void ReadFrom(string xml) |
|||
{ |
|||
var v = new Point2D(1, 2); |
|||
AssertGeometry.AreEqual(v, Point2D.ReadFrom(XmlReader.Create(new StringReader(xml)))); |
|||
} |
|||
|
|||
[Test] |
|||
public void OfVector() |
|||
{ |
|||
var p = Point2D.OfVector(DenseVector.OfArray(new[] { 1, 2.0 })); |
|||
Assert.AreEqual(1, p.X); |
|||
Assert.AreEqual(2, p.Y); |
|||
|
|||
Assert.Throws<ArgumentException>(() => Point2D.OfVector(DenseVector.OfArray(new[] { 1, 2, 3.0 }))); |
|||
} |
|||
|
|||
[Test] |
|||
public void ToDenseVector() |
|||
{ |
|||
var p = new Point2D(1, 2); |
|||
var v = p.ToVector(); |
|||
Assert.AreEqual(2, v.Count); |
|||
Assert.AreEqual(1, v[0]); |
|||
Assert.AreEqual(2, v[1]); |
|||
} |
|||
|
|||
[TestCase("0, 0", "1, 0", 1.0)] |
|||
[TestCase("2, 0", "1, 0", 1.0)] |
|||
[TestCase("2, 0", "-1, 0", 3.0)] |
|||
[TestCase("0, 2", "0, -1", 3.0)] |
|||
public void DistanceTo(string p1s, string p2s, double expected) |
|||
{ |
|||
var p1 = Point2D.Parse(p1s); |
|||
var p2 = Point2D.Parse(p2s); |
|||
Assert.AreEqual(expected, p1.DistanceTo(p2)); |
|||
Assert.AreEqual(expected, p2.DistanceTo(p1)); |
|||
} |
|||
|
|||
[TestCase("0, 0", "1, 2", "0.5, 1")] |
|||
[TestCase("-1, -2", "1, 2", "0, 0")] |
|||
public void MidPoint(string p1s, string p2s, string eps) |
|||
{ |
|||
var p1 = Point2D.Parse(p1s); |
|||
var p2 = Point2D.Parse(p2s); |
|||
var centroids = new[] |
|||
{ |
|||
Point2D.Centroid(p1, p2), |
|||
Point2D.MidPoint(p1, p2), |
|||
}; |
|||
var expected = Point2D.Parse(eps); |
|||
foreach (var centroid in centroids) |
|||
{ |
|||
AssertGeometry.AreEqual(expected, centroid); |
|||
} |
|||
} |
|||
|
|||
[TestCase("1, 0", "90 °", "0, 1")] |
|||
[TestCase("1, 0", "-90 °", "0, -1")] |
|||
[TestCase("1, 0", "45 °", "0.71, 0.71")] |
|||
[TestCase("1, 0", "-45 °", "0.71, -0.71")] |
|||
[TestCase("1, 0", "30 °", "0.87, 0.5")] |
|||
[TestCase("-5, 0", "30 °", "-4.33, -2.5")] |
|||
public void RotateTest(string ps, string avs, string eps) |
|||
{ |
|||
var p = Point2D.Parse(ps); |
|||
var av = Angle.Parse(avs); |
|||
var expected = Point2D.Parse(eps); |
|||
var rm = Matrix2D.Rotation(av); |
|||
var actual = p.TransformBy(rm); |
|||
AssertGeometry.AreEqual(expected, actual, 1e-2); |
|||
} |
|||
|
|||
[TestCase("0, 0", "1, 2", "1, 2")] |
|||
public void VectorTo(string p1s, string p2s, string evs) |
|||
{ |
|||
var p1 = Point2D.Parse(p1s); |
|||
var p2 = Point2D.Parse(p2s); |
|||
var actual = p1.VectorTo(p2); |
|||
var expected = Vector2D.Parse(evs); |
|||
Assert.AreEqual(expected, actual); |
|||
} |
|||
|
|||
[TestCase("1, 2", "1, 2")] |
|||
public void ToVector(string ps, string evs) |
|||
{ |
|||
var p1 = Point2D.Parse(ps); |
|||
var actual = p1.ToVector2D(); |
|||
var expected = Vector2D.Parse(evs); |
|||
Assert.AreEqual(expected, actual); |
|||
} |
|||
|
|||
[TestCase("1, 2", "1, 2", 1e-4, true)] |
|||
[TestCase("-1, 2", "-1, 2", 1e-4, true)] |
|||
[TestCase("1, 2", "3, 4", 1e-4, false)] |
|||
public void Equals(string p1s, string p2s, double tol, bool expected) |
|||
{ |
|||
var p1 = Point2D.Parse(p1s); |
|||
var p2 = Point2D.Parse(p2s); |
|||
Assert.AreEqual(expected, p1 == p2); |
|||
Assert.AreEqual(expected, p2 == p1); |
|||
Assert.AreEqual(!expected, p1 != p2); |
|||
Assert.AreEqual(!expected, p2 != p1); |
|||
|
|||
Assert.AreEqual(expected, p1.Equals(p2)); |
|||
Assert.AreEqual(expected, p1.Equals((object)p2)); |
|||
Assert.AreEqual(expected, Equals(p1, p2)); |
|||
Assert.AreEqual(expected, p1.Equals(p2, tol)); |
|||
Assert.AreNotEqual(expected, p1 != p2); |
|||
Assert.AreEqual(false, p1.Equals(null)); |
|||
} |
|||
|
|||
[TestCase("-2, 0", null, "(-2,\u00A00)")] |
|||
[TestCase("-2, 0", "N2", "(-2.00,\u00A00.00)")] |
|||
public void ToString(string vs, string format, string expected) |
|||
{ |
|||
var v = Point2D.Parse(vs); |
|||
var actual = v.ToString(format); |
|||
Assert.AreEqual(expected, actual); |
|||
Assert.AreEqual(v, Point2D.Parse(actual)); |
|||
} |
|||
|
|||
[Test] |
|||
public void XmlRoundtrip() |
|||
{ |
|||
var v = new Point2D(1, 2); |
|||
AssertXml.XmlRoundTrips(v, @"<Point2D X=""1"" Y=""2"" />", (e, a) => AssertGeometry.AreEqual(e, a)); |
|||
} |
|||
|
|||
[Test] |
|||
public void XmlContainerRoundtrip() |
|||
{ |
|||
var container = new AssertXml.Container<Point2D> |
|||
{ |
|||
Value1 = new Point2D(1, 2), |
|||
Value2 = new Point2D(3, 4) |
|||
}; |
|||
var expected = "<ContainerOfPoint2D>\r\n" + |
|||
" <Value1 X=\"1\" Y=\"2\"></Value1>\r\n" + |
|||
" <Value2 X=\"3\" Y=\"4\"></Value2>\r\n" + |
|||
"</ContainerOfPoint2D>"; |
|||
var roundTrip = AssertXml.XmlSerializerRoundTrip(container, expected); |
|||
AssertGeometry.AreEqual(container.Value1, roundTrip.Value1); |
|||
AssertGeometry.AreEqual(container.Value2, roundTrip.Value2); |
|||
} |
|||
|
|||
[Test] |
|||
public void XmlElements() |
|||
{ |
|||
var v = new Point2D(1, 2); |
|||
var serializer = new XmlSerializer(typeof(Point2D)); |
|||
AssertGeometry.AreEqual(v, (Point2D)serializer.Deserialize(new StringReader(@"<Point2D><X>1</X><Y>2</Y></Point2D>"))); |
|||
} |
|||
|
|||
[Test] |
|||
public void XmlContainerElements() |
|||
{ |
|||
var container = new AssertXml.Container<Point2D> |
|||
{ |
|||
Value1 = new Point2D(1, 2), |
|||
Value2 = new Point2D(3, 4) |
|||
}; |
|||
var xml = "<ContainerOfPoint2D>\r\n" + |
|||
" <Value1><X>1</X><Y>2</Y></Value1>\r\n" + |
|||
" <Value2><X>3</X><Y>4</Y></Value2>\r\n" + |
|||
"</ContainerOfPoint2D>"; |
|||
var serializer = new XmlSerializer(typeof(AssertXml.Container<Point2D>)); |
|||
var deserialized = (AssertXml.Container<Point2D>)serializer.Deserialize(new StringReader(xml)); |
|||
AssertGeometry.AreEqual(container.Value1, deserialized.Value1); |
|||
AssertGeometry.AreEqual(container.Value2, deserialized.Value2); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,72 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using MathNet.Numerics.Spatial.Euclidean2D; |
|||
using NUnit.Framework; |
|||
|
|||
namespace MathNet.Numerics.Tests.Spatial.Euclidean2D |
|||
{ |
|||
[TestFixture] |
|||
public class PolyLine2DTests |
|||
{ |
|||
[TestCase("0,0;1,1;2,2;3,3", 1, "1,1")] |
|||
[TestCase("0,0;1,1;2,2;3,3", 0, "0,0")] |
|||
[TestCase("0,0;1,1;2,2;3,3", 3, "3,3")] |
|||
public void IndexAccessorTest(string points, int index, string expected) |
|||
{ |
|||
var testElement = new PolyLine2D(from x in points.Split(';') select Point2D.Parse(x)); |
|||
var checkElement = Point2D.Parse(expected); |
|||
AssertGeometry.AreEqual(checkElement, testElement.Vertices.Skip(index).First()); |
|||
} |
|||
|
|||
[TestCase("0,0;0,1", 1.0)] |
|||
[TestCase("0,0;0,1;1,1", 2.0)] |
|||
[TestCase("0,-1.5;0,1;1,1", 3.5)] |
|||
public void GetPolyLineLengthTests(string points, double expected) |
|||
{ |
|||
var testElement = new PolyLine2D(from x in points.Split(';') select Point2D.Parse(x)); |
|||
|
|||
Assert.AreEqual(expected, testElement.Length); |
|||
} |
|||
|
|||
[TestCase("0,-1.5;0,1;1,1", 1.0, "1,1")] |
|||
[TestCase("0,-1.5;0,1;1,1", 0.0, "0,-1.5")] |
|||
[TestCase("0,0;0,1;1,1", 0.25, "0,0.5")] |
|||
[TestCase("0,0;0,1;1,1", 0.5, "0,1")] |
|||
[TestCase("0,0;0,1;1,1", 0.75, "0.5,1")] |
|||
public void GetPointAtFractionAlongCurve(string points, double fraction, string expected) |
|||
{ |
|||
// Note that this method also tests GetPointAtLengthFromStart(...)
|
|||
var testElement = new PolyLine2D(from x in points.Split(';') select Point2D.Parse(x)); |
|||
var checkElement = Point2D.Parse(expected); |
|||
|
|||
Assert.AreEqual(checkElement, testElement.GetPointAtFractionAlongCurve(fraction)); |
|||
} |
|||
|
|||
[TestCase("0,-1.5;0,1;1,1", 2.0, "1,1")] |
|||
[TestCase("0,-1.5;0,1;1,1", -5, "0,-1.5")] |
|||
public void GetPointAtFractionAlongCurveThrowsArgumentException(string points, double fraction, string expected) |
|||
{ |
|||
var testElement = new PolyLine2D(from x in points.Split(';') select Point2D.Parse(x)); |
|||
Assert.Throws<ArgumentException>(() => { testElement.GetPointAtFractionAlongCurve(fraction); }); |
|||
} |
|||
|
|||
[TestCase("0,0;0,1;1,1", "0,-1", "0,0")] // Off Endpoint
|
|||
[TestCase("0,0;0,1;1,1", "2,1", "1,1")] // Off Endpoint
|
|||
[TestCase("0,0;0,1;1,1", "-1,2", "0,1")] // Off Corner
|
|||
[TestCase("0,0;0,1;1,1", "0,0", "0,0")] // On Endpoint
|
|||
[TestCase("0,0;0,1;1,1", "1,1", "1,1")] // On Endpoint
|
|||
[TestCase("0,0;0,1;1,1", "0,1", "0,1")] // On Corner
|
|||
[TestCase("0,0;0,1;1,1", "0,0.5", "0,0.5")] // On Curve
|
|||
[TestCase("0,0;0,1;1,1", "-1,0.5", "0,0.5")] // Off curve
|
|||
[TestCase("0,0;0,1;1,1", "0.5,1", "0.5,1")] // On Curve
|
|||
[TestCase("0,0;0,1;1,1", "0.5,1.5", "0.5,1")] // Off curve
|
|||
public void ClosestPointToTest(string points, string testPoint, string expectedPoint) |
|||
{ |
|||
var testCurve = new PolyLine2D(from x in points.Split(';') select Point2D.Parse(x)); |
|||
var test = Point2D.Parse(testPoint); |
|||
var expected = Point2D.Parse(expectedPoint); |
|||
|
|||
Assert.AreEqual(expected, testCurve.ClosestPointTo(test)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,186 @@ |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using MathNet.Numerics.Spatial; |
|||
using MathNet.Numerics.Spatial.Euclidean2D; |
|||
using NUnit.Framework; |
|||
|
|||
namespace MathNet.Numerics.Tests.Spatial.Euclidean2D |
|||
{ |
|||
[TestFixture] |
|||
public class Polygon2DTests |
|||
{ |
|||
[Test] |
|||
public void ConstructorTest() |
|||
{ |
|||
var polygon = TestPolygon1(); |
|||
var checkList = new List<Point2D> { new Point2D(0, 0), new Point2D(0.25, 0.5), new Point2D(1, 1), new Point2D(-1, 1), new Point2D(0.5, -0.5) }; |
|||
CollectionAssert.AreEqual(checkList, polygon.Vertices); |
|||
} |
|||
|
|||
[TestCase("0,0;2,2;3,1;2,0", "1,1", "1,1;3,3;4,2;3,1")] |
|||
[TestCase("0,0;2,2;3,1;2,0", "-1,-1", "-1,-1;1,1;2,0;1,-1")] |
|||
public void TranslatePolygon(string points, string vectorString, string expectedPolygon) |
|||
{ |
|||
var testElement = new Polygon2D(from x in points.Split(';') select Point2D.Parse(x)); |
|||
var expected = new Polygon2D(from x in expectedPolygon.Split(';') select Point2D.Parse(x)); |
|||
Vector2D vector = Vector2D.Parse(vectorString); |
|||
var result = testElement.TranslateBy(vector); |
|||
Assert.AreEqual(expected, result); |
|||
} |
|||
|
|||
[TestCase("0,0;1,2;-1,2", System.Math.PI, "0,0;-1,-2;1,-2")] |
|||
public void RotatePolygon(string points, double angle, string expectedPolygon) |
|||
{ |
|||
var testElement = new Polygon2D(from x in points.Split(';') select Point2D.Parse(x)); |
|||
var expected = new Polygon2D(from x in expectedPolygon.Split(';') select Point2D.Parse(x)); |
|||
Angle a = Angle.FromRadians(angle); |
|||
var result = testElement.Rotate(a); |
|||
Assert.IsTrue(expected.Equals(result, 0.001)); |
|||
} |
|||
|
|||
[TestCase("0,0;1,2;-1,2", "0,0:1,2;1,2:-1,2;-1,2:0,0")] |
|||
public void PolygonEdges(string stringpoints, string lines) |
|||
{ |
|||
List<Point2D> points = (from x in stringpoints.Split(';') select Point2D.Parse(x)).ToList(); |
|||
var lineset = lines.Split(';').Select(t => LineSegment2D.Parse(t.Split(':').First(), t.Split(':').Last())).ToList(); |
|||
|
|||
var poly = new Polygon2D(points); |
|||
CollectionAssert.AreEquivalent(lineset, poly.Edges); |
|||
} |
|||
|
|||
[Test] |
|||
public void ConstructorTest_ClipsStartOnDuplicate() |
|||
{ |
|||
// Test to make sure that if the constructor point list is given to the polygon constructor with the first and last points
|
|||
// being duplicates, the point at the beginning of the list is removed
|
|||
var polygon = TestPolygon2(); |
|||
var checkList = new List<Point2D> { new Point2D(0.25, 0.5), new Point2D(1, 1), new Point2D(-1, 1), new Point2D(0.5, -0.5), new Point2D(0, 0) }; |
|||
CollectionAssert.AreEqual(checkList, polygon.Vertices); |
|||
} |
|||
|
|||
[TestCase(0.5, 0, true)] |
|||
[TestCase(0.35, 0, true)] |
|||
[TestCase(0.5, 0.5, true)] |
|||
[TestCase(0.75, 0.1, false)] |
|||
[TestCase(0.75, -0.1, true)] |
|||
[TestCase(0.5, -0.5, false)] |
|||
[TestCase(0.25, 0.5, false)] |
|||
[TestCase(0.25, -0.5, false)] |
|||
[TestCase(0.0, 0, false)] |
|||
[TestCase(1.5, 0, false)] |
|||
public void IsPointInPolygonTest1(double x, double y, bool outcome) |
|||
{ |
|||
var testPoint = new Point2D(x, y); |
|||
var testPoly = TestPolygon3(); |
|||
|
|||
Assert.AreEqual(outcome, testPoly.EnclosesPoint(testPoint)); |
|||
} |
|||
|
|||
[TestCase(0.5, 0, true)] |
|||
[TestCase(0.35, 0, true)] |
|||
[TestCase(0.5, 0.5, true)] |
|||
[TestCase(0.75, 0.1, false)] |
|||
[TestCase(0.75, -0.1, true)] |
|||
[TestCase(0.5, -0.5, false)] |
|||
[TestCase(0.25, 0.5, false)] |
|||
[TestCase(0.25, -0.5, false)] |
|||
[TestCase(0.0, 0, false)] |
|||
[TestCase(1.5, 0, false)] |
|||
public void IsPointInPolygonTest2(double x, double y, bool outcome) |
|||
{ |
|||
var testPoint = new Point2D(x, y); |
|||
var testPoly = TestPolygon4(); |
|||
|
|||
Assert.AreEqual(outcome, testPoly.EnclosesPoint(testPoint)); |
|||
} |
|||
|
|||
// These test cases were generated using scipy.spatial's ConvexHull method
|
|||
[TestCase("0.27,0.41;0.87,0.67;0.7,0.33;0.5,0.61;0.04,0.23;0.73,0.14;0.84,0.02;0.25,0.23;0.12,0.2;0.37,0.78", "0.87,0.67;0.37,0.78;0.04,0.23;0.12,0.2;0.84,0.02")] |
|||
[TestCase("0.81,0.25;0.77,0.15;0.17,0.48;0.4,0.58;0.29,0.92;0.37,0.26;0.7,0.91;0.04,0.1;0.39,0.73;0.7,0.12", "0.7,0.91;0.29,0.92;0.04,0.1;0.7,0.12;0.77,0.15;0.81,0.25")] |
|||
[TestCase("0.87,0.39;0.83,0.42;0.75,0.62;0.91,0.49;0.18,0.63;0.17,0.95;0.22,0.5;0.93,0.41;0.66,0.79;0.32,0.42", "0.66,0.79;0.17,0.95;0.18,0.63;0.22,0.5;0.32,0.42;0.87,0.39;0.93,0.41;0.91,0.49")] |
|||
[TestCase("0.18,0.39;0.91,0.3;0.35,0.53;0.91,0.38;0.49,0.28;0.61,0.22;0.27,0.18;0.44,0.06;0.5,0.79;0.78,0.22", "0.91,0.38;0.5,0.79;0.18,0.39;0.27,0.18;0.44,0.06;0.78,0.22;0.91,0.3")] |
|||
[TestCase("0.89,0.55;0.98,0.24;0.03,0.2;0.51,0.99;0.72,0.32;0.56,0.87;0.1,0.75;0.64,0.16;0.82,0.73;0.17,0.46", "0.89,0.55;0.82,0.73;0.51,0.99;0.1,0.75;0.03,0.2;0.64,0.16;0.98,0.24")] |
|||
[TestCase("-201.573,100.940;197.083,21.031;161.021,-29.414;114.223,-23.998;230.290,-68.246;-32.272,182.239;-173.345,72.736;-175.435,-176.273;90.810,-97.350;-196.942,216.594;67.759,-162.464;67.454,-174.844;-89.116,171.982;-18.421,11.935;73.816,-180.169;-103.560,-36.297;-233.800,194.296;-64.463,166.811;-17.182,83.403;-72.010,219.944", "-72.01,219.944;-196.942,216.594;-233.8,194.296;-175.435,-176.273;73.816,-180.169;230.29,-68.246;197.083,21.031")] |
|||
public void ConvexHullTest(string points, string expected) |
|||
{ |
|||
var testPoints = (from x in points.Split(';') select Point2D.Parse(x)).ToList(); |
|||
var expectedPoints = (from x in expected.Split(';') select Point2D.Parse(x)).ToList(); |
|||
|
|||
var hullClockwise = Polygon2D.GetConvexHullFromPoints(testPoints, true); |
|||
|
|||
var clockwiseVertices = hullClockwise.Vertices; |
|||
CollectionAssert.AreEqual(expectedPoints, clockwiseVertices); |
|||
/* |
|||
for (var i = 0; i < hullClockwise.VertexCount; i++) |
|||
{ |
|||
Assert.That(ClockwiseVerticies[i], Is.EqualTo(expectedPoints[i])); |
|||
} |
|||
*/ |
|||
|
|||
var hullCounterClockwise = Polygon2D.GetConvexHullFromPoints(testPoints, false); |
|||
var counterClockwiseVertices = hullCounterClockwise.Vertices; |
|||
expectedPoints.Reverse(); |
|||
CollectionAssert.AreEqual(expectedPoints, counterClockwiseVertices); |
|||
/* |
|||
for (var i = 0; i < hullCounterClockwise.VertexCount; i++) |
|||
{ |
|||
Assert.That(counterClockwiseVerticies[i], Is.EqualTo(expectedPoints[hullCounterClockwise.VertexCount - 1 - i])); |
|||
} |
|||
*/ |
|||
|
|||
var pointsNotOnConvexHull = testPoints.Except(hullCounterClockwise.Vertices); |
|||
foreach (var pointNotOnConvexHull in pointsNotOnConvexHull) |
|||
{ |
|||
var pointIsInsideConvexHull = hullCounterClockwise.EnclosesPoint(pointNotOnConvexHull); |
|||
Assert.That(pointIsInsideConvexHull); |
|||
} |
|||
|
|||
// second check: if we remove any point from the convex hull and build a new convex hull
|
|||
// then that point should be outside the new convex hull; if it's inside then our new
|
|||
// convex hull is the actual convex hull, which means the original one wasn't!
|
|||
foreach (var pointToRemove in counterClockwiseVertices) |
|||
{ |
|||
var convexHullWithPointRemoved = new Polygon2D(hullCounterClockwise.Vertices.Except(new[] { pointToRemove })); |
|||
var pointIsInsideConvexHull = |
|||
convexHullWithPointRemoved.EnclosesPoint(pointToRemove); |
|||
Assert.That(pointIsInsideConvexHull, Is.Not.True); |
|||
} |
|||
} |
|||
|
|||
[TestCase("0,0;0.4,0;0.5,0;0.6,0;1,0;1,.25;1,.75;1,1;0,1;0,0.5", "1,0;1,1;0,1;0,0")] |
|||
public void ReduceComplexity(string points, string reduced) |
|||
{ |
|||
var testPoints = from x in points.Split(';') select Point2D.Parse(x); |
|||
var expectedPoints = from x in reduced.Split(';') select Point2D.Parse(x); |
|||
var poly = new Polygon2D(testPoints); |
|||
var expected = new Polygon2D(expectedPoints); |
|||
var thinned = poly.ReduceComplexity(0.00001); |
|||
|
|||
CollectionAssert.AreEqual(expected.Vertices, thinned.Vertices); |
|||
} |
|||
|
|||
private static Polygon2D TestPolygon1() |
|||
{ |
|||
var points = from x in new[] { "0,0", "0.25,0.5", "1,1", "-1,1", "0.5,-0.5" } select Point2D.Parse(x); |
|||
return new Polygon2D(points); |
|||
} |
|||
|
|||
private static Polygon2D TestPolygon2() |
|||
{ |
|||
var points = from x in new[] { "0,0", "0.25,0.5", "1,1", "-1,1", "0.5,-0.5", "0,0" } select Point2D.Parse(x); |
|||
return new Polygon2D(points); |
|||
} |
|||
|
|||
private static Polygon2D TestPolygon3() |
|||
{ |
|||
var points = from x in new[] { "0.25,0", "0.5,1", "1,-1" } select Point2D.Parse(x); |
|||
return new Polygon2D(points); |
|||
} |
|||
|
|||
private static Polygon2D TestPolygon4() |
|||
{ |
|||
var points = from x in new[] { "0.5,1", "1,-1", "0.25,0" } select Point2D.Parse(x); |
|||
return new Polygon2D(points); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,469 @@ |
|||
// ReSharper disable InconsistentNaming
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
using System.Xml; |
|||
using System.Xml.Serialization; |
|||
using MathNet.Numerics.LinearAlgebra; |
|||
using MathNet.Numerics.Spatial; |
|||
using MathNet.Numerics.Spatial.Euclidean2D; |
|||
using NUnit.Framework; |
|||
|
|||
namespace MathNet.Numerics.Tests.Spatial.Euclidean2D |
|||
{ |
|||
[TestFixture] |
|||
public class Vector2DTests |
|||
{ |
|||
[Test] |
|||
public void Ctor() |
|||
{ |
|||
var v = new Vector2D(1, 2); |
|||
Assert.AreEqual(1, v.X); |
|||
Assert.AreEqual(2, v.Y); |
|||
} |
|||
|
|||
[TestCase("-1, -2", "1, 2", "0, 0")] |
|||
public void OperatorAdd(string v1s, string v2s, string evs) |
|||
{ |
|||
var v1 = Vector2D.Parse(v1s); |
|||
var v2 = Vector2D.Parse(v2s); |
|||
var expected = Vector2D.Parse(evs); |
|||
Assert.AreEqual(expected, v1 + v2); |
|||
Assert.AreEqual(expected, v2 + v1); |
|||
} |
|||
|
|||
[TestCase("-1, -2", "1, 2", "-2, -4")] |
|||
public void OperatorSubtract(string v1s, string v2s, string evs) |
|||
{ |
|||
var v1 = Vector2D.Parse(v1s); |
|||
var v2 = Vector2D.Parse(v2s); |
|||
var expected = Vector2D.Parse(evs); |
|||
Assert.AreEqual(expected, v1 - v2); |
|||
} |
|||
|
|||
[TestCase("-1, -2", "1, 2")] |
|||
public void OperatorNegate(string vs, string evs) |
|||
{ |
|||
var v = Vector2D.Parse(vs); |
|||
var expected = Vector2D.Parse(evs); |
|||
Assert.AreEqual(expected, -v); |
|||
} |
|||
|
|||
[TestCase("-1, -2", 2, "-2, -4")] |
|||
public void OperatorMultiply(string vs, double d, string evs) |
|||
{ |
|||
var v = Vector2D.Parse(vs); |
|||
var expected = Vector2D.Parse(evs); |
|||
Assert.AreEqual(expected, v * d); |
|||
Assert.AreEqual(expected, d * v); |
|||
} |
|||
|
|||
[TestCase("-1, -2", 2, "-0.5, -1")] |
|||
public void OperatorDivide(string vs, double d, string evs) |
|||
{ |
|||
var v = Vector2D.Parse(vs); |
|||
var actual = v / d; |
|||
var expected = Vector2D.Parse(evs); |
|||
Assert.AreEqual(expected, actual); |
|||
} |
|||
|
|||
[TestCase(5, "90 °", "0, 5")] |
|||
[TestCase(3, "-90 °", "0, -3")] |
|||
[TestCase(1, "45 °", "0.71, 0.71")] |
|||
[TestCase(1, "-45 °", "0.71, -0.71")] |
|||
[TestCase(1, "0 °", "1, 0")] |
|||
[TestCase(1, "180 °", "-1, 0")] |
|||
public void FromPolar(int radius, string avs, string eps) |
|||
{ |
|||
var angle = Angle.Parse(avs); |
|||
var v = Vector2D.FromPolar(radius, angle); |
|||
var ep = Vector2D.Parse(eps); |
|||
AssertGeometry.AreEqual(ep, v, 1e-2); |
|||
} |
|||
|
|||
[Test] |
|||
public void FromPolarFailsWhenNegativeRadius() |
|||
{ |
|||
Assert.Throws<ArgumentOutOfRangeException>(() => Vector2D.FromPolar(-1.0, Angle.FromRadians(0))); |
|||
} |
|||
|
|||
[Test] |
|||
public void OfVector() |
|||
{ |
|||
var v = Vector2D.OfVector(Vector<double>.Build.Dense(new[] { 1.0, 2 })); |
|||
Assert.AreEqual(1, v.X); |
|||
Assert.AreEqual(2, v.Y); |
|||
|
|||
Assert.Throws<ArgumentException>(() => Vector2D.OfVector(Vector<double>.Build.Dense(new[] { 1.0 }))); |
|||
Assert.Throws<ArgumentException>(() => Vector2D.OfVector(Vector<double>.Build.Dense(new[] { 1.0, 2, 3 }))); |
|||
} |
|||
|
|||
[TestCase("-1,1", -1, 1)] |
|||
[TestCase("1, 2", 1, 2)] |
|||
[TestCase("1.2; 3.4", 1.2, 3.4)] |
|||
[TestCase("1.2;3.4", 1.2, 3.4)] |
|||
[TestCase("1.2 ; 3.4", 1.2, 3.4)] |
|||
[TestCase("1,2; 3,4", 1.2, 3.4)] |
|||
[TestCase("1.2, 3.4", 1.2, 3.4)] |
|||
[TestCase("1.2 3.4", 1.2, 3.4)] |
|||
[TestCase("1.2,\u00A03.4", 1.2, 3.4)] |
|||
[TestCase("1.2\u00A03.4", 1.2, 3.4)] |
|||
[TestCase("(1.2, 3.4)", 1.2, 3.4)] |
|||
[TestCase("1,2\u00A03,4", 1.2, 3.4)] |
|||
[TestCase("(.1, 2.3e-4)", 0.1, 0.00023000000000000001)] |
|||
public void Parse(string text, double expectedX, double expectedY) |
|||
{ |
|||
Assert.AreEqual(true, Vector2D.TryParse(text, out var p)); |
|||
Assert.AreEqual(expectedX, p.X); |
|||
Assert.AreEqual(expectedY, p.Y); |
|||
|
|||
p = Vector2D.Parse(text); |
|||
Assert.AreEqual(expectedX, p.X); |
|||
Assert.AreEqual(expectedY, p.Y); |
|||
|
|||
p = Vector2D.Parse(p.ToString()); |
|||
Assert.AreEqual(expectedX, p.X); |
|||
Assert.AreEqual(expectedY, p.Y); |
|||
} |
|||
|
|||
[TestCase("1.2")] |
|||
[TestCase("1; 2; 3")] |
|||
public void ParseFails(string text) |
|||
{ |
|||
Assert.AreEqual(false, Vector2D.TryParse(text, out _)); |
|||
Assert.Throws<FormatException>(() => Vector2D.Parse(text)); |
|||
} |
|||
|
|||
[TestCase(@"<Vector2D X=""1"" Y=""2"" />")] |
|||
[TestCase(@"<Vector2D Y=""2"" X=""1""/>")] |
|||
[TestCase(@"<Vector2D><X>1</X><Y>2</Y></Vector2D>")] |
|||
[TestCase(@"<Vector2D><Y>2</Y><X>1</X></Vector2D>")] |
|||
public void ReadFrom(string xml) |
|||
{ |
|||
var v = new Vector2D(1, 2); |
|||
AssertGeometry.AreEqual(v, Vector2D.ReadFrom(XmlReader.Create(new StringReader(xml)))); |
|||
} |
|||
|
|||
[TestCase("-1, -2", "1, 2", "0, 0")] |
|||
public void Add(string v1s, string v2s, string evs) |
|||
{ |
|||
var v1 = Vector2D.Parse(v1s); |
|||
var v2 = Vector2D.Parse(v2s); |
|||
var expected = Vector2D.Parse(evs); |
|||
Assert.AreEqual(expected, v1.Add(v2)); |
|||
Assert.AreEqual(expected, v2.Add(v1)); |
|||
} |
|||
|
|||
[TestCase("-1, -2", "1, 2", "-2, -4")] |
|||
public void Subtract(string v1s, string v2s, string evs) |
|||
{ |
|||
var v1 = Vector2D.Parse(v1s); |
|||
var v2 = Vector2D.Parse(v2s); |
|||
var expected = Vector2D.Parse(evs); |
|||
Assert.AreEqual(expected, v1.Subtract(v2)); |
|||
} |
|||
|
|||
[TestCase("-1, -2", "1, 2")] |
|||
public void Negate(string vs, string evs) |
|||
{ |
|||
var v = Vector2D.Parse(vs); |
|||
var expected = Vector2D.Parse(evs); |
|||
Assert.AreEqual(expected, v.Negate()); |
|||
} |
|||
|
|||
[TestCase("-1, -2", 2, "-2, -4")] |
|||
public void ScaleBy(string vs, double d, string evs) |
|||
{ |
|||
var v = Vector2D.Parse(vs); |
|||
Assert.AreEqual(Vector2D.Parse(evs), v.ScaleBy(d)); |
|||
} |
|||
|
|||
[TestCase("2, 0", 2)] |
|||
[TestCase("-2, 0", 2)] |
|||
[TestCase("0, 2", 2)] |
|||
public void Length(string vs, double expected) |
|||
{ |
|||
var v = Vector2D.Parse(vs); |
|||
Assert.AreEqual(expected, v.Length, 1e-6); |
|||
} |
|||
|
|||
[Test] |
|||
public void ToDenseVector() |
|||
{ |
|||
var v = new Vector2D(1, 2); |
|||
var actual = v.ToVector(); |
|||
Assert.AreEqual(2, actual.Count); |
|||
Assert.AreEqual(1, actual[0]); |
|||
Assert.AreEqual(2, actual[1]); |
|||
} |
|||
|
|||
[TestCase("1, 0", "1, 0", 1e-4, false)] |
|||
[TestCase("1, 0", "0, -1", 1e-4, true)] |
|||
[TestCase("1, 0", "0, 1", 1e-4, true)] |
|||
[TestCase("0, 1", "1, 0", 1e-4, true)] |
|||
[TestCase("0, 1", "0, 1", 1e-4, false)] |
|||
public void IsPerpendicularTo(string v1s, string v2s, double tol, bool expected) |
|||
{ |
|||
var v1 = Vector2D.Parse(v1s); |
|||
var v2 = Vector2D.Parse(v2s); |
|||
Assert.AreEqual(expected, v1.IsPerpendicularTo(v2, tol)); |
|||
Assert.AreEqual(expected, v2.IsPerpendicularTo(v1, tol)); |
|||
Assert.AreEqual(expected, v1.IsPerpendicularTo(v2, Angle.FromRadians(tol))); |
|||
Assert.AreEqual(expected, v2.IsPerpendicularTo(v1, Angle.FromRadians(tol))); |
|||
} |
|||
|
|||
[TestCase("1, 0", "1, 0", 1e-10, true)] |
|||
[TestCase("1, 0", "-1, 0", 1e-10, true)] |
|||
[TestCase("1, 0", "1, 1", 1e-10, false)] |
|||
[TestCase("1, 1", "1, 1", 1e-10, true)] |
|||
[TestCase("1, -1", "-1, 1", 1e-10, true)] |
|||
[TestCase("1, 0.5", "-1, -0.5", 1e-10, true)] |
|||
[TestCase("1, 0.5", "1, 0.5001", 1e-10, false)] |
|||
[TestCase("1, 0.5", "1, 0.5001", 1e-8, true)] // Demonstration of the effect of tolerance
|
|||
public void IsParallelToByDoubleTolerance(string v1s, string v2s, double tol, bool expected) |
|||
{ |
|||
var v1 = Vector2D.Parse(v1s); |
|||
var v2 = Vector2D.Parse(v2s); |
|||
Assert.AreEqual(expected, v1.IsParallelTo(v2, tol)); |
|||
Assert.AreEqual(expected, v2.IsParallelTo(v1, tol)); |
|||
} |
|||
|
|||
[TestCase("1, 0", "1, 0", 1e-4, true)] |
|||
[TestCase("1, 0", "-1, 0", 1e-4, true)] |
|||
[TestCase("1, 0", "1, 1", 1e-4, false)] |
|||
[TestCase("1, 1", "1, 1", 1e-4, true)] |
|||
[TestCase("1, -1", "-1, 1", 1e-4, true)] |
|||
[TestCase("1, 0", "1, 0.001", 0.06, true)] |
|||
[TestCase("1, 0", "1, -0.001", 0.06, true)] |
|||
[TestCase("-1, 0", "1, 0.001", 0.06, true)] |
|||
[TestCase("-1, 0", "1, -0.001", 0.06, true)] |
|||
[TestCase("1, 0", "1, 0.001", 0.05, false)] |
|||
[TestCase("1, 0", "1, -0.001", 0.05, false)] |
|||
[TestCase("-1, 0", "1, 0.001", 0.05, false)] |
|||
[TestCase("-1, 0", "1, -0.001", 0.05, false)] |
|||
[TestCase("1, 0.5", "-1, -0.5", 1e-4, true)] |
|||
public void IsParallelToByAngleTolerance(string v1s, string v2s, double degreesTolerance, bool expected) |
|||
{ |
|||
var v1 = Vector2D.Parse(v1s); |
|||
var v2 = Vector2D.Parse(v2s); |
|||
Assert.AreEqual(expected, v1.IsParallelTo(v2, Angle.FromDegrees(degreesTolerance))); |
|||
Assert.AreEqual(expected, v2.IsParallelTo(v1, Angle.FromDegrees(degreesTolerance))); |
|||
} |
|||
|
|||
[TestCase("1, 0", "90°", "0, 1")] |
|||
[TestCase("1, 0", "-270°", "0, 1")] |
|||
[TestCase("1, 0", "-90°", "0, -1")] |
|||
[TestCase("1, 0", "270°", "0, -1")] |
|||
[TestCase("1, 0", "180°", "-1, 0")] |
|||
[TestCase("1, 0", "0°", "1, 0")] |
|||
[TestCase("0, 1", "-90°", "1, 0")] |
|||
public void Rotate(string vs, string @as, string evs) |
|||
{ |
|||
var v = Vector2D.Parse(vs); |
|||
var angle = Angle.Parse(@as); |
|||
var expected = Vector2D.Parse(evs); |
|||
AssertGeometry.AreEqual(expected, v.Rotate(angle), 0.01); |
|||
} |
|||
|
|||
[TestCase("1, 2", "3, 4", 11)] |
|||
public void DotProduct(string vs, string evs, double expected) |
|||
{ |
|||
var v1 = Vector2D.Parse(vs); |
|||
var v2 = Vector2D.Parse(evs); |
|||
Assert.AreEqual(expected, v1.DotProduct(v2)); |
|||
} |
|||
|
|||
[TestCase("2, 3", "0.55470019, 0.83205029")] |
|||
public void Normalize(string vs, string evs) |
|||
{ |
|||
var v1 = Vector2D.Parse(vs); |
|||
var expected = Vector2D.Parse(evs); |
|||
AssertGeometry.AreEqual(expected, v1.Normalize()); |
|||
} |
|||
|
|||
[TestCase("1,0", "0,1", "270°", "-90°")] |
|||
[TestCase("0,1", "1,0", "90°", "90°")] |
|||
[TestCase("-0.99985, 0.01745", "-1, 0", "359°", "-1°")] |
|||
[TestCase("-0.99985, -0.01745", "-1, 0", "1°", "1°")] |
|||
[TestCase("0.99985, 0.01745", "1, 0", "1°", "1°")] |
|||
[TestCase("0.99985, -0.01745", "1, 0", "359°", "-1°")] |
|||
public void SignedAngleTo(string v1s, string v2s, string expectedClockWise, string expectedNegative) |
|||
{ |
|||
var v1 = Vector2D.Parse(v1s); |
|||
var v2 = Vector2D.Parse(v2s); |
|||
var cw = v1.SignedAngleTo(v2, true); |
|||
var expected = Angle.Parse(expectedClockWise); |
|||
Assert.AreEqual(expected.Degrees, cw.Degrees, 1e-3); |
|||
var cwNeg = v1.SignedAngleTo(v2, true, true); |
|||
Assert.AreEqual(Angle.Parse(expectedNegative).Degrees, cwNeg.Degrees, 1e-3); |
|||
|
|||
var ccw = v1.SignedAngleTo(v2, false); |
|||
Assert.AreEqual(360 - expected.Degrees, ccw.Degrees, 1e-3); |
|||
} |
|||
|
|||
[TestCase("1, 0", "0, 1", false, 90)] |
|||
[TestCase("1, 0", "0, 1", true, 270)] |
|||
[TestCase("1, 0", "0, -1", true, 90)] |
|||
[TestCase("1, 0", "0, -1", false, 270)] |
|||
[TestCase("1, 0", "-1, 0", true, 180)] |
|||
[TestCase("1, 0", "-1, 0", false, 180)] |
|||
[TestCase("1, 0", "1, 0", false, 0)] |
|||
[TestCase("0, 1", "1, 0", true, 90)] |
|||
public void SignedAngleTo(string v1s, string v2s, bool clockWise, float expected) |
|||
{ |
|||
var v1 = Vector2D.Parse(v1s); |
|||
var v2 = Vector2D.Parse(v2s); |
|||
var av = v1.SignedAngleTo(v2, clockWise); |
|||
Assert.AreEqual(expected, av.Degrees, 0.1); |
|||
} |
|||
|
|||
[Test] |
|||
public void CheckCachedXAxis() |
|||
{ |
|||
AssertGeometry.AreEqual(new Vector2D(1, 0), Vector2D.XAxis); |
|||
} |
|||
|
|||
[Test] |
|||
public void CheckCachedYAxis() |
|||
{ |
|||
AssertGeometry.AreEqual(new Vector2D(0, 1), Vector2D.YAxis); |
|||
} |
|||
|
|||
[TestCase("1,0", "0,1", "90°")] |
|||
[TestCase("2,0", "0,3", "90°")] |
|||
[TestCase("1,0", "0,-1", "90°")] |
|||
[TestCase("0,1", "1,0", "90°")] |
|||
[TestCase("-0.99985, 0.01745", "-1, 0", "1°")] |
|||
[TestCase("-0.99985, -0.01745", "-1, 0", "1°")] |
|||
[TestCase("0.99985, 0.01745", "1, 0", "1°")] |
|||
[TestCase("0.99985, -0.01745", "1, 0", "1°")] |
|||
[TestCase("-0.99985, -0.01745", "1, 0", "179°")] |
|||
[TestCase("-0.99985, 0.01745", "1, 0", "179°")] |
|||
public void AngleTo(string v1s, string v2s, string expectedAngle) |
|||
{ |
|||
var v1 = Vector2D.Parse(v1s); |
|||
var v2 = Vector2D.Parse(v2s); |
|||
|
|||
var angle = v1.AngleTo(v2); |
|||
var expected = Angle.Parse(expectedAngle); |
|||
Assert.AreEqual(expected.Degrees, angle.Degrees, 1e-3); |
|||
|
|||
angle = v2.AngleTo(v1); |
|||
Assert.AreEqual(expected.Degrees, angle.Degrees, 1e-3); |
|||
} |
|||
|
|||
[TestCase("1,0", "0,1", 1)] |
|||
[TestCase("-1,0", "0,1", -1)] |
|||
[TestCase("0.5003,-0.7066", "0.0739,0.7981", 0.452)] |
|||
[TestCase("0.7097,0.6059", "0.0142,-0.7630", -0.550)] |
|||
[TestCase("-0.6864,0.7036", "-0.8541,-0.1124", 0.678)] |
|||
[TestCase("-0.2738,0.6783", "0.1695,0.9110", -0.364)] |
|||
public void CrossProducts(string v1s, string v2s, double expected) |
|||
{ |
|||
// Generated test data from http://calculator.tutorvista.com/math/8/cross-product-calculator.html
|
|||
var v1 = Vector2D.Parse(v1s); |
|||
var v2 = Vector2D.Parse(v2s); |
|||
|
|||
var cross = v1.CrossProduct(v2); |
|||
|
|||
Assert.AreEqual(expected, cross, 1e-3); |
|||
} |
|||
|
|||
[TestCase("1,0", "2,0", "1,0")] |
|||
[TestCase("1,1", "2,0", "1,0")] |
|||
[TestCase("1,0", "-2,0", "1,0")] |
|||
[TestCase("-1,1", "2,0", "-1,0")] |
|||
[TestCase("-0.0563,-0.2904", "-0.3671,-0.7945", "-0.120,-0.261")] |
|||
[TestCase("0.4610,0.9067", "-0.7948,-0.7748", "0.690,0.672")] |
|||
[TestCase("0.3916,-0.9644", "-0.0873,0.0978", "0.653,-0.731")] |
|||
public void VectorProjection(string v1s, string v2s, string exs) |
|||
{ |
|||
var v1 = Vector2D.Parse(v1s); |
|||
var v2 = Vector2D.Parse(v2s); |
|||
var ex = Vector2D.Parse(exs); |
|||
|
|||
AssertGeometry.AreEqual(ex, v1.ProjectOn(v2), 1e-3); |
|||
} |
|||
|
|||
[TestCase("1, 0", "1, 0", 1e-4, true)] |
|||
[TestCase("-1, 1", "-1, 1", 1e-4, true)] |
|||
[TestCase("1, 0", "1, 1", 1e-4, false)] |
|||
public void Equals(string v1s, string v2s, double tol, bool expected) |
|||
{ |
|||
var v1 = Vector2D.Parse(v1s); |
|||
var v2 = Vector2D.Parse(v2s); |
|||
Assert.AreEqual(expected, v1 == v2); |
|||
Assert.AreEqual(expected, v2 == v1); |
|||
Assert.AreNotEqual(expected, v1 != v2); |
|||
Assert.AreNotEqual(expected, v2 != v1); |
|||
Assert.AreEqual(expected, v1.Equals(v2)); |
|||
Assert.AreEqual(expected, v1.Equals((object)v2)); |
|||
Assert.AreEqual(expected, Equals(v1, v2)); |
|||
Assert.AreEqual(expected, v1.Equals(v2, tol)); |
|||
Assert.IsFalse(default(Vector2D).Equals(null)); |
|||
} |
|||
|
|||
[Test] |
|||
public void EqualsFailsWhenNegativeTolerance() |
|||
{ |
|||
var v1 = new Vector2D(0, 0); |
|||
var v2 = new Vector2D(1, 1); |
|||
Assert.Throws<ArgumentException>(() => v1.Equals(v2, -0.01)); |
|||
} |
|||
|
|||
[TestCase("-2, 0", null, "(-2,\u00A00)")] |
|||
[TestCase("-2, 0", "N2", "(-2.00,\u00A00.00)")] |
|||
public void ToString(string vs, string format, string expected) |
|||
{ |
|||
var v = Vector2D.Parse(vs); |
|||
var actual = v.ToString(format); |
|||
Assert.AreEqual(expected, actual); |
|||
Assert.AreEqual(v, Vector2D.Parse(actual)); |
|||
} |
|||
|
|||
[Test] |
|||
public void XmlRoundtrip() |
|||
{ |
|||
var v = new Vector2D(1, 2); |
|||
AssertXml.XmlRoundTrips(v, @"<Vector2D X=""1"" Y=""2"" />", (e, a) => AssertGeometry.AreEqual(e, a)); |
|||
} |
|||
|
|||
[Test] |
|||
public void XmlContainerRoundtrip() |
|||
{ |
|||
var container = new AssertXml.Container<Vector2D> |
|||
{ |
|||
Value1 = new Vector2D(1, 2), |
|||
Value2 = new Vector2D(3, 4) |
|||
}; |
|||
var expected = "<ContainerOfVector2D>\r\n" + |
|||
" <Value1 X=\"1\" Y=\"2\"></Value1>\r\n" + |
|||
" <Value2 X=\"3\" Y=\"4\"></Value2>\r\n" + |
|||
"</ContainerOfVector2D>"; |
|||
var roundTrip = AssertXml.XmlSerializerRoundTrip(container, expected); |
|||
AssertGeometry.AreEqual(container.Value1, roundTrip.Value1); |
|||
AssertGeometry.AreEqual(container.Value2, roundTrip.Value2); |
|||
} |
|||
|
|||
[Test] |
|||
public void XmlElements() |
|||
{ |
|||
var v = new Vector2D(1, 2); |
|||
var serializer = new XmlSerializer(typeof(Vector2D)); |
|||
AssertGeometry.AreEqual(v, (Vector2D)serializer.Deserialize(new StringReader(@"<Vector2D><X>1</X><Y>2</Y></Vector2D>"))); |
|||
} |
|||
|
|||
[Test] |
|||
public void XmlContainerElements() |
|||
{ |
|||
var xml = "<ContainerOfVector2D>\r\n" + |
|||
" <Value1><X>1</X><Y>2</Y></Value1>\r\n" + |
|||
" <Value2><X>3</X><Y>4</Y></Value2>\r\n" + |
|||
"</ContainerOfVector2D>"; |
|||
var serializer = new XmlSerializer(typeof(AssertXml.Container<Vector2D>)); |
|||
var deserialized = (AssertXml.Container<Vector2D>)serializer.Deserialize(new StringReader(xml)); |
|||
AssertGeometry.AreEqual(new Vector2D(1, 2), deserialized.Value1); |
|||
AssertGeometry.AreEqual(new Vector2D(3, 4), deserialized.Value2); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,69 @@ |
|||
// ReSharper disable InconsistentNaming
|
|||
|
|||
using System; |
|||
using MathNet.Numerics.Spatial.Euclidean3D; |
|||
using NUnit.Framework; |
|||
|
|||
namespace MathNet.Numerics.Tests.Spatial.Euclidean3D |
|||
{ |
|||
[TestFixture] |
|||
public class Circle3DTests |
|||
{ |
|||
[TestCase("0, 0, 0", 2.5)] |
|||
[TestCase("2, -4, 0", 4.7)] |
|||
public void CircleCenterRadius(string p1s, double radius) |
|||
{ |
|||
var center = Point3D.Parse(p1s); |
|||
var circle = new Circle3D(center, UnitVector3D.ZAxis, radius); |
|||
Assert.AreEqual(2 * radius, circle.Diameter, double.Epsilon); |
|||
Assert.AreEqual(2 * Math.PI * radius, circle.Circumference, double.Epsilon); |
|||
Assert.AreEqual(Math.PI * radius * radius, circle.Area, double.Epsilon); |
|||
} |
|||
|
|||
[TestCase("0,0,0", "5,0,0", "2.5,0,0", 2.5)] |
|||
[TestCase("23.56,15.241,0", "62.15,-12.984,0", "42.8550,1.1285,0", 23.90522289)] |
|||
public void Circle2Points(string p1s, string p2s, string centers, double radius) |
|||
{ |
|||
var p1 = Point3D.Parse(p1s); |
|||
var p2 = Point3D.Parse(p2s); |
|||
var circle3D = Circle3D.FromPointsAndAxis(p1, p2, UnitVector3D.ZAxis); |
|||
AssertGeometry.AreEqual(circle3D.CenterPoint, Point3D.Parse(centers)); |
|||
Assert.AreEqual(circle3D.Radius, radius, 1e-6); |
|||
} |
|||
|
|||
// Test cases randomly generated from GOM Inspect Professional v8
|
|||
[TestCase("-1,0,0", "0,1,0", "1,0,0", "0,0,0", 1)] |
|||
[TestCase("3.5184432,0.3072766,1.5733767", "2.6311562,3.8537550,4.1648900", "1.7918408,3.3085796,0.1773063", "2.7223188,2.4699561,2.2155024", 2.3923465)] |
|||
[TestCase("3.9940300,4.7087055,3.0356712", "1.7448842,3.5524049,2.9346235", "3.3923391,0.6820635,2.8792665", "3.6015409,2.7091562,2.9554706", 2.0392835)] |
|||
[TestCase("1.1572695,1.7427232,3.8991802", "3.8499789,2.6866347,1.1215183", "4.4666442,3.1640346,2.6081508", "2.6534515,2.3079965,2.6873057", 2.0066725)] |
|||
[TestCase("2.8983684,2.6350014,3.6266714", "3.9347082,1.2248094,3.9586336", "1.3540560,4.4935097,4.4614937", "2.5949041,2.2684548,7.7958547", 4.1962526)] |
|||
[TestCase("3.2522525,3.4579030,2.5739476", "2.5922429,2.5575788,4.4536494", "0.9654999,3.9766348,1.5029361", "1.4925488,3.2060788,3.1067942", 1.8557743)] |
|||
[TestCase("1.4907420,4.4495954,1.2951978", "2.3796522,3.6326081,0.4917802", "3.6332795,0.3779172,3.4856655", "2.3105721,2.5275616,2.8479118", 2.6033163)] |
|||
[TestCase("4.0537333,0.7137006,1.8633552", "0.7591453,2.7025442,4.5177595", "2.4149884,2.4450949,0.7413665", "2.3306759,1.8283553,3.0064357", 2.3490455)] |
|||
[TestCase("2.3488295,3.4356098,2.3024682", "2.0004679,2.0740716,4.8011107", "4.1253165,3.8784045,1.5045145", "4.3383477,2.6362129,3.7888115", 2.6089145)] |
|||
[TestCase("2.7730017,4.7353519,1.6764519", "4.4640538,3.8917330,1.7939499", "2.9657457,0.7421344,0.7106418", "2.8705245,2.7387444,1.1937714", 2.0564369)] |
|||
[TestCase("0.3309697,1.4510555,4.3876068", "3.3591227,3.2904666,1.6724443", "0.6636883,0.8606021,3.9895073", "1.7839226,2.6021881,3.1186384", 2.2464326)] |
|||
public void CircleFromThreePoints(string p1s, string p2s, string p3s, string centers, double radius) |
|||
{ |
|||
var p1 = Point3D.Parse(p1s); |
|||
var p2 = Point3D.Parse(p2s); |
|||
var p3 = Point3D.Parse(p3s); |
|||
var center = Point3D.Parse(centers); |
|||
|
|||
var circle = Circle3D.FromPoints(p1, p2, p3); |
|||
|
|||
AssertGeometry.AreEqual(center, circle.CenterPoint); |
|||
Assert.AreEqual(radius, circle.Radius, 1e-6); |
|||
} |
|||
|
|||
[Test] |
|||
public void CircleFromThreePointsArgumentException() |
|||
{ |
|||
var p1 = new Point3D(0, 0, 0); |
|||
var p2 = new Point3D(-1, 0, 0); |
|||
var p3 = new Point3D(1, 0, 0); |
|||
|
|||
Assert.Throws<InvalidOperationException>(() => Circle3D.FromPoints(p1, p2, p3)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,311 @@ |
|||
using System; |
|||
using MathNet.Numerics.Spatial; |
|||
using MathNet.Numerics.Spatial.Euclidean3D; |
|||
using NUnit.Framework; |
|||
|
|||
namespace MathNet.Numerics.Tests.Spatial.Euclidean3D |
|||
{ |
|||
// ReSharper disable InconsistentNaming
|
|||
[TestFixture] |
|||
public class CoordinateSystem3DTests |
|||
{ |
|||
private const string X = "1; 0 ; 0"; |
|||
private const string Y = "0; 1; 0"; |
|||
private const string Z = "0; 0; 1"; |
|||
private const string NegativeX = "-1; 0; 0"; |
|||
private const string NegativeY = "0; -1; 0"; |
|||
private const string NegativeZ = "0; 0; -1"; |
|||
|
|||
[TestCase("1, 2, 3", "4, 5, 6", "7, 8, 9", "-1, -2, -3")] |
|||
public void ConstructorTest(string ps, string xs, string ys, string zs) |
|||
{ |
|||
var origin = Point3D.Parse(ps); |
|||
var xAxis = Vector3D.Parse(xs); |
|||
var yAxis = Vector3D.Parse(ys); |
|||
var zAxis = Vector3D.Parse(zs); |
|||
var css = new[] |
|||
{ |
|||
new CoordinateSystem3D(origin, xAxis, yAxis, zAxis), |
|||
new CoordinateSystem3D(xAxis, yAxis, zAxis, origin) |
|||
}; |
|||
foreach (var cs in css) |
|||
{ |
|||
AssertGeometry.AreEqual(origin, cs.Origin); |
|||
AssertGeometry.AreEqual(xAxis, cs.XAxis); |
|||
AssertGeometry.AreEqual(yAxis, cs.YAxis); |
|||
AssertGeometry.AreEqual(zAxis, cs.ZAxis); |
|||
} |
|||
} |
|||
|
|||
[TestCase("o:{1, 2e-6, -3} x:{1, 2, 3} y:{3, 3, 3} z:{4, 4, 4}", "1, 2e-6, -3", "1, 2, 3", "3, 3, 3", "4, 4, 4")] |
|||
public void ParseTests(string s, string ops, string xs, string ys, string zs) |
|||
{ |
|||
var cs = CoordinateSystem3D.Parse(s); |
|||
AssertGeometry.AreEqual(Point3D.Parse(ops), cs.Origin); |
|||
AssertGeometry.AreEqual(Vector3D.Parse(xs), cs.XAxis); |
|||
AssertGeometry.AreEqual(Vector3D.Parse(ys), cs.YAxis); |
|||
AssertGeometry.AreEqual(Vector3D.Parse(zs), cs.ZAxis); |
|||
} |
|||
|
|||
[TestCase("1, 2, 3", "90°", "0, 0, 1", "-2, 1, 3")] |
|||
[TestCase("1, 2, 3", "90°", "0, 0, -1", "2, -1, 3")] |
|||
[TestCase("1, 2, 3", "-90°", "0, 0, 1", "2, -1, 3")] |
|||
[TestCase("1, 2, 3", "180°", "0, 0, 1", "-1, -2, 3")] |
|||
[TestCase("1, 2, 3", "270°", "0, 0, 1", "2, -1, 3")] |
|||
[TestCase("1, 2, 3", "90°", "1, 0, 0", "1, -3, 2")] |
|||
[TestCase("1, 2, 3", "-90°", "1, 0, 0", "1, 3, -2")] |
|||
[TestCase("1, 2, 3", "90°", "-1, 0, 0", "1, 3, -2")] |
|||
[TestCase("1, 2, 3", "90°", "0, 1, 0", "3, 2, -1")] |
|||
[TestCase("1, 2, 3", "-90°", "0, 1, 0", "-3, 2, 1")] |
|||
public void RotationAroundVector(string ps, string @as, string vs, string eps) |
|||
{ |
|||
var p = Point3D.Parse(ps); |
|||
var angle = Angle.Parse(@as); |
|||
var coordinateSystems = new[] |
|||
{ |
|||
CoordinateSystem3D.Rotation(angle, UnitVector3D.Parse(vs)), |
|||
CoordinateSystem3D.Rotation(angle, Vector3D.Parse(vs)), |
|||
}; |
|||
var expected = Point3D.Parse(eps); |
|||
foreach (var coordinateSystem in coordinateSystems) |
|||
{ |
|||
var rotatedPoint = coordinateSystem.Transform(p); |
|||
AssertGeometry.AreEqual(expected, rotatedPoint); |
|||
} |
|||
} |
|||
|
|||
[TestCase("0°", "0°", "0°", "1, 2, 3", "1, 2, 3")] |
|||
[TestCase("90°", "0°", "0°", "1, 2, 3", "-2, 1, 3")] |
|||
[TestCase("-90°", "0°", "0°", "1, 2, 3", "2, -1, 3")] |
|||
[TestCase("0°", "90°", "0°", "1, 2, 3", "3, 2, -1")] |
|||
[TestCase("0°", "-90°", "0°", "1, 2, 3", "-3, 2, 1")] |
|||
[TestCase("0°", "0°", "90°", "1, 2, 3", "1, -3, 2")] |
|||
[TestCase("0°", "0°", "-90°", "1, 2, 3", "1, 3, -2")] |
|||
public void RotationYawPitchRoll(string yaws, string pitchs, string rolls, string ps, string eps) |
|||
{ |
|||
var p = Point3D.Parse(ps); |
|||
var yaw = Angle.Parse(yaws); |
|||
var pitch = Angle.Parse(pitchs); |
|||
var roll = Angle.Parse(rolls); |
|||
var coordinateSystems = new[] |
|||
{ |
|||
CoordinateSystem3D.Rotation(yaw, pitch, roll), |
|||
}; |
|||
var expected = Point3D.Parse(eps); |
|||
foreach (var coordinateSystem in coordinateSystems) |
|||
{ |
|||
var rotatedPoint = coordinateSystem.Transform(p); |
|||
AssertGeometry.AreEqual(expected, rotatedPoint); |
|||
} |
|||
} |
|||
|
|||
[TestCase("1, 2, 3", "0, 0, 1", "1, 2, 4")] |
|||
[TestCase("1, 2, 3", "0, 0, -1", "1, 2, 2")] |
|||
[TestCase("1, 2, 3", "0, 0, 0", "1, 2, 3")] |
|||
[TestCase("1, 2, 3", "0, 1, 0", "1, 3, 3")] |
|||
[TestCase("1, 2, 3", "0, -1, 0", "1, 1, 3")] |
|||
[TestCase("1, 2, 3", "1, 0, 0", "2, 2, 3")] |
|||
[TestCase("1, 2, 3", "-1, 0, 0", "0, 2, 3")] |
|||
public void Translation(string ps, string vs, string eps) |
|||
{ |
|||
var p = Point3D.Parse(ps); |
|||
var cs = CoordinateSystem3D.Translation(Vector3D.Parse(vs)); |
|||
var tp = cs.Transform(p); |
|||
Console.WriteLine(cs.ToString()); |
|||
AssertGeometry.AreEqual(Point3D.Parse(eps), tp); |
|||
} |
|||
|
|||
[TestCase(X, X, null)] |
|||
[TestCase(X, X, X)] |
|||
[TestCase(X, X, Y)] |
|||
[TestCase(X, X, Z)] |
|||
[TestCase(X, NegativeX, null)] |
|||
[TestCase(X, NegativeX, Z)] |
|||
[TestCase(X, NegativeX, Y)] |
|||
[TestCase(X, Y, null)] |
|||
[TestCase(X, Z, null)] |
|||
[TestCase(Y, Y, null)] |
|||
[TestCase(Y, Y, X)] |
|||
[TestCase(Y, NegativeY, null)] |
|||
[TestCase(Y, NegativeY, X)] |
|||
[TestCase(Y, NegativeY, Z)] |
|||
[TestCase(Z, NegativeZ, null)] |
|||
[TestCase(Z, NegativeZ, X)] |
|||
[TestCase(Z, NegativeZ, Y)] |
|||
[TestCase("1, 2, 3", "-1, 0, -1", null)] |
|||
public void RotateToTest(string v1s, string v2s, string @as) |
|||
{ |
|||
var axis = string.IsNullOrEmpty(@as) ? (UnitVector3D?)null : Vector3D.Parse(@as).Normalize(); |
|||
var v1 = Vector3D.Parse(v1s).Normalize(); |
|||
var v2 = Vector3D.Parse(v2s).Normalize(); |
|||
var actual = CoordinateSystem3D.RotateTo(v1, v2, axis); |
|||
Console.WriteLine(actual); |
|||
var rv = actual.Transform(v1); |
|||
AssertGeometry.AreEqual(v2, rv); |
|||
actual = CoordinateSystem3D.RotateTo(v2, v1, axis); |
|||
rv = actual.Transform(v2); |
|||
AssertGeometry.AreEqual(v1, rv); |
|||
} |
|||
|
|||
[Test] |
|||
public void InvertTest() |
|||
{ |
|||
Assert.Inconclusive("Test this?"); |
|||
} |
|||
|
|||
[Test] |
|||
public void EqualityNullOperator() |
|||
{ |
|||
string test = "o:{1, 2e-6, -3} x:{1, 2, 3} y:{3, 3, 3} z:{4, 4, 4}"; |
|||
var cs = CoordinateSystem3D.Parse(test); |
|||
|
|||
Assert.IsFalse(cs == null); |
|||
} |
|||
|
|||
[Test] |
|||
public void EqualityNullOperatorTrue() |
|||
{ |
|||
CoordinateSystem3D cs = null; |
|||
|
|||
Assert.IsTrue(cs == null); |
|||
} |
|||
|
|||
[Test] |
|||
public void EqualityNotNullOperator() |
|||
{ |
|||
string test = "o:{1, 2e-6, -3} x:{1, 2, 3} y:{3, 3, 3} z:{4, 4, 4}"; |
|||
var cs = CoordinateSystem3D.Parse(test); |
|||
|
|||
Assert.IsTrue(cs != null); |
|||
} |
|||
|
|||
[Test] |
|||
public void EqualityNotNullOperatorFalse() |
|||
{ |
|||
CoordinateSystem3D cs = null; |
|||
|
|||
Assert.IsFalse(cs != null); |
|||
} |
|||
|
|||
[Test] |
|||
public void EqualityNull() |
|||
{ |
|||
string test = "o:{1, 2e-6, -3} x:{1, 2, 3} y:{3, 3, 3} z:{4, 4, 4}"; |
|||
var cs = CoordinateSystem3D.Parse(test); |
|||
|
|||
Assert.IsFalse(cs.Equals(null)); |
|||
} |
|||
|
|||
[TestCase("1; -5; 3", "1; -5; 3", "o:{0, 0, 0} x:{1, 0, 0} y:{0, 1, 0} z:{0, 0, 1}")] |
|||
public void TransformPoint(string ps, string eps, string css) |
|||
{ |
|||
var p = Point3D.Parse(ps); |
|||
var cs = CoordinateSystem3D.Parse(css); |
|||
var actual = p.TransformBy(cs); |
|||
var expected = Point3D.Parse(eps); |
|||
AssertGeometry.AreEqual(expected, actual, float.Epsilon); |
|||
} |
|||
|
|||
[TestCase("1; 2; 3", "1; 2; 3", "o:{0, 0, 0} x:{1, 0, 0} y:{0, 1, 0} z:{0, 0, 1}")] |
|||
[TestCase("1; 2; 3", "1; 2; 3", "o:{3, 4, 5} x:{1, 0, 0} y:{0, 1, 0} z:{0, 0, 1}")] |
|||
public void TransformVector(string vs, string evs, string css) |
|||
{ |
|||
var v = Vector3D.Parse(vs); |
|||
var cs = CoordinateSystem3D.Parse(css); |
|||
var actual = cs.Transform(v); |
|||
var expected = Vector3D.Parse(evs); |
|||
AssertGeometry.AreEqual(expected, actual); |
|||
} |
|||
|
|||
[Test] |
|||
public void TransformUnitVector() |
|||
{ |
|||
var cs = CoordinateSystem3D.Rotation(Angle.FromDegrees(90), UnitVector3D.ZAxis); |
|||
var uv = UnitVector3D.XAxis; |
|||
var actual = cs.Transform(uv); |
|||
AssertGeometry.AreEqual(UnitVector3D.YAxis, actual); |
|||
} |
|||
|
|||
[TestCase("o:{0, 0, 0} x:{1, 0, 0} y:{0, 1, 0} z:{0, 0, 1}", "o:{0, 0, 0} x:{1, 0, 0} y:{0, 1, 0} z:{0, 0, 1}")] |
|||
[TestCase("o:{0, 0, 0} x:{10, 0, 0} y:{0, 1, 0} z:{0, 0, 1}", "o:{1, 0, 0} x:{0.1, 0, 0} y:{0, 1, 0} z:{0, 0, 1}")] |
|||
[TestCase("o:{0, 0, 0} x:{10, 0, 0} y:{0, 1, 0} z:{0, 0, 1}", "o:{1, 0, 0} x:{1, 0, 0} y:{0, 1, 0} z:{0, 0, 1}")] |
|||
[TestCase("o:{1, 2, -7} x:{10, 0, 0} y:{0, 1, 0} z:{0, 0, 1}", "o:{0, 0, 0} x:{1, 0, 0} y:{0, 1, 0} z:{0, 0, 1}")] |
|||
[TestCase("o:{1, 2, -7} x:{10, 0.1, 0} y:{0, 1.2, 0.1} z:{0.1, 0, 1}", "o:{2, 5, 1} x:{0.1, 2, 0} y:{0.2, -1, 0} z:{0, 0.4, 1}")] |
|||
public void SetToAlignCoordinateSystemsTest(string fcss, string tcss) |
|||
{ |
|||
var fcs = CoordinateSystem3D.Parse(fcss); |
|||
var tcs = CoordinateSystem3D.Parse(tcss); |
|||
|
|||
var css = new[] |
|||
{ |
|||
CoordinateSystem3D.SetToAlignCoordinateSystems(fcs.Origin, fcs.XAxis, fcs.YAxis, fcs.ZAxis, tcs.Origin, tcs.XAxis, tcs.YAxis, tcs.ZAxis), |
|||
CoordinateSystem3D.CreateMappingCoordinateSystem(fcs, tcs) |
|||
}; |
|||
foreach (var cs in css) |
|||
{ |
|||
var aligned = cs.Transform(fcs); |
|||
AssertGeometry.AreEqual(tcs.Origin, aligned.Origin); |
|||
|
|||
AssertGeometry.AreEqual(tcs.XAxis, aligned.XAxis); |
|||
|
|||
AssertGeometry.AreEqual(tcs.YAxis, aligned.YAxis); |
|||
|
|||
AssertGeometry.AreEqual(tcs.ZAxis, aligned.ZAxis); |
|||
} |
|||
} |
|||
|
|||
[TestCase(X, Y, Z)] |
|||
[TestCase(NegativeX, Y, Z)] |
|||
[TestCase(NegativeX, Y, null)] |
|||
[TestCase(X, Y, null)] |
|||
[TestCase(X, Y, "0,0,1")] |
|||
[TestCase("1,-1, 1", "0, 1, 1", null)] |
|||
[TestCase(X, Z, Y)] |
|||
public void SetToRotateToTest(string vs, string vts, string axisString) |
|||
{ |
|||
var v = UnitVector3D.Parse(vs, tolerance: 1); |
|||
var vt = UnitVector3D.Parse(vts, tolerance: 1); |
|||
UnitVector3D? axis = null; |
|||
if (axisString != null) |
|||
{ |
|||
axis = UnitVector3D.Parse(axisString); |
|||
} |
|||
|
|||
var cs = CoordinateSystem3D.RotateTo(v, vt, axis); |
|||
var rv = cs.Transform(v); |
|||
AssertGeometry.AreEqual(vt, rv); |
|||
|
|||
var invert = cs.Invert(); |
|||
var rotateBack = invert.Transform(rv); |
|||
AssertGeometry.AreEqual(v, rotateBack); |
|||
|
|||
cs = CoordinateSystem3D.RotateTo(vt, v, axis); |
|||
rotateBack = cs.Transform(rv); |
|||
AssertGeometry.AreEqual(v, rotateBack); |
|||
} |
|||
|
|||
[TestCase("o:{1, 2, -7} x:{10, 0, 0} y:{0, 1, 0} z:{0, 0, 1}", "o:{0, 0, 0} x:{1, 0, 0} y:{0, 1, 0} z:{0, 0, 1}")] |
|||
public void Transform(string cs1s, string cs2s) |
|||
{ |
|||
var cs1 = CoordinateSystem3D.Parse(cs1s); |
|||
var cs2 = CoordinateSystem3D.Parse(cs2s); |
|||
var actual = cs1.Transform(cs2); |
|||
var expected = new CoordinateSystem3D(cs1.Multiply(cs2)); |
|||
AssertGeometry.AreEqual(expected, actual); |
|||
} |
|||
|
|||
[Test] |
|||
public void XmlRoundTrips() |
|||
{ |
|||
var cs = new CoordinateSystem3D(new Point3D(1, -2, 3), new Vector3D(0, 1, 0), new Vector3D(0, 0, 1), new Vector3D(1, 0, 0)); |
|||
string expected = @"
|
|||
<CoordinateSystem3D> |
|||
<Origin X=""1"" Y=""-2"" Z=""3"" /> |
|||
<XAxis X=""0"" Y=""1"" Z=""0"" /> |
|||
<YAxis X=""0"" Y=""0"" Z=""1"" /> |
|||
<ZAxis X=""1"" Y=""0"" Z=""0"" /> |
|||
</CoordinateSystem3D>";
|
|||
AssertXml.XmlRoundTrips(cs, expected, (e, a) => AssertGeometry.AreEqual(e, a)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,198 @@ |
|||
using System; |
|||
using MathNet.Numerics.Spatial; |
|||
using MathNet.Numerics.Spatial.Euclidean3D; |
|||
using NUnit.Framework; |
|||
|
|||
namespace MathNet.Numerics.Tests.Spatial.Euclidean3D |
|||
{ |
|||
[TestFixture] |
|||
public class Line3DTests |
|||
{ |
|||
[Test] |
|||
public void Ctor() |
|||
{ |
|||
Assert.Throws<ArgumentException>(() => new Line3D(Point3D.Origin, Point3D.Origin)); |
|||
} |
|||
|
|||
[TestCase("0, 0, 0", "1, -1, 1", "1, -1, 1")] |
|||
public void DirectionsTest(string p1s, string p2s, string evs) |
|||
{ |
|||
var l = Line3D.Parse(p1s, p2s); |
|||
var excpected = UnitVector3D.Parse(evs, tolerance: 1); |
|||
AssertGeometry.AreEqual(excpected, l.Direction); |
|||
} |
|||
|
|||
[TestCase("0, 0, 0", "1, -1, 1", "0, 0, 0", "1, 0, 0", "0, 0, 0", "0, -1, 1")] |
|||
public void ProjectOn(string p1s, string p2s, string rootPoint, string unitVector, string ep1s, string ep2s) |
|||
{ |
|||
var p1 = Point3D.Parse(p1s); |
|||
var p2 = Point3D.Parse(p2s); |
|||
var line = new Line3D(p1, p2); |
|||
var plane = new Plane3D(Point3D.Parse(rootPoint), UnitVector3D.Parse(unitVector)); |
|||
var expected = new Line3D(Point3D.Parse(ep1s), Point3D.Parse(ep2s)); |
|||
AssertGeometry.AreEqual(expected, line.ProjectOn(plane)); |
|||
} |
|||
|
|||
[TestCase("0, 0, 0", "1, -2, 3", 3.741657)] |
|||
public void Length(string p1s, string p2s, double expected) |
|||
{ |
|||
var p1 = Point3D.Parse(p1s); |
|||
var p2 = Point3D.Parse(p2s); |
|||
var l = new Line3D(p1, p2); |
|||
Assert.AreEqual(expected, l.Length, 1e-6); |
|||
} |
|||
|
|||
[TestCase("0, 0, 0", "1, -1, 1", "0, 0, 0", "1, -1, 1", true)] |
|||
[TestCase("0, 0, 2", "1, -1, 1", "0, 0, 0", "1, -1, 1", false)] |
|||
[TestCase("0, 0, 0", "1, -1, 1", "0, 0, 0", "2, -1, 1", false)] |
|||
public void Equals(string p1s, string p2s, string p3s, string p4s, bool expected) |
|||
{ |
|||
var line1 = new Line3D(Point3D.Parse(p1s), Point3D.Parse(p2s)); |
|||
var line2 = new Line3D(Point3D.Parse(p3s), Point3D.Parse(p4s)); |
|||
Assert.AreEqual(expected, line1.Equals(line2)); |
|||
Assert.AreEqual(expected, line1 == line2); |
|||
Assert.AreEqual(!expected, line1 != line2); |
|||
} |
|||
|
|||
[TestCase("0, 0, 0", "1, 0, 0", "0.5, 1, 0", true, "0.5, 0, 0")] |
|||
[TestCase("0, 0, 0", "1, 0, 0", "0.5, 1, 0", false, "0.5, 0, 0")] |
|||
[TestCase("0, 0, 0", "1, 0, 0", "2, 1, 0", true, "1, 0, 0")] |
|||
[TestCase("0, 0, 0", "1, 0, 0", "2, 1, 0", false, "2, 0, 0")] |
|||
[TestCase("0, 0, 0", "1, 0, 0", "-2, 1, 0", true, "0, 0, 0")] |
|||
[TestCase("0, 0, 0", "1, 0, 0", "-2, 1, 0", false, "-2, 0, 0")] |
|||
public void LineToTest(string p1s, string p2s, string ps, bool mustStartFromLine, string sps) |
|||
{ |
|||
var p1 = Point3D.Parse(p1s); |
|||
var p2 = Point3D.Parse(p2s); |
|||
var l = new Line3D(p1, p2); |
|||
var p = Point3D.Parse(ps); |
|||
var actual = l.LineTo(p, mustStartFromLine); |
|||
AssertGeometry.AreEqual(Point3D.Parse(sps), actual.StartPoint, 1e-6); |
|||
AssertGeometry.AreEqual(p, actual.EndPoint, 1e-6); |
|||
} |
|||
|
|||
[TestCase("1, 2, 3", "4, 5, 6", @"<Line3D><StartPoint X=""1"" Y=""2"" Z=""3"" /><EndPoint X=""4"" Y=""5"" Z=""6"" /></Line3D>")] |
|||
public void XmlTests(string p1s, string p2s, string xml) |
|||
{ |
|||
var p1 = Point3D.Parse(p1s); |
|||
var p2 = Point3D.Parse(p2s); |
|||
var l = new Line3D(p1, p2); |
|||
AssertXml.XmlRoundTrips(l, xml, (e, a) => AssertGeometry.AreEqual(e, a)); |
|||
} |
|||
|
|||
[TestCase("0,0,0", "0,0,1", "0,0,0", "0,0,0", Description = "Start point")] |
|||
[TestCase("0,0,0", "0,0,1", "0,0,1", "0,0,1", Description = "End point")] |
|||
[TestCase("0,0,0", "0,0,1", "1,0,.25", "0,0,.25")] |
|||
[TestCase("0,0,0", "0,0,1", "0,0,-1", "0,0,0")] |
|||
[TestCase("0,0,0", "0,0,1", "0,0,3", "0,0,1")] |
|||
public void ClosestPointToWithinSegment(string start, string end, string point, string expected) |
|||
{ |
|||
var line = Line3D.Parse(start, end); |
|||
var p = Point3D.Parse(point); |
|||
var e = Point3D.Parse(expected); |
|||
|
|||
Assert.AreEqual(e, line.ClosestPointTo(p, true)); |
|||
} |
|||
|
|||
[TestCase("0,0,0", "0,0,1", "0,0,0", "0,0,0", Description = "Start point")] |
|||
[TestCase("0,0,0", "0,0,1", "0,0,1", "0,0,1", Description = "End point")] |
|||
[TestCase("0,0,0", "0,0,1", "1,0,.25", "0,0,.25")] |
|||
[TestCase("0,0,0", "0,0,1", "0,0,-1", "0,0,-1")] |
|||
[TestCase("0,0,0", "0,0,1", "0,0,3", "0,0,3")] |
|||
public void ClosestPointToOutsideSegment(string start, string end, string point, string expected) |
|||
{ |
|||
var line = Line3D.Parse(start, end); |
|||
var p = Point3D.Parse(point); |
|||
var e = Point3D.Parse(expected); |
|||
|
|||
Assert.AreEqual(e, line.ClosestPointTo(p, false)); |
|||
} |
|||
|
|||
[TestCase("0,0,0", "0,0,1", "0,1,1", "0,1,2", true)] |
|||
[TestCase("0,0,0", "0,0,-1", "0,1,1", "0,1,2", true)] |
|||
[TestCase("0,0,0", "0,0.5,-1", "0,1,1", "0,1,2", false)] |
|||
[TestCase("0,0,0", "0,0.00001,-1.0000", "0,1,1", "0,1,2", false)] |
|||
public void IsParallelToWithinDoubleTol(string s1, string e1, string s2, string e2, bool expected) |
|||
{ |
|||
var line1 = Line3D.Parse(s1, e1); |
|||
var line2 = Line3D.Parse(s2, e2); |
|||
|
|||
Assert.AreEqual(expected, line1.IsParallelTo(line2)); |
|||
} |
|||
|
|||
[TestCase("0,0,0", "0,0,1", "0,1,1", "0,1,2", 0.01, true)] |
|||
[TestCase("0,0,0", "0,0,-1", "0,1,1", "0,1,2", 0.01, true)] |
|||
[TestCase("0,0,0", "0,0.5,-1", "0,1,1", "0,1,2", 0.01, false)] |
|||
[TestCase("0,0,0", "0,0.001,-1.0000", "0,1,1", "0,1,2", 0.05, false)] |
|||
[TestCase("0,0,0", "0,0.001,-1.0000", "0,1,1", "0,1,2", 0.06, true)] |
|||
public void IsParallelToWithinAngleTol(string s1, string e1, string s2, string e2, double degreesTol, bool expected) |
|||
{ |
|||
var line1 = Line3D.Parse(s1, e1); |
|||
var line2 = Line3D.Parse(s2, e2); |
|||
|
|||
Assert.AreEqual(expected, line1.IsParallelTo(line2, Angle.FromDegrees(degreesTol))); |
|||
} |
|||
|
|||
[TestCase("0,0,0", "1,0,0", "1,1,1", "2,1,1", "0,0,0", "0,1,1")] // Parallel case
|
|||
[TestCase("0,0,0", "1,0,0", "2,-1,0", "2,0,0", "2,0,0", "2,0,0")] // Intersecting case
|
|||
[TestCase("0.3097538,3.0725982,3.9317042", "1.3945620,6.4927958,2.1094821", "2.4486204,5.1947760,6.3369721", "8.4010954,9.5691708,5.1665254", "-0.1891954,1.4995044,4.7698213", "-0.7471721,2.8462306,6.9653670")] // Randomly generated in GOM Inspect Professional V8
|
|||
[TestCase("6.7042836,5.1490163,0.3655590", "7.6915457,0.8511235,0.8627290", "0.6890053,6.6207933,3.4147472", "5.6116149,1.7160727,5.4461589", "7.5505158,1.4650754,0.7917085", "5.7356234,1.5925149,5.4973334")] // Randomly generated in GOM Inspect Professional V8
|
|||
[TestCase("2.9663973,7.1338954,9.7732130", "6.4625826,8.7716408,3.7015737", "1.8924447,4.9060507,1.8755412", "1.9542505,5.8440396,8.2251863", "1.8254726,6.5994432,11.7545962", "1.9889190,6.3701828,11.7868723")] // Randomly generated in GOM Inspect Professional V8
|
|||
[TestCase("7.3107741,0.0835261,3.0516366", "1.0013621,9.6730070,3.6824080", "3.2195097,0.8726049,1.0448256", "4.5620367,8.6714351,3.3311693", "3.7856774,5.4412119,3.4040514", "4.0462563,5.6752319,2.4527875")] // Randomly generated in GOM Inspect Professional V8
|
|||
[TestCase("6.8171425,2.0728553,1.9235196", "3.0360117,0.0371047,0.5410118", "0.4798755,2.8536110,0.4402877", "9.4038162,7.5597521,0.3068954", "2.9867513,0.0105830,0.5230005", "1.2671009,3.2687632,0.4285205")] // Randomly generated in GOM Inspect Professional V8
|
|||
[TestCase("0.9826196,9.8736629,3.7966845", "3.9607685,8.7036678,0.3921888", "0.2333178,4.9282166,5.6478261", "5.8182917,0.7224496,6.8152397", "-0.7871493,10.5689341,5.8198105", "-3.0149659,7.3743380,4.9688451")] // Randomly generated in GOM Inspect Professional V8
|
|||
[TestCase("3.1369768,8.4887731,0.1371429", "4.9427402,3.5584831,9.4250139", "1.0628142,7.4582519,3.7246752", "3.7045709,0.1811190,4.7211734", "3.8813087,6.4565178,3.9655839", "1.7121301,5.6696095,3.9696039")] // Randomly generated in GOM Inspect Professional V8
|
|||
[TestCase("8.7598151,0.2378638,3.0353259", "0.5266014,9.6548588,2.7893143", "1.8918146,3.4742242,7.0646539", "7.3796941,7.0882850,4.4899767", "4.5791946,5.0195789,2.9104073", "5.3106500,5.7257093,5.4606833")] // Randomly generated in GOM Inspect Professional V8
|
|||
[TestCase("7.6132801,6.8593303,3.7729401", "7.7434088,2.0184714,6.0037938", "4.1910423,0.6022826,6.7846734", "1.6554785,3.0341358,8.7351072", "7.8041730,-0.2419887,7.0455007", "5.8721457,-1.0100597,5.4915169")] // Randomly generated in GOM Inspect Professional V8
|
|||
[TestCase("0.3818483,0.4893437,8.1438438", "4.6619509,7.8881811,4.7600744", "0.7792658,4.6454081,8.3927247", "0.2002105,7.4120903,3.5542593", "2.2611504,3.7380159,6.6581026", "0.6866122,5.0880999,7.6185307")] // Randomly generated in GOM Inspect Professional V8
|
|||
public void ClosestPointsBetween(string s1, string e1, string s2, string e2, string cp1, string cp2) |
|||
{ |
|||
var l1 = Line3D.Parse(s1, e1); |
|||
var l2 = Line3D.Parse(s2, e2); |
|||
|
|||
var result = l1.ClosestPointsBetween(l2); |
|||
|
|||
AssertGeometry.AreEqual(Point3D.Parse(cp1), result.Item1); |
|||
AssertGeometry.AreEqual(Point3D.Parse(cp2), result.Item2); |
|||
} |
|||
|
|||
[TestCase("0,0,0", "1,0,0", "0.5,1,0", "1.5,1,0", "1,0,0", "1,1,0")] // Parallel case
|
|||
[TestCase("0,0,0", "1,0,0", "3,1,0", "3,2,0", "1,0,0", "3,1,0")] // Endpoint Case
|
|||
[TestCase("1,0,0", "0,0,0", "3,1,0", "3,2,0", "1,0,0", "3,1,0")] // Endpoint Case
|
|||
[TestCase("0,0,0", "1,0,0", "3,2,0", "3,1,0", "1,0,0", "3,1,0")] // Endpoint Case
|
|||
[TestCase("1,0,0", "0,0,0", "3,2,0", "3,1,0", "1,0,0", "3,1,0")] // Endpoint Case
|
|||
[TestCase("5.6925969,1.3884847,7.1713834", "5.1573193,9.7184415,0.8644498", "0.6567836,8.3850115,1.5273528", "7.1182449,9.0049546,9.1872098", "5.3056396,7.4102899,2.6120410", "3.0759605,8.6171187,4.3952101")] // projection between segments, generated in GOM Inspect Professional V8
|
|||
[TestCase("3.0803549,8.1101503,4.2072541", "0.9167489,5.4057168,0.0942629", "3.6443155,1.9841677,2.1280020", "4.0865344,8.8738039,9.2944797", "3.0803549,8.1101503,4.2072541", "3.8982350,5.9401568,6.2429519")] // projection from endpoint, generated in GOM Inspect Professional V8
|
|||
[TestCase("2.0809966,9.3100446,9.4661138", "6.0883386,5.5240161,2.7490910", "8.4523738,2.6004881,8.8473518", "5.5868380,5.4932213,1.1649868", "6.0883386,5.5240161,2.7490910", "6.0992239,4.9759722,2.5386692")] // projection from endpoint, generated in GOM Inspect Professional V8
|
|||
[TestCase("6.6149320,7.8081445,4.6267089", "5.3733678,7.5372568,0.4121304", "7.9879025,7.5486791,5.8931379", "1.4971100,6.2860737,3.2138409", "6.6149320,7.8081445,4.6267089", "6.4606595,7.2515959,5.2627161")] // projection from endpoint, generated in GOM Inspect Professional V8
|
|||
[TestCase("4.7238306,4.7424963,7.9590086", "9.2276709,8.3299427,1.0349775", "7.3828132,6.3559129,8.7078245", "4.6487651,2.8181310,8.5972384", "4.7238306,4.7424963,7.9590086", "5.5976910,4.0460147,8.6356203")] // projection from endpoint, generated in GOM Inspect Professional V8
|
|||
[TestCase("7.1035997,1.9299120,3.4688193", "8.5433252,5.8883905,9.7941707", "3.3053692,6.3729100,3.5626868", "8.4883669,8.1557493,7.2000211", "8.3560381,5.3734507,8.9713356", "8.4883669,8.1557493,7.2000211")] // projection from endpoint, generated in GOM Inspect Professional V8
|
|||
[TestCase("7.4520661,5.7569419,4.1686608", "4.2367431,3.5840889,2.7405165", "1.3188110,5.7542366,2.7702002", "7.7144529,5.0792324,0.2292819", "4.7448074,3.9274289,2.9661826", "4.3479012,5.4345425,1.5667759")] // projection between segments, generated in GOM Inspect Professional V8
|
|||
[TestCase("7.5543831,9.7934598,9.5348209", "6.5205418,0.3092162,8.7907210", "0.4877286,0.3419443,2.5644342", "8.6578287,6.1098998,5.8827401", "7.1211660,5.8192172,9.2230160", "8.4262398,5.9464019,5.7886797")] // projection between segments, generated in GOM Inspect Professional V8
|
|||
[TestCase("6.0543887,5.4347267,4.3429352", "4.2117265,4.7630853,1.3218313", "8.9537706,1.5933994,0.6307145", "2.7936886,7.2837201,1.8965656", "4.5061578,4.8704041,1.8045609", "4.8831652,5.3535848,1.4671937")] // projection between segments, generated in GOM Inspect Professional V8
|
|||
[TestCase("6.5771629,2.8557455,9.8087521", "5.4359776,8.6172816,5.7508093", "7.8904712,4.3099454,2.4107493", "8.1402295,0.9894932,3.8694855", "5.7508984,7.0273316,6.8706367", "7.8904712,4.3099454,2.4107493")] // projection from endpoint, generated in GOM Inspect Professional V8
|
|||
[TestCase("1.1294829,5.4847586,8.1420946", "7.2489863,0.3206420,0.8259188", "5.8457205,6.7040761,3.0411085", "8.9017428,0.9704561,3.8471667", "5.7071649,1.6217517,2.6692442", "7.8717570,2.9028855,3.5754970")] // projection between segments, generated in GOM Inspect Professional V8
|
|||
[TestCase("0.8765316,4.0262533,7.1988316", "4.3383388,7.0189039,1.4430865", "1.9687345,6.4066677,1.9041603", "0.6533722,5.3636394,1.3877450", "3.5259020,6.3165714,2.7938779", "1.9687345,6.4066677,1.9041603")] // projection from endpoint, generated in GOM Inspect Professional V8
|
|||
[TestCase("0.3580051,9.3271145,8.5768069", "2.3779489,4.3772771,1.6443451", "1.0661185,9.0362165,3.9415240", "7.6388414,1.7341324,0.4120660", "1.8279811,5.7249636,3.5318384", "2.9136360,6.9836839,2.9494336")] // projection between segments, generated in GOM Inspect Professional V8
|
|||
[TestCase("6.1264627,6.5812576,3.9222538", "1.5068838,5.6589456,0.0446154", "0.8368111,1.7808290,7.1053143", "8.6670395,3.8812950,1.4077528", "5.4376749,6.4437392,3.3440907", "6.1998837,3.2194782,3.2029458")] // projection between segments, generated in GOM Inspect Professional V8
|
|||
[TestCase("5.2548631,0.4534686,7.9484333", "2.8554822,5.3465480,5.8755276", "0.3950940,1.3010814,5.5699850", "6.0302455,6.0816738,9.9995164", "3.7687405,3.4841320,6.6645221", "2.9986425,3.5098071,7.6165137")] // projection between segments, generated in GOM Inspect Professional V8
|
|||
[TestCase("4.7098248,1.2821556,4.9827025", "6.4808992,9.9281018,2.2789106", "9.6036733,2.5927984,2.8488436", "7.2373861,1.4947206,2.8305463", "4.9620442,2.5134284,4.5976544", "7.2373861,1.4947206,2.8305463")] // projection from endpoint, generated in GOM Inspect Professional V8
|
|||
[TestCase("0.5036993,0.6001582,2.2439019", "9.0252221,6.6637385,5.1333177", "6.9023714,0.8286511,9.3846113", "7.8635188,7.6077266,0.1778680", "6.9835948,5.2109969,4.4410576", "7.4521485,4.7062875,4.1183470")] // projection between segments, generated in GOM Inspect Professional V8
|
|||
[TestCase("4.1414680,4.8821393,2.4051430", "2.1773492,6.4060895,2.8305709", "0.5806575,3.1182178,9.4735442", "9.3828570,0.6330684,7.6857961", "4.1414680,4.8821393,2.4051430", "4.5936441,1.9852201,8.6584969")] // projection from endpoint, generated in GOM Inspect Professional V8
|
|||
[TestCase("3.6062333,0.6118218,5.2241603", "0.7544416,1.5864715,8.4712397", "2.3437474,4.9755332,1.7418572", "9.8825574,1.3070092,8.6204338", "3.6062333,0.6118218,5.2241603", "5.5154683,3.4321153,4.6358053")] // projection from endpoint, generated in GOM Inspect Professional V8
|
|||
[TestCase("2.2607133,8.3082403,6.7904628", "5.0325175,8.7431170,3.9781037", "6.4334028,4.8699270,1.1961501", "1.7809363,2.4707254,6.2966301", "5.0325175,8.7431170,3.9781037", "5.4392405,4.3572536,2.2860462")] // projection from endpoint, generated in GOM Inspect Professional V8
|
|||
[TestCase("6.1786725,3.6854264,9.2902405", "2.6667579,9.5505050,9.5018463", "2.0599944,1.6033445,0.6954832", "3.1884883,6.4163288,9.0715930", "4.1912356,7.0045487,9.4099909", "3.1884883,6.4163288,9.0715930")] // projection from endpoint, generated in GOM Inspect Professional V8
|
|||
[TestCase("8.8292667,0.7124560,8.2423649", "0.3649094,7.1453826,3.0669636", "2.5889872,1.1761708,7.2524548", "5.4661666,6.7986776,4.9964301", "4.3314255,4.1308233,5.4922289", "4.2263025,4.3757686,5.9686197")] // projection between segments, generated in GOM Inspect Professional V8
|
|||
[TestCase("6.0241017,5.1715162,5.7250655", "5.6868388,6.0031583,1.2902594", "3.4800129,9.7922534,2.4761596", "0.0589551,3.4081038,0.9383102", "5.6945715,5.9840905,1.3919397", "2.3316866,7.6493234,1.9599588")] // projection between segments, generated in GOM Inspect Professional V8
|
|||
public void ClosestPointsBetweenOnSegment(string s1, string e1, string s2, string e2, string cp1, string cp2) |
|||
{ |
|||
var l1 = Line3D.Parse(s1, e1); |
|||
var l2 = Line3D.Parse(s2, e2); |
|||
|
|||
var result = l1.ClosestPointsBetween(l2, true); |
|||
|
|||
AssertGeometry.AreEqual(Point3D.Parse(cp1), result.Item1); |
|||
AssertGeometry.AreEqual(Point3D.Parse(cp2), result.Item2); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,143 @@ |
|||
using System; |
|||
using MathNet.Numerics.Spatial; |
|||
using MathNet.Numerics.Spatial.Euclidean3D; |
|||
using NUnit.Framework; |
|||
|
|||
namespace MathNet.Numerics.Tests.Spatial.Euclidean3D |
|||
{ |
|||
/// <summary>
|
|||
/// Tests for LineSegment3D
|
|||
/// </summary>
|
|||
[TestFixture] |
|||
public class LineSegment3DTests |
|||
{ |
|||
[Test] |
|||
public void Ctor() |
|||
{ |
|||
Assert.Throws<ArgumentException>(() => new LineSegment3D(Point3D.Origin, Point3D.Origin)); |
|||
} |
|||
|
|||
[TestCase("0, 0, 0", "1, -1, 1", "1, -1, 1")] |
|||
public void DirectionsTest(string p1s, string p2s, string evs) |
|||
{ |
|||
var l = LineSegment3D.Parse(p1s, p2s); |
|||
var excpected = UnitVector3D.Parse(evs, tolerance: 1); |
|||
AssertGeometry.AreEqual(excpected, l.Direction); |
|||
} |
|||
|
|||
[TestCase("0, 0, 0", "1, -2, 3", 3.741657)] |
|||
public void Length(string p1s, string p2s, double expected) |
|||
{ |
|||
var p1 = Point3D.Parse(p1s); |
|||
var p2 = Point3D.Parse(p2s); |
|||
var l = new LineSegment3D(p1, p2); |
|||
Assert.AreEqual(expected, l.Length, 1e-6); |
|||
} |
|||
|
|||
[TestCase("0, 0, 0", "1, -1, 1", "0, 0, 0", "1, -1, 1", true)] |
|||
[TestCase("0, 0, 2", "1, -1, 1", "0, 0, 0", "1, -1, 1", false)] |
|||
[TestCase("0, 0, 0", "1, -1, 1", "0, 0, 0", "2, -1, 1", false)] |
|||
public void Equals(string p1s, string p2s, string p3s, string p4s, bool expected) |
|||
{ |
|||
var line1 = new LineSegment3D(Point3D.Parse(p1s), Point3D.Parse(p2s)); |
|||
var line2 = new LineSegment3D(Point3D.Parse(p3s), Point3D.Parse(p4s)); |
|||
Assert.AreEqual(expected, line1.Equals(line2)); |
|||
Assert.AreEqual(expected, line1 == line2); |
|||
Assert.AreEqual(!expected, line1 != line2); |
|||
} |
|||
|
|||
[TestCase("1,1,1", "3,1,1", "1,1,0", "2,2,1", "4,2,1")] |
|||
[TestCase("1,1,1", "3,1,1", "-1,-1,0", "0,0,1", "2,0,1")] |
|||
public void TranslateBy(string spoint1, string spoint2, string svector, string spoint3, string spoint4) |
|||
{ |
|||
var line = LineSegment3D.Parse(spoint1, spoint2); |
|||
var expected = LineSegment3D.Parse(spoint3, spoint4); |
|||
var vector = Vector3D.Parse(svector); |
|||
Assert.AreEqual(expected.Length, line.Length); |
|||
Assert.AreEqual(expected, line.TranslateBy(vector)); |
|||
} |
|||
|
|||
[TestCase("0, 0, 0", "1, 0, 0", "0.5, 1, 0", "0.5, 0, 0")] |
|||
[TestCase("0, 0, 0", "1, 0, 0", "2, 1, 0", "1, 0, 0")] |
|||
[TestCase("0, 0, 0", "1, 0, 0", "-2, 1, 0", "0, 0, 0")] |
|||
public void LineToTest(string p1s, string p2s, string ps, string sps) |
|||
{ |
|||
var p1 = Point3D.Parse(p1s); |
|||
var p2 = Point3D.Parse(p2s); |
|||
var l = new LineSegment3D(p1, p2); |
|||
var p = Point3D.Parse(ps); |
|||
var actual = l.LineTo(p); |
|||
AssertGeometry.AreEqual(Point3D.Parse(sps), actual.StartPoint, 1e-6); |
|||
AssertGeometry.AreEqual(p, actual.EndPoint, 1e-6); |
|||
} |
|||
|
|||
[TestCase("0,0,0", "0,0,1", "0,0,0", "0,0,0", Description = "Start point")] |
|||
[TestCase("0,0,0", "0,0,1", "0,0,1", "0,0,1", Description = "End point")] |
|||
[TestCase("0,0,0", "0,0,1", "1,0,.25", "0,0,.25")] |
|||
[TestCase("0,0,0", "0,0,1", "0,0,-1", "0,0,0")] |
|||
[TestCase("0,0,0", "0,0,1", "0,0,3", "0,0,1")] |
|||
public void ClosestPointTo(string start, string end, string point, string expected) |
|||
{ |
|||
var line = LineSegment3D.Parse(start, end); |
|||
var p = Point3D.Parse(point); |
|||
var e = Point3D.Parse(expected); |
|||
|
|||
Assert.AreEqual(e, line.ClosestPointTo(p)); |
|||
} |
|||
|
|||
[TestCase("0,0,0", "0,0,1", "0,1,1", "0,1,2", 0.00001, true)] |
|||
[TestCase("0,0,0", "0,0,-1", "0,1,1", "0,1,2", 0.00001, true)] |
|||
[TestCase("0,0,0", "0,0.5,-1", "0,1,1", "0,1,2", 0.00001, false)] |
|||
[TestCase("0,0,0", "0,0.00001,-1.0000", "0,1,1", "0,1,2", 0.00001, false)] |
|||
[TestCase("0,0,0", "0,0,1", "0,1,1", "0,1,2", 0.01, true)] |
|||
[TestCase("0,0,0", "0,0,-1", "0,1,1", "0,1,2", 0.01, true)] |
|||
[TestCase("0,0,0", "0,0.5,-1", "0,1,1", "0,1,2", 0.01, false)] |
|||
[TestCase("0,0,0", "0,0.001,-1.0000", "0,1,1", "0,1,2", 0.05, false)] |
|||
[TestCase("0,0,0", "0,0.001,-1.0000", "0,1,1", "0,1,2", 0.06, true)] |
|||
public void IsParallelToWithinAngleTol(string s1, string e1, string s2, string e2, double degreesTol, bool expected) |
|||
{ |
|||
var line1 = LineSegment3D.Parse(s1, e1); |
|||
var line2 = LineSegment3D.Parse(s2, e2); |
|||
|
|||
Assert.AreEqual(expected, line1.IsParallelTo(line2, Angle.FromDegrees(degreesTol))); |
|||
} |
|||
|
|||
[TestCase("0,0,0", "1,0,0", "0.5,1,0", "1.5,1,0", "1,0,0", "1,1,0")] // Parallel case
|
|||
[TestCase("0,0,0", "1,0,0", "3,1,0", "3,2,0", "1,0,0", "3,1,0")] // Endpoint Case
|
|||
[TestCase("1,0,0", "0,0,0", "3,1,0", "3,2,0", "1,0,0", "3,1,0")] // Endpoint Case
|
|||
[TestCase("0,0,0", "1,0,0", "3,2,0", "3,1,0", "1,0,0", "3,1,0")] // Endpoint Case
|
|||
[TestCase("1,0,0", "0,0,0", "3,2,0", "3,1,0", "1,0,0", "3,1,0")] // Endpoint Case
|
|||
[TestCase("5.6925969,1.3884847,7.1713834", "5.1573193,9.7184415,0.8644498", "0.6567836,8.3850115,1.5273528", "7.1182449,9.0049546,9.1872098", "5.3056396,7.4102899,2.6120410", "3.0759605,8.6171187,4.3952101")] // projection between segments, generated in GOM Inspect Professional V8
|
|||
[TestCase("3.0803549,8.1101503,4.2072541", "0.9167489,5.4057168,0.0942629", "3.6443155,1.9841677,2.1280020", "4.0865344,8.8738039,9.2944797", "3.0803549,8.1101503,4.2072541", "3.8982350,5.9401568,6.2429519")] // projection from endpoint, generated in GOM Inspect Professional V8
|
|||
[TestCase("2.0809966,9.3100446,9.4661138", "6.0883386,5.5240161,2.7490910", "8.4523738,2.6004881,8.8473518", "5.5868380,5.4932213,1.1649868", "6.0883386,5.5240161,2.7490910", "6.0992239,4.9759722,2.5386692")] // projection from endpoint, generated in GOM Inspect Professional V8
|
|||
[TestCase("6.6149320,7.8081445,4.6267089", "5.3733678,7.5372568,0.4121304", "7.9879025,7.5486791,5.8931379", "1.4971100,6.2860737,3.2138409", "6.6149320,7.8081445,4.6267089", "6.4606595,7.2515959,5.2627161")] // projection from endpoint, generated in GOM Inspect Professional V8
|
|||
[TestCase("4.7238306,4.7424963,7.9590086", "9.2276709,8.3299427,1.0349775", "7.3828132,6.3559129,8.7078245", "4.6487651,2.8181310,8.5972384", "4.7238306,4.7424963,7.9590086", "5.5976910,4.0460147,8.6356203")] // projection from endpoint, generated in GOM Inspect Professional V8
|
|||
[TestCase("7.1035997,1.9299120,3.4688193", "8.5433252,5.8883905,9.7941707", "3.3053692,6.3729100,3.5626868", "8.4883669,8.1557493,7.2000211", "8.3560381,5.3734507,8.9713356", "8.4883669,8.1557493,7.2000211")] // projection from endpoint, generated in GOM Inspect Professional V8
|
|||
[TestCase("7.4520661,5.7569419,4.1686608", "4.2367431,3.5840889,2.7405165", "1.3188110,5.7542366,2.7702002", "7.7144529,5.0792324,0.2292819", "4.7448074,3.9274289,2.9661826", "4.3479012,5.4345425,1.5667759")] // projection between segments, generated in GOM Inspect Professional V8
|
|||
[TestCase("7.5543831,9.7934598,9.5348209", "6.5205418,0.3092162,8.7907210", "0.4877286,0.3419443,2.5644342", "8.6578287,6.1098998,5.8827401", "7.1211660,5.8192172,9.2230160", "8.4262398,5.9464019,5.7886797")] // projection between segments, generated in GOM Inspect Professional V8
|
|||
[TestCase("6.0543887,5.4347267,4.3429352", "4.2117265,4.7630853,1.3218313", "8.9537706,1.5933994,0.6307145", "2.7936886,7.2837201,1.8965656", "4.5061578,4.8704041,1.8045609", "4.8831652,5.3535848,1.4671937")] // projection between segments, generated in GOM Inspect Professional V8
|
|||
[TestCase("6.5771629,2.8557455,9.8087521", "5.4359776,8.6172816,5.7508093", "7.8904712,4.3099454,2.4107493", "8.1402295,0.9894932,3.8694855", "5.7508984,7.0273316,6.8706367", "7.8904712,4.3099454,2.4107493")] // projection from endpoint, generated in GOM Inspect Professional V8
|
|||
[TestCase("1.1294829,5.4847586,8.1420946", "7.2489863,0.3206420,0.8259188", "5.8457205,6.7040761,3.0411085", "8.9017428,0.9704561,3.8471667", "5.7071649,1.6217517,2.6692442", "7.8717570,2.9028855,3.5754970")] // projection between segments, generated in GOM Inspect Professional V8
|
|||
[TestCase("0.8765316,4.0262533,7.1988316", "4.3383388,7.0189039,1.4430865", "1.9687345,6.4066677,1.9041603", "0.6533722,5.3636394,1.3877450", "3.5259020,6.3165714,2.7938779", "1.9687345,6.4066677,1.9041603")] // projection from endpoint, generated in GOM Inspect Professional V8
|
|||
[TestCase("0.3580051,9.3271145,8.5768069", "2.3779489,4.3772771,1.6443451", "1.0661185,9.0362165,3.9415240", "7.6388414,1.7341324,0.4120660", "1.8279811,5.7249636,3.5318384", "2.9136360,6.9836839,2.9494336")] // projection between segments, generated in GOM Inspect Professional V8
|
|||
[TestCase("6.1264627,6.5812576,3.9222538", "1.5068838,5.6589456,0.0446154", "0.8368111,1.7808290,7.1053143", "8.6670395,3.8812950,1.4077528", "5.4376749,6.4437392,3.3440907", "6.1998837,3.2194782,3.2029458")] // projection between segments, generated in GOM Inspect Professional V8
|
|||
[TestCase("5.2548631,0.4534686,7.9484333", "2.8554822,5.3465480,5.8755276", "0.3950940,1.3010814,5.5699850", "6.0302455,6.0816738,9.9995164", "3.7687405,3.4841320,6.6645221", "2.9986425,3.5098071,7.6165137")] // projection between segments, generated in GOM Inspect Professional V8
|
|||
[TestCase("4.7098248,1.2821556,4.9827025", "6.4808992,9.9281018,2.2789106", "9.6036733,2.5927984,2.8488436", "7.2373861,1.4947206,2.8305463", "4.9620442,2.5134284,4.5976544", "7.2373861,1.4947206,2.8305463")] // projection from endpoint, generated in GOM Inspect Professional V8
|
|||
[TestCase("0.5036993,0.6001582,2.2439019", "9.0252221,6.6637385,5.1333177", "6.9023714,0.8286511,9.3846113", "7.8635188,7.6077266,0.1778680", "6.9835948,5.2109969,4.4410576", "7.4521485,4.7062875,4.1183470")] // projection between segments, generated in GOM Inspect Professional V8
|
|||
[TestCase("4.1414680,4.8821393,2.4051430", "2.1773492,6.4060895,2.8305709", "0.5806575,3.1182178,9.4735442", "9.3828570,0.6330684,7.6857961", "4.1414680,4.8821393,2.4051430", "4.5936441,1.9852201,8.6584969")] // projection from endpoint, generated in GOM Inspect Professional V8
|
|||
[TestCase("3.6062333,0.6118218,5.2241603", "0.7544416,1.5864715,8.4712397", "2.3437474,4.9755332,1.7418572", "9.8825574,1.3070092,8.6204338", "3.6062333,0.6118218,5.2241603", "5.5154683,3.4321153,4.6358053")] // projection from endpoint, generated in GOM Inspect Professional V8
|
|||
[TestCase("2.2607133,8.3082403,6.7904628", "5.0325175,8.7431170,3.9781037", "6.4334028,4.8699270,1.1961501", "1.7809363,2.4707254,6.2966301", "5.0325175,8.7431170,3.9781037", "5.4392405,4.3572536,2.2860462")] // projection from endpoint, generated in GOM Inspect Professional V8
|
|||
[TestCase("6.1786725,3.6854264,9.2902405", "2.6667579,9.5505050,9.5018463", "2.0599944,1.6033445,0.6954832", "3.1884883,6.4163288,9.0715930", "4.1912356,7.0045487,9.4099909", "3.1884883,6.4163288,9.0715930")] // projection from endpoint, generated in GOM Inspect Professional V8
|
|||
[TestCase("8.8292667,0.7124560,8.2423649", "0.3649094,7.1453826,3.0669636", "2.5889872,1.1761708,7.2524548", "5.4661666,6.7986776,4.9964301", "4.3314255,4.1308233,5.4922289", "4.2263025,4.3757686,5.9686197")] // projection between segments, generated in GOM Inspect Professional V8
|
|||
[TestCase("6.0241017,5.1715162,5.7250655", "5.6868388,6.0031583,1.2902594", "3.4800129,9.7922534,2.4761596", "0.0589551,3.4081038,0.9383102", "5.6945715,5.9840905,1.3919397", "2.3316866,7.6493234,1.9599588")] // projection between segments, generated in GOM Inspect Professional V8
|
|||
public void ClosestPointsBetweenOnSegment(string s1, string e1, string s2, string e2, string cp1, string cp2) |
|||
{ |
|||
var l1 = LineSegment3D.Parse(s1, e1); |
|||
var l2 = LineSegment3D.Parse(s2, e2); |
|||
|
|||
Assert.AreEqual(true, l1.TryShortestLineTo(l2, Angle.FromRadians(0.00001), out var result)); |
|||
AssertGeometry.AreEqual(Point3D.Parse(cp1), result.StartPoint); |
|||
AssertGeometry.AreEqual(Point3D.Parse(cp2), result.EndPoint); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,238 @@ |
|||
// ReSharper disable InconsistentNaming
|
|||
|
|||
using System; |
|||
using System.Linq; |
|||
using MathNet.Numerics.Spatial.Euclidean3D; |
|||
using NUnit.Framework; |
|||
|
|||
namespace MathNet.Numerics.Tests.Spatial.Euclidean3D |
|||
{ |
|||
[TestFixture] |
|||
public class Plane3DTests |
|||
{ |
|||
private const string X = "1; 0 ; 0"; |
|||
private const string Z = "0; 0; 1"; |
|||
private const string NegativeZ = "0; 0; -1"; |
|||
private const string ZeroPoint = "0; 0; 0"; |
|||
|
|||
[Test] |
|||
public void Ctor() |
|||
{ |
|||
var plane1 = new Plane3D(new Point3D(0, 0, 3), UnitVector3D.ZAxis); |
|||
var plane2 = new Plane3D(0, 0, 3, -3); |
|||
var plane3 = new Plane3D(UnitVector3D.ZAxis, 3); |
|||
var plane4 = Plane3D.FromPoints(new Point3D(0, 0, 3), new Point3D(5, 3, 3), new Point3D(-2, 1, 3)); |
|||
AssertGeometry.AreEqual(plane1, plane2); |
|||
AssertGeometry.AreEqual(plane1, plane3); |
|||
AssertGeometry.AreEqual(plane1, plane4); |
|||
} |
|||
|
|||
[TestCase("0, 0, 0", "1, 0, 0", "0, 0, 0", "1, 0, 0")] |
|||
public void Parse(string rootPoint, string unitVector, string pds, string vds) |
|||
{ |
|||
var plane = new Plane3D(Point3D.Parse(rootPoint), UnitVector3D.Parse(unitVector)); |
|||
AssertGeometry.AreEqual(Point3D.Parse(pds), plane.RootPoint); |
|||
AssertGeometry.AreEqual(Vector3D.Parse(vds), plane.Normal); |
|||
} |
|||
|
|||
[TestCase("1, 0, 0, 0", "0, 0, 0", "1, 0, 0")] |
|||
public void Parse2(string s, string pds, string vds) |
|||
{ |
|||
var plane = this.GetPlaneFrom4Doubles(s); |
|||
AssertGeometry.AreEqual(Point3D.Parse(pds), plane.RootPoint); |
|||
AssertGeometry.AreEqual(Vector3D.Parse(vds), plane.Normal); |
|||
} |
|||
|
|||
[TestCase(ZeroPoint, "0, 0, 0", "0, 0, 1", ZeroPoint)] |
|||
[TestCase(ZeroPoint, "0, 0, -1", "0, 0, 1", "0; 0;-1")] |
|||
[TestCase(ZeroPoint, "0, 0, 1", "0, 0, -1", "0; 0; 1")] |
|||
[TestCase("1; 2; 3", "0, 0, 0", "0, 0, 1", "1; 2; 0")] |
|||
public void ProjectPointOn(string ps, string rootPoint, string unitVector, string eps) |
|||
{ |
|||
var plane = new Plane3D(Point3D.Parse(rootPoint), UnitVector3D.Parse(unitVector)); |
|||
var projectedPoint = plane.Project(Point3D.Parse(ps)); |
|||
var expected = Point3D.Parse(eps); |
|||
AssertGeometry.AreEqual(expected, projectedPoint, float.Epsilon); |
|||
} |
|||
|
|||
[TestCase(ZeroPoint, Z, ZeroPoint, 0)] |
|||
[TestCase(ZeroPoint, Z, "1; 2; 0", 0)] |
|||
[TestCase(ZeroPoint, Z, "1; -2; 0", 0)] |
|||
[TestCase(ZeroPoint, Z, "1; 2; 3", 3)] |
|||
[TestCase(ZeroPoint, Z, "-1; 2; -3", -3)] |
|||
[TestCase(ZeroPoint, NegativeZ, ZeroPoint, 0)] |
|||
[TestCase(ZeroPoint, NegativeZ, "1; 2; 1", -1)] |
|||
[TestCase(ZeroPoint, NegativeZ, "1; 2; -1", 1)] |
|||
[TestCase("0; 0; -1", NegativeZ, ZeroPoint, -1)] |
|||
[TestCase("0; 0; 1", NegativeZ, ZeroPoint, 1)] |
|||
[TestCase(ZeroPoint, X, "1; 0; 0", 1)] |
|||
[TestCase("188,6578; 147,0620; 66,0170", Z, "118,6578; 147,0620; 126,1170", 60.1)] |
|||
public void SignedDistanceToPoint(string prps, string pns, string ps, double expected) |
|||
{ |
|||
var plane = new Plane3D(UnitVector3D.Parse(pns), Point3D.Parse(prps)); |
|||
var p = Point3D.Parse(ps); |
|||
Assert.AreEqual(expected, plane.SignedDistanceTo(p), 1E-6); |
|||
} |
|||
|
|||
[TestCase(ZeroPoint, Z, ZeroPoint, Z, 0)] |
|||
[TestCase(ZeroPoint, Z, "0;0;1", Z, 1)] |
|||
[TestCase(ZeroPoint, Z, "0;0;-1", Z, -1)] |
|||
[TestCase(ZeroPoint, NegativeZ, "0;0;-1", Z, 1)] |
|||
public void SignedDistanceToOtherPlane(string prps, string pns, string otherPlaneRootPointString, string otherPlaneNormalString, double expectedValue) |
|||
{ |
|||
var plane = new Plane3D(UnitVector3D.Parse(pns), Point3D.Parse(prps)); |
|||
var otherPlane = new Plane3D(UnitVector3D.Parse(otherPlaneNormalString), Point3D.Parse(otherPlaneRootPointString)); |
|||
Assert.AreEqual(expectedValue, plane.SignedDistanceTo(otherPlane), 1E-6); |
|||
} |
|||
|
|||
[TestCase(ZeroPoint, Z, ZeroPoint, Z, 0)] |
|||
[TestCase(ZeroPoint, Z, ZeroPoint, X, 0)] |
|||
[TestCase(ZeroPoint, Z, "0;0;1", X, 1)] |
|||
public void SignedDistanceToRay(string prps, string pns, string rayThroughPointString, string rayDirectionString, double expectedValue) |
|||
{ |
|||
var plane = new Plane3D(UnitVector3D.Parse(pns), Point3D.Parse(prps)); |
|||
var otherPlane = new Ray3D(Point3D.Parse(rayThroughPointString), UnitVector3D.Parse(rayDirectionString)); |
|||
Assert.AreEqual(expectedValue, plane.SignedDistanceTo(otherPlane), 1E-6); |
|||
} |
|||
|
|||
[Test] |
|||
public void ProjectLineOn() |
|||
{ |
|||
var unitVector = UnitVector3D.ZAxis; |
|||
var rootPoint = new Point3D(0, 0, 1); |
|||
var plane = new Plane3D(unitVector, rootPoint); |
|||
|
|||
var line = new LineSegment3D(new Point3D(0, 0, 0), new Point3D(1, 0, 0)); |
|||
var projectOn = plane.Project(line); |
|||
AssertGeometry.AreEqual(new LineSegment3D(new Point3D(0, 0, 1), new Point3D(1, 0, 1)), projectOn, float.Epsilon); |
|||
} |
|||
|
|||
[Test] |
|||
public void ProjectVectorOn() |
|||
{ |
|||
var unitVector = UnitVector3D.ZAxis; |
|||
var rootPoint = new Point3D(0, 0, 1); |
|||
var plane = new Plane3D(unitVector, rootPoint); |
|||
var vector = new Vector3D(1, 0, 0); |
|||
var projectOn = plane.Project(vector); |
|||
AssertGeometry.AreEqual(new Vector3D(1, 0, 0), projectOn.Direction, float.Epsilon); |
|||
AssertGeometry.AreEqual(new Point3D(0, 0, 1), projectOn.ThroughPoint, float.Epsilon); |
|||
} |
|||
|
|||
[TestCase("0, 0, 0", "0, 0, 1", "0, 0, 0", "0, 1, 0", "0, 0, 0", "-1, 0, 0")] |
|||
[TestCase("0, 0, 2", "0, 0, 1", "0, 0, 0", "0, 1, 0", "0, 0, 2", "-1, 0, 0")] |
|||
public void InterSectionWithPlane(string rootPoint1, string unitVector1, string rootPoint2, string unitVector2, string eps, string evs) |
|||
{ |
|||
var plane1 = new Plane3D(Point3D.Parse(rootPoint1), UnitVector3D.Parse(unitVector1)); |
|||
var plane2 = new Plane3D(Point3D.Parse(rootPoint2), UnitVector3D.Parse(unitVector2)); |
|||
var intersections = new[] |
|||
{ |
|||
plane1.IntersectionWith(plane2), |
|||
plane2.IntersectionWith(plane1) |
|||
}; |
|||
foreach (var intersection in intersections) |
|||
{ |
|||
AssertGeometry.AreEqual(Point3D.Parse(eps), intersection.ThroughPoint); |
|||
AssertGeometry.AreEqual(UnitVector3D.Parse(evs), intersection.Direction); |
|||
} |
|||
} |
|||
|
|||
[TestCase("0, 0, 0", "0, 0, 1", "0, 0, 0", "0, 0, 1", "0, 0, 0", "0, 0, 0")] |
|||
public void InterSectionWithPlaneTest_BadArgument(string rootPoint1, string unitVector1, string rootPoint2, string unitVector2, string eps, string evs) |
|||
{ |
|||
var plane1 = new Plane3D(Point3D.Parse(rootPoint1), UnitVector3D.Parse(unitVector1)); |
|||
var plane2 = new Plane3D(Point3D.Parse(rootPoint2), UnitVector3D.Parse(unitVector2)); |
|||
|
|||
Assert.Throws<ArgumentException>(() => plane1.IntersectionWith(plane2)); |
|||
Assert.Throws<ArgumentException>(() => plane2.IntersectionWith(plane1)); |
|||
} |
|||
|
|||
[Test] |
|||
public void MirrorPoint() |
|||
{ |
|||
var plane = new Plane3D(UnitVector3D.ZAxis, new Point3D(0, 0, 0)); |
|||
var point3D = new Point3D(1, 2, 3); |
|||
var mirrorAbout = plane.MirrorAbout(point3D); |
|||
AssertGeometry.AreEqual(new Point3D(1, 2, -3), mirrorAbout, float.Epsilon); |
|||
} |
|||
|
|||
[Test] |
|||
public void SignOfD() |
|||
{ |
|||
var plane1 = new Plane3D(UnitVector3D.ZAxis, new Point3D(0, 0, 100)); |
|||
Assert.AreEqual(-100, plane1.D); |
|||
} |
|||
|
|||
[Test] |
|||
public void InterSectionPointDifferentOrder() |
|||
{ |
|||
var plane1 = new Plane3D(UnitVector3D.Create(0.8, 0.3, 0.01), new Point3D(20, 0, 0)); |
|||
var plane2 = new Plane3D(UnitVector3D.Create(0.002, 1, 0.1), new Point3D(0, 0, 0)); |
|||
var plane3 = new Plane3D(UnitVector3D.Create(0.5, 0.5, 1), new Point3D(0, 0, -30)); |
|||
var pointFromPlanes1 = Plane3D.PointFromPlanes(plane1, plane2, plane3); |
|||
var pointFromPlanes2 = Plane3D.PointFromPlanes(plane2, plane1, plane3); |
|||
var pointFromPlanes3 = Plane3D.PointFromPlanes(plane3, plane1, plane2); |
|||
AssertGeometry.AreEqual(pointFromPlanes1, pointFromPlanes2, 1E-10); |
|||
AssertGeometry.AreEqual(pointFromPlanes3, pointFromPlanes2, 1E-10); |
|||
} |
|||
|
|||
[TestCase("0, 0, 0", "1, 0, 0", "0, 0, 0", "0, 1, 0", "0, 0, 0", "0, 0, 1", "0, 0, 0")] |
|||
[TestCase("0, 0, 0", "-1, 0, 0", "0, 0, 0", "0, 1, 0", "0, 0, 0", "0, 0, 1", "0, 0, 0")] |
|||
[TestCase("20, 0, 0", "1, 0, 0", "0, 0, 0", "0, 1, 0", "0, 0, -30", "0, 0, 1", "20, 0, -30")] |
|||
public void PointFromPlanes(string rootPoint1, string unitVector1, string rootPoint2, string unitVector2, string rootPoint3, string unitVector3, string eps) |
|||
{ |
|||
var plane1 = new Plane3D(Point3D.Parse(rootPoint1), UnitVector3D.Parse(unitVector1)); |
|||
var plane2 = new Plane3D(Point3D.Parse(rootPoint2), UnitVector3D.Parse(unitVector2)); |
|||
var plane3 = new Plane3D(Point3D.Parse(rootPoint3), UnitVector3D.Parse(unitVector3)); |
|||
var points = new[] |
|||
{ |
|||
Plane3D.PointFromPlanes(plane1, plane2, plane3), |
|||
Plane3D.PointFromPlanes(plane2, plane1, plane3), |
|||
Plane3D.PointFromPlanes(plane1, plane3, plane2), |
|||
Plane3D.PointFromPlanes(plane2, plane3, plane1), |
|||
Plane3D.PointFromPlanes(plane3, plane2, plane1), |
|||
Plane3D.PointFromPlanes(plane3, plane1, plane2), |
|||
}; |
|||
var expected = Point3D.Parse(eps); |
|||
foreach (var point in points) |
|||
{ |
|||
AssertGeometry.AreEqual(expected, point); |
|||
} |
|||
} |
|||
|
|||
[TestCase("1, 1, 0, -12", "-1, 1, 0, -12", "0, 0, 1, -5", "0, 16.970563, 5")] |
|||
public void PointFromPlanes2(string planeString1, string planeString2, string planeString3, string eps) |
|||
{ |
|||
var plane1 = this.GetPlaneFrom4Doubles(planeString1); |
|||
var plane2 = this.GetPlaneFrom4Doubles(planeString2); |
|||
var plane3 = this.GetPlaneFrom4Doubles(planeString3); |
|||
var points = new[] |
|||
{ |
|||
Plane3D.PointFromPlanes(plane1, plane2, plane3), |
|||
Plane3D.PointFromPlanes(plane2, plane1, plane3), |
|||
Plane3D.PointFromPlanes(plane1, plane3, plane2), |
|||
Plane3D.PointFromPlanes(plane2, plane3, plane1), |
|||
Plane3D.PointFromPlanes(plane3, plane2, plane1), |
|||
Plane3D.PointFromPlanes(plane3, plane1, plane2), |
|||
}; |
|||
var expected = Point3D.Parse(eps); |
|||
foreach (var point in points) |
|||
{ |
|||
AssertGeometry.AreEqual(expected, point); |
|||
} |
|||
} |
|||
|
|||
[TestCase("0, 0, 0", "0, 0, 1", @"<Plane3D><RootPoint X=""0"" Y=""0"" Z=""0"" /><Normal X=""0"" Y=""0"" Z=""1"" /></Plane3D>")] |
|||
public void XmlRoundTrips(string rootPoint, string unitVector, string xml) |
|||
{ |
|||
var plane = new Plane3D(Point3D.Parse(rootPoint), UnitVector3D.Parse(unitVector)); |
|||
AssertXml.XmlRoundTrips(plane, xml, (e, a) => AssertGeometry.AreEqual(e, a)); |
|||
} |
|||
|
|||
private Plane3D GetPlaneFrom4Doubles(string inputstring) |
|||
{ |
|||
var numbers = inputstring.Split(',').Select(t => double.Parse(t)).ToArray(); |
|||
return new Plane3D(numbers[0], numbers[1], numbers[2], numbers[3]); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,294 @@ |
|||
// ReSharper disable InconsistentNaming
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
using System.Xml; |
|||
using System.Xml.Serialization; |
|||
using MathNet.Numerics.Spatial.Euclidean3D; |
|||
using NUnit.Framework; |
|||
|
|||
namespace MathNet.Numerics.Tests.Spatial.Euclidean3D |
|||
{ |
|||
[TestFixture] |
|||
public class Point3DTests |
|||
{ |
|||
[Test] |
|||
public void Ctor() |
|||
{ |
|||
var actual = new Point3D(1, 2, 3); |
|||
Assert.AreEqual(1, actual.X, 1e-6); |
|||
Assert.AreEqual(2, actual.Y, 1e-6); |
|||
Assert.AreEqual(3, actual.Z, 1e-6); |
|||
} |
|||
|
|||
[TestCase("-1,1,-1", -1, 1, -1)] |
|||
[TestCase("1, 2, 3", 1, 2, 3)] |
|||
[TestCase("1.2; 3.4; 5.6", 1.2, 3.4, 5.6)] |
|||
[TestCase("1.2;3.4;5.6", 1.2, 3.4, 5.6)] |
|||
[TestCase("1.2 ; 3.4 ; 5.6", 1.2, 3.4, 5.6)] |
|||
[TestCase("1,2; 3,4; 5,6", 1.2, 3.4, 5.6)] |
|||
[TestCase("1.2, 3.4, 5.6", 1.2, 3.4, 5.6)] |
|||
[TestCase("1.2 3.4 5.6", 1.2, 3.4, 5.6)] |
|||
[TestCase("1.2,\u00A03.4\u00A05.6", 1.2, 3.4, 5.6)] |
|||
[TestCase("1.2\u00A03.4\u00A05.6", 1.2, 3.4, 5.6)] |
|||
[TestCase("(1.2, 3.4 5.6)", 1.2, 3.4, 5.6)] |
|||
[TestCase("1,2\u00A03,4\u00A05,6", 1.2, 3.4, 5.6)] |
|||
[TestCase("(.1, 2.3e-4,1)", 0.1, 0.00023000000000000001, 1)] |
|||
[TestCase("1.0 , 2.5,3.3", 1, 2.5, 3.3)] |
|||
[TestCase("1,0 ; 2,5;3,3", 1, 2.5, 3.3)] |
|||
[TestCase("1.0 ; 2.5;3.3", 1, 2.5, 3.3)] |
|||
[TestCase("1.0,2.5,-3.3", 1, 2.5, -3.3)] |
|||
[TestCase("1;2;3", 1, 2, 3)] |
|||
public void Parse(string text, double expectedX, double expectedY, double expectedZ) |
|||
{ |
|||
Assert.AreEqual(true, Point3D.TryParse(text, out var p)); |
|||
Assert.AreEqual(expectedX, p.X); |
|||
Assert.AreEqual(expectedY, p.Y); |
|||
Assert.AreEqual(expectedZ, p.Z); |
|||
|
|||
p = Point3D.Parse(text); |
|||
Assert.AreEqual(expectedX, p.X); |
|||
Assert.AreEqual(expectedY, p.Y); |
|||
Assert.AreEqual(expectedZ, p.Z); |
|||
|
|||
p = Point3D.Parse(p.ToString()); |
|||
Assert.AreEqual(expectedX, p.X); |
|||
Assert.AreEqual(expectedY, p.Y); |
|||
Assert.AreEqual(expectedZ, p.Z); |
|||
} |
|||
|
|||
[TestCase("1.2")] |
|||
[TestCase("1,2; 2.3; 3")] |
|||
[TestCase("1; 2; 3; 4")] |
|||
public void ParseFails(string text) |
|||
{ |
|||
Assert.AreEqual(false, Point3D.TryParse(text, out _)); |
|||
Assert.Throws<FormatException>(() => Point3D.Parse(text)); |
|||
} |
|||
|
|||
[TestCase("<Point3D X=\"1\" Y=\"-2\" Z=\"3\" />")] |
|||
[TestCase("<Point3D Y=\"-2\" Z=\"3\" X=\"1\"/>")] |
|||
[TestCase("<Point3D Z=\"3\" X=\"1\" Y=\"-2\" />")] |
|||
[TestCase("<Point3D><X>1</X><Y>-2</Y><Z>3</Z></Point3D>")] |
|||
[TestCase("<Point3D><Y>-2</Y><Z>3</Z><X>1</X></Point3D>")] |
|||
[TestCase("<Point3D><Z>3</Z><X>1</X><Y>-2</Y></Point3D>")] |
|||
public void ReadFrom(string xml) |
|||
{ |
|||
using (var reader = new StringReader(xml)) |
|||
{ |
|||
var actual = Point3D.ReadFrom(XmlReader.Create(reader)); |
|||
Assert.AreEqual(new Point3D(1, -2, 3), actual); |
|||
} |
|||
} |
|||
|
|||
[Test] |
|||
public void ToDenseVector() |
|||
{ |
|||
var p = new Point3D(1, 2, 3); |
|||
var vector = p.ToVector(); |
|||
Assert.AreEqual(3, vector.Count); |
|||
Assert.AreEqual(1, vector[0], 1e-6); |
|||
Assert.AreEqual(2, vector[1], 1e-6); |
|||
Assert.AreEqual(3, vector[2], 1e-6); |
|||
|
|||
var roundtripped = Point3D.OfVector(vector); |
|||
Assert.AreEqual(1, roundtripped.X, 1e-6); |
|||
Assert.AreEqual(2, roundtripped.Y, 1e-6); |
|||
Assert.AreEqual(3, roundtripped.Z, 1e-6); |
|||
} |
|||
|
|||
[TestCase("1, 2, 3", "1, 2, 3", 1e-4, true)] |
|||
[TestCase("1, 2, 3", "4, 5, 6", 1e-4, false)] |
|||
public void Equals(string p1s, string p2s, double tol, bool expected) |
|||
{ |
|||
var p1 = Point3D.Parse(p1s); |
|||
var p2 = Point3D.Parse(p2s); |
|||
Assert.AreEqual(expected, p1 == p2); |
|||
Assert.AreEqual(expected, p1.Equals(p2)); |
|||
Assert.AreEqual(expected, p1.Equals((object)p2)); |
|||
Assert.AreEqual(expected, Equals(p1, p2)); |
|||
Assert.AreEqual(expected, p1.Equals(p2, tol)); |
|||
Assert.AreNotEqual(expected, p1 != p2); |
|||
} |
|||
|
|||
[TestCase("0, 0, 0", "0, 0, 1", "0, 0, 0.5")] |
|||
[TestCase("0, 0, 1", "0, 0, 0", "0, 0, 0.5")] |
|||
[TestCase("0, 0, 0", "0, 0, 0", "0, 0, 0")] |
|||
[TestCase("1, 1, 1", "3, 3, 3", "2, 2, 2")] |
|||
[TestCase("-3, -3, -3", "3, 3, 3", "0, 0, 0")] |
|||
public void MidPoint(string p1s, string p2s, string eps) |
|||
{ |
|||
var p1 = Point3D.Parse(p1s); |
|||
var p2 = Point3D.Parse(p2s); |
|||
var ep = Point3D.Parse(eps); |
|||
var mp = Point3D.MidPoint(p1, p2); |
|||
AssertGeometry.AreEqual(ep, mp, 1e-9); |
|||
var centroid = Point3D.Centroid(p1, p2); |
|||
AssertGeometry.AreEqual(ep, centroid, 1e-9); |
|||
} |
|||
|
|||
[TestCase("0, 0, 0", "0, 0, 1", "0, 0, 0", "0, 1, 0", "0, 0, 0", "1, 0, 0", "0, 0, 0")] |
|||
[TestCase("0, 0, 5", "0, 0, 1", "0, 4, 0", "0, 1, 0", "3, 0, 0", "1, 0, 0", "3, 4, 5")] |
|||
public void FromPlanes(string rootPoint1, string unitVector1, string rootPoint2, string unitVector2, string rootPoint3, string unitVector3, string eps) |
|||
{ |
|||
var plane1 = new Plane3D(Point3D.Parse(rootPoint1), UnitVector3D.Parse(unitVector1)); |
|||
var plane2 = new Plane3D(Point3D.Parse(rootPoint2), UnitVector3D.Parse(unitVector2)); |
|||
var plane3 = new Plane3D(Point3D.Parse(rootPoint3), UnitVector3D.Parse(unitVector3)); |
|||
var p1 = Point3D.IntersectionOf(plane1, plane2, plane3); |
|||
var p2 = Point3D.IntersectionOf(plane2, plane1, plane3); |
|||
var p3 = Point3D.IntersectionOf(plane2, plane3, plane1); |
|||
var p4 = Point3D.IntersectionOf(plane3, plane1, plane2); |
|||
var p5 = Point3D.IntersectionOf(plane3, plane2, plane1); |
|||
var ep = Point3D.Parse(eps); |
|||
foreach (var p in new[] { p1, p2, p3, p4, p5 }) |
|||
{ |
|||
AssertGeometry.AreEqual(ep, p); |
|||
} |
|||
} |
|||
|
|||
[TestCase("0, 0, 0", "0, 0, 0", "0, 0, 1", "0, 0, 0")] |
|||
[TestCase("0, 0, 1", "0, 0, 0", "0, 0, 1", "0, 0, -1")] |
|||
public void MirrorAbout(string ps, string rootPoint, string unitVector, string eps) |
|||
{ |
|||
var p = Point3D.Parse(ps); |
|||
var p2 = new Plane3D(Point3D.Parse(rootPoint), UnitVector3D.Parse(unitVector)); |
|||
var actual = p.MirrorAbout(p2); |
|||
|
|||
var ep = Point3D.Parse(eps); |
|||
AssertGeometry.AreEqual(ep, actual); |
|||
} |
|||
|
|||
[TestCase("0, 0, 0", "0, 0, 0", "0, 0, 1", "0, 0, 0")] |
|||
[TestCase("0, 0, 1", "0, 0, 0", "0, 0, 1", "0, 0, 0")] |
|||
[TestCase("0, 0, 1", "0, 10, 0", "0, 1, 0", "0, 10, 1")] |
|||
public void ProjectOnTests(string ps, string rootPoint, string unitVector, string eps) |
|||
{ |
|||
var p = Point3D.Parse(ps); |
|||
var p2 = new Plane3D(Point3D.Parse(rootPoint), UnitVector3D.Parse(unitVector)); |
|||
var actual = p.ProjectOn(p2); |
|||
|
|||
var ep = Point3D.Parse(eps); |
|||
AssertGeometry.AreEqual(ep, actual); |
|||
} |
|||
|
|||
[TestCase("1, 2, 3", "1, 0, 0", "2, 2, 3")] |
|||
[TestCase("1, 2, 3", "0, 1, 0", "1, 3, 3")] |
|||
[TestCase("1, 2, 3", "0, 0, 1", "1, 2, 4")] |
|||
public void AddVector(string ps, string vs, string eps) |
|||
{ |
|||
var p = Point3D.Parse(ps); |
|||
var actuals = new[] |
|||
{ |
|||
p + Vector3D.Parse(vs), |
|||
p + UnitVector3D.Parse(vs) |
|||
}; |
|||
var expected = Point3D.Parse(eps); |
|||
foreach (var actual in actuals) |
|||
{ |
|||
Assert.AreEqual(expected, actual); |
|||
} |
|||
} |
|||
|
|||
[TestCase("1, 2, 3", "1, 0, 0", "0, 2, 3")] |
|||
[TestCase("1, 2, 3", "0, 1, 0", "1, 1, 3")] |
|||
[TestCase("1, 2, 3", "0, 0, 1", "1, 2, 2")] |
|||
public void SubtractVector(string ps, string vs, string eps) |
|||
{ |
|||
var p = Point3D.Parse(ps); |
|||
var actuals = new[] |
|||
{ |
|||
p - Vector3D.Parse(vs), |
|||
p - UnitVector3D.Parse(vs) |
|||
}; |
|||
var expected = Point3D.Parse(eps); |
|||
foreach (var actual in actuals) |
|||
{ |
|||
Assert.AreEqual(expected, actual); |
|||
} |
|||
} |
|||
|
|||
[TestCase("1, 2, 3", "4, 8, 16", "-3, -6, -13")] |
|||
public void SubtractPoint(string p1s, string p2s, string evs) |
|||
{ |
|||
var p1 = Point3D.Parse(p1s); |
|||
var p2 = Point3D.Parse(p2s); |
|||
|
|||
var expected = Vector3D.Parse(evs); |
|||
Assert.AreEqual(expected, p1 - p2); |
|||
} |
|||
|
|||
[TestCase("0,0,0", "1,0,0", 1)] |
|||
[TestCase("1,1,1", "2,1,1", 1)] |
|||
public void DistanceTo(string p1s, string p2s, double d) |
|||
{ |
|||
var p1 = Point3D.Parse(p1s); |
|||
var p2 = Point3D.Parse(p2s); |
|||
|
|||
Assert.AreEqual(d, p1.DistanceTo(p2), 1e-6); |
|||
Assert.AreEqual(d, p2.DistanceTo(p1), 1e-6); |
|||
} |
|||
|
|||
[TestCase("-1 ; 2;-3")] |
|||
public void ToVectorAndBack(string ps) |
|||
{ |
|||
var p = Point3D.Parse(ps); |
|||
AssertGeometry.AreEqual(p, p.ToVector3D().ToPoint3D(), 1e-9); |
|||
} |
|||
|
|||
[TestCase("-2, 0, 1e-4", null, "(-2, 0, 0.0001)", 1e-4)] |
|||
[TestCase("-2, 0, 1e-4", "F2", "(-2.00, 0.00, 0.00)", 1e-4)] |
|||
public void ToString(string vs, string format, string expected, double tolerance) |
|||
{ |
|||
var p = Point3D.Parse(vs); |
|||
var actual = p.ToString(format); |
|||
Assert.AreEqual(expected, actual); |
|||
AssertGeometry.AreEqual(p, Point3D.Parse(actual), tolerance); |
|||
} |
|||
|
|||
[Test] |
|||
public void XmlRoundtrip() |
|||
{ |
|||
var p = new Point3D(1, -2, 3); |
|||
var xml = @"<Point3D X=""1"" Y=""-2"" Z=""3"" />"; |
|||
AssertXml.XmlRoundTrips(p, xml, (expected, actual) => AssertGeometry.AreEqual(expected, actual)); |
|||
} |
|||
|
|||
[Test] |
|||
public void XmlContainerRoundtrip() |
|||
{ |
|||
var container = new AssertXml.Container<Point3D> |
|||
{ |
|||
Value1 = new Point3D(1, 2, 3), |
|||
Value2 = new Point3D(4, 5, 6) |
|||
}; |
|||
var expected = "<ContainerOfPoint3D>\r\n" + |
|||
" <Value1 X=\"1\" Y=\"2\" Z=\"3\"></Value1>\r\n" + |
|||
" <Value2 X=\"4\" Y=\"5\" Z=\"6\"></Value2>\r\n" + |
|||
"</ContainerOfPoint3D>"; |
|||
var roundTrip = AssertXml.XmlSerializerRoundTrip(container, expected); |
|||
AssertGeometry.AreEqual(container.Value1, roundTrip.Value1); |
|||
AssertGeometry.AreEqual(container.Value2, roundTrip.Value2); |
|||
} |
|||
|
|||
[Test] |
|||
public void XmlElements() |
|||
{ |
|||
var v = new Point3D(1, 2, 3); |
|||
var serializer = new XmlSerializer(typeof(Point3D)); |
|||
AssertGeometry.AreEqual(v, (Point3D)serializer.Deserialize(new StringReader(@"<Point3D><X>1</X><Y>2</Y><Z>3</Z></Point3D>"))); |
|||
} |
|||
|
|||
[Test] |
|||
public void XmlContainerElements() |
|||
{ |
|||
var xml = "<ContainerOfPoint3D>\r\n" + |
|||
" <Value1><X>1</X><Y>2</Y><Z>3</Z></Value1>\r\n" + |
|||
" <Value2><X>4</X><Y>5</Y><Z>6</Z></Value2>\r\n" + |
|||
"</ContainerOfPoint3D>"; |
|||
var serializer = new XmlSerializer(typeof(AssertXml.Container<Point3D>)); |
|||
var deserialized = (AssertXml.Container<Point3D>)serializer.Deserialize(new StringReader(xml)); |
|||
AssertGeometry.AreEqual(new Point3D(1, 2, 3), deserialized.Value1); |
|||
AssertGeometry.AreEqual(new Point3D(4, 5, 6), deserialized.Value2); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,72 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using MathNet.Numerics.Spatial.Euclidean3D; |
|||
using NUnit.Framework; |
|||
|
|||
namespace MathNet.Numerics.Tests.Spatial.Euclidean3D |
|||
{ |
|||
[TestFixture] |
|||
public class PolyLine3DTests |
|||
{ |
|||
[TestCase("0,0,1;1,1,0;2,2,1;3,3,0", 1, "1,1,0")] |
|||
[TestCase("0,0,1;1,1,0;2,2,1;3,3,0", 0, "0,0,1")] |
|||
[TestCase("0,0,1;1,1,0;2,2,1;3,3,0", 3, "3,3,0")] |
|||
public void IndexAccessorTest(string points, int index, string expected) |
|||
{ |
|||
var testElement = new PolyLine3D(from x in points.Split(';') select Point3D.Parse(x)); |
|||
var checkElement = Point3D.Parse(expected); |
|||
AssertGeometry.AreEqual(checkElement, testElement.Vertices.Skip(index).First()); |
|||
} |
|||
|
|||
[TestCase("0,0,0;0,1,0", 1.0)] |
|||
[TestCase("0,0,0;0,1,0;1,1,0", 2.0)] |
|||
[TestCase("0,-1.5,0;0,1,0;1,1,0", 3.5)] |
|||
public void GetPolyLineLengthTests(string points, double expected) |
|||
{ |
|||
var testElement = new PolyLine3D(from x in points.Split(';') select Point3D.Parse(x)); |
|||
|
|||
Assert.AreEqual(expected, testElement.Length, 1e-10); |
|||
} |
|||
|
|||
[TestCase("0,-1.5,0;0,1,0;1,1,0", 1.0, "1,1,0")] |
|||
[TestCase("0,-1.5,0;0,1,0;1,1,0", 0.0, "0,-1.5,0")] |
|||
[TestCase("0,0,0;0,1,0;1,1,0", 0.25, "0,0.5,0")] |
|||
[TestCase("0,0,0;0,1,0;1,1,0", 0.5, "0,1,0")] |
|||
[TestCase("0,0,0;0,1,0;1,1,0", 0.75, "0.5,1,0")] |
|||
public void GetPointAtFractionAlongCurve(string points, double fraction, string expected) |
|||
{ |
|||
// Note that this method also tests GetPointAtLengthFromStart(...)
|
|||
var testElement = new PolyLine3D(from x in points.Split(';') select Point3D.Parse(x)); |
|||
var checkElement = Point3D.Parse(expected); |
|||
|
|||
AssertGeometry.AreEqual(checkElement, testElement.GetPointAtFractionAlongCurve(fraction)); |
|||
} |
|||
|
|||
[TestCase("0,-1.5,0;0,1,0;1,1,0", 2.0, "1,1,0")] |
|||
[TestCase("0,-1.5,0;0,1,0;1,1,0", -5, "0,-1.5,0")] |
|||
public void GetPointAtFractionAlongCurveThrowsArgumentException(string points, double fraction, string expected) |
|||
{ |
|||
var testElement = new PolyLine3D(from x in points.Split(';') select Point3D.Parse(x)); |
|||
Assert.Throws<ArgumentException>(() => { testElement.GetPointAtFractionAlongCurve(fraction); }); |
|||
} |
|||
|
|||
[TestCase("0,0,0 ; 0,1,1 ; 1,1,2", "0,-1,0", "0,0,0")] // Off Endpoint
|
|||
[TestCase("0,0,0 ; 0,1,1 ; 1,1,2", "2,1,2", "1,1,2")] // Off Endpoint
|
|||
[TestCase("0,0,0 ; 0,1,1 ; 1,1,2", "-1,2,1", "0,1,1")] // Off Corner
|
|||
[TestCase("0,0,0 ; 0,1,1 ; 1,1,2", "0,0,0", "0,0,0")] // On Endpoint
|
|||
[TestCase("0,0,0 ; 0,1,1 ; 1,1,2", "1,1,2", "1,1,2")] // On Endpoint
|
|||
[TestCase("0,0,0 ; 0,1,1 ; 1,1,2", "0,1,1", "0,1,1")] // On Corner
|
|||
[TestCase("0,0,0 ; 0,1,1 ; 1,1,2", "0,0.5,0.5", "0,0.5,0.5")] // On Curve
|
|||
[TestCase("0,0,0 ; 0,1,1 ; 1,1,2", "-1,0.5,0.5", "0,0.5,0.5")] // Off curve
|
|||
[TestCase("0,0,0 ; 0,1,1 ; 1,1,2", "0.5,1,1.5", "0.5,1,1.5")] // On Curve
|
|||
[TestCase("0,0,0 ; 0,1,1 ; 1,1,2", "0.5,1.5,1.5", "0.5,1,1.5")] // Off curve
|
|||
public void ClosestPointToTest(string points, string testPoint, string expectedPoint) |
|||
{ |
|||
var testCurve = new PolyLine3D(from x in points.Split(';') select Point3D.Parse(x)); |
|||
var test = Point3D.Parse(testPoint); |
|||
var expected = Point3D.Parse(expectedPoint); |
|||
|
|||
AssertGeometry.AreEqual(expected, testCurve.ClosestPointTo(test), 1e-06); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,59 @@ |
|||
// ReSharper disable InconsistentNaming
|
|||
|
|||
using MathNet.Numerics.Spatial.Euclidean3D; |
|||
using NUnit.Framework; |
|||
|
|||
namespace MathNet.Numerics.Tests.Spatial.Euclidean3D |
|||
{ |
|||
[TestFixture] |
|||
public class Ray3DTests |
|||
{ |
|||
[TestCase("1, 2, 3", "0, 0, 1", "1, 2, 3", "0, 0, 1")] |
|||
public void Parse(string rootPoint, string unitVector, string eps, string evs) |
|||
{ |
|||
var ray = new Ray3D(Point3D.Parse(rootPoint), UnitVector3D.Parse(unitVector)); |
|||
AssertGeometry.AreEqual(Point3D.Parse(eps), ray.ThroughPoint); |
|||
AssertGeometry.AreEqual(Vector3D.Parse(evs), ray.Direction); |
|||
} |
|||
|
|||
[TestCase("0, 0, 0", "0, 0, 1", "0, 0, 0", "0, 1, 0", "0, 0, 0", "-1, 0, 0")] |
|||
[TestCase("0, 0, 2", "0, 0, 1", "0, 0, 0", "0, 1, 0", "0, 0, 2", "-1, 0, 0")] |
|||
public void IntersectionOf(string rootPoint1, string unitVector1, string rootPoint2, string unitVector2, string eps, string evs) |
|||
{ |
|||
var plane1 = new Plane3D(Point3D.Parse(rootPoint1), UnitVector3D.Parse(unitVector1)); |
|||
var plane2 = new Plane3D(Point3D.Parse(rootPoint2), UnitVector3D.Parse(unitVector2)); |
|||
var actual = Ray3D.IntersectionOf(plane1, plane2); |
|||
var expected = Ray3D.Parse(eps, evs); |
|||
AssertGeometry.AreEqual(expected, actual); |
|||
} |
|||
|
|||
[Test] |
|||
public void LineToTest() |
|||
{ |
|||
var ray = new Ray3D(new Point3D(0, 0, 0), UnitVector3D.ZAxis); |
|||
var point3D = new Point3D(1, 0, 0); |
|||
var line3DTo = ray.ShortestLineTo(point3D); |
|||
AssertGeometry.AreEqual(new Point3D(0, 0, 0), line3DTo.StartPoint); |
|||
AssertGeometry.AreEqual(point3D, line3DTo.EndPoint, float.Epsilon); |
|||
} |
|||
|
|||
[TestCase("0, 0, 0", "1, -1, 1", "0, 0, 0", "1, -1, 1", true)] |
|||
[TestCase("0, 0, 2", "1, -1, 1", "0, 0, 0", "1, -1, 1", false)] |
|||
[TestCase("0, 0, 0", "1, -1, 1", "0, 0, 0", "2, -1, 1", false)] |
|||
public void Equals(string p1s, string v1s, string p2s, string v2s, bool expected) |
|||
{ |
|||
var ray1 = new Ray3D(Point3D.Parse(p1s), UnitVector3D.Parse(v1s, tolerance: 2)); |
|||
var ray2 = new Ray3D(Point3D.Parse(p2s), UnitVector3D.Parse(v2s, tolerance: 2)); |
|||
Assert.AreEqual(expected, ray1.Equals(ray2)); |
|||
Assert.AreEqual(expected, ray1 == ray2); |
|||
Assert.AreEqual(!expected, ray1 != ray2); |
|||
} |
|||
|
|||
[TestCase("1, 2, 3", "-0.2672612419124244, 0.53452248382484879, 0.80178372573727319", false, @"<Ray3D><ThroughPoint X=""1"" Y=""2"" Z=""3"" /><Direction X=""-0.2672612419124244"" Y=""0.53452248382484879"" Z=""0.80178372573727319"" /></Ray3D>")] |
|||
public void XmlTests(string ps, string vs, bool asElements, string xml) |
|||
{ |
|||
var ray = new Ray3D(Point3D.Parse(ps), UnitVector3D.Parse(vs)); |
|||
AssertXml.XmlRoundTrips(ray, xml, (e, a) => AssertGeometry.AreEqual(e, a, 1e-6)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,220 @@ |
|||
// ReSharper disable InconsistentNaming
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
using System.Xml; |
|||
using System.Xml.Serialization; |
|||
using MathNet.Numerics.Spatial.Euclidean3D; |
|||
using NUnit.Framework; |
|||
|
|||
namespace MathNet.Numerics.Tests.Spatial.Euclidean3D |
|||
{ |
|||
[TestFixture] |
|||
public class UnitVector3DTests |
|||
{ |
|||
[Test] |
|||
public void Create() |
|||
{ |
|||
var actual = UnitVector3D.Create(1, -2, 3); |
|||
Assert.AreEqual(0.2672612419124244, actual.X); |
|||
Assert.AreEqual(-0.53452248382484879, actual.Y); |
|||
Assert.AreEqual(0.80178372573727319, actual.Z); |
|||
|
|||
actual = UnitVector3D.Create(0.2672612419124244, -0.53452248382484879, 0.80178372573727319); |
|||
Assert.AreEqual(0.2672612419124244, actual.X); |
|||
Assert.AreEqual(-0.53452248382484879, actual.Y); |
|||
Assert.AreEqual(0.80178372573727319, actual.Z); |
|||
|
|||
Assert.Throws<ArgumentOutOfRangeException>(() => UnitVector3D.Create(double.NaN, 2, 3)); |
|||
Assert.Throws<ArgumentOutOfRangeException>(() => UnitVector3D.Create(double.PositiveInfinity, 2, 3)); |
|||
Assert.Throws<ArgumentOutOfRangeException>(() => UnitVector3D.Create(double.NegativeInfinity, 2, 3)); |
|||
} |
|||
|
|||
[TestCase("1,0; 0; 0,0", 1, 0, 0)] |
|||
[TestCase("0; 1,0; 0,0", 0, 1, 0)] |
|||
[TestCase("0; 0,0; 1,0", 0, 0, 1)] |
|||
[TestCase("1.0; 0; 0.0", 1, 0, 0)] |
|||
[TestCase("0; 1.0; 0.0", 0, 1, 0)] |
|||
[TestCase("0; 0.0; 1.0", 0, 0, 1)] |
|||
public void Parse(string text, double expectedX, double expectedY, double expectedZ) |
|||
{ |
|||
Assert.AreEqual(true, UnitVector3D.TryParse(text, out var p)); |
|||
Assert.AreEqual(expectedX, p.X); |
|||
Assert.AreEqual(expectedY, p.Y); |
|||
Assert.AreEqual(expectedZ, p.Z); |
|||
|
|||
p = UnitVector3D.Parse(text); |
|||
Assert.AreEqual(expectedX, p.X); |
|||
Assert.AreEqual(expectedY, p.Y); |
|||
Assert.AreEqual(expectedZ, p.Z); |
|||
|
|||
p = UnitVector3D.Parse(p.ToString()); |
|||
Assert.AreEqual(expectedX, p.X); |
|||
Assert.AreEqual(expectedY, p.Y); |
|||
Assert.AreEqual(expectedZ, p.Z); |
|||
} |
|||
|
|||
[TestCase("1; 2; 3")] |
|||
[TestCase("1.2")] |
|||
[TestCase("1,2; 2.3; 3")] |
|||
[TestCase("1; 2; 3; 4")] |
|||
public void ParseFails(string text) |
|||
{ |
|||
Assert.AreEqual(false, UnitVector3D.TryParse(text, out _)); |
|||
Assert.Throws<FormatException>(() => UnitVector3D.Parse(text)); |
|||
} |
|||
|
|||
[Test] |
|||
public void ToDenseVector() |
|||
{ |
|||
var uv = UnitVector3D.Create(0.2672612419124244, -0.53452248382484879, 0.80178372573727319); |
|||
var vector = uv.ToVector(); |
|||
Assert.AreEqual(3, vector.Count); |
|||
Assert.AreEqual(0.2672612419124244, vector[0]); |
|||
Assert.AreEqual(-0.53452248382484879, vector[1]); |
|||
Assert.AreEqual(0.80178372573727319, vector[2]); |
|||
|
|||
var roundtripped = UnitVector3D.OfVector(vector); |
|||
Assert.AreEqual(0.2672612419124244, roundtripped.X); |
|||
Assert.AreEqual(-0.53452248382484879, roundtripped.Y); |
|||
Assert.AreEqual(0.80178372573727319, roundtripped.Z); |
|||
} |
|||
|
|||
[TestCase("1, 0, 0", "1, 0, 0", 1e-4, true)] |
|||
[TestCase("0, 1, 0", "0, 1, 0", 1e-4, true)] |
|||
[TestCase("0, 0, 1", "0, 0, 1", 1e-4, true)] |
|||
[TestCase("1, 0, 0", "0, 1, 0", 1e-4, false)] |
|||
[TestCase("0, 1, 0", "1, 0, 0", 1e-4, false)] |
|||
[TestCase("0, 0, 1", "0, 1, 0", 1e-4, false)] |
|||
public void Equals(string p1s, string p2s, double tol, bool expected) |
|||
{ |
|||
var v1 = UnitVector3D.Parse(p1s); |
|||
var v2 = UnitVector3D.Parse(p2s); |
|||
var vector3D = v1.ToVector3D(); |
|||
Assert.AreEqual(expected, v1 == v2); |
|||
Assert.IsTrue(v1 == vector3D); |
|||
Assert.IsTrue(vector3D == v1); |
|||
|
|||
Assert.AreEqual(expected, v1.Equals(v2)); |
|||
Assert.IsTrue(v1.Equals(vector3D)); |
|||
Assert.IsTrue(vector3D.Equals(v1)); |
|||
Assert.AreEqual(expected, v1.Equals(v2.ToVector3D())); |
|||
Assert.AreEqual(expected, v2.ToVector3D().Equals(v1)); |
|||
|
|||
Assert.AreEqual(expected, v1.Equals((object)v2)); |
|||
Assert.AreEqual(expected, Equals(v1, v2)); |
|||
|
|||
Assert.AreEqual(expected, v1.Equals(v2, tol)); |
|||
Assert.AreNotEqual(expected, v1 != v2); |
|||
Assert.AreNotEqual(expected, v1 != v2.ToVector3D()); |
|||
Assert.AreNotEqual(expected, v2.ToVector3D() != v1); |
|||
} |
|||
|
|||
[TestCase("1; 0; 0", 5, "5; 0; 0")] |
|||
[TestCase("1; 0; 0", -5, "-5; 0; 0")] |
|||
[TestCase("-1; 0; 0", 5, "-5; 0; 0")] |
|||
[TestCase("-1; 0; 0", -5, "5; 0; 0")] |
|||
[TestCase("0; 1; 0", 5, "0; 5; 0")] |
|||
[TestCase("0; 0; 1", 5, "0; 0; 5")] |
|||
public void Scale(string ivs, double s, string exs) |
|||
{ |
|||
var uv = UnitVector3D.Parse(ivs); |
|||
var v = uv.ScaleBy(s); |
|||
AssertGeometry.AreEqual(Vector3D.Parse(exs), v, float.Epsilon); |
|||
} |
|||
|
|||
[TestCase("1; 0; 0", "1; 0; 0", 1)] |
|||
[TestCase("1; 0; 0", "-1; 0; 0", -1)] |
|||
[TestCase("1; 0; 0", "0; -1; 0", 0)] |
|||
public void DotProduct(string v1s, string v2s, double expected) |
|||
{ |
|||
var uv1 = UnitVector3D.Parse(v1s); |
|||
var uv2 = UnitVector3D.Parse(v2s); |
|||
var dp = uv1.DotProduct(uv2); |
|||
Assert.AreEqual(dp, expected, 1e-9); |
|||
Assert.IsTrue(dp <= 1); |
|||
Assert.IsTrue(dp >= -1); |
|||
} |
|||
|
|||
[TestCase("-1, 0, 0", null, "(-1, 0, 0)", 1e-4)] |
|||
[TestCase("-1, 0, 1e-4", "F2", "(-1.00, 0.00, 0.00)", 1e-3)] |
|||
public void ToString(string vs, string format, string expected, double tolerance) |
|||
{ |
|||
var v = UnitVector3D.Parse(vs); |
|||
var actual = v.ToString(format); |
|||
Assert.AreEqual(expected, actual); |
|||
AssertGeometry.AreEqual(v, UnitVector3D.Parse(actual), tolerance); |
|||
} |
|||
|
|||
[TestCase("1,0,0", 3, "3,0,0")] |
|||
public void MultiplyTest(string unitVectorAsString, double multiplier, string expected) |
|||
{ |
|||
var unitVector3D = UnitVector3D.Parse(unitVectorAsString); |
|||
Assert.AreEqual(Vector3D.Parse(expected), multiplier * unitVector3D); |
|||
} |
|||
|
|||
[TestCase("<UnitVector3D X=\"0.2672612419124244\" Y=\"-0.53452248382484879\" Z=\"0.80178372573727319\" />")] |
|||
[TestCase("<UnitVector3D Y=\"-0.53452248382484879\" Z=\"0.80178372573727319\" X=\"0.2672612419124244\"/>")] |
|||
[TestCase("<UnitVector3D Z=\"0.80178372573727319\" X=\"0.2672612419124244\" Y=\"-0.53452248382484879\" />")] |
|||
[TestCase("<UnitVector3D><X>0.2672612419124244</X><Y>-0.53452248382484879</Y><Z>0.80178372573727319</Z></UnitVector3D>")] |
|||
[TestCase("<UnitVector3D><Y>-0.53452248382484879</Y><Z>0.80178372573727319</Z><X>0.2672612419124244</X></UnitVector3D>")] |
|||
[TestCase("<UnitVector3D><Z>0.80178372573727319</Z><X>0.2672612419124244</X><Y>-0.53452248382484879</Y></UnitVector3D>")] |
|||
public void ReadFrom(string xml) |
|||
{ |
|||
using (var reader = new StringReader(xml)) |
|||
{ |
|||
var actual = UnitVector3D.ReadFrom(XmlReader.Create(reader)); |
|||
Assert.AreEqual(UnitVector3D.Create(0.2672612419124244, -0.53452248382484879, 0.80178372573727319), actual); |
|||
} |
|||
} |
|||
|
|||
[Test] |
|||
public void XmlRoundtrip() |
|||
{ |
|||
var uv = UnitVector3D.Create(0.2672612419124244, -0.53452248382484879, 0.80178372573727319); |
|||
var xml = "<UnitVector3D X=\"0.2672612419124244\" Y=\"-0.53452248382484879\" Z=\"0.80178372573727319\" />"; |
|||
AssertXml.XmlRoundTrips(uv, xml, (expected, actual) => AssertGeometry.AreEqual(expected, actual)); |
|||
} |
|||
|
|||
[Test] |
|||
public void XmlContainerRoundtrip() |
|||
{ |
|||
var container = new AssertXml.Container<UnitVector3D> |
|||
{ |
|||
Value1 = UnitVector3D.Create(0.2672612419124244, -0.53452248382484879, 0.80178372573727319), |
|||
Value2 = UnitVector3D.Create(1, 0, 0) |
|||
}; |
|||
var expected = "<ContainerOfUnitVector3D>\r\n" + |
|||
" <Value1 X=\"0.2672612419124244\" Y=\"-0.53452248382484879\" Z=\"0.80178372573727319\"></Value1>\r\n" + |
|||
" <Value2 X=\"1\" Y=\"0\" Z=\"0\"></Value2>\r\n" + |
|||
"</ContainerOfUnitVector3D>"; |
|||
var roundTrip = AssertXml.XmlSerializerRoundTrip(container, expected); |
|||
AssertGeometry.AreEqual(container.Value1, roundTrip.Value1); |
|||
AssertGeometry.AreEqual(container.Value2, roundTrip.Value2); |
|||
} |
|||
|
|||
[Test] |
|||
public void XmlElements() |
|||
{ |
|||
var v = UnitVector3D.Create(0.2672612419124244, -0.53452248382484879, 0.80178372573727319); |
|||
var serializer = new XmlSerializer(typeof(UnitVector3D)); |
|||
using (var reader = new StringReader("<UnitVector3D><X>0.2672612419124244</X><Y>-0.53452248382484879</Y><Z>0.80178372573727319</Z></UnitVector3D>")) |
|||
{ |
|||
AssertGeometry.AreEqual(v, (UnitVector3D)serializer.Deserialize(reader)); |
|||
} |
|||
} |
|||
|
|||
[Test] |
|||
public void XmlContainerElements() |
|||
{ |
|||
var xml = "<ContainerOfUnitVector3D>\r\n" + |
|||
" <Value1><X>0.2672612419124244</X><Y>-0.53452248382484879</Y><Z>0.80178372573727319</Z></Value1>\r\n" + |
|||
" <Value2><X>1</X><Y>0</Y><Z>0</Z></Value2>\r\n" + |
|||
"</ContainerOfUnitVector3D>"; |
|||
var serializer = new XmlSerializer(typeof(AssertXml.Container<UnitVector3D>)); |
|||
var deserialized = (AssertXml.Container<UnitVector3D>)serializer.Deserialize(new StringReader(xml)); |
|||
AssertGeometry.AreEqual(UnitVector3D.Create(0.2672612419124244, -0.53452248382484879, 0.80178372573727319), deserialized.Value1); |
|||
AssertGeometry.AreEqual(UnitVector3D.Create(1, 0, 0), deserialized.Value2); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,492 @@ |
|||
// ReSharper disable InconsistentNaming
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
using System.Xml; |
|||
using System.Xml.Serialization; |
|||
using MathNet.Numerics.Spatial; |
|||
using MathNet.Numerics.Spatial.Euclidean3D; |
|||
using NUnit.Framework; |
|||
|
|||
namespace MathNet.Numerics.Tests.Spatial.Euclidean3D |
|||
{ |
|||
[TestFixture] |
|||
public class Vector3DTests |
|||
{ |
|||
private const string X = "1; 0 ; 0"; |
|||
private const string Y = "0; 1; 0"; |
|||
private const string Z = "0; 0; 1"; |
|||
private const string NegativeX = "-1; 0; 0"; |
|||
private const string NegativeY = "0; -1; 0"; |
|||
private const string NegativeZ = "0; 0; -1"; |
|||
|
|||
[Test] |
|||
public void Ctor() |
|||
{ |
|||
var v = new Vector3D(1, 2, 3); |
|||
Assert.AreEqual(1, v.X); |
|||
Assert.AreEqual(2, v.Y); |
|||
Assert.AreEqual(3, v.Z); |
|||
} |
|||
|
|||
[TestCase("1,2,-3", 3, "3,6,-9")] |
|||
public void OperatorMultiply(string vectorAsString, double multiplier, string expected) |
|||
{ |
|||
var vector = Vector3D.Parse(vectorAsString); |
|||
AssertGeometry.AreEqual(Vector3D.Parse(expected), multiplier * vector, 1e-6); |
|||
} |
|||
|
|||
[TestCase("-1,1,-1", -1, 1, -1)] |
|||
[TestCase("1, 2, 3", 1, 2, 3)] |
|||
[TestCase("1.2; 3.4; 5.6", 1.2, 3.4, 5.6)] |
|||
[TestCase("1.2;3.4;5.6", 1.2, 3.4, 5.6)] |
|||
[TestCase("1.2 ; 3.4 ; 5.6", 1.2, 3.4, 5.6)] |
|||
[TestCase("1,2; 3,4; 5,6", 1.2, 3.4, 5.6)] |
|||
[TestCase("1.2, 3.4, 5.6", 1.2, 3.4, 5.6)] |
|||
[TestCase("1.2 3.4 5.6", 1.2, 3.4, 5.6)] |
|||
[TestCase("1.2,\u00A03.4\u00A05.6", 1.2, 3.4, 5.6)] |
|||
[TestCase("1.2\u00A03.4\u00A05.6", 1.2, 3.4, 5.6)] |
|||
[TestCase("(1.2, 3.4 5.6)", 1.2, 3.4, 5.6)] |
|||
[TestCase("1,2\u00A03,4\u00A05,6", 1.2, 3.4, 5.6)] |
|||
[TestCase("(.1, 2.3e-4,1)", 0.1, 0.00023000000000000001, 1)] |
|||
[TestCase("1.0 , 2.5,3.3", 1, 2.5, 3.3)] |
|||
[TestCase("1,0 ; 2,5;3,3", 1, 2.5, 3.3)] |
|||
[TestCase("1.0 ; 2.5;3.3", 1, 2.5, 3.3)] |
|||
[TestCase("1.0,2.5,-3.3", 1, 2.5, -3.3)] |
|||
[TestCase("1;2;3", 1, 2, 3)] |
|||
public void Parse(string text, double expectedX, double expectedY, double expectedZ) |
|||
{ |
|||
Assert.AreEqual(true, Vector3D.TryParse(text, out var p)); |
|||
Assert.AreEqual(expectedX, p.X); |
|||
Assert.AreEqual(expectedY, p.Y); |
|||
Assert.AreEqual(expectedZ, p.Z); |
|||
|
|||
p = Vector3D.Parse(text); |
|||
Assert.AreEqual(expectedX, p.X); |
|||
Assert.AreEqual(expectedY, p.Y); |
|||
Assert.AreEqual(expectedZ, p.Z); |
|||
|
|||
p = Vector3D.Parse(p.ToString()); |
|||
Assert.AreEqual(expectedX, p.X); |
|||
Assert.AreEqual(expectedY, p.Y); |
|||
Assert.AreEqual(expectedZ, p.Z); |
|||
} |
|||
|
|||
[TestCase("1.2")] |
|||
[TestCase("1,2; 2.3; 3")] |
|||
[TestCase("1; 2; 3; 4")] |
|||
public void ParseFails(string text) |
|||
{ |
|||
Assert.AreEqual(false, Vector3D.TryParse(text, out _)); |
|||
Assert.Throws<FormatException>(() => Vector3D.Parse(text)); |
|||
} |
|||
|
|||
[TestCase("<Vector3D X=\"1\" Y=\"-2\" Z=\"3\" />")] |
|||
[TestCase("<Vector3D Y=\"-2\" Z=\"3\" X=\"1\"/>")] |
|||
[TestCase("<Vector3D Z=\"3\" X=\"1\" Y=\"-2\" />")] |
|||
[TestCase("<Vector3D><X>1</X><Y>-2</Y><Z>3</Z></Vector3D>")] |
|||
[TestCase("<Vector3D><Y>-2</Y><Z>3</Z><X>1</X></Vector3D>")] |
|||
[TestCase("<Vector3D><Z>3</Z><X>1</X><Y>-2</Y></Vector3D>")] |
|||
public void ReadFrom(string xml) |
|||
{ |
|||
using (var reader = new StringReader(xml)) |
|||
{ |
|||
var actual = Vector3D.ReadFrom(XmlReader.Create(reader)); |
|||
Assert.AreEqual(new Vector3D(1, -2, 3), actual); |
|||
} |
|||
} |
|||
|
|||
[Test] |
|||
public void ToDenseVector() |
|||
{ |
|||
var v = new Vector3D(1, 2, 3); |
|||
var vector = v.ToVector(); |
|||
Assert.AreEqual(3, vector.Count); |
|||
Assert.AreEqual(1, vector[0]); |
|||
Assert.AreEqual(2, vector[1]); |
|||
Assert.AreEqual(3, vector[2]); |
|||
|
|||
var roundtripped = Vector3D.OfVector(vector); |
|||
Assert.AreEqual(1, roundtripped.X); |
|||
Assert.AreEqual(2, roundtripped.Y); |
|||
Assert.AreEqual(3, roundtripped.Z); |
|||
} |
|||
|
|||
[TestCase("1; 0 ; 0")] |
|||
[TestCase("1; 1 ; 0")] |
|||
[TestCase("1; -1 ; 0")] |
|||
public void Orthogonal(string vs) |
|||
{ |
|||
var v = Vector3D.Parse(vs); |
|||
var orthogonal = v.Orthogonal; |
|||
Assert.IsTrue(orthogonal.DotProduct(v) < 1e-6); |
|||
} |
|||
|
|||
[TestCase("0; 0 ; 0")] |
|||
public void Orthogonal_BadArgument(string vs) |
|||
{ |
|||
var v = Vector3D.Parse(vs); |
|||
#pragma warning disable SA1312 // Variable names must begin with lower-case letter
|
|||
Assert.Throws<InvalidOperationException>(() => { var _ = v.Orthogonal; }); |
|||
#pragma warning restore SA1312 // Variable names must begin with lower-case letter
|
|||
} |
|||
|
|||
[TestCase(X, Y, Z)] |
|||
[TestCase(X, "1, 1, 0", Z)] |
|||
[TestCase(X, NegativeY, NegativeZ)] |
|||
[TestCase(Y, Z, X)] |
|||
[TestCase(Y, "0.1, 0.1, 1", "1, 0, -0.1", Description = "Almost Z")] |
|||
[TestCase(Y, "-0.1, -0.1, 1", "1, 0, 0.1", Description = "Almost Z men minus")] |
|||
public void CrossProduct(string v1s, string v2s, string ves) |
|||
{ |
|||
var vector1 = Vector3D.Parse(v1s); |
|||
var vector2 = Vector3D.Parse(v2s); |
|||
var expected = Vector3D.Parse(ves); |
|||
var crossProduct = vector1.CrossProduct(vector2); |
|||
AssertGeometry.AreEqual(expected, crossProduct, 1E-6); |
|||
} |
|||
|
|||
[TestCase(X, Y, Z, 90)] |
|||
[TestCase(X, X, Z, 0)] |
|||
[TestCase(X, NegativeY, Z, -90)] |
|||
[TestCase(X, NegativeX, Z, 180)] |
|||
public void SignedAngleTo(string fromString, string toString, string axisString, double degreeAngle) |
|||
{ |
|||
var fromVector = Vector3D.Parse(fromString); |
|||
var toVector = Vector3D.Parse(toString); |
|||
var aboutVector = Vector3D.Parse(axisString); |
|||
Assert.AreEqual(degreeAngle, fromVector.SignedAngleTo(toVector, aboutVector.Normalize()).Degrees, 1E-6); |
|||
} |
|||
|
|||
[TestCase("1; 0; 1", Y, "-1; 0; 1", "90°")] |
|||
public void SignedAngleToArbitraryVector(string fromString, string toString, string axisString, string @as) |
|||
{ |
|||
var fromVector = Vector3D.Parse(fromString); |
|||
var toVector = Vector3D.Parse(toString); |
|||
var aboutVector = Vector3D.Parse(axisString); |
|||
var angle = Angle.Parse(@as); |
|||
Assert.AreEqual(angle.Degrees, fromVector.SignedAngleTo(toVector.Normalize(), aboutVector.Normalize()).Degrees, 1E-6); |
|||
} |
|||
|
|||
[TestCase(X, 5)] |
|||
[TestCase(Y, 5)] |
|||
[TestCase("1; 1; 0", 5)] |
|||
[TestCase("1; 0; 1", 5)] |
|||
[TestCase("0; 1; 1", 5)] |
|||
[TestCase("1; 1; 1", 5)] |
|||
[TestCase(X, 90)] |
|||
[TestCase(Y, 90)] |
|||
[TestCase("1; 1; 0", 90)] |
|||
[TestCase("1; 0; 1", 90)] |
|||
[TestCase("0; 1; 1", 90)] |
|||
[TestCase("1; 1; 1", 90)] |
|||
[TestCase("1; 0; 1", -90)] |
|||
[TestCase("1; 0; 1", 180)] |
|||
[TestCase("1; 0; 1", 0)] |
|||
public void SignedAngleTo_RotationAroundZ(string vectorDoubles, double rotationInDegrees) |
|||
{ |
|||
var vector = Vector3D.Parse(vectorDoubles); |
|||
var angle = Angle.FromDegrees(rotationInDegrees); |
|||
var rotated = Vector3D.OfVector(Matrix3D.RotationAroundZAxis(angle).Multiply(vector.ToVector())); |
|||
var actual = vector.SignedAngleTo(rotated, Vector3D.Parse(Z).Normalize()); |
|||
Assert.AreEqual(rotationInDegrees, actual.Degrees, 1E-6); |
|||
} |
|||
|
|||
[TestCase(X, Z, 90, Y)] |
|||
public void Rotate(string vs, string avs, double deg, string evs) |
|||
{ |
|||
var v = Vector3D.Parse(vs); |
|||
var about = Vector3D.Parse(avs); |
|||
var expected = Vector3D.Parse(evs); |
|||
var rotated = v.Rotate(about, Angle.FromDegrees(deg)); |
|||
AssertGeometry.AreEqual(expected, rotated, 1E-6); |
|||
|
|||
rotated = v.Rotate(about.Normalize(), Angle.FromDegrees(deg)); |
|||
AssertGeometry.AreEqual(expected, rotated, 1E-6); |
|||
} |
|||
|
|||
[TestCase("X", X)] |
|||
[TestCase("Y", Y)] |
|||
[TestCase("Z", Z)] |
|||
public void SignedAngleTo_Itself(string axisDummy, string aboutDoubles) |
|||
{ |
|||
var vector = new Vector3D(1, 1, 1); |
|||
var aboutVector = Vector3D.Parse(aboutDoubles); |
|||
var angle = vector.SignedAngleTo(vector, aboutVector.Normalize()); |
|||
Assert.AreEqual(0, angle.Degrees, 1E-6); |
|||
} |
|||
|
|||
[TestCase(X, Y, "90°")] |
|||
[TestCase(Y, X, "90°")] |
|||
[TestCase(X, Z, "90°")] |
|||
[TestCase(Z, X, "90°")] |
|||
[TestCase(Y, Z, "90°")] |
|||
[TestCase(Z, Y, "90°")] |
|||
[TestCase(X, X, "0°")] |
|||
[TestCase(Y, Y, "0°")] |
|||
[TestCase(Z, Z, "0°")] |
|||
[TestCase(X, NegativeY, "90°")] |
|||
[TestCase(Y, NegativeY, "180°")] |
|||
[TestCase(Z, NegativeZ, "180°")] |
|||
[TestCase("1; 1; 0", X, "45°")] |
|||
[TestCase("1; 1; 0", Y, "45°")] |
|||
[TestCase("1; 1; 0", Z, "90°")] |
|||
[TestCase("2; 2; 0", "0; 0; 2", "90°")] |
|||
[TestCase("1; 1; 1", X, "54.74°")] |
|||
[TestCase("1; 1; 1", Y, "54.74°")] |
|||
[TestCase("1; 1; 1", Z, "54.74°")] |
|||
[TestCase("1; 0; 0", "1; 0; 0", "0°")] |
|||
[TestCase("-1; -1; 1", "-1; -1; 1", "0°")] |
|||
[TestCase("1; 1; 1", "-1; -1; -1", "180°")] |
|||
public void AngleTo(string v1s, string v2s, string ea) |
|||
{ |
|||
var v1 = Vector3D.Parse(v1s); |
|||
var v2 = Vector3D.Parse(v2s); |
|||
var angles = new[] |
|||
{ |
|||
v1.AngleTo(v2), |
|||
v2.AngleTo(v1) |
|||
}; |
|||
var expected = Angle.Parse(ea); |
|||
foreach (var angle in angles) |
|||
{ |
|||
Assert.AreEqual(expected.Radians, angle.Radians, 1E-2); |
|||
} |
|||
} |
|||
|
|||
[TestCase("5; 0; 0", "1; 0 ; 0")] |
|||
[TestCase("-5; 0; 0", "-1; 0 ; 0")] |
|||
[TestCase("0; 5; 0", "0; 1 ; 0")] |
|||
[TestCase("0; -5; 0", "0; -1 ; 0")] |
|||
[TestCase("0; 0; 5", "0; 0 ; 1")] |
|||
[TestCase("0; 0; -5", "0; 0 ; -1")] |
|||
[TestCase("2; 2; 2", "0,577350269189626; 0,577350269189626; 0,577350269189626")] |
|||
[TestCase("-2; 15; 2", "-0,131024356416084; 0,982682673120628; 0,131024356416084")] |
|||
public void Normalize(string vs, string evs) |
|||
{ |
|||
var vector = Vector3D.Parse(vs); |
|||
var uv = vector.Normalize(); |
|||
var expected = UnitVector3D.Parse(evs); |
|||
AssertGeometry.AreEqual(expected, uv, 1E-6); |
|||
} |
|||
|
|||
[TestCase("0; 0; 0", "0; 0 ; 0")] |
|||
public void Normalize_BadArgument(string vs, string evs) |
|||
{ |
|||
var vector = Vector3D.Parse(vs); |
|||
//// ReSharper disable once ReturnValueOfPureMethodIsNotUsed
|
|||
Assert.Throws<InvalidOperationException>(() => vector.Normalize()); |
|||
} |
|||
|
|||
[TestCase("1, -1, 10", 5, "5, -5, 50")] |
|||
public void Scale(string vs, double s, string evs) |
|||
{ |
|||
var v = Vector3D.Parse(vs); |
|||
var actual = v.ScaleBy(s); |
|||
AssertGeometry.AreEqual(Vector3D.Parse(evs), actual, 1e-6); |
|||
} |
|||
|
|||
[TestCase("5;0;0", 5)] |
|||
[TestCase("-5;0;0", 5)] |
|||
[TestCase("-3;0;4", 5)] |
|||
public void Length(string vectorString, double length) |
|||
{ |
|||
var vector = Vector3D.Parse(vectorString); |
|||
Assert.AreEqual(length, vector.Length); |
|||
} |
|||
|
|||
[TestCase(X, X, true)] |
|||
[TestCase(X, NegativeX, true)] |
|||
[TestCase(Y, Y, true)] |
|||
[TestCase(Y, NegativeY, true)] |
|||
[TestCase(Z, NegativeZ, true)] |
|||
[TestCase(Z, Z, true)] |
|||
[TestCase("1;-8;7", "1;-8;7", true)] |
|||
[TestCase(X, "1;-8;7", false)] |
|||
[TestCase("1;-1.2;0", Z, false)] |
|||
public void IsParallelTo(string vector1, string vector2, bool expected) |
|||
{ |
|||
var v1 = Vector3D.Parse(vector1); |
|||
var v2 = Vector3D.Parse(vector2); |
|||
Assert.AreEqual(true, v1.IsParallelTo(v1, 1E-6)); |
|||
Assert.AreEqual(true, v2.IsParallelTo(v2, 1E-6)); |
|||
Assert.AreEqual(expected, v1.IsParallelTo(v2, 1E-6)); |
|||
Assert.AreEqual(expected, v2.IsParallelTo(v1, 1E-6)); |
|||
} |
|||
|
|||
[TestCase("0,1,0", "0,1, 0", 1e-10, true)] |
|||
[TestCase("0,1,0", "0,-1, 0", 1e-10, true)] |
|||
[TestCase("0,1,0", "0,1, 1", 1e-10, false)] |
|||
[TestCase("0,1,1", "0,1, 1", 1e-10, true)] |
|||
[TestCase("0,1,-1", "0,-1, 1", 1e-10, true)] |
|||
[TestCase("0,1,0", "0,1, 0.001", 1e-10, false)] |
|||
[TestCase("0,1,0", "0,1, -0.001", 1e-10, false)] |
|||
[TestCase("0,-1,0", "0,1, 0.001", 1e-10, false)] |
|||
[TestCase("0,-1,0", "0,1, -0.001", 1e-10, false)] |
|||
[TestCase("0,1,0", "0,1, 0.001", 1e-6, true, Description = "These test cases demonstrate the effect of the tolerance")] |
|||
[TestCase("0,1,0", "0,1, -0.001", 1e-6, true, Description = "These test cases demonstrate the effect of the tolerance")] |
|||
[TestCase("0,-1,0", "0,1, 0.001", 1e-6, true, Description = "These test cases demonstrate the effect of the tolerance")] |
|||
[TestCase("0,-1,0", "0,1, -0.001", 1e-6, true, Description = "These test cases demonstrate the effect of the tolerance")] |
|||
[TestCase("0,1,0.5", "0,-1, -0.5", 1e-10, true)] |
|||
public void IsParallelToByDoubleTolerance(string v1s, string v2s, double tolerance, bool expected) |
|||
{ |
|||
var v1 = Vector3D.Parse(v1s); |
|||
var v2 = Vector3D.Parse(v2s); |
|||
Assert.AreEqual(expected, v1.IsParallelTo(v2, tolerance)); |
|||
Assert.AreEqual(expected, v2.IsParallelTo(v1, tolerance)); |
|||
} |
|||
|
|||
[TestCase("0,1,0", "0,1, 0", 1e-10, true)] |
|||
[TestCase("0,1,0", "0,-1, 0", 1e-10, true)] |
|||
[TestCase("0,1,0", "0,1, 1", 1e-10, false)] |
|||
[TestCase("0,1,1", "0,1, 1", 1e-10, true)] |
|||
[TestCase("0,1,-1", "0,-1, 1", 1e-10, true)] |
|||
[TestCase("0,1,0", "0,1, 0.001", 1e-10, false)] |
|||
[TestCase("0,1,0", "0,1, -0.001", 1e-10, false)] |
|||
[TestCase("0,-1,0", "0,1, 0.001", 1e-10, false)] |
|||
[TestCase("0,-1,0", "0,1, -0.001", 1e-10, false)] |
|||
[TestCase("0,1,0", "0,1, 0.001", 1e-6, true, Description = "These test cases demonstrate the effect of the tolerance")] |
|||
[TestCase("0,1,0", "0,1, -0.001", 1e-6, true, Description = "These test cases demonstrate the effect of the tolerance")] |
|||
[TestCase("0,-1,0", "0,1, 0.001", 1e-6, true, Description = "These test cases demonstrate the effect of the tolerance")] |
|||
[TestCase("0,-1,0", "0,1, -0.001", 1e-6, true, Description = "These test cases demonstrate the effect of the tolerance")] |
|||
[TestCase("0,1,0.5", "0,-1, -0.5", 1e-10, true)] |
|||
public void IsParallelToUnitVectorByDoubleTolerance(string v1s, string v2s, double tolerance, bool expected) |
|||
{ |
|||
var v1 = Vector3D.Parse(v1s); |
|||
var v2 = Vector3D.Parse(v2s).Normalize(); |
|||
Assert.AreEqual(expected, v1.IsParallelTo(v2, tolerance)); |
|||
Assert.AreEqual(expected, v2.IsParallelTo(v1, tolerance)); |
|||
} |
|||
|
|||
[TestCase("0,1,0", "0,1, 0", 1e-4, true)] |
|||
[TestCase("0,1,0", "0,-1, 0", 1e-4, true)] |
|||
[TestCase("0,1,0", "0,1, 1", 1e-4, false)] |
|||
[TestCase("0,1,1", "0,1, 1", 1e-4, true)] |
|||
[TestCase("0,1,-1", "0,-1, 1", 1e-4, true)] |
|||
[TestCase("0,1,0", "0,1, 0.001", 0.06, true)] |
|||
[TestCase("0,1,0", "0,1, -0.001", 0.06, true)] |
|||
[TestCase("0,-1,0", "0,1, 0.001", 0.06, true)] |
|||
[TestCase("0,-1,0", "0,1, -0.001", 0.06, true)] |
|||
[TestCase("0,1,0", "0,1, 0.001", 0.05, false)] |
|||
[TestCase("0,1,0", "0,1, -0.001", 0.05, false)] |
|||
[TestCase("0,-1,0", "0,1, 0.001", 0.05, false)] |
|||
[TestCase("0,-1,0", "0,1, -0.001", 0.05, false)] |
|||
[TestCase("0,1,0.5", "0,-1, -0.5", 1e-4, true)] |
|||
public void IsParallelToByAngleTolerance(string v1s, string v2s, double degreesTolerance, bool expected) |
|||
{ |
|||
var v1 = Vector3D.Parse(v1s); |
|||
var v2 = Vector3D.Parse(v2s); |
|||
Assert.AreEqual(expected, v1.IsParallelTo(v2, Angle.FromDegrees(degreesTolerance))); |
|||
Assert.AreEqual(expected, v2.IsParallelTo(v1, Angle.FromDegrees(degreesTolerance))); |
|||
} |
|||
|
|||
[TestCase("0,1,0", "0,1, 0", 1e-4, true)] |
|||
[TestCase("0,1,0", "0,-1, 0", 1e-4, true)] |
|||
[TestCase("0,1,0", "0,1, 1", 1e-4, false)] |
|||
[TestCase("0,1,1", "0,1, 1", 1e-4, true)] |
|||
[TestCase("0,1,-1", "0,-1, 1", 1e-4, true)] |
|||
[TestCase("0,1,0", "0,1, 0.001", 0.06, true)] |
|||
[TestCase("0,1,0", "0,1, -0.001", 0.06, true)] |
|||
[TestCase("0,-1,0", "0,1, 0.001", 0.06, true)] |
|||
[TestCase("0,-1,0", "0,1, -0.001", 0.06, true)] |
|||
[TestCase("0,1,0", "0,1, 0.001", 0.05, false)] |
|||
[TestCase("0,1,0", "0,1, -0.001", 0.05, false)] |
|||
[TestCase("0,-1,0", "0,1, 0.001", 0.05, false)] |
|||
[TestCase("0,-1,0", "0,1, -0.001", 0.05, false)] |
|||
[TestCase("0,1,0.5", "0,-1, -0.5", 1e-4, true)] |
|||
public void IsParallelToUnitVectorByAngleTolerance(string v1s, string v2s, double degreesTolerance, bool expected) |
|||
{ |
|||
var v1 = Vector3D.Parse(v1s); |
|||
var v2 = Vector3D.Parse(v2s).Normalize(); |
|||
Assert.AreEqual(expected, v1.IsParallelTo(v2, Angle.FromDegrees(degreesTolerance))); |
|||
Assert.AreEqual(expected, v2.IsParallelTo(v1, Angle.FromDegrees(degreesTolerance))); |
|||
} |
|||
|
|||
[TestCase(X, X, false)] |
|||
[TestCase(NegativeX, X, false)] |
|||
[TestCase("-11;0;0", X, false)] |
|||
[TestCase("1;1;0", X, false)] |
|||
[TestCase(X, Y, true)] |
|||
[TestCase(X, Z, true)] |
|||
[TestCase(Y, X, true)] |
|||
[TestCase(Y, Z, true)] |
|||
[TestCase(Z, Y, true)] |
|||
[TestCase(Z, X, true)] |
|||
public void IsPerpendicularTo(string v1s, string v2s, bool expected) |
|||
{ |
|||
var v1 = Vector3D.Parse(v1s); |
|||
var v2 = Vector3D.Parse(v2s); |
|||
Assert.AreEqual(expected, v1.IsPerpendicularTo(v2)); |
|||
} |
|||
|
|||
[TestCase("1, 2, 3", "1, 2, 3", 1e-4, true)] |
|||
[TestCase("1, 2, 3", "4, 5, 6", 1e-4, false)] |
|||
public void Equals(string p1s, string p2s, double tol, bool expected) |
|||
{ |
|||
var v1 = Vector3D.Parse(p1s); |
|||
var v2 = Vector3D.Parse(p2s); |
|||
Assert.AreEqual(expected, v1 == v2); |
|||
Assert.AreEqual(expected, v1.Equals(v2)); |
|||
Assert.AreEqual(expected, v1.Equals((object)v2)); |
|||
Assert.AreEqual(expected, Equals(v1, v2)); |
|||
Assert.AreEqual(expected, v1.Equals(v2, tol)); |
|||
Assert.AreNotEqual(expected, v1 != v2); |
|||
} |
|||
|
|||
[TestCase("-2, 0, 1e-4", null, "(-2, 0, 0.0001)", 1e-4)] |
|||
[TestCase("-2, 0, 1e-4", "F2", "(-2.00, 0.00, 0.00)", 1e-4)] |
|||
public void ToString(string vs, string format, string expected, double tolerance) |
|||
{ |
|||
var v = Vector3D.Parse(vs); |
|||
var actual = v.ToString(format); |
|||
Assert.AreEqual(expected, actual); |
|||
AssertGeometry.AreEqual(v, Vector3D.Parse(actual), tolerance); |
|||
} |
|||
|
|||
[Test] |
|||
public void XmlRoundtrip() |
|||
{ |
|||
var p = new Vector3D(1, -2, 3); |
|||
var xml = @"<Vector3D X=""1"" Y=""-2"" Z=""3"" />"; |
|||
AssertXml.XmlRoundTrips(p, xml, (expected, actual) => AssertGeometry.AreEqual(expected, actual)); |
|||
} |
|||
|
|||
[Test] |
|||
public void XmlContainerRoundtrip() |
|||
{ |
|||
var container = new AssertXml.Container<Vector3D> |
|||
{ |
|||
Value1 = new Vector3D(1, 2, 3), |
|||
Value2 = new Vector3D(4, 5, 6) |
|||
}; |
|||
var expected = "<ContainerOfVector3D>\r\n" + |
|||
" <Value1 X=\"1\" Y=\"2\" Z=\"3\"></Value1>\r\n" + |
|||
" <Value2 X=\"4\" Y=\"5\" Z=\"6\"></Value2>\r\n" + |
|||
"</ContainerOfVector3D>"; |
|||
var roundTrip = AssertXml.XmlSerializerRoundTrip(container, expected); |
|||
AssertGeometry.AreEqual(container.Value1, roundTrip.Value1); |
|||
AssertGeometry.AreEqual(container.Value2, roundTrip.Value2); |
|||
} |
|||
|
|||
[Test] |
|||
public void XmlElements() |
|||
{ |
|||
var v = new Vector3D(1, 2, 3); |
|||
var serializer = new XmlSerializer(typeof(Vector3D)); |
|||
AssertGeometry.AreEqual(v, (Vector3D)serializer.Deserialize(new StringReader(@"<Vector3D><X>1</X><Y>2</Y><Z>3</Z></Vector3D>"))); |
|||
} |
|||
|
|||
[Test] |
|||
public void XmlContainerElements() |
|||
{ |
|||
var xml = "<ContainerOfVector3D>\r\n" + |
|||
" <Value1><X>1</X><Y>2</Y><Z>3</Z></Value1>\r\n" + |
|||
" <Value2><X>4</X><Y>5</Y><Z>6</Z></Value2>\r\n" + |
|||
"</ContainerOfVector3D>"; |
|||
var serializer = new XmlSerializer(typeof(AssertXml.Container<Vector3D>)); |
|||
var deserialized = (AssertXml.Container<Vector3D>)serializer.Deserialize(new StringReader(xml)); |
|||
AssertGeometry.AreEqual(new Vector3D(1, 2, 3), deserialized.Value1); |
|||
AssertGeometry.AreEqual(new Vector3D(4, 5, 6), deserialized.Value2); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,397 @@ |
|||
using System; |
|||
using System.Globalization; |
|||
using System.Xml; |
|||
using System.Xml.Schema; |
|||
using System.Xml.Serialization; |
|||
using MathNet.Numerics.Spatial.Internal; |
|||
|
|||
namespace MathNet.Numerics.Spatial |
|||
{ |
|||
/// <summary>
|
|||
/// An angle
|
|||
/// </summary>
|
|||
[Serializable] |
|||
public struct Angle : IComparable<Angle>, IEquatable<Angle>, IFormattable, IXmlSerializable |
|||
{ |
|||
/// <summary>
|
|||
/// The value in radians
|
|||
/// </summary>
|
|||
public readonly double Radians; |
|||
|
|||
/// <summary>
|
|||
/// Conversion factor for converting Radians to Degrees
|
|||
/// </summary>
|
|||
private const double RadToDeg = 180.0 / Math.PI; |
|||
|
|||
/// <summary>
|
|||
/// Conversion factor for converting Degrees to Radians
|
|||
/// </summary>
|
|||
private const double DegToRad = Math.PI / 180.0; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Angle"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="radians">The value in Radians</param>
|
|||
private Angle(double radians) |
|||
{ |
|||
this.Radians = radians; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the value in degrees
|
|||
/// </summary>
|
|||
public double Degrees => this.Radians * RadToDeg; |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether two specified Angles are equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first angle to compare</param>
|
|||
/// <param name="right">The second angle to compare</param>
|
|||
/// <returns>True if the angles are the same; otherwise false.</returns>
|
|||
public static bool operator ==(Angle left, Angle right) |
|||
{ |
|||
return left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether two specified Angles are not equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first angle to compare</param>
|
|||
/// <param name="right">The second angle to compare</param>
|
|||
/// <returns>True if the angles are different; otherwise false.</returns>
|
|||
public static bool operator !=(Angle left, Angle right) |
|||
{ |
|||
return !left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates if a specified Angles is less than another.
|
|||
/// </summary>
|
|||
/// <param name="left">The first angle to compare</param>
|
|||
/// <param name="right">The second angle to compare</param>
|
|||
/// <returns>True if the first angle is less than the second angle; otherwise false.</returns>
|
|||
public static bool operator <(Angle left, Angle right) |
|||
{ |
|||
return left.Radians < right.Radians; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates if a specified Angles is greater than another.
|
|||
/// </summary>
|
|||
/// <param name="left">The first angle to compare</param>
|
|||
/// <param name="right">The second angle to compare</param>
|
|||
/// <returns>True if the first angle is greater than the second angle; otherwise false.</returns>
|
|||
public static bool operator >(Angle left, Angle right) |
|||
{ |
|||
return left.Radians > right.Radians; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates if a specified Angles is less than or equal to another.
|
|||
/// </summary>
|
|||
/// <param name="left">The first angle to compare</param>
|
|||
/// <param name="right">The second angle to compare</param>
|
|||
/// <returns>True if the first angle is less than or equal to the second angle; otherwise false.</returns>
|
|||
public static bool operator <=(Angle left, Angle right) |
|||
{ |
|||
return left.Radians <= right.Radians; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates if a specified Angles is greater than or equal to another.
|
|||
/// </summary>
|
|||
/// <param name="left">The first angle to compare</param>
|
|||
/// <param name="right">The second angle to compare</param>
|
|||
/// <returns>True if the first angle is greater than or equal to the second angle; otherwise false.</returns>
|
|||
public static bool operator >=(Angle left, Angle right) |
|||
{ |
|||
return left.Radians >= right.Radians; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Multiplies an Angle by a scalar
|
|||
/// </summary>
|
|||
/// <param name="left">The scalar.</param>
|
|||
/// <param name="right">The angle.</param>
|
|||
/// <returns>A new angle equal to the product of the angle and the scalar.</returns>
|
|||
public static Angle operator *(double left, Angle right) |
|||
{ |
|||
return new Angle(left * right.Radians); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Multiplies an Angle by a scalar
|
|||
/// </summary>
|
|||
/// <param name="left">The angle.</param>
|
|||
/// <param name="right">The scalar.</param>
|
|||
/// <returns>A new angle equal to the product of the angle and the scalar.</returns>
|
|||
public static Angle operator *(Angle left, double right) |
|||
{ |
|||
return new Angle(left.Radians * right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Divides an Angle by a scalar
|
|||
/// </summary>
|
|||
/// <param name="left">The angle.</param>
|
|||
/// <param name="right">The scalar.</param>
|
|||
/// <returns>A new angle equal to the division of the angle by the scalar.</returns>
|
|||
public static Angle operator /(Angle left, double right) |
|||
{ |
|||
return new Angle(left.Radians / right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds two angles together
|
|||
/// </summary>
|
|||
/// <param name="left">The first angle.</param>
|
|||
/// <param name="right">The second angle.</param>
|
|||
/// <returns>A new Angle equal to the sum of the provided angles.</returns>
|
|||
public static Angle operator +(Angle left, Angle right) |
|||
{ |
|||
return new Angle(left.Radians + right.Radians); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Subtracts the second angle from the first
|
|||
/// </summary>
|
|||
/// <param name="left">The first angle.</param>
|
|||
/// <param name="right">The second angle.</param>
|
|||
/// <returns>A new Angle equal to the difference of the provided angles.</returns>
|
|||
public static Angle operator -(Angle left, Angle right) |
|||
{ |
|||
return new Angle(left.Radians - right.Radians); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Negates the angle
|
|||
/// </summary>
|
|||
/// <param name="angle">The angle to negate.</param>
|
|||
/// <returns>The negated angle.</returns>
|
|||
public static Angle operator -(Angle angle) |
|||
{ |
|||
return new Angle(-1 * angle.Radians); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Attempts to convert a string into an <see cref="Angle"/>
|
|||
/// </summary>
|
|||
/// <param name="text">The string to be converted</param>
|
|||
/// <param name="result">Am <see cref="Angle"/></param>
|
|||
/// <returns>True if <paramref name="text"/> could be parsed.</returns>
|
|||
public static bool TryParse(string text, out Angle result) |
|||
{ |
|||
return TryParse(text, null, out result); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Attempts to convert a string into an <see cref="Angle"/>
|
|||
/// </summary>
|
|||
/// <param name="text">The string to be converted</param>
|
|||
/// <param name="formatProvider">The <see cref="IFormatProvider"/></param>
|
|||
/// <param name="result">An <see cref="Angle"/></param>
|
|||
/// <returns>True if <paramref name="text"/> could be parsed.</returns>
|
|||
public static bool TryParse(string text, IFormatProvider formatProvider, out Angle result) |
|||
{ |
|||
if (Text.TryParseAngle(text, formatProvider, out result)) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
result = default(Angle); |
|||
return false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Attempts to convert a string into an <see cref="Angle"/>
|
|||
/// </summary>
|
|||
/// <param name="value">The string to be converted</param>
|
|||
/// <param name="formatProvider">The <see cref="IFormatProvider"/></param>
|
|||
/// <returns>An <see cref="Angle"/></returns>
|
|||
public static Angle Parse(string value, IFormatProvider formatProvider = null) |
|||
{ |
|||
if (TryParse(value, formatProvider, out var p)) |
|||
{ |
|||
return p; |
|||
} |
|||
|
|||
throw new FormatException($"Could not parse an Angle from the string {value}"); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a new instance of Angle.
|
|||
/// </summary>
|
|||
/// <param name="value">The value in degrees.</param>
|
|||
/// <returns> A new instance of the <see cref="Angle"/> struct.</returns>
|
|||
public static Angle FromDegrees(double value) |
|||
{ |
|||
return new Angle(value * DegToRad); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a new instance of Angle.
|
|||
/// </summary>
|
|||
/// <param name="value">The value in radians.</param>
|
|||
/// <returns> A new instance of the <see cref="Angle"/> struct.</returns>
|
|||
public static Angle FromRadians(double value) |
|||
{ |
|||
return new Angle(value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a new instance of Angle from the sexagesimal format of the angle in degrees, minutes, seconds
|
|||
/// </summary>
|
|||
/// <param name="degrees">The degrees of the angle</param>
|
|||
/// <param name="minutes">The minutes of the angle</param>
|
|||
/// <param name="seconds">The seconds of the angle</param>
|
|||
/// <returns>A new instance of the <see cref="Angle"/> struct.</returns>
|
|||
public static Angle FromSexagesimal(int degrees, int minutes, double seconds) |
|||
{ |
|||
return Angle.FromDegrees(degrees + (minutes / 60.0F) + (seconds / 3600.0F)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates an <see cref="Angle"/> from an <see cref="XmlReader"/>.
|
|||
/// </summary>
|
|||
/// <param name="reader">An <see cref="XmlReader"/> positioned at the node to read into this <see cref="Angle"/>.</param>
|
|||
/// <returns>An <see cref="Angle"/> that contains the data read from the reader.</returns>
|
|||
public static Angle ReadFrom(XmlReader reader) |
|||
{ |
|||
return reader.ReadElementAs<Angle>(); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public override string ToString() |
|||
{ |
|||
return this.ToString(null, NumberFormatInfo.CurrentInfo); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a string representation of the Angle using the provided format
|
|||
/// </summary>
|
|||
/// <param name="format">a string indicating the desired format of the double.</param>
|
|||
/// <returns>The string representation of this instance.</returns>
|
|||
public string ToString(string format) |
|||
{ |
|||
return this.ToString(format, NumberFormatInfo.CurrentInfo); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a string representation of this instance using the provided <see cref="IFormatProvider"/>
|
|||
/// </summary>
|
|||
/// <param name="provider">A <see cref="IFormatProvider"/></param>
|
|||
/// <returns>The string representation of this instance.</returns>
|
|||
public string ToString(IFormatProvider provider) |
|||
{ |
|||
return this.ToString(null, NumberFormatInfo.GetInstance(provider)); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public string ToString(string format, IFormatProvider provider) |
|||
{ |
|||
return $"{this.Radians.ToString(format, provider)}\u00A0rad"; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public int CompareTo(Angle value) |
|||
{ |
|||
return this.Radians.CompareTo(value.Radians); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value indicating whether this instance is equal to a specified <see cref="T:MathNet.Numerics.Spatial.Angle"/> object within the given tolerance.
|
|||
/// </summary>
|
|||
/// <returns>
|
|||
/// true if <paramref name="other"/> represents the same angle as this instance; otherwise, false.
|
|||
/// </returns>
|
|||
/// <param name="other">An <see cref="T:MathNet.Numerics.Spatial.Angle"/> object to compare with this instance.</param>
|
|||
/// <param name="tolerance">The maximum difference for being considered equal</param>
|
|||
public bool Equals(Angle other, double tolerance) |
|||
{ |
|||
return Math.Abs(this.Radians - other.Radians) < tolerance; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value indicating whether this instance is equal to a specified <see cref="T:MathNet.Numerics.Spatial.Angle"/> object within the given tolerance.
|
|||
/// </summary>
|
|||
/// <returns>
|
|||
/// true if <paramref name="other"/> represents the same angle as this instance; otherwise, false.
|
|||
/// </returns>
|
|||
/// <param name="other">An <see cref="T:MathNet.Numerics.Spatial.Angle"/> object to compare with this instance.</param>
|
|||
/// <param name="tolerance">The maximum difference for being considered equal</param>
|
|||
public bool Equals(Angle other, Angle tolerance) |
|||
{ |
|||
return Math.Abs(this.Radians - other.Radians) < tolerance.Radians; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public bool Equals(Angle other) => this.Radians.Equals(other.Radians); |
|||
|
|||
/// <inheritdoc />
|
|||
public override bool Equals(object obj) => obj is Angle a && this.Equals(a); |
|||
|
|||
/// <inheritdoc />
|
|||
public override int GetHashCode() => HashCode.Combine(this.Radians); |
|||
|
|||
/// <inheritdoc />
|
|||
XmlSchema IXmlSerializable.GetSchema() |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
void IXmlSerializable.ReadXml(XmlReader reader) |
|||
{ |
|||
if (reader.TryReadAttributeAsDouble("Value", out var value) || |
|||
reader.TryReadAttributeAsDouble("Radians", out value)) |
|||
{ |
|||
reader.Skip(); |
|||
this = FromRadians(value); |
|||
return; |
|||
} |
|||
|
|||
if (reader.TryReadAttributeAsDouble("Degrees", out value)) |
|||
{ |
|||
reader.Skip(); |
|||
this = FromDegrees(value); |
|||
return; |
|||
} |
|||
|
|||
if (reader.Read()) |
|||
{ |
|||
if (reader.HasValue) |
|||
{ |
|||
this = FromRadians(reader.ReadContentAsDouble()); |
|||
reader.Skip(); |
|||
return; |
|||
} |
|||
|
|||
if (reader.MoveToContent() == XmlNodeType.Element) |
|||
{ |
|||
if (reader.TryReadElementContentAsDouble("Value", out value) || |
|||
reader.TryReadElementContentAsDouble("Radians", out value)) |
|||
{ |
|||
reader.Skip(); |
|||
this = FromRadians(value); |
|||
return; |
|||
} |
|||
|
|||
if (reader.TryReadElementContentAsDouble("Degrees", out value)) |
|||
{ |
|||
reader.Skip(); |
|||
this = FromDegrees(value); |
|||
return; |
|||
} |
|||
} |
|||
} |
|||
|
|||
throw new XmlException("Could not read an Angle"); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
void IXmlSerializable.WriteXml(XmlWriter writer) |
|||
{ |
|||
writer.WriteAttribute("Value", this.Radians); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,137 @@ |
|||
using System; |
|||
using System.Diagnostics.Contracts; |
|||
using MathNet.Numerics.Spatial.Internal; |
|||
|
|||
namespace MathNet.Numerics.Spatial.Euclidean2D |
|||
{ |
|||
/// <summary>
|
|||
/// Describes a standard 2 dimensional circle
|
|||
/// </summary>
|
|||
public struct Circle2D : IEquatable<Circle2D> |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Circle2D"/> struct.
|
|||
/// Creates a Circle of a given radius from a center point
|
|||
/// </summary>
|
|||
/// <param name="center">The location of the center</param>
|
|||
/// <param name="radius">The radius of the circle</param>
|
|||
public Circle2D(Point2D center, double radius) |
|||
{ |
|||
this.Center = center; |
|||
this.Radius = radius; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the center point of the circle
|
|||
/// </summary>
|
|||
public Point2D Center { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the radius of the circle
|
|||
/// </summary>
|
|||
public double Radius { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the circumference of the circle
|
|||
/// </summary>
|
|||
[Pure] |
|||
public double Circumference => 2 * this.Radius * Math.PI; |
|||
|
|||
/// <summary>
|
|||
/// Gets the diameter of the circle
|
|||
/// </summary>
|
|||
[Pure] |
|||
public double Diameter => 2 * this.Radius; |
|||
|
|||
/// <summary>
|
|||
/// Gets the area of the circle
|
|||
/// </summary>
|
|||
[Pure] |
|||
public double Area => this.Radius * this.Radius * Math.PI; |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether each pair of elements in two specified circles is equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first circle to compare.</param>
|
|||
/// <param name="right">The second circle to compare.</param>
|
|||
/// <returns>True if the circles are the same; otherwise false.</returns>
|
|||
public static bool operator ==(Circle2D left, Circle2D right) |
|||
{ |
|||
return left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether any pair of elements in two specified circles is not equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first circle to compare.</param>
|
|||
/// <param name="right">The second circle to compare</param>
|
|||
/// <returns>True if the circles are different; otherwise false.</returns>
|
|||
public static bool operator !=(Circle2D left, Circle2D right) |
|||
{ |
|||
return !left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a <see cref="Circle2D"/> circle from three points which lie along its circumference.
|
|||
/// Points may not be collinear
|
|||
/// </summary>
|
|||
/// <param name="pointA">The first point on the circle.</param>
|
|||
/// <param name="pointB">The second point on the circle.</param>
|
|||
/// <param name="pointC">The third point on the circle.</param>
|
|||
/// <returns>A Circle which is defined by the three specified points</returns>
|
|||
/// <exception cref="ArgumentException">An exception is thrown if no possible circle can be formed from the points</exception>
|
|||
public static Circle2D FromPoints(Point2D pointA, Point2D pointB, Point2D pointC) |
|||
{ |
|||
// ReSharper disable InconsistentNaming
|
|||
var midpointAB = Point2D.MidPoint(pointA, pointB); |
|||
var midpointBC = Point2D.MidPoint(pointB, pointC); |
|||
var gradientAB = (pointB.Y - pointA.Y) / (pointB.X - pointA.X); |
|||
var gradientBC = (pointC.Y - pointB.Y) / (pointC.X - pointB.X); |
|||
var gradientl1 = -1 / gradientAB; |
|||
var gradientl2 = -1 / gradientBC; |
|||
|
|||
// ReSharper restore InconsistentNaming
|
|||
var denominator = gradientl2 - gradientl1; |
|||
var nominator = midpointAB.Y - (gradientl1 * midpointAB.X) + (gradientl2 * midpointBC.X) - midpointBC.Y; |
|||
var centerX = nominator / denominator; |
|||
var centerY = (gradientl1 * (centerX - midpointAB.X)) + midpointAB.Y; |
|||
var center = new Point2D(centerX, centerY); |
|||
|
|||
if (double.IsNaN(center.X) || double.IsNaN(center.Y) || double.IsInfinity(center.X) || double.IsInfinity(center.Y)) |
|||
{ |
|||
throw new ArgumentException("Points cannot form a circle, are they collinear?"); |
|||
} |
|||
|
|||
return new Circle2D(center, center.DistanceTo(pointA)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value to indicate if a pair of circles are equal
|
|||
/// </summary>
|
|||
/// <param name="c">The circle to compare against.</param>
|
|||
/// <param name="tolerance">A tolerance (epsilon) to adjust for floating point error</param>
|
|||
/// <returns>true if the points are equal; otherwise false</returns>
|
|||
[Pure] |
|||
public bool Equals(Circle2D c, double tolerance) |
|||
{ |
|||
if (tolerance < 0) |
|||
{ |
|||
throw new ArgumentException("epsilon < 0"); |
|||
} |
|||
|
|||
return Math.Abs(c.Radius - this.Radius) < tolerance && this.Center.Equals(c.Center, tolerance); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public bool Equals(Circle2D c) => this.Radius.Equals(c.Radius) && this.Center.Equals(c.Center); |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override bool Equals(object obj) => obj is Circle2D c && this.Equals(c); |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override int GetHashCode() => HashCode.Combine(this.Center, this.Radius); |
|||
} |
|||
} |
|||
@ -0,0 +1,270 @@ |
|||
using System; |
|||
using System.Diagnostics.Contracts; |
|||
|
|||
namespace MathNet.Numerics.Spatial.Euclidean2D |
|||
{ |
|||
/// <summary>
|
|||
/// This structure represents a line between two points in 2-space. It allows for operations such as
|
|||
/// computing the length, direction, projections to, comparisons, and shifting by a vector.
|
|||
/// </summary>
|
|||
public struct Line2D : IEquatable<Line2D> |
|||
{ |
|||
/// <summary>
|
|||
/// The starting point of the line segment
|
|||
/// </summary>
|
|||
public readonly Point2D StartPoint; |
|||
|
|||
/// <summary>
|
|||
/// The end point of the line segment
|
|||
/// </summary>
|
|||
public readonly Point2D EndPoint; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Line2D"/> struct.
|
|||
/// Throws an ArgumentException if the <paramref name="startPoint"/> is equal to the <paramref name="endPoint"/>.
|
|||
/// </summary>
|
|||
/// <param name="startPoint">the starting point of the line segment.</param>
|
|||
/// <param name="endPoint">the ending point of the line segment</param>
|
|||
public Line2D(Point2D startPoint, Point2D endPoint) |
|||
{ |
|||
if (startPoint == endPoint) |
|||
{ |
|||
throw new ArgumentException("The Line2D starting and ending points cannot be identical"); |
|||
} |
|||
|
|||
this.StartPoint = startPoint; |
|||
this.EndPoint = endPoint; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the distance from <see cref="StartPoint"/> to <see cref="EndPoint"/>
|
|||
/// </summary>
|
|||
[Pure] |
|||
public double Length => this.StartPoint.DistanceTo(this.EndPoint); |
|||
|
|||
/// <summary>
|
|||
/// Gets a normalized vector in the direction from <see cref="StartPoint"/> to <see cref="EndPoint"/>
|
|||
/// </summary>
|
|||
[Pure] |
|||
public Vector2D Direction => this.StartPoint.VectorTo(this.EndPoint).Normalize(); |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether each pair of elements in two specified lines is equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first line to compare</param>
|
|||
/// <param name="right">The second line to compare</param>
|
|||
/// <returns>True if the lines are the same; otherwise false.</returns>
|
|||
public static bool operator ==(Line2D left, Line2D right) |
|||
{ |
|||
return left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether any pair of elements in two specified lines is not equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first line to compare</param>
|
|||
/// <param name="right">The second line to compare</param>
|
|||
/// <returns>True if the lines are different; otherwise false.</returns>
|
|||
public static bool operator !=(Line2D left, Line2D right) |
|||
{ |
|||
return !left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a vector to the start point and end point of the line
|
|||
/// </summary>
|
|||
/// <param name="offset">The vector to add</param>
|
|||
/// <param name="line">The line</param>
|
|||
/// <returns>A new <see cref="Line2D"/> at the adjusted points</returns>
|
|||
public static Line2D operator +(Vector2D offset, Line2D line) |
|||
{ |
|||
return new Line2D(line.StartPoint + offset, line.EndPoint + offset); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a vector to the start point and end point of the line
|
|||
/// </summary>
|
|||
/// <param name="line">The line</param>
|
|||
/// <param name="offset">The vector to add</param>
|
|||
/// <returns>A new line at the adjusted points</returns>
|
|||
public static Line2D operator +(Line2D line, Vector2D offset) |
|||
{ |
|||
return offset + line; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Subtracts a vector from the start point and end point of the line
|
|||
/// </summary>
|
|||
/// <param name="line">The line</param>
|
|||
/// <param name="offset">The vector to subtract</param>
|
|||
/// <returns>A new line at the adjusted points</returns>
|
|||
public static Line2D operator -(Line2D line, Vector2D offset) |
|||
{ |
|||
return line + (-offset); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a new <see cref="Line2D"/> from a pair of strings which represent points.
|
|||
/// See <see cref="Point2D.Parse(string, IFormatProvider)" /> for details on acceptable formats.
|
|||
/// </summary>
|
|||
/// <param name="startPointString">The string representation of the first point.</param>
|
|||
/// <param name="endPointString">The string representation of the second point.</param>
|
|||
/// <returns>A line segment from the first point to the second point.</returns>
|
|||
public static Line2D Parse(string startPointString, string endPointString) |
|||
{ |
|||
return new Line2D(Point2D.Parse(startPointString), Point2D.Parse(endPointString)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the shortest line between this line and a point.
|
|||
/// </summary>
|
|||
/// <param name="p">the point to create a line to</param>
|
|||
/// <param name="mustStartBetweenAndEnd">If false the start point can extend beyond the start and endpoint of the line</param>
|
|||
/// <returns>The shortest line between the line and the point</returns>
|
|||
[Pure] |
|||
public Line2D LineTo(Point2D p, bool mustStartBetweenAndEnd) |
|||
{ |
|||
return new Line2D(this.ClosestPointTo(p, mustStartBetweenAndEnd), p); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the closest point on the line to the given point.
|
|||
/// </summary>
|
|||
/// <param name="p">The point that the returned point is the closest point on the line to</param>
|
|||
/// <param name="mustBeOnSegment">If true the returned point is contained by the segment ends, otherwise it can be anywhere on the projected line</param>
|
|||
/// <returns>The closest point on the line to the provided point</returns>
|
|||
[Pure] |
|||
public Point2D ClosestPointTo(Point2D p, bool mustBeOnSegment) |
|||
{ |
|||
var v = this.StartPoint.VectorTo(p); |
|||
var dotProduct = v.DotProduct(this.Direction); |
|||
if (mustBeOnSegment) |
|||
{ |
|||
if (dotProduct < 0) |
|||
{ |
|||
dotProduct = 0; |
|||
} |
|||
|
|||
var l = this.Length; |
|||
if (dotProduct > l) |
|||
{ |
|||
dotProduct = l; |
|||
} |
|||
} |
|||
|
|||
var alongVector = dotProduct * this.Direction; |
|||
return this.StartPoint + alongVector; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compute the intersection between two lines with parallelism considered by the double floating point precision
|
|||
/// on the cross product of the two directions.
|
|||
/// </summary>
|
|||
/// <param name="other">The other line to compute the intersection with</param>
|
|||
/// <returns>The point at the intersection of two lines, or null if the lines are parallel.</returns>
|
|||
[Pure] |
|||
public Point2D? IntersectWith(Line2D other) |
|||
{ |
|||
if (this.IsParallelTo(other)) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
// http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
|
|||
var p = this.StartPoint; |
|||
var q = other.StartPoint; |
|||
var r = this.StartPoint.VectorTo(this.EndPoint); |
|||
var s = other.StartPoint.VectorTo(other.EndPoint); |
|||
|
|||
var t = (q - p).CrossProduct(s) / r.CrossProduct(s); |
|||
|
|||
return p + (t * r); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compute the intersection between two lines if the angle between them is greater than a specified
|
|||
/// angle tolerance.
|
|||
/// </summary>
|
|||
/// <param name="other">The other line to compute the intersection with</param>
|
|||
/// <param name="tolerance">The tolerance used when checking if the lines are parallel</param>
|
|||
/// <returns>The point at the intersection of two lines, or null if the lines are parallel.</returns>
|
|||
[Pure] |
|||
public Point2D? IntersectWith(Line2D other, Angle tolerance) |
|||
{ |
|||
if (this.IsParallelTo(other, tolerance)) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
// http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
|
|||
var p = this.StartPoint; |
|||
var q = other.StartPoint; |
|||
var r = this.StartPoint.VectorTo(this.EndPoint); |
|||
var s = other.StartPoint.VectorTo(other.EndPoint); |
|||
|
|||
var t = (q - p).CrossProduct(s) / r.CrossProduct(s); |
|||
|
|||
return p + (t * r); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Checks to determine whether or not two lines are parallel to each other, using the dot product within
|
|||
/// the double precision specified in the MathNet.Numerics package.
|
|||
/// </summary>
|
|||
/// <param name="other">The other line to check this one against</param>
|
|||
/// <returns>True if the lines are parallel, false if they are not</returns>
|
|||
[Pure] |
|||
public bool IsParallelTo(Line2D other) |
|||
{ |
|||
return this.Direction.IsParallelTo(other.Direction, Precision.DoublePrecision * 2); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Checks to determine whether or not two lines are parallel to each other within a specified angle tolerance
|
|||
/// </summary>
|
|||
/// <param name="other">The other line to check this one against</param>
|
|||
/// <param name="tolerance">If the angle between line directions is less than this value, the method returns true</param>
|
|||
/// <returns>True if the lines are parallel within the angle tolerance, false if they are not</returns>
|
|||
[Pure] |
|||
public bool IsParallelTo(Line2D other, Angle tolerance) |
|||
{ |
|||
return this.Direction.IsParallelTo(other.Direction, tolerance); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[Pure] |
|||
public override string ToString() |
|||
{ |
|||
return $"StartPoint: {this.StartPoint}, EndPoint: {this.EndPoint}"; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[Pure] |
|||
public bool Equals(Line2D other) |
|||
{ |
|||
return this.StartPoint.Equals(other.StartPoint) && this.EndPoint.Equals(other.EndPoint); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override bool Equals(object obj) |
|||
{ |
|||
if (ReferenceEquals(null, obj)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return obj is Line2D d && this.Equals(d); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override int GetHashCode() |
|||
{ |
|||
unchecked |
|||
{ |
|||
return (this.StartPoint.GetHashCode() * 397) ^ this.EndPoint.GetHashCode(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,207 @@ |
|||
using System; |
|||
using System.Diagnostics.Contracts; |
|||
using MathNet.Numerics.Spatial.Internal; |
|||
|
|||
namespace MathNet.Numerics.Spatial.Euclidean2D |
|||
{ |
|||
/// <summary>
|
|||
/// This structure represents a line between two points in 2-space. It allows for operations such as
|
|||
/// computing the length, direction, comparisons, and shifting by a vector.
|
|||
/// </summary>
|
|||
public struct LineSegment2D : IEquatable<LineSegment2D> |
|||
{ |
|||
/// <summary>
|
|||
/// The starting point of the line segment
|
|||
/// </summary>
|
|||
public readonly Point2D StartPoint; |
|||
|
|||
/// <summary>
|
|||
/// The end point of the line segment
|
|||
/// </summary>
|
|||
public readonly Point2D EndPoint; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="LineSegment2D"/> struct.
|
|||
/// Throws an ArgumentException if the <paramref name="startPoint"/> is equal to the <paramref name="endPoint"/>.
|
|||
/// </summary>
|
|||
/// <param name="startPoint">the starting point of the line segment.</param>
|
|||
/// <param name="endPoint">the ending point of the line segment</param>
|
|||
public LineSegment2D(Point2D startPoint, Point2D endPoint) |
|||
{ |
|||
if (startPoint == endPoint) |
|||
{ |
|||
throw new ArgumentException("The segment starting and ending points cannot be identical"); |
|||
} |
|||
|
|||
this.StartPoint = startPoint; |
|||
this.EndPoint = endPoint; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the distance from <see cref="StartPoint"/> to <see cref="EndPoint"/>
|
|||
/// </summary>
|
|||
[Pure] |
|||
public double Length => this.StartPoint.DistanceTo(this.EndPoint); |
|||
|
|||
/// <summary>
|
|||
/// Gets a normalized vector in the direction from <see cref="StartPoint"/> to <see cref="EndPoint"/>
|
|||
/// </summary>
|
|||
[Pure] |
|||
public Vector2D Direction => this.StartPoint.VectorTo(this.EndPoint).Normalize(); |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether each pair of elements in two specified lines is equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first line to compare</param>
|
|||
/// <param name="right">The second line to compare</param>
|
|||
/// <returns>True if the lines are the same; otherwise false.</returns>
|
|||
public static bool operator ==(LineSegment2D left, LineSegment2D right) |
|||
{ |
|||
return left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether any pair of elements in two specified lines is not equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first line to compare</param>
|
|||
/// <param name="right">The second line to compare</param>
|
|||
/// <returns>True if the lines are different; otherwise false.</returns>
|
|||
public static bool operator !=(LineSegment2D left, LineSegment2D right) |
|||
{ |
|||
return !left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a new <see cref="Line2D"/> from a pair of strings which represent points.
|
|||
/// See <see cref="Point2D.Parse(string, IFormatProvider)" /> for details on acceptable formats.
|
|||
/// </summary>
|
|||
/// <param name="startPointString">The string representation of the first point.</param>
|
|||
/// <param name="endPointString">The string representation of the second point.</param>
|
|||
/// <returns>A line segment from the first point to the second point.</returns>
|
|||
public static LineSegment2D Parse(string startPointString, string endPointString) |
|||
{ |
|||
return new LineSegment2D(Point2D.Parse(startPointString), Point2D.Parse(endPointString)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Translates a line according to a provided vector
|
|||
/// </summary>
|
|||
/// <param name="vector">A vector to apply</param>
|
|||
/// <returns>A new translated line segment</returns>
|
|||
public LineSegment2D TranslateBy(Vector2D vector) |
|||
{ |
|||
var startVector = this.StartPoint.ToVector2D().Add(vector); |
|||
var endVector = this.EndPoint.ToVector2D().Add(vector); |
|||
return new LineSegment2D(new Point2D(startVector.X, startVector.Y), new Point2D(endVector.X, endVector.Y)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a new line segment between the closest point on this line segment and a point.
|
|||
/// </summary>
|
|||
/// <param name="p">the point to create a line to</param>
|
|||
/// <returns>A line segment between the point and the nearest point on this segment.</returns>
|
|||
[Pure] |
|||
public LineSegment2D LineTo(Point2D p) |
|||
{ |
|||
return new LineSegment2D(this.ClosestPointTo(p), p); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the closest point on the line to the given point.
|
|||
/// </summary>
|
|||
/// <param name="p">The point that the returned point is the closest point on the line to</param>
|
|||
/// <returns>The closest point on the line to the provided point</returns>
|
|||
[Pure] |
|||
public Point2D ClosestPointTo(Point2D p) |
|||
{ |
|||
var v = this.StartPoint.VectorTo(p); |
|||
var dotProduct = v.DotProduct(this.Direction); |
|||
|
|||
if (dotProduct < 0) |
|||
{ |
|||
dotProduct = 0; |
|||
} |
|||
|
|||
var l = this.Length; |
|||
if (dotProduct > l) |
|||
{ |
|||
dotProduct = l; |
|||
} |
|||
|
|||
var alongVector = dotProduct * this.Direction; |
|||
return this.StartPoint + alongVector; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compute the intersection between two lines if the angle between them is greater than a specified
|
|||
/// angle tolerance.
|
|||
/// </summary>
|
|||
/// <param name="other">The other line to compute the intersection with</param>
|
|||
/// <param name="intersection">When this method returns, contains the intersection point, if the conversion succeeded, or the default point if the conversion failed.</param>
|
|||
/// <param name="tolerance">The tolerance used when checking if the lines are parallel</param>
|
|||
/// <returns>True if an intersection exists; otherwise false</returns>
|
|||
[Pure] |
|||
public bool TryIntersect(LineSegment2D other, out Point2D intersection, Angle tolerance) |
|||
{ |
|||
if (this.IsParallelTo(other, tolerance)) |
|||
{ |
|||
intersection = default(Point2D); |
|||
return false; |
|||
} |
|||
|
|||
// http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
|
|||
var p = this.StartPoint; |
|||
var q = other.StartPoint; |
|||
var r = this.StartPoint.VectorTo(this.EndPoint); |
|||
var s = other.StartPoint.VectorTo(other.EndPoint); |
|||
|
|||
var t = (q - p).CrossProduct(s) / r.CrossProduct(s); |
|||
|
|||
intersection = p + (t * r); |
|||
return true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Checks to determine whether or not two line segments are parallel to each other within a specified angle tolerance
|
|||
/// </summary>
|
|||
/// <param name="other">The other line to check this one against</param>
|
|||
/// <param name="tolerance">If the angle between line directions is less than this value, the method returns true</param>
|
|||
/// <returns>True if the lines are parallel within the angle tolerance, false if they are not</returns>
|
|||
[Pure] |
|||
public bool IsParallelTo(LineSegment2D other, Angle tolerance) |
|||
{ |
|||
return this.Direction.IsParallelTo(other.Direction, tolerance); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[Pure] |
|||
public override string ToString() |
|||
{ |
|||
return $"StartPoint: {this.StartPoint}, EndPoint: {this.EndPoint}"; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value to indicate if a pair of line segments are equal
|
|||
/// </summary>
|
|||
/// <param name="other">The line segment to compare against.</param>
|
|||
/// <param name="tolerance">A tolerance (epsilon) to adjust for floating point error</param>
|
|||
/// <returns>True if the line segments are equal; otherwise false</returns>
|
|||
[Pure] |
|||
public bool Equals(LineSegment2D other, double tolerance) |
|||
{ |
|||
return this.StartPoint.Equals(other.StartPoint, tolerance) && this.EndPoint.Equals(other.EndPoint, tolerance); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[Pure] |
|||
public bool Equals(LineSegment2D l) => this.StartPoint.Equals(l.StartPoint) && this.EndPoint.Equals(l.EndPoint); |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override bool Equals(object obj) => obj is LineSegment2D l && this.Equals(l); |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override int GetHashCode() => HashCode.Combine(this.StartPoint, this.EndPoint); |
|||
} |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
using System; |
|||
using MathNet.Numerics.LinearAlgebra.Double; |
|||
|
|||
namespace MathNet.Numerics.Spatial.Euclidean2D |
|||
{ |
|||
/// <summary>
|
|||
/// Helper class for creating matrices for manipulating 2D-elements
|
|||
/// </summary>
|
|||
public static class Matrix2D |
|||
{ |
|||
/// <summary>
|
|||
/// Creates a rotation about the z-axis
|
|||
/// </summary>
|
|||
/// <param name="rotation">The angle of rotation</param>
|
|||
/// <returns>A transform matrix</returns>
|
|||
public static DenseMatrix Rotation(Angle rotation) |
|||
{ |
|||
double c = Math.Cos(rotation.Radians); |
|||
double s = Math.Sin(rotation.Radians); |
|||
return Create(c, -s, s, c); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates an arbitrary 2D transform
|
|||
/// </summary>
|
|||
/// <param name="m11">Element at m[1,1]</param>
|
|||
/// <param name="m12">Element at m[1,2]</param>
|
|||
/// <param name="m21">Element at m[2,1]</param>
|
|||
/// <param name="m22">Element at m[2,2]</param>
|
|||
/// <returns>A transform matrix</returns>
|
|||
public static DenseMatrix Create(double m11, double m12, double m21, double m22) |
|||
{ |
|||
return new DenseMatrix(2, 2, new[] { m11, m21, m12, m22 }); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,366 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics.Contracts; |
|||
using System.Globalization; |
|||
using System.Linq; |
|||
using System.Xml; |
|||
using System.Xml.Schema; |
|||
using System.Xml.Serialization; |
|||
using MathNet.Numerics.LinearAlgebra; |
|||
using MathNet.Numerics.Spatial.Internal; |
|||
|
|||
namespace MathNet.Numerics.Spatial.Euclidean2D |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a point in 2 dimensional space
|
|||
/// </summary>
|
|||
[Serializable] |
|||
public struct Point2D : IXmlSerializable, IEquatable<Point2D>, IFormattable |
|||
{ |
|||
/// <summary>
|
|||
/// The x coordinate
|
|||
/// </summary>
|
|||
public readonly double X; |
|||
|
|||
/// <summary>
|
|||
/// The y coordinate
|
|||
/// </summary>
|
|||
public readonly double Y; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Point2D"/> struct.
|
|||
/// Creates a point for given coordinates (x, y)
|
|||
/// </summary>
|
|||
/// <param name="x">The x coordinate</param>
|
|||
/// <param name="y">The y coordinate</param>
|
|||
public Point2D(double x, double y) |
|||
{ |
|||
this.X = x; |
|||
this.Y = y; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a point at the origin (0,0)
|
|||
/// </summary>
|
|||
public static Point2D Origin => new Point2D(0, 0); |
|||
|
|||
/// <summary>
|
|||
/// Adds a point and a vector together
|
|||
/// </summary>
|
|||
/// <param name="point">A point</param>
|
|||
/// <param name="vector">A vector</param>
|
|||
/// <returns>A new point at the summed location</returns>
|
|||
public static Point2D operator +(Point2D point, Vector2D vector) |
|||
{ |
|||
return new Point2D(point.X + vector.X, point.Y + vector.Y); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Subtracts a vector from a point
|
|||
/// </summary>
|
|||
/// <param name="left">A point</param>
|
|||
/// <param name="right">A vector</param>
|
|||
/// <returns>A new point at the difference</returns>
|
|||
public static Point2D operator -(Point2D left, Vector2D right) |
|||
{ |
|||
return new Point2D(left.X - right.X, left.Y - right.Y); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Subtracts the first point from the second point
|
|||
/// </summary>
|
|||
/// <param name="left">The first point</param>
|
|||
/// <param name="right">The second point</param>
|
|||
/// <returns>A vector pointing to the difference</returns>
|
|||
public static Vector2D operator -(Point2D left, Point2D right) |
|||
{ |
|||
return new Vector2D(left.X - right.X, left.Y - right.Y); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether each pair of elements in two specified points is equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first point to compare</param>
|
|||
/// <param name="right">The second point to compare</param>
|
|||
/// <returns>True if the points are the same; otherwise false.</returns>
|
|||
public static bool operator ==(Point2D left, Point2D right) |
|||
{ |
|||
return left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether any pair of elements in two specified points is not equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first point to compare</param>
|
|||
/// <param name="right">The second point to compare</param>
|
|||
/// <returns>True if the points are different; otherwise false.</returns>
|
|||
public static bool operator !=(Point2D left, Point2D right) |
|||
{ |
|||
return !left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Point2D"/> struct.
|
|||
/// Creates a point r from origin rotated a counterclockwise from X-Axis
|
|||
/// </summary>
|
|||
/// <param name="radius">distance from origin</param>
|
|||
/// <param name="angle">the angle</param>
|
|||
/// <returns>The <see cref="Point2D"/></returns>
|
|||
public static Point2D FromPolar(double radius, Angle angle) |
|||
{ |
|||
if (radius < 0) |
|||
{ |
|||
throw new ArgumentOutOfRangeException(nameof(radius), radius, "Expected a radius greater than or equal to zero."); |
|||
} |
|||
|
|||
return new Point2D( |
|||
radius * Math.Cos(angle.Radians), |
|||
radius * Math.Sin(angle.Radians)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Attempts to convert a string of the form x,y into a point
|
|||
/// </summary>
|
|||
/// <param name="text">The string to be converted</param>
|
|||
/// <param name="result">A point at the coordinates specified</param>
|
|||
/// <returns>True if <paramref name="text"/> could be parsed.</returns>
|
|||
public static bool TryParse(string text, out Point2D result) |
|||
{ |
|||
return TryParse(text, null, out result); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Attempts to convert a string of the form x,y into a point
|
|||
/// </summary>
|
|||
/// <param name="text">The string to be converted</param>
|
|||
/// <param name="formatProvider">The <see cref="IFormatProvider"/></param>
|
|||
/// <param name="result">A point at the coordinates specified</param>
|
|||
/// <returns>True if <paramref name="text"/> could be parsed.</returns>
|
|||
public static bool TryParse(string text, IFormatProvider formatProvider, out Point2D result) |
|||
{ |
|||
if (Text.TryParse2D(text, formatProvider, out var x, out var y)) |
|||
{ |
|||
result = new Point2D(x, y); |
|||
return true; |
|||
} |
|||
|
|||
result = default(Point2D); |
|||
return false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Attempts to convert a string of the form x,y into a point
|
|||
/// </summary>
|
|||
/// <param name="value">The string to be converted</param>
|
|||
/// <param name="formatProvider">The <see cref="IFormatProvider"/></param>
|
|||
/// <returns>A point at the coordinates specified</returns>
|
|||
public static Point2D Parse(string value, IFormatProvider formatProvider = null) |
|||
{ |
|||
if (TryParse(value, formatProvider, out var p)) |
|||
{ |
|||
return p; |
|||
} |
|||
|
|||
throw new FormatException($"Could not parse a Point2D from the string {value}"); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates an <see cref="Point2D"/> from an <see cref="XmlReader"/>.
|
|||
/// </summary>
|
|||
/// <param name="reader">An <see cref="XmlReader"/> positioned at the node to read into this <see cref="Point2D"/>.</param>
|
|||
/// <returns>An <see cref="Point2D"/> that contains the data read from the reader.</returns>
|
|||
public static Point2D ReadFrom(XmlReader reader) |
|||
{ |
|||
return reader.ReadElementAs<Point2D>(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the centeroid or center of mass of any set of points
|
|||
/// </summary>
|
|||
/// <param name="points">a list of points</param>
|
|||
/// <returns>the centeroid point</returns>
|
|||
public static Point2D Centroid(IEnumerable<Point2D> points) |
|||
{ |
|||
return Centroid(points.ToArray()); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the centeroid or center of mass of any set of points
|
|||
/// </summary>
|
|||
/// <param name="points">a list of points</param>
|
|||
/// <returns>the centeroid point</returns>
|
|||
public static Point2D Centroid(params Point2D[] points) |
|||
{ |
|||
return new Point2D( |
|||
points.Average(point => point.X), |
|||
points.Average(point => point.Y)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a point midway between the provided points <paramref name="point1"/> and <paramref name="point2"/>
|
|||
/// </summary>
|
|||
/// <param name="point1">point A</param>
|
|||
/// <param name="point2">point B</param>
|
|||
/// <returns>a new point midway between the provided points</returns>
|
|||
public static Point2D MidPoint(Point2D point1, Point2D point2) |
|||
{ |
|||
return Centroid(point1, point2); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Create a new Point2D from a Math.NET Numerics vector of length 2.
|
|||
/// </summary>
|
|||
/// <param name="vector"> A vector with length 2 to populate the created instance with.</param>
|
|||
/// <returns> A <see cref="Point2D"/></returns>
|
|||
public static Point2D OfVector(Vector<double> vector) |
|||
{ |
|||
if (vector.Count != 2) |
|||
{ |
|||
throw new ArgumentException("The vector length must be 2 in order to convert it to a Point2D"); |
|||
} |
|||
|
|||
return new Point2D(vector.At(0), vector.At(1)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies a transform matrix to the point
|
|||
/// </summary>
|
|||
/// <param name="m">A transform matrix</param>
|
|||
/// <returns>A new point</returns>
|
|||
public Point2D TransformBy(Matrix<double> m) |
|||
{ |
|||
return OfVector(m.Multiply(this.ToVector())); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a vector from this point to another point
|
|||
/// </summary>
|
|||
/// <param name="otherPoint">The point to which the vector should go</param>
|
|||
/// <returns>A vector pointing to the other point.</returns>
|
|||
[Pure] |
|||
public Vector2D VectorTo(Point2D otherPoint) |
|||
{ |
|||
return otherPoint - this; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Finds the straight line distance to another point
|
|||
/// </summary>
|
|||
/// <param name="otherPoint">The other point</param>
|
|||
/// <returns>a distance measure</returns>
|
|||
[Pure] |
|||
public double DistanceTo(Point2D otherPoint) |
|||
{ |
|||
var vector = this.VectorTo(otherPoint); |
|||
return vector.Length; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts this point into a vector from the origin
|
|||
/// </summary>
|
|||
/// <returns>A vector equivalent to this point</returns>
|
|||
[Pure] |
|||
public Vector2D ToVector2D() |
|||
{ |
|||
return new Vector2D(this.X, this.Y); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Convert to a Math.NET Numerics dense vector of length 2.
|
|||
/// </summary>
|
|||
/// <returns> A <see cref="Vector{Double}"/> with the x and y values from this instance.</returns>
|
|||
[Pure] |
|||
public Vector<double> ToVector() |
|||
{ |
|||
return Vector<double>.Build.Dense(new[] { this.X, this.Y }); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override string ToString() |
|||
{ |
|||
return this.ToString(null, CultureInfo.InvariantCulture); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a string representation of this instance using the provided <see cref="IFormatProvider"/>
|
|||
/// </summary>
|
|||
/// <param name="provider">A <see cref="IFormatProvider"/></param>
|
|||
/// <returns>The string representation of this instance.</returns>
|
|||
[Pure] |
|||
public string ToString(IFormatProvider provider) |
|||
{ |
|||
return this.ToString(null, provider); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public string ToString(string format, IFormatProvider provider = null) |
|||
{ |
|||
var numberFormatInfo = provider != null ? NumberFormatInfo.GetInstance(provider) : CultureInfo.InvariantCulture.NumberFormat; |
|||
var separator = numberFormatInfo.NumberDecimalSeparator == "," ? ";" : ","; |
|||
return $"({this.X.ToString(format, numberFormatInfo)}{separator}\u00A0{this.Y.ToString(format, numberFormatInfo)})"; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value to indicate if a pair of points are equal
|
|||
/// </summary>
|
|||
/// <param name="other">The point to compare against.</param>
|
|||
/// <param name="tolerance">A tolerance (epsilon) to adjust for floating point error</param>
|
|||
/// <returns>true if the points are equal; otherwise false</returns>
|
|||
[Pure] |
|||
public bool Equals(Point2D other, double tolerance) |
|||
{ |
|||
if (tolerance < 0) |
|||
{ |
|||
throw new ArgumentException("epsilon < 0"); |
|||
} |
|||
|
|||
return Math.Abs(other.X - this.X) < tolerance && |
|||
Math.Abs(other.Y - this.Y) < tolerance; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public bool Equals(Point2D other) => this.X.Equals(other.X) && this.Y.Equals(other.Y); |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override bool Equals(object obj) => obj is Point2D p && this.Equals(p); |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override int GetHashCode() => HashCode.Combine(this.X, this.Y); |
|||
|
|||
/// <inheritdoc />
|
|||
XmlSchema IXmlSerializable.GetSchema() => null; |
|||
|
|||
/// <inheritdoc />
|
|||
void IXmlSerializable.ReadXml(XmlReader reader) |
|||
{ |
|||
if (reader.TryReadAttributeAsDouble("X", out var x) && |
|||
reader.TryReadAttributeAsDouble("Y", out var y)) |
|||
{ |
|||
reader.Skip(); |
|||
this = new Point2D(x, y); |
|||
return; |
|||
} |
|||
|
|||
if (reader.TryReadChildElementsAsDoubles("X", "Y", out x, out y)) |
|||
{ |
|||
reader.Skip(); |
|||
this = new Point2D(x, y); |
|||
return; |
|||
} |
|||
|
|||
throw new XmlException("Could not read a Point2D"); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
void IXmlSerializable.WriteXml(XmlWriter writer) |
|||
{ |
|||
writer.WriteAttribute("X", this.X); |
|||
writer.WriteAttribute("Y", this.Y); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,313 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics.Contracts; |
|||
using System.Linq; |
|||
using MathNet.Numerics.Spatial.Internal; |
|||
|
|||
namespace MathNet.Numerics.Spatial.Euclidean2D |
|||
{ |
|||
/// <summary>
|
|||
/// The PolyLine2D class represents a 2D curve in space made up of line segments joined end-to-end, and is
|
|||
/// stored as a sequential list of 2D points.
|
|||
/// </summary>
|
|||
public class PolyLine2D : IEquatable<PolyLine2D> |
|||
{ |
|||
/// <summary>
|
|||
/// Internal storage for the points
|
|||
/// </summary>
|
|||
private readonly List<Point2D> points; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PolyLine2D"/> class.
|
|||
/// Creates a PolyLine2D from a pre-existing IEnumerable of Point2Ds
|
|||
/// </summary>
|
|||
/// <param name="points">A list of points.</param>
|
|||
public PolyLine2D(IEnumerable<Point2D> points) |
|||
{ |
|||
this.points = new List<Point2D>(points); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of vertices in the polyline.
|
|||
/// </summary>
|
|||
public int VertexCount => this.points.Count; |
|||
|
|||
/// <summary>
|
|||
/// Gets the length of the polyline as the sum of the length of the individual segments
|
|||
/// </summary>
|
|||
public double Length => this.GetPolyLineLength(); |
|||
|
|||
/// <summary>
|
|||
/// Gets a list of vertices
|
|||
/// </summary>
|
|||
public IEnumerable<Point2D> Vertices |
|||
{ |
|||
get |
|||
{ |
|||
foreach (var point in this.points) |
|||
{ |
|||
yield return point; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether each pair of elements in two specified lines is equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first line to compare</param>
|
|||
/// <param name="right">The second line to compare</param>
|
|||
/// <returns>True if the lines are the same; otherwise false.</returns>
|
|||
public static bool operator ==(PolyLine2D left, PolyLine2D right) |
|||
{ |
|||
return left?.Equals(right) == true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether any pair of elements in two specified lines is not equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first line to compare</param>
|
|||
/// <param name="right">The second line to compare</param>
|
|||
/// <returns>True if the lines are different; otherwise false.</returns>
|
|||
public static bool operator !=(PolyLine2D left, PolyLine2D right) |
|||
{ |
|||
return left?.Equals(right) != true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reduce the complexity of a manifold of points represented as an IEnumerable of Point2D objects by
|
|||
/// iteratively removing all nonadjacent points which would each result in an error of less than the
|
|||
/// single step tolerance if removed. Iterate until no further changes are made.
|
|||
/// </summary>
|
|||
/// <param name="points">A list of points.</param>
|
|||
/// <param name="singleStepTolerance">The tolerance (epsilon) for comparing sameness of line segments</param>
|
|||
/// <returns>A new PolyLine2D with same segments merged.</returns>
|
|||
public static PolyLine2D ReduceComplexity(IEnumerable<Point2D> points, double singleStepTolerance) |
|||
{ |
|||
var manifold = points.ToList(); |
|||
var n = manifold.Count; |
|||
|
|||
manifold = ReduceComplexitySingleStep(manifold, singleStepTolerance).ToList(); |
|||
var n1 = manifold.Count; |
|||
|
|||
while (n1 != n) |
|||
{ |
|||
n = n1; |
|||
manifold = ReduceComplexitySingleStep(manifold, singleStepTolerance).ToList(); |
|||
n1 = manifold.Count; |
|||
} |
|||
|
|||
return new PolyLine2D(manifold); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Get the point at a fractional distance along the curve. For instance, fraction=0.5 will return
|
|||
/// the point halfway along the length of the polyline.
|
|||
/// </summary>
|
|||
/// <param name="fraction">The fractional length at which to compute the point</param>
|
|||
/// <returns>A point a fraction of the way along the line.</returns>
|
|||
public Point2D GetPointAtFractionAlongCurve(double fraction) |
|||
{ |
|||
if (fraction > 1 || fraction < 0) |
|||
{ |
|||
throw new ArgumentException("fraction must be between 0 and 1"); |
|||
} |
|||
|
|||
return this.GetPointAtLengthFromStart(fraction * this.Length); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Get the point at a specified distance along the curve. A negative argument will return the first point,
|
|||
/// an argument greater than the length of the curve will return the last point.
|
|||
/// </summary>
|
|||
/// <param name="lengthFromStart">The distance from the first point along the curve at which to return a point</param>
|
|||
/// <returns>A point which is the specified distance along the line</returns>
|
|||
public Point2D GetPointAtLengthFromStart(double lengthFromStart) |
|||
{ |
|||
var length = this.Length; |
|||
if (lengthFromStart >= length) |
|||
{ |
|||
return this.points.Last(); |
|||
} |
|||
|
|||
if (lengthFromStart <= 0) |
|||
{ |
|||
return this.points.First(); |
|||
} |
|||
|
|||
double cumulativeLength = 0; |
|||
var i = 0; |
|||
while (true) |
|||
{ |
|||
var nextLength = cumulativeLength + this.points[i].DistanceTo(this.points[i + 1]); |
|||
if (cumulativeLength <= lengthFromStart && nextLength > lengthFromStart) |
|||
{ |
|||
var leftover = lengthFromStart - cumulativeLength; |
|||
var direction = this.points[i].VectorTo(this.points[i + 1]).Normalize(); |
|||
return this.points[i] + (direction * leftover); |
|||
} |
|||
else |
|||
{ |
|||
cumulativeLength = nextLength; |
|||
i++; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the closest point on the polyline to the given point.
|
|||
/// </summary>
|
|||
/// <param name="p">a point</param>
|
|||
/// <returns>A point which is the closest to the given point but still on the line.</returns>
|
|||
public Point2D ClosestPointTo(Point2D p) |
|||
{ |
|||
var minError = double.MaxValue; |
|||
var closest = default(Point2D); |
|||
|
|||
for (var i = 0; i < this.VertexCount - 1; i++) |
|||
{ |
|||
var segment = new LineSegment2D(this.points[i], this.points[i + 1]); |
|||
var projected = segment.ClosestPointTo(p); |
|||
var error = p.DistanceTo(projected); |
|||
if (error < minError) |
|||
{ |
|||
minError = error; |
|||
closest = projected; |
|||
} |
|||
} |
|||
|
|||
return closest; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value to indicate if a pair of polylines are equal
|
|||
/// </summary>
|
|||
/// <param name="other">The polyline to compare against.</param>
|
|||
/// <param name="tolerance">A tolerance (epsilon) to adjust for floating point error</param>
|
|||
/// <returns>true if the polylines are equal; otherwise false</returns>
|
|||
[Pure] |
|||
public bool Equals(PolyLine2D other, double tolerance) |
|||
{ |
|||
if (this.VertexCount != other?.VertexCount) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
for (var i = 0; i < this.points.Count; i++) |
|||
{ |
|||
if (!this.points[i].Equals(other.points[i], tolerance)) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public bool Equals(PolyLine2D other) |
|||
{ |
|||
if (this.VertexCount != other?.VertexCount) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
for (var i = 0; i < this.points.Count; i++) |
|||
{ |
|||
if (!this.points[i].Equals(other.points[i])) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override bool Equals(object obj) |
|||
{ |
|||
return obj is PolyLine2D polyLine2D && |
|||
this.Equals(polyLine2D); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override int GetHashCode() |
|||
{ |
|||
return HashCode.CombineMany(this.points); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reduce the complexity of a manifold of points represented as an IEnumerable of Point2D objects.
|
|||
/// This algorithm goes through each point in the manifold and computes the error that would be introduced
|
|||
/// from the original if that point were removed. Then it removes nonadjacent points to produce a
|
|||
/// reduced size manifold.
|
|||
/// </summary>
|
|||
/// <param name="points">A list of points</param>
|
|||
/// <param name="tolerance">Tolerance (Epsilon) to apply to determine if segments are to be merged.</param>
|
|||
/// <returns>A new list of points minus any segment which was merged.</returns>
|
|||
private static IEnumerable<Point2D> ReduceComplexitySingleStep(IEnumerable<Point2D> points, double tolerance) |
|||
{ |
|||
var manifold = points.ToList(); |
|||
var errorByIndex = new double[manifold.Count]; |
|||
|
|||
// At this point we will loop through the list of points (excluding the first and the last) and
|
|||
// examine every adjacent triplet. The middle point is tested against the segment created by
|
|||
// the two end points, and the error that would result in its deletion is computed as the length
|
|||
// of the point's projection onto the segment.
|
|||
for (var i = 1; i < manifold.Count - 1; i++) |
|||
{ |
|||
// TODO: simplify this to remove all of the value copying
|
|||
var v0 = manifold[i - 1]; |
|||
var v1 = manifold[i]; |
|||
var v2 = manifold[i + 1]; |
|||
var projected = new LineSegment2D(v0, v2).ClosestPointTo(v1); |
|||
|
|||
var error = v1.VectorTo(projected).Length; |
|||
errorByIndex[i] = error; |
|||
} |
|||
|
|||
// Now go through the list of errors and remove nonadjacent points with less than the error tolerance
|
|||
var thinnedPoints = new List<Point2D>(); |
|||
var preserveMe = 0; |
|||
for (var i = 0; i < errorByIndex.Length - 1; i++) |
|||
{ |
|||
if (i == preserveMe) |
|||
{ |
|||
thinnedPoints.Add(manifold[i]); |
|||
} |
|||
else |
|||
{ |
|||
if (errorByIndex[i] < tolerance) |
|||
{ |
|||
preserveMe = i + 1; |
|||
} |
|||
else |
|||
{ |
|||
thinnedPoints.Add(manifold[i]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
thinnedPoints.Add(manifold.Last()); |
|||
|
|||
return thinnedPoints; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Computes the length of the polyline by summing the lengths of the individual segments
|
|||
/// </summary>
|
|||
/// <returns>The length of the line</returns>
|
|||
private double GetPolyLineLength() |
|||
{ |
|||
double length = 0; |
|||
for (var i = 0; i < this.points.Count - 1; ++i) |
|||
{ |
|||
length += this.points[i].DistanceTo(this.points[i + 1]); |
|||
} |
|||
|
|||
return length; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,334 @@ |
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics.Contracts; |
|||
using System.Linq; |
|||
using MathNet.Numerics.Spatial.Internal; |
|||
using MathNet.Numerics.Spatial.Internal.ConvexHull; |
|||
|
|||
namespace MathNet.Numerics.Spatial.Euclidean2D |
|||
{ |
|||
/// <summary>
|
|||
/// Class to represent a closed polygon.
|
|||
/// </summary>
|
|||
public class Polygon2D : IEquatable<Polygon2D> |
|||
{ |
|||
/// <summary>
|
|||
/// A list of vertices.
|
|||
/// </summary>
|
|||
private readonly ImmutableList<Point2D> points; |
|||
|
|||
/// <summary>
|
|||
/// A list of edges. This list is lazy loaded on demand.
|
|||
/// </summary>
|
|||
private ImmutableList<LineSegment2D> edges; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Polygon2D"/> class.
|
|||
/// At least three points are needed to construct a polygon. If less are passed an ArgumentException is thrown.
|
|||
/// </summary>
|
|||
/// <param name="vertices">A list of vertices.</param>
|
|||
public Polygon2D(IEnumerable<Point2D> vertices) |
|||
: this(vertices.ToArray()) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Polygon2D"/> class.
|
|||
/// At least three points are needed to construct a polygon. If less are passed an ArgumentException is thrown.
|
|||
/// </summary>
|
|||
/// <param name="vertices">A list of vertices.</param>
|
|||
public Polygon2D(params Point2D[] vertices) |
|||
{ |
|||
if (vertices.Length < 3) |
|||
{ |
|||
throw new ArgumentException("Cannot create a polygon out of less than three points"); |
|||
} |
|||
|
|||
if (vertices[0].Equals(vertices[vertices.Length - 1])) |
|||
{ |
|||
this.points = ImmutableList.Create(vertices.Skip(1).ToArray()); |
|||
} |
|||
else |
|||
{ |
|||
this.points = ImmutableList.Create(vertices); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a list of vertices
|
|||
/// </summary>
|
|||
public IEnumerable<Point2D> Vertices |
|||
{ |
|||
get |
|||
{ |
|||
foreach (var point in this.points) |
|||
{ |
|||
yield return point; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a list of Edges
|
|||
/// </summary>
|
|||
public IEnumerable<LineSegment2D> Edges |
|||
{ |
|||
get |
|||
{ |
|||
if (this.edges == null) |
|||
{ |
|||
this.PopulateEdgeList(); |
|||
} |
|||
|
|||
foreach (var edge in this.edges) |
|||
{ |
|||
yield return edge; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of vertices in the polygon.
|
|||
/// </summary>
|
|||
public int VertexCount => this.points.Count; |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether each point in two specified polygons is equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first polygon to compare</param>
|
|||
/// <param name="right">The second polygon to compare</param>
|
|||
/// <returns>True if the polygons are the same; otherwise false.</returns>
|
|||
public static bool operator ==(Polygon2D left, Polygon2D right) |
|||
{ |
|||
return left?.Equals(right) == true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether any point in two specified polygons is not equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first polygon to compare</param>
|
|||
/// <param name="right">The second polygon to compare</param>
|
|||
/// <returns>True if the polygons are different; otherwise false.</returns>
|
|||
public static bool operator !=(Polygon2D left, Polygon2D right) |
|||
{ |
|||
return left?.Equals(right) != true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compute whether or not two polygons are colliding based on whether or not the vertices of
|
|||
/// either are enclosed within the shape of the other. This is a simple means of detecting collisions
|
|||
/// that can fail if the two polygons are heavily overlapped in such a way that one protrudes through
|
|||
/// the other and out its opposing side without any vertices being enclosed.
|
|||
/// </summary>
|
|||
/// <param name="a">The first polygon.</param>
|
|||
/// <param name="b">The second polygon</param>
|
|||
/// <returns>True if the vertices collide; otherwise false.</returns>
|
|||
public static bool ArePolygonVerticesColliding(Polygon2D a, Polygon2D b) |
|||
{ |
|||
return a.points.Any(b.EnclosesPoint) || b.points.Any(a.EnclosesPoint); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Using algorithm from Ouellet - https://www.codeproject.com/Articles/1210225/Fast-and-improved-D-Convex-Hull-algorithm-and-its, take an IEnumerable of Point2Ds and computes the
|
|||
/// two dimensional convex hull, returning it as a Polygon2D object.
|
|||
/// </summary>
|
|||
/// <param name="pointList">A list of points</param>
|
|||
/// <param name="clockWise">
|
|||
/// In which direction to return the points on the convex hull.
|
|||
/// If true, clockwise. Otherwise counter clockwise
|
|||
/// </param>
|
|||
/// <returns>A polygon.</returns>
|
|||
public static Polygon2D GetConvexHullFromPoints(IEnumerable<Point2D> pointList, bool clockWise = true) |
|||
{ |
|||
int count = pointList.Count(); |
|||
|
|||
// Perform basic validation of the input point cloud for cases of less than
|
|||
// four points being given
|
|||
if (count <= 2) |
|||
{ |
|||
throw new ArgumentException("Must have at least 3 points in the polygon to compute the convex hull"); |
|||
} |
|||
|
|||
if (count <= 3) |
|||
{ |
|||
return new Polygon2D(pointList); |
|||
} |
|||
|
|||
var chull = new ConvexHull(pointList, false); |
|||
chull.CalcConvexHull(); |
|||
var hullPoints = chull.GetResultsAsArrayOfPoint(); |
|||
|
|||
// Order the hull points by angle to the centroid
|
|||
var centroid = Point2D.Centroid(hullPoints); |
|||
var xAxis = new Vector2D(1, 0); |
|||
var results = (from x in hullPoints select new Tuple<Angle, Point2D>(centroid.VectorTo(x).SignedAngleTo(xAxis, clockWise), x)).ToList(); |
|||
results.Sort((a, b) => a.Item1.CompareTo(b.Item1)); |
|||
|
|||
return new Polygon2D(from x in results select x.Item2); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Test whether a point is enclosed within a polygon. Points on the polygon edges are not
|
|||
/// counted as contained within the polygon.
|
|||
/// </summary>
|
|||
/// <param name="p">A point.</param>
|
|||
/// <returns>True if the point is inside the polygon; otherwise false.</returns>
|
|||
public bool EnclosesPoint(Point2D p) |
|||
{ |
|||
var c = false; |
|||
for (int i = 0, j = this.points.Count - 1; i < this.points.Count; j = i++) |
|||
{ |
|||
if (((this.points[i].Y > p.Y) != (this.points[j].Y > p.Y)) && |
|||
(p.X < ((this.points[j].X - this.points[i].X) * (p.Y - this.points[i].Y) / (this.points[j].Y - this.points[i].Y)) + this.points[i].X)) |
|||
{ |
|||
c = !c; |
|||
} |
|||
} |
|||
|
|||
return c; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a new polygon from the existing polygon by removing any edges whose adjacent segments are considered colinear within the provided tolerance
|
|||
/// </summary>
|
|||
/// <param name="singleStepTolerance">The tolerance by which adjacent edges should be considered collinear.</param>
|
|||
/// <returns>A polygon</returns>
|
|||
public Polygon2D ReduceComplexity(double singleStepTolerance) |
|||
{ |
|||
return new Polygon2D(PolyLine2D.ReduceComplexity(this.ToPolyLine2D().Vertices, singleStepTolerance).Vertices); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a polygon rotated around the origin
|
|||
/// </summary>
|
|||
/// <param name="angle">The angle by which to rotate.</param>
|
|||
/// <returns>A new polygon that has been rotated.</returns>
|
|||
public Polygon2D Rotate(Angle angle) |
|||
{ |
|||
var rotated = this.points.Select(t => Point2D.Origin + t.ToVector2D().Rotate(angle)).ToArray(); |
|||
return new Polygon2D(rotated); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a new polygon which is translated (moved) by a vector
|
|||
/// </summary>
|
|||
/// <param name="vector">A vector.</param>
|
|||
/// <returns>A new polygon that has been translated.</returns>
|
|||
public Polygon2D TranslateBy(Vector2D vector) |
|||
{ |
|||
var newPoints = from p in this.points select p + vector; |
|||
return new Polygon2D(newPoints); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Rotate the polygon around the specified point
|
|||
/// </summary>
|
|||
/// <param name="angle">The angle by which to rotate</param>
|
|||
/// <param name="center">A point at which to rotate around</param>
|
|||
/// <returns>A new polygon that has been rotated.</returns>
|
|||
public Polygon2D RotateAround(Angle angle, Point2D center) |
|||
{ |
|||
// Shift to the origin
|
|||
var shiftVector = center.VectorTo(Point2D.Origin); |
|||
var tempPoly = this.TranslateBy(shiftVector); |
|||
|
|||
// Rotate
|
|||
var rotatedPoly = tempPoly.Rotate(angle); |
|||
|
|||
// Shift back
|
|||
return rotatedPoly.TranslateBy(-shiftVector); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts the polygon into a PolyLine2D
|
|||
/// </summary>
|
|||
/// <returns>A polyline</returns>
|
|||
public PolyLine2D ToPolyLine2D() |
|||
{ |
|||
var points = this.points.ToList(); |
|||
points.Add(points.First()); |
|||
return new PolyLine2D(points); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value to indicate if a pair of polygons are equal
|
|||
/// </summary>
|
|||
/// <param name="other">The polygon to compare against.</param>
|
|||
/// <param name="tolerance">A tolerance (epsilon) to adjust for floating point error</param>
|
|||
/// <returns>true if the polygons are equal; otherwise false</returns>
|
|||
[Pure] |
|||
public bool Equals(Polygon2D other, double tolerance) |
|||
{ |
|||
if (this.VertexCount != other?.VertexCount) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
for (var i = 0; i < this.points.Count; i++) |
|||
{ |
|||
if (!this.points[i].Equals(other.points[i], tolerance)) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public bool Equals(Polygon2D other) |
|||
{ |
|||
if (this.VertexCount != other?.VertexCount) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
for (var i = 0; i < this.points.Count; i++) |
|||
{ |
|||
if (!this.points[i].Equals(other.points[i])) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override bool Equals(object obj) |
|||
{ |
|||
if (obj is null) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return obj is Polygon2D d && this.Equals(d); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override int GetHashCode() |
|||
{ |
|||
return HashCode.CombineMany(this.points); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Populates the edge list
|
|||
/// </summary>
|
|||
private void PopulateEdgeList() |
|||
{ |
|||
var localedges = new List<LineSegment2D>(this.points.Count); |
|||
for (var i = 0; i < this.points.Count - 1; i++) |
|||
{ |
|||
var edge = new LineSegment2D(this.points[i], this.points[i + 1]); |
|||
localedges.Add(edge); |
|||
} |
|||
|
|||
localedges.Add(new LineSegment2D(this.points[this.points.Count - 1], this.points[0])); // complete loop
|
|||
this.edges = ImmutableList.Create(localedges); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,568 @@ |
|||
using System; |
|||
using System.Diagnostics.Contracts; |
|||
using System.Globalization; |
|||
using System.Xml; |
|||
using System.Xml.Schema; |
|||
using System.Xml.Serialization; |
|||
using MathNet.Numerics.LinearAlgebra; |
|||
using MathNet.Numerics.Spatial.Internal; |
|||
|
|||
namespace MathNet.Numerics.Spatial.Euclidean2D |
|||
{ |
|||
/// <summary>
|
|||
/// A struct representing a vector in 2D space
|
|||
/// </summary>
|
|||
[Serializable] |
|||
public struct Vector2D : IXmlSerializable, IEquatable<Vector2D>, IFormattable |
|||
{ |
|||
/// <summary>
|
|||
/// The x component.
|
|||
/// </summary>
|
|||
public readonly double X; |
|||
|
|||
/// <summary>
|
|||
/// The y component.
|
|||
/// </summary>
|
|||
public readonly double Y; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Vector2D"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="x">The x component.</param>
|
|||
/// <param name="y">The y component.</param>
|
|||
public Vector2D(double x, double y) |
|||
{ |
|||
this.X = x; |
|||
this.Y = y; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a vector representing the X Axis
|
|||
/// </summary>
|
|||
public static Vector2D XAxis { get; } = new Vector2D(1, 0); |
|||
|
|||
/// <summary>
|
|||
/// Gets a vector representing the Y Axis
|
|||
/// </summary>
|
|||
public static Vector2D YAxis { get; } = new Vector2D(0, 1); |
|||
|
|||
/// <summary>
|
|||
/// Gets the length of the vector
|
|||
/// </summary>
|
|||
[Pure] |
|||
public double Length => Math.Sqrt((this.X * this.X) + (this.Y * this.Y)); |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether each pair of elements in two specified vectors is equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first vector to compare.</param>
|
|||
/// <param name="right">The second vector to compare.</param>
|
|||
/// <returns>True if the vectors are the same; otherwise false.</returns>
|
|||
public static bool operator ==(Vector2D left, Vector2D right) |
|||
{ |
|||
return left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether any pair of elements in two specified vectors is not equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first vector to compare.</param>
|
|||
/// <param name="right">The second vector to compare.</param>
|
|||
/// <returns>True if the vectors are different; otherwise false.</returns>
|
|||
public static bool operator !=(Vector2D left, Vector2D right) |
|||
{ |
|||
return !left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds two vectors
|
|||
/// </summary>
|
|||
/// <param name="left">The first vector</param>
|
|||
/// <param name="right">The second vector</param>
|
|||
/// <returns>A new summed vector</returns>
|
|||
public static Vector2D operator +(Vector2D left, Vector2D right) |
|||
{ |
|||
return left.Add(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Subtracts two vectors
|
|||
/// </summary>
|
|||
/// <param name="left">The first vector</param>
|
|||
/// <param name="right">The second vector</param>
|
|||
/// <returns>A new difference vector</returns>
|
|||
public static Vector2D operator -(Vector2D left, Vector2D right) |
|||
{ |
|||
return left.Subtract(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Negates the vector
|
|||
/// </summary>
|
|||
/// <param name="v">A vector to negate</param>
|
|||
/// <returns>A new negated vector</returns>
|
|||
public static Vector2D operator -(Vector2D v) |
|||
{ |
|||
return v.Negate(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Multiplies a vector by a scalar
|
|||
/// </summary>
|
|||
/// <param name="d">A scalar</param>
|
|||
/// <param name="v">A vector</param>
|
|||
/// <returns>A scaled vector</returns>
|
|||
public static Vector2D operator *(double d, Vector2D v) |
|||
{ |
|||
return new Vector2D(d * v.X, d * v.Y); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Multiplies a vector by a scalar
|
|||
/// </summary>
|
|||
/// <param name="v">A vector</param>
|
|||
/// <param name="d">A scalar</param>
|
|||
/// <returns>A scaled vector</returns>
|
|||
public static Vector2D operator *(Vector2D v, double d) |
|||
{ |
|||
return d * v; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Divides a vector by a scalar
|
|||
/// </summary>
|
|||
/// <param name="v">A vector</param>
|
|||
/// <param name="d">A scalar</param>
|
|||
/// <returns>A scaled vector</returns>
|
|||
public static Vector2D operator /(Vector2D v, double d) |
|||
{ |
|||
return new Vector2D(v.X / d, v.Y / d); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a Vector from Polar coordinates
|
|||
/// </summary>
|
|||
/// <param name="radius">The distance of the point from the origin</param>
|
|||
/// <param name="angle">The angle of the point as measured from the X Axis</param>
|
|||
/// <returns>A vector.</returns>
|
|||
public static Vector2D FromPolar(double radius, Angle angle) |
|||
{ |
|||
if (radius < 0) |
|||
{ |
|||
throw new ArgumentOutOfRangeException(nameof(radius), radius, "Expected a radius greater than or equal to zero."); |
|||
} |
|||
|
|||
return new Vector2D( |
|||
radius * Math.Cos(angle.Radians), |
|||
radius * Math.Sin(angle.Radians)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Attempts to convert a string of the form x,y into a point
|
|||
/// </summary>
|
|||
/// <param name="text">The string to be converted</param>
|
|||
/// <param name="result">A point at the coordinates specified</param>
|
|||
/// <returns>True if <paramref name="text"/> could be parsed.</returns>
|
|||
public static bool TryParse(string text, out Vector2D result) |
|||
{ |
|||
return TryParse(text, null, out result); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Attempts to convert a string of the form x,y into a point
|
|||
/// </summary>
|
|||
/// <param name="text">The string to be converted</param>
|
|||
/// <param name="formatProvider">The <see cref="IFormatProvider"/></param>
|
|||
/// <param name="result">A point at the coordinates specified</param>
|
|||
/// <returns>True if <paramref name="text"/> could be parsed.</returns>
|
|||
public static bool TryParse(string text, IFormatProvider formatProvider, out Vector2D result) |
|||
{ |
|||
if (Text.TryParse2D(text, formatProvider, out var x, out var y)) |
|||
{ |
|||
result = new Vector2D(x, y); |
|||
return true; |
|||
} |
|||
|
|||
result = default(Vector2D); |
|||
return false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Attempts to convert a string of the form x,y into a point
|
|||
/// </summary>
|
|||
/// <param name="value">The string to be converted</param>
|
|||
/// <param name="formatProvider">The <see cref="IFormatProvider"/></param>
|
|||
/// <returns>A point at the coordinates specified</returns>
|
|||
public static Vector2D Parse(string value, IFormatProvider formatProvider = null) |
|||
{ |
|||
if (TryParse(value, formatProvider, out var p)) |
|||
{ |
|||
return p; |
|||
} |
|||
|
|||
throw new FormatException($"Could not parse a Vector2D from the string {value}"); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates an <see cref="Vector2D"/> from an <see cref="XmlReader"/>.
|
|||
/// </summary>
|
|||
/// <param name="reader">An <see cref="XmlReader"/> positioned at the node to read into this <see cref="Vector2D"/>.</param>
|
|||
/// <returns>An <see cref="Vector2D"/> that contains the data read from the reader.</returns>
|
|||
public static Vector2D ReadFrom(XmlReader reader) |
|||
{ |
|||
return reader.ReadElementAs<Vector2D>(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Create a new <see cref="Vector2D"/> from a Math.NET Numerics vector of length 2.
|
|||
/// </summary>
|
|||
/// <param name="vector"> A vector with length 2 to populate the created instance with.</param>
|
|||
/// <returns> A <see cref="Vector2D"/></returns>
|
|||
[Pure] |
|||
public static Vector2D OfVector(Vector<double> vector) |
|||
{ |
|||
if (vector.Count != 2) |
|||
{ |
|||
throw new ArgumentException("The vector length must be 2 in order to convert it to a Vector2D"); |
|||
} |
|||
|
|||
return new Vector2D(vector.At(0), vector.At(1)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Computes whether or not this vector is perpendicular to <paramref name="other"/> vector by:
|
|||
/// 1. Normalizing both
|
|||
/// 2. Computing the dot product.
|
|||
/// 3. Comparing 1- Math.Abs(dot product) to <paramref name="tolerance"/>
|
|||
/// </summary>
|
|||
/// <param name="other">The other <see cref="Vector2D"/></param>
|
|||
/// <param name="tolerance">The tolerance for when vectors are said to be parallel</param>
|
|||
/// <returns>True if the vector dot product is within the given double tolerance of unity, false if not</returns>
|
|||
[Pure] |
|||
public bool IsParallelTo(Vector2D other, double tolerance = 1e-10) |
|||
{ |
|||
var dp = Math.Abs(this.Normalize().DotProduct(other.Normalize())); |
|||
return Math.Abs(1 - dp) <= tolerance; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Computes whether or not this vector is parallel to another vector within a given angle tolerance.
|
|||
/// </summary>
|
|||
/// <param name="other">The other <see cref="Vector2D"/></param>
|
|||
/// <param name="tolerance">The tolerance for when vectors are said to be parallel</param>
|
|||
/// <returns>True if the vectors are parallel within the angle tolerance, false if they are not</returns>
|
|||
[Pure] |
|||
public bool IsParallelTo(Vector2D other, Angle tolerance) |
|||
{ |
|||
// Compute the angle between these vectors
|
|||
var angle = this.AngleTo(other); |
|||
if (angle < tolerance) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
// Compute the 180° opposite of the angle
|
|||
return Angle.FromRadians(Math.PI) - angle < tolerance; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Computes whether or not this vector is perpendicular to <paramref name="other"/> vector by:
|
|||
/// 1. Normalizing both
|
|||
/// 2. Computing the dot product.
|
|||
/// 3. Comparing Math.Abs(dot product) to <paramref name="tolerance"/>
|
|||
/// </summary>
|
|||
/// <param name="other">The other <see cref="Vector2D"/></param>
|
|||
/// <param name="tolerance">The tolerance for when vectors are said to be parallel</param>
|
|||
/// <returns>True if the vector dot product is within the given double tolerance of unity, false if not</returns>
|
|||
[Pure] |
|||
public bool IsPerpendicularTo(Vector2D other, double tolerance = 1e-10) |
|||
{ |
|||
return Math.Abs(this.Normalize().DotProduct(other.Normalize())) < tolerance; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Computes whether or not this vector is parallel to another vector within a given angle tolerance.
|
|||
/// </summary>
|
|||
/// <param name="other">The other <see cref="Vector2D"/></param>
|
|||
/// <param name="tolerance">The tolerance for when vectors are said to be parallel</param>
|
|||
/// <returns>True if the vectors are parallel within the angle tolerance, false if they are not</returns>
|
|||
[Pure] |
|||
public bool IsPerpendicularTo(Vector2D other, Angle tolerance) |
|||
{ |
|||
var angle = this.AngleTo(other); |
|||
const double Perpendicular = Math.PI / 2; |
|||
return Math.Abs(angle.Radians - Perpendicular) < tolerance.Radians; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compute the signed angle to another vector.
|
|||
/// </summary>
|
|||
/// <param name="other">The other <see cref="Vector2D"/></param>
|
|||
/// <param name="clockWise">Positive in clockwise direction</param>
|
|||
/// <param name="returnNegative">When true and the result is > 180° a negative value is returned</param>
|
|||
/// <returns>The angle between the vectors.</returns>
|
|||
[Pure] |
|||
public Angle SignedAngleTo(Vector2D other, bool clockWise = false, bool returnNegative = false) |
|||
{ |
|||
var sign = clockWise ? -1 : 1; |
|||
var a1 = Math.Atan2(this.Y, this.X); |
|||
if (a1 < 0) |
|||
{ |
|||
a1 += 2 * Math.PI; |
|||
} |
|||
|
|||
var a2 = Math.Atan2(other.Y, other.X); |
|||
if (a2 < 0) |
|||
{ |
|||
a2 += 2 * Math.PI; |
|||
} |
|||
|
|||
var a = sign * (a2 - a1); |
|||
if (a < 0 && !returnNegative) |
|||
{ |
|||
a += 2 * Math.PI; |
|||
} |
|||
|
|||
if (a > Math.PI && returnNegative) |
|||
{ |
|||
a -= 2 * Math.PI; |
|||
} |
|||
|
|||
return Angle.FromRadians(a); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compute the angle between this vector and another using the arccosine of the dot product.
|
|||
/// </summary>
|
|||
/// <param name="other">The other <see cref="Vector2D"/></param>
|
|||
/// <returns>The angle between vectors, with a range between 0° and 180°</returns>
|
|||
[Pure] |
|||
public Angle AngleTo(Vector2D other) |
|||
{ |
|||
return Angle.FromRadians( |
|||
Math.Abs( |
|||
Math.Atan2( |
|||
(this.X * other.Y) - (other.X * this.Y), |
|||
(this.X * other.X) + (this.Y * other.Y)))); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Rotates a Vector by an angle
|
|||
/// </summary>
|
|||
/// <param name="angle">The angle.</param>
|
|||
/// <returns>A new rotated vector.</returns>
|
|||
[Pure] |
|||
public Vector2D Rotate(Angle angle) |
|||
{ |
|||
var cs = Math.Cos(angle.Radians); |
|||
var sn = Math.Sin(angle.Radians); |
|||
var x = (this.X * cs) - (this.Y * sn); |
|||
var y = (this.X * sn) + (this.Y * cs); |
|||
return new Vector2D(x, y); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Perform the dot product on a pair of vectors
|
|||
/// </summary>
|
|||
/// <param name="other">The second vector</param>
|
|||
/// <returns>The result of the dot product.</returns>
|
|||
[Pure] |
|||
public double DotProduct(Vector2D other) |
|||
{ |
|||
return (this.X * other.X) + (this.Y * other.Y); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Performs the 2D 'cross product' as if the 2D vectors were really 3D vectors in the z=0 plane, returning
|
|||
/// the scalar magnitude and direction of the resulting z value.
|
|||
/// Formula: (this.X * other.Y) - (this.Y * other.X)
|
|||
/// </summary>
|
|||
/// <param name="other">The other <see cref="Vector2D"/></param>
|
|||
/// <returns>(this.X * other.Y) - (this.Y * other.X)</returns>
|
|||
[Pure] |
|||
public double CrossProduct(Vector2D other) |
|||
{ |
|||
// Though the cross product is undefined in 2D space, this is a useful mathematical operation to
|
|||
// determine angular direction and to compute the area of 2D shapes
|
|||
return (this.X * other.Y) - (this.Y * other.X); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Projects this vector onto another vector
|
|||
/// </summary>
|
|||
/// <param name="other">The other <see cref="Vector2D"/></param>
|
|||
/// <returns>A <see cref="Vector2D"/> representing this vector projected on <paramref name="other"/></returns>
|
|||
[Pure] |
|||
public Vector2D ProjectOn(Vector2D other) |
|||
{ |
|||
return other * (this.DotProduct(other) / other.DotProduct(other)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a new unit vector from the existing vector.
|
|||
/// </summary>
|
|||
/// <returns>A new unit vector in the same direction as the original vector</returns>
|
|||
[Pure] |
|||
public Vector2D Normalize() |
|||
{ |
|||
var l = this.Length; |
|||
return new Vector2D(this.X / l, this.Y / l); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Scales the vector by the provided value
|
|||
/// </summary>
|
|||
/// <param name="d">a scaling factor</param>
|
|||
/// <returns>A new scale adjusted vector</returns>
|
|||
[Pure] |
|||
public Vector2D ScaleBy(double d) |
|||
{ |
|||
return new Vector2D(d * this.X, d * this.Y); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the negative of the vector
|
|||
/// </summary>
|
|||
/// <returns>A new negated vector.</returns>
|
|||
[Pure] |
|||
public Vector2D Negate() |
|||
{ |
|||
return new Vector2D(-1 * this.X, -1 * this.Y); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Subtracts a vector from this vector.
|
|||
/// </summary>
|
|||
/// <param name="v">A vector to subtract</param>
|
|||
/// <returns>A new vector which is the difference of the current vector and the provided vector</returns>
|
|||
[Pure] |
|||
public Vector2D Subtract(Vector2D v) |
|||
{ |
|||
return new Vector2D(this.X - v.X, this.Y - v.Y); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a vector to this vector
|
|||
/// </summary>
|
|||
/// <param name="v">A vector to add</param>
|
|||
/// <returns>A new vector which is the sum of the existing vector and the provided vector</returns>
|
|||
[Pure] |
|||
public Vector2D Add(Vector2D v) |
|||
{ |
|||
return new Vector2D(this.X + v.X, this.Y + v.Y); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Transforms a vector by multiplying it against a provided matrix
|
|||
/// </summary>
|
|||
/// <param name="m">The matrix to multiply</param>
|
|||
/// <returns>A new transformed vector</returns>
|
|||
[Pure] |
|||
public Vector2D TransformBy(Matrix<double> m) |
|||
{ |
|||
var transformed = m.Multiply(this.ToVector()); |
|||
return new Vector2D(transformed.At(0), transformed.At(1)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Convert to a Math.NET Numerics dense vector of length 2.
|
|||
/// </summary>
|
|||
/// <returns> A <see cref="Vector{Double}"/> with the x and y values from this instance.</returns>
|
|||
[Pure] |
|||
public Vector<double> ToVector() |
|||
{ |
|||
return Vector<double>.Build.Dense(new[] { this.X, this.Y }); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compare this instance with <paramref name="other"/>
|
|||
/// </summary>
|
|||
/// <param name="other">The other <see cref="Vector2D"/></param>
|
|||
/// <param name="tolerance">The tolerance when comparing the x and y components</param>
|
|||
/// <returns>True if found to be equal.</returns>
|
|||
[Pure] |
|||
public bool Equals(Vector2D other, double tolerance) |
|||
{ |
|||
if (tolerance < 0) |
|||
{ |
|||
throw new ArgumentException("epsilon < 0"); |
|||
} |
|||
|
|||
return Math.Abs(other.X - this.X) < tolerance && |
|||
Math.Abs(other.Y - this.Y) < tolerance; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public bool Equals(Vector2D other) => this.X.Equals(other.X) && this.Y.Equals(other.Y); |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override bool Equals(object obj) => obj is Vector2D v && this.Equals(v); |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override int GetHashCode() => HashCode.Combine(this.X, this.Y); |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override string ToString() |
|||
{ |
|||
return this.ToString(null, CultureInfo.InvariantCulture); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a string representation of this instance using the provided <see cref="IFormatProvider"/>
|
|||
/// </summary>
|
|||
/// <param name="provider">A <see cref="IFormatProvider"/></param>
|
|||
/// <returns>The string representation of this instance.</returns>
|
|||
[Pure] |
|||
public string ToString(IFormatProvider provider) |
|||
{ |
|||
return this.ToString(null, provider); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public string ToString(string format, IFormatProvider provider = null) |
|||
{ |
|||
var numberFormatInfo = provider != null ? NumberFormatInfo.GetInstance(provider) : CultureInfo.InvariantCulture.NumberFormat; |
|||
var separator = numberFormatInfo.NumberDecimalSeparator == "," ? ";" : ","; |
|||
return $"({this.X.ToString(format, numberFormatInfo)}{separator}\u00A0{this.Y.ToString(format, numberFormatInfo)})"; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
XmlSchema IXmlSerializable.GetSchema() |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
void IXmlSerializable.ReadXml(XmlReader reader) |
|||
{ |
|||
if (reader.TryReadAttributeAsDouble("X", out var x) && |
|||
reader.TryReadAttributeAsDouble("Y", out var y)) |
|||
{ |
|||
reader.Skip(); |
|||
this = new Vector2D(x, y); |
|||
return; |
|||
} |
|||
|
|||
if (reader.TryReadChildElementsAsDoubles("X", "Y", out x, out y)) |
|||
{ |
|||
reader.Skip(); |
|||
this = new Vector2D(x, y); |
|||
return; |
|||
} |
|||
|
|||
throw new XmlException("Could not read a Vector2D"); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
void IXmlSerializable.WriteXml(XmlWriter writer) |
|||
{ |
|||
writer.WriteAttribute("X", this.X); |
|||
writer.WriteAttribute("Y", this.Y); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,167 @@ |
|||
using System; |
|||
using System.Diagnostics.Contracts; |
|||
using MathNet.Numerics.Spatial.Internal; |
|||
|
|||
namespace MathNet.Numerics.Spatial.Euclidean3D |
|||
{ |
|||
/// <summary>
|
|||
/// Describes a 3 dimensional circle
|
|||
/// </summary>
|
|||
[Serializable] |
|||
public struct Circle3D : IEquatable<Circle3D> |
|||
{ |
|||
/// <summary>
|
|||
/// The center of the circle
|
|||
/// </summary>
|
|||
public readonly Point3D CenterPoint; |
|||
|
|||
/// <summary>
|
|||
/// the axis of the circle
|
|||
/// </summary>
|
|||
public readonly UnitVector3D Axis; |
|||
|
|||
/// <summary>
|
|||
/// the radius of the circle
|
|||
/// </summary>
|
|||
public readonly double Radius; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Circle3D"/> struct.
|
|||
/// Constructs a Circle3D with a given <paramref name="radius"/> at a <paramref name="centerPoint"/> orientated to the <paramref name="axis"/>
|
|||
/// </summary>
|
|||
/// <param name="centerPoint">The center of the circle</param>
|
|||
/// <param name="axis">the axis of the circle</param>
|
|||
/// <param name="radius">the radius of the circle</param>
|
|||
public Circle3D(Point3D centerPoint, UnitVector3D axis, double radius) |
|||
{ |
|||
this.CenterPoint = centerPoint; |
|||
this.Axis = axis; |
|||
this.Radius = radius; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the diameter of the circle
|
|||
/// </summary>
|
|||
[Pure] |
|||
public double Diameter => 2 * this.Radius; |
|||
|
|||
/// <summary>
|
|||
/// Gets the circumference of the circle
|
|||
/// </summary>
|
|||
[Pure] |
|||
public double Circumference => 2 * Math.PI * this.Radius; |
|||
|
|||
/// <summary>
|
|||
/// Gets the area of the circle
|
|||
/// </summary>
|
|||
[Pure] |
|||
public double Area => this.Radius * this.Radius * Math.PI; |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether each pair of elements in two specified circles is equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first circle to compare</param>
|
|||
/// <param name="right">The second circle to compare</param>
|
|||
/// <returns>True if the circles are the same; otherwise false.</returns>
|
|||
public static bool operator ==(Circle3D left, Circle3D right) |
|||
{ |
|||
return left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether any pair of elements in two specified circles is not equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first circle to compare</param>
|
|||
/// <param name="right">The second circle to compare</param>
|
|||
/// <returns>True if the circles are different; otherwise false.</returns>
|
|||
public static bool operator !=(Circle3D left, Circle3D right) |
|||
{ |
|||
return !left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Circle3D"/> struct.
|
|||
/// Create a circle from three points which lie along its circumference.
|
|||
/// </summary>
|
|||
/// <param name="p1">The first point on the circle</param>
|
|||
/// <param name="p2">The second point on the circle</param>
|
|||
/// <param name="p3">The third point on the circle</param>
|
|||
/// <returns>A <see cref="Circle3D"/></returns>
|
|||
public static Circle3D FromPoints(Point3D p1, Point3D p2, Point3D p3) |
|||
{ |
|||
// https://www.physicsforums.com/threads/equation-of-a-circle-through-3-points-in-3d-space.173847/
|
|||
//// ReSharper disable InconsistentNaming
|
|||
var p1p2 = p2 - p1; |
|||
var p2p3 = p3 - p2; |
|||
//// ReSharper restore InconsistentNaming
|
|||
|
|||
var axis = p1p2.CrossProduct(p2p3).Normalize(); |
|||
var midPointA = p1 + (0.5 * p1p2); |
|||
var midPointB = p2 + (0.5 * p2p3); |
|||
|
|||
var directionA = p1p2.CrossProduct(axis); |
|||
var directionB = p2p3.CrossProduct(axis); |
|||
|
|||
var bisectorA = new Ray3D(midPointA, directionA); |
|||
var bisectorB = Plane3D.FromPoints(midPointB, midPointB + directionB.Normalize(), midPointB + axis); |
|||
|
|||
var center = bisectorA.IntersectionWith(bisectorB); |
|||
if (center == null) |
|||
{ |
|||
throw new ArgumentException("A circle cannot be created from these points, are they collinear?"); |
|||
} |
|||
|
|||
return new Circle3D(center.Value, axis, center.Value.DistanceTo(p1)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Circle3D"/> struct.
|
|||
/// Create a circle from the midpoint between two points, in a direction along a specified axis
|
|||
/// </summary>
|
|||
/// <param name="p1">First point on the circumference of the circle</param>
|
|||
/// <param name="p2">Second point on the circumference of the circle</param>
|
|||
/// <param name="axis">Direction of the plane in which the circle lies</param>
|
|||
/// <returns>A <see cref="Circle3D"/></returns>
|
|||
public static Circle3D FromPointsAndAxis(Point3D p1, Point3D p2, UnitVector3D axis) |
|||
{ |
|||
var cp = Point3D.MidPoint(p1, p2); |
|||
return new Circle3D(cp, axis, cp.DistanceTo(p1)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value to indicate if a pair of circles are equal
|
|||
/// </summary>
|
|||
/// <param name="c">The circle to compare against.</param>
|
|||
/// <param name="tolerance">A tolerance (epsilon) to adjust for floating point error</param>
|
|||
/// <returns>true if the points are equal; otherwise false</returns>
|
|||
[Pure] |
|||
public bool Equals(Circle3D c, double tolerance) |
|||
{ |
|||
if (tolerance < 0) |
|||
{ |
|||
throw new ArgumentException("epsilon < 0"); |
|||
} |
|||
|
|||
return Math.Abs(c.Radius - this.Radius) < tolerance |
|||
&& this.Axis.Equals(c.Axis, tolerance) |
|||
&& this.CenterPoint.Equals(c.CenterPoint, tolerance); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public bool Equals(Circle3D c) |
|||
{ |
|||
return this.CenterPoint.Equals(c.CenterPoint) |
|||
&& this.Axis.Equals(c.Axis) |
|||
&& this.Radius.Equals(c.Radius); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override bool Equals(object obj) => obj is Circle3D c && this.Equals(c); |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override int GetHashCode() => HashCode.Combine(this.CenterPoint, this.Axis, this.Radius); |
|||
} |
|||
} |
|||
@ -0,0 +1,714 @@ |
|||
using System; |
|||
using System.Diagnostics.Contracts; |
|||
using System.Text.RegularExpressions; |
|||
using System.Xml; |
|||
using System.Xml.Linq; |
|||
using System.Xml.Schema; |
|||
using System.Xml.Serialization; |
|||
using MathNet.Numerics.LinearAlgebra; |
|||
using MathNet.Numerics.Spatial.Internal; |
|||
|
|||
namespace MathNet.Numerics.Spatial.Euclidean3D |
|||
{ |
|||
/// <summary>
|
|||
/// A coordinate system
|
|||
/// </summary>
|
|||
[Serializable] |
|||
public class CoordinateSystem3D : LinearAlgebra.Double.DenseMatrix, IEquatable<CoordinateSystem3D>, IXmlSerializable |
|||
{ |
|||
/// <summary>
|
|||
/// A local regex pattern for 3D items
|
|||
/// </summary>
|
|||
private static readonly string Item3DPattern = string.Format(@"^ *\(?(?<x>{0}){1}(?<y>{0}){1}(?<z>{0})\)? *$", @"[+-]?\d*(?:[.,]\d+)?(?:[eE][+-]?\d+)?", @" *[,;] *").Trim('^', '$'); |
|||
|
|||
/// <summary>
|
|||
/// A local regex pattern for a coordinate system
|
|||
/// </summary>
|
|||
private static readonly string CsPattern = string.Format(@"^ *o: *{{(?<op>{0})}} *x: *{{(?<xv>{0})}} *y: *{{(?<yv>{0})}} *z: *{{(?<zv>{0})}} *$", Item3DPattern); |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CoordinateSystem3D"/> class.
|
|||
/// </summary>
|
|||
public CoordinateSystem3D() |
|||
: this(new Point3D(0, 0, 0), UnitVector3D.XAxis.ToVector3D(), UnitVector3D.YAxis.ToVector3D(), UnitVector3D.ZAxis.ToVector3D()) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CoordinateSystem3D"/> class.
|
|||
/// </summary>
|
|||
/// <param name="xAxis">The x axis</param>
|
|||
/// <param name="yAxis">The y axis</param>
|
|||
/// <param name="zAxis">The z axis</param>
|
|||
/// <param name="origin">The origin</param>
|
|||
public CoordinateSystem3D(Vector3D xAxis, Vector3D yAxis, Vector3D zAxis, Point3D origin) |
|||
: this(origin, xAxis, yAxis, zAxis) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CoordinateSystem3D"/> class.
|
|||
/// </summary>
|
|||
/// <param name="origin">The origin</param>
|
|||
/// <param name="xAxis">The x axis</param>
|
|||
/// <param name="yAxis">The y axis</param>
|
|||
/// <param name="zAxis">The z axis</param>
|
|||
public CoordinateSystem3D(Point3D origin, UnitVector3D xAxis, UnitVector3D yAxis, UnitVector3D zAxis) |
|||
: this(origin, xAxis.ToVector3D(), yAxis.ToVector3D(), zAxis.ToVector3D()) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CoordinateSystem3D"/> class.
|
|||
/// </summary>
|
|||
/// <param name="origin">The origin</param>
|
|||
/// <param name="xAxis">The x axis</param>
|
|||
/// <param name="yAxis">The y axis</param>
|
|||
/// <param name="zAxis">The z axis</param>
|
|||
public CoordinateSystem3D(Point3D origin, Vector3D xAxis, Vector3D yAxis, Vector3D zAxis) |
|||
: base(4) |
|||
{ |
|||
this.SetColumn(0, new[] { xAxis.X, xAxis.Y, xAxis.Z, 0 }); |
|||
this.SetColumn(1, new[] { yAxis.X, yAxis.Y, yAxis.Z, 0 }); |
|||
this.SetColumn(2, new[] { zAxis.X, zAxis.Y, zAxis.Z, 0 }); |
|||
this.SetColumn(3, new[] { origin.X, origin.Y, origin.Z, 1 }); |
|||
} |
|||
|
|||
////public CoordinateSystem(Vector3D x, Vector3D y, Vector3D z, Vector3D offsetToBase)
|
|||
//// : this(x, y, z, offsetToBase.ToPoint3D())
|
|||
////{
|
|||
////}
|
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CoordinateSystem3D"/> class.
|
|||
/// </summary>
|
|||
/// <param name="matrix">A matrix</param>
|
|||
public CoordinateSystem3D(Matrix<double> matrix) |
|||
: base(4, 4, matrix.ToColumnMajorArray()) |
|||
{ |
|||
if (matrix.RowCount != 4) |
|||
{ |
|||
throw new ArgumentException("RowCount must be 4"); |
|||
} |
|||
|
|||
if (matrix.ColumnCount != 4) |
|||
{ |
|||
throw new ArgumentException("ColumnCount must be 4"); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the X Axis
|
|||
/// </summary>
|
|||
public Vector3D XAxis |
|||
{ |
|||
get |
|||
{ |
|||
var row = this.SubMatrix(0, 3, 0, 1).ToRowMajorArray(); |
|||
return new Vector3D(row[0], row[1], row[2]); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the Y Axis
|
|||
/// </summary>
|
|||
public Vector3D YAxis |
|||
{ |
|||
get |
|||
{ |
|||
var row = this.SubMatrix(0, 3, 1, 1).ToRowMajorArray(); |
|||
return new Vector3D(row[0], row[1], row[2]); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the z Axis
|
|||
/// </summary>
|
|||
public Vector3D ZAxis |
|||
{ |
|||
get |
|||
{ |
|||
var row = this.SubMatrix(0, 3, 2, 1).ToRowMajorArray(); |
|||
return new Vector3D(row[0], row[1], row[2]); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the point of origin
|
|||
/// </summary>
|
|||
public Point3D Origin |
|||
{ |
|||
get |
|||
{ |
|||
var row = this.SubMatrix(0, 3, 3, 1).ToRowMajorArray(); |
|||
return new Point3D(row[0], row[1], row[2]); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the offset to origin
|
|||
/// </summary>
|
|||
public Vector3D OffsetToBase |
|||
{ |
|||
get { return this.Origin.ToVector3D(); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the base change matrix
|
|||
/// </summary>
|
|||
public CoordinateSystem3D BaseChangeMatrix |
|||
{ |
|||
get |
|||
{ |
|||
var matrix = Build.DenseOfColumnVectors(this.XAxis.ToVector(), this.YAxis.ToVector(), this.ZAxis.ToVector()); |
|||
var cs = new CoordinateSystem3D(this); |
|||
cs.SetRotationSubMatrix(matrix.Transpose()); |
|||
return cs; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether each pair of elements in two specified coordinate system is equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first coordinate system to compare</param>
|
|||
/// <param name="right">The second coordinate system to compare</param>
|
|||
/// <returns>True if the coordinate system are the same; otherwise false.</returns>
|
|||
public static bool operator ==(CoordinateSystem3D left, CoordinateSystem3D right) |
|||
{ |
|||
return CoordinateSystem3D.Equals(left, right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether any pair of elements in two specified coordinate system is not equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first coordinate system to compare</param>
|
|||
/// <param name="right">The second coordinate system to compare</param>
|
|||
/// <returns>True if the coordinate systems are different; otherwise false.</returns>
|
|||
public static bool operator !=(CoordinateSystem3D left, CoordinateSystem3D right) |
|||
{ |
|||
return !CoordinateSystem3D.Equals(left, right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a coordinate system from a string
|
|||
/// </summary>
|
|||
/// <param name="s">The string</param>
|
|||
/// <returns>A coordinate system</returns>
|
|||
public static CoordinateSystem3D Parse(string s) |
|||
{ |
|||
var match = Regex.Match(s, CsPattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Singleline); |
|||
var o = Point3D.Parse(match.Groups["op"].Value); |
|||
var x = Vector3D.Parse(match.Groups["xv"].Value); |
|||
var y = Vector3D.Parse(match.Groups["yv"].Value); |
|||
var z = Vector3D.Parse(match.Groups["zv"].Value); |
|||
return new CoordinateSystem3D(o, x, y, z); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sets to the matrix of rotation that aligns the 'from' vector with the 'to' vector.
|
|||
/// The optional Axis argument may be used when the two vectors are perpendicular and in opposite directions to specify a specific solution, but is otherwise ignored.
|
|||
/// </summary>
|
|||
/// <param name="fromVector3D">Input Vector object to align from.</param>
|
|||
/// <param name="toVector3D">Input Vector object to align to.</param>
|
|||
/// <param name="axis">Input Vector object. </param>
|
|||
/// <returns>A rotated coordinate system </returns>
|
|||
public static CoordinateSystem3D RotateTo(UnitVector3D fromVector3D, UnitVector3D toVector3D, UnitVector3D? axis = null) |
|||
{ |
|||
var r = Matrix3D.RotationTo(fromVector3D, toVector3D, axis); |
|||
var coordinateSystem = new CoordinateSystem3D(); |
|||
var cs = SetRotationSubMatrix(r, coordinateSystem); |
|||
return cs; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a coordinate system that rotates
|
|||
/// </summary>
|
|||
/// <param name="angle">Angle to rotate</param>
|
|||
/// <param name="v">Vector to rotate about</param>
|
|||
/// <returns>A rotating coordinate system</returns>
|
|||
public static CoordinateSystem3D Rotation(Angle angle, UnitVector3D v) |
|||
{ |
|||
var m = Build.Dense(4, 4); |
|||
m.SetSubMatrix(0, 3, 0, 3, Matrix3D.RotationAroundArbitraryVector(v, angle)); |
|||
m[3, 3] = 1; |
|||
return new CoordinateSystem3D(m); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a coordinate system that rotates
|
|||
/// </summary>
|
|||
/// <param name="angle">Angle to rotate</param>
|
|||
/// <param name="v">Vector to rotate about</param>
|
|||
/// <returns>A rotated coordinate system</returns>
|
|||
public static CoordinateSystem3D Rotation(Angle angle, Vector3D v) |
|||
{ |
|||
return Rotation(angle, v.Normalize()); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Rotation around Z (yaw) then around Y (pitch) and then around X (roll)
|
|||
/// http://en.wikipedia.org/wiki/Aircraft_principal_axes
|
|||
/// </summary>
|
|||
/// <param name="yaw">Rotates around Z</param>
|
|||
/// <param name="pitch">Rotates around Y</param>
|
|||
/// <param name="roll">Rotates around X</param>
|
|||
/// <returns>A rotated coordinate system</returns>
|
|||
public static CoordinateSystem3D Rotation(Angle yaw, Angle pitch, Angle roll) |
|||
{ |
|||
var cs = new CoordinateSystem3D(); |
|||
var yt = Yaw(yaw); |
|||
var pt = Pitch(pitch); |
|||
var rt = Roll(roll); |
|||
return rt.Transform(pt.Transform(yt.Transform(cs))); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Rotates around Z
|
|||
/// </summary>
|
|||
/// <param name="av">An angle</param>
|
|||
/// <returns>A rotated coordinate system</returns>
|
|||
public static CoordinateSystem3D Yaw(Angle av) |
|||
{ |
|||
return Rotation(av, UnitVector3D.ZAxis); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Rotates around Y
|
|||
/// </summary>
|
|||
/// <param name="av">An angle</param>
|
|||
/// <returns>A rotated coordinate system</returns>
|
|||
public static CoordinateSystem3D Pitch(Angle av) |
|||
{ |
|||
return Rotation(av, UnitVector3D.YAxis); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Rotates around X
|
|||
/// </summary>
|
|||
/// <param name="av">An angle</param>
|
|||
/// <returns>A rotated coordinate system</returns>
|
|||
public static CoordinateSystem3D Roll(Angle av) |
|||
{ |
|||
return Rotation(av, UnitVector3D.XAxis); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a coordinate system that maps from the 'from' coordinate system to the 'to' coordinate system.
|
|||
/// </summary>
|
|||
/// <param name="fromCs">The from coordinate system</param>
|
|||
/// <param name="toCs">The to coordinate system</param>
|
|||
/// <returns>A mapping coordinate system</returns>
|
|||
public static CoordinateSystem3D CreateMappingCoordinateSystem(CoordinateSystem3D fromCs, CoordinateSystem3D toCs) |
|||
{ |
|||
var m = toCs.Multiply(fromCs.Inverse()); |
|||
m[3, 3] = 1; |
|||
return new CoordinateSystem3D(m); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sets this matrix to be the matrix that maps from the 'from' coordinate system to the 'to' coordinate system.
|
|||
/// </summary>
|
|||
/// <param name="fromOrigin">Input Point3D that defines the origin to map the coordinate system from.</param>
|
|||
/// <param name="fromXAxis">Input Vector3D object that defines the X-axis to map the coordinate system from.</param>
|
|||
/// <param name="fromYAxis">Input Vector3D object that defines the Y-axis to map the coordinate system from.</param>
|
|||
/// <param name="fromZAxis">Input Vector3D object that defines the Z-axis to map the coordinate system from.</param>
|
|||
/// <param name="toOrigin">Input Point3D object that defines the origin to map the coordinate system to.</param>
|
|||
/// <param name="toXAxis">Input Vector3D object that defines the X-axis to map the coordinate system to.</param>
|
|||
/// <param name="toYAxis">Input Vector3D object that defines the Y-axis to map the coordinate system to.</param>
|
|||
/// <param name="toZAxis">Input Vector3D object that defines the Z-axis to map the coordinate system to.</param>
|
|||
/// <returns>A mapping coordinate system</returns>
|
|||
public static CoordinateSystem3D SetToAlignCoordinateSystems(Point3D fromOrigin, Vector3D fromXAxis, Vector3D fromYAxis, Vector3D fromZAxis, Point3D toOrigin, Vector3D toXAxis, Vector3D toYAxis, Vector3D toZAxis) |
|||
{ |
|||
var cs1 = new CoordinateSystem3D(fromOrigin, fromXAxis, fromYAxis, fromZAxis); |
|||
var cs2 = new CoordinateSystem3D(toOrigin, toXAxis, toYAxis, toZAxis); |
|||
var mcs = CreateMappingCoordinateSystem(cs1, cs2); |
|||
return mcs; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a translation
|
|||
/// </summary>
|
|||
/// <param name="translation">A translation vector</param>
|
|||
/// <returns>A translated coordinate system</returns>
|
|||
public static CoordinateSystem3D Translation(Vector3D translation) |
|||
{ |
|||
return new CoordinateSystem3D(translation.ToPoint3D(), UnitVector3D.XAxis, UnitVector3D.YAxis, UnitVector3D.ZAxis); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a rotating coordinate system
|
|||
/// </summary>
|
|||
/// <param name="r">A 3×3 matrix with the rotation portion</param>
|
|||
/// <param name="coordinateSystem">A rotated coordinate system</param>
|
|||
/// <returns>A rotating coordinate system</returns>
|
|||
public static CoordinateSystem3D SetRotationSubMatrix(Matrix<double> r, CoordinateSystem3D coordinateSystem) |
|||
{ |
|||
if (r.RowCount != 3 || r.ColumnCount != 3) |
|||
{ |
|||
throw new ArgumentOutOfRangeException(); |
|||
} |
|||
|
|||
var cs = new CoordinateSystem3D(coordinateSystem.Origin, coordinateSystem.XAxis, coordinateSystem.YAxis, coordinateSystem.ZAxis); |
|||
cs.SetSubMatrix(0, r.RowCount, 0, r.ColumnCount, r); |
|||
return cs; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a rotation submatrix from a coordinate system
|
|||
/// </summary>
|
|||
/// <param name="coordinateSystem">a coordinate system</param>
|
|||
/// <returns>A rotation matrix</returns>
|
|||
public static Matrix<double> GetRotationSubMatrix(CoordinateSystem3D coordinateSystem) |
|||
{ |
|||
return coordinateSystem.SubMatrix(0, 3, 0, 3); |
|||
} |
|||
|
|||
////public CoordinateSystem SetCoordinateSystem(Matrix<double> matrix)
|
|||
////{
|
|||
//// if (matrix.ColumnCount != 4 || matrix.RowCount != 4)
|
|||
//// throw new ArgumentException("Not a 4x4 matrix!");
|
|||
//// return new CoordinateSystem(matrix);
|
|||
////}
|
|||
|
|||
/// <summary>
|
|||
/// Resets rotations preserves scales
|
|||
/// </summary>
|
|||
/// <returns>A coordinate system with reset rotation</returns>
|
|||
public CoordinateSystem3D ResetRotations() |
|||
{ |
|||
var x = this.XAxis.Length * UnitVector3D.XAxis; |
|||
var y = this.YAxis.Length * UnitVector3D.YAxis; |
|||
var z = this.ZAxis.Length * UnitVector3D.ZAxis; |
|||
return new CoordinateSystem3D(x, y, z, this.Origin); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Rotates a coordinate system around a vector
|
|||
/// </summary>
|
|||
/// <param name="about">The vector</param>
|
|||
/// <param name="angle">An angle</param>
|
|||
/// <returns>A rotated coordinate system</returns>
|
|||
public CoordinateSystem3D RotateCoordSysAroundVector(UnitVector3D about, Angle angle) |
|||
{ |
|||
var rcs = Rotation(angle, about); |
|||
return rcs.Transform(this); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Rotate without Reset
|
|||
/// </summary>
|
|||
/// <param name="yaw">The yaw</param>
|
|||
/// <param name="pitch">The pitch</param>
|
|||
/// <param name="roll">The roll</param>
|
|||
/// <returns>A rotated coordinate system</returns>
|
|||
public CoordinateSystem3D RotateNoReset(Angle yaw, Angle pitch, Angle roll) |
|||
{ |
|||
var rcs = Rotation(yaw, pitch, roll); |
|||
return rcs.Transform(this); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Translates a coordinate system
|
|||
/// </summary>
|
|||
/// <param name="v">a translation vector</param>
|
|||
/// <returns>A translated coordinate system</returns>
|
|||
public CoordinateSystem3D OffsetBy(Vector3D v) |
|||
{ |
|||
return new CoordinateSystem3D(this.Origin + v, this.XAxis, this.YAxis, this.ZAxis); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Translates a coordinate system
|
|||
/// </summary>
|
|||
/// <param name="v">a translation vector</param>
|
|||
/// <returns>A translated coordinate system</returns>
|
|||
public CoordinateSystem3D OffsetBy(UnitVector3D v) |
|||
{ |
|||
return new CoordinateSystem3D(this.Origin + v, this.XAxis, this.YAxis, this.ZAxis); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Transforms a ray according to this change matrix
|
|||
/// </summary>
|
|||
/// <param name="r">a ray</param>
|
|||
/// <returns>a transformed ray</returns>
|
|||
public Ray3D TransformToCoordSys(Ray3D r) |
|||
{ |
|||
var p = r.ThroughPoint; |
|||
var uv = r.Direction; |
|||
|
|||
// The position and the vector are transformed
|
|||
var baseChangeMatrix = this.BaseChangeMatrix; |
|||
var point = baseChangeMatrix.Transform(p) + this.OffsetToBase; |
|||
var direction = uv.TransformBy((Matrix<double>)baseChangeMatrix); |
|||
return new Ray3D(point, direction); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Transforms a point according to this change matrix
|
|||
/// </summary>
|
|||
/// <param name="p">a point</param>
|
|||
/// <returns>a transformed point</returns>
|
|||
public Point3D TransformToCoordSys(Point3D p) |
|||
{ |
|||
var baseChangeMatrix = this.BaseChangeMatrix; |
|||
var point = baseChangeMatrix.Transform(p) + this.OffsetToBase; |
|||
return point; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Transforms a ray according to the inverse of this change matrix
|
|||
/// </summary>
|
|||
/// <param name="r">a ray</param>
|
|||
/// <returns>a transformed ray</returns>
|
|||
public Ray3D TransformFromCoordSys(Ray3D r) |
|||
{ |
|||
var p = r.ThroughPoint; |
|||
var uv = r.Direction; |
|||
|
|||
// The position and the vector are transformed
|
|||
var point = this.BaseChangeMatrix.Invert().Transform(p) + this.OffsetToBase; |
|||
var direction = this.BaseChangeMatrix.Invert().Transform(uv); |
|||
return new Ray3D(point, direction); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Transforms a point according to the inverse of this change matrix
|
|||
/// </summary>
|
|||
/// <param name="p">a point</param>
|
|||
/// <returns>a transformed point</returns>
|
|||
public Point3D TransformFromCoordSys(Point3D p) |
|||
{ |
|||
var point = this.BaseChangeMatrix.Invert().Transform(p) + this.OffsetToBase; |
|||
return point; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a rotation submatrix
|
|||
/// </summary>
|
|||
/// <param name="r">a matrix</param>
|
|||
/// <returns>a coordinate system</returns>
|
|||
public CoordinateSystem3D SetRotationSubMatrix(Matrix<double> r) |
|||
{ |
|||
return SetRotationSubMatrix(r, this); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a translation coordinate system
|
|||
/// </summary>
|
|||
/// <param name="v">a vector</param>
|
|||
/// <returns>a coordinate system</returns>
|
|||
public CoordinateSystem3D SetTranslation(Vector3D v) |
|||
{ |
|||
return new CoordinateSystem3D(v.ToPoint3D(), this.XAxis, this.YAxis, this.ZAxis); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a rotation sub matrix
|
|||
/// </summary>
|
|||
/// <returns>a rotation sub matrix</returns>
|
|||
public Matrix<double> GetRotationSubMatrix() |
|||
{ |
|||
return GetRotationSubMatrix(this); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Transforms a vector and returns the transformed vector
|
|||
/// </summary>
|
|||
/// <param name="v">A vector</param>
|
|||
/// <returns>A transformed vector</returns>
|
|||
public Vector3D Transform(Vector3D v) |
|||
{ |
|||
var v3 = Vector<double>.Build.Dense(new[] { v.X, v.Y, v.Z }); |
|||
this.GetRotationSubMatrix().Multiply(v3, v3); |
|||
return new Vector3D(v3[0], v3[1], v3[2]); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Transforms a vector and returns the transformed vector
|
|||
/// </summary>
|
|||
/// <param name="v">a unit vector</param>
|
|||
/// <returns>A transformed vector</returns>
|
|||
public Vector3D Transform(UnitVector3D v) |
|||
{ |
|||
var v3 = Vector<double>.Build.Dense(new[] { v.X, v.Y, v.Z }); |
|||
this.GetRotationSubMatrix().Multiply(v3, v3); |
|||
return new Vector3D(v3[0], v3[1], v3[2]); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Transforms a point and returns the transformed point
|
|||
/// </summary>
|
|||
/// <param name="p">a point</param>
|
|||
/// <returns>A transformed point</returns>
|
|||
public Point3D Transform(Point3D p) |
|||
{ |
|||
var v4 = Vector<double>.Build.Dense(new[] { p.X, p.Y, p.Z, 1 }); |
|||
this.Multiply(v4, v4); |
|||
return new Point3D(v4[0], v4[1], v4[2]); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Transforms a coordinate system and returns the transformed
|
|||
/// </summary>
|
|||
/// <param name="cs">a coordinate system</param>
|
|||
/// <returns>A transformed coordinate system</returns>
|
|||
public CoordinateSystem3D Transform(CoordinateSystem3D cs) |
|||
{ |
|||
return new CoordinateSystem3D(this.Multiply(cs)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Transforms a line segment.
|
|||
/// </summary>
|
|||
/// <param name="l">A line segment</param>
|
|||
/// <returns>The transformed line segment</returns>
|
|||
public LineSegment3D Transform(LineSegment3D l) |
|||
{ |
|||
return new LineSegment3D(this.Transform(l.StartPoint), this.Transform(l.EndPoint)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Transforms a ray and returns the transformed.
|
|||
/// </summary>
|
|||
/// <param name="ray">A ray</param>
|
|||
/// <returns>A transformed ray</returns>
|
|||
public Ray3D Transform(Ray3D ray) |
|||
{ |
|||
return new Ray3D(this.Transform(ray.ThroughPoint), this.Transform(ray.Direction)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Transforms a coordinate system
|
|||
/// </summary>
|
|||
/// <param name="matrix">a matrix</param>
|
|||
/// <returns>A transformed coordinate system</returns>
|
|||
public CoordinateSystem3D TransformBy(Matrix<double> matrix) |
|||
{ |
|||
return new CoordinateSystem3D(matrix.Multiply(this)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Transforms this by the coordinate system and returns the transformed.
|
|||
/// </summary>
|
|||
/// <param name="cs">a coordinate system</param>
|
|||
/// <returns>a transformed coordinate system</returns>
|
|||
public CoordinateSystem3D TransformBy(CoordinateSystem3D cs) |
|||
{ |
|||
return cs.Transform(this); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Inverts this coordinate system
|
|||
/// </summary>
|
|||
/// <returns>An inverted coordinate system</returns>
|
|||
public CoordinateSystem3D Invert() |
|||
{ |
|||
return new CoordinateSystem3D(this.Inverse()); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value to indicate if this CoordinateSystem is equivalent to a another CoordinateSystem
|
|||
/// </summary>
|
|||
/// <param name="other">The CoordinateSystem to compare against.</param>
|
|||
/// <param name="tolerance">A tolerance (epsilon) to adjust for floating point error</param>
|
|||
/// <returns>true if the CoordinateSystems are equal; otherwise false</returns>
|
|||
[Pure] |
|||
public bool Equals(CoordinateSystem3D other, double tolerance) |
|||
{ |
|||
if (this.Values.Length != other?.Values.Length) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
for (var i = 0; i < this.Values.Length; i++) |
|||
{ |
|||
if (Math.Abs(this.Values[i] - other.Values[i]) > tolerance) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public bool Equals(CoordinateSystem3D other) |
|||
{ |
|||
if (this.Values.Length != other?.Values.Length) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
for (var i = 0; i < this.Values.Length; i++) |
|||
{ |
|||
// ReSharper disable once CompareOfFloatsByEqualityOperator
|
|||
if (this.Values[i] != other.Values[i]) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override bool Equals(object obj) |
|||
{ |
|||
if (obj is null) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return obj is CoordinateSystem3D cs && this.Equals(cs); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override int GetHashCode() => HashCode.CombineMany(this.Values); |
|||
|
|||
/// <summary>
|
|||
/// Returns a string representation of the coordinate system
|
|||
/// </summary>
|
|||
/// <returns>a string</returns>
|
|||
public new string ToString() |
|||
{ |
|||
return $"Origin: {this.Origin}, XAxis: {this.XAxis}, YAxis: {this.YAxis}, ZAxis: {this.ZAxis}"; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
XmlSchema IXmlSerializable.GetSchema() |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
void IXmlSerializable.ReadXml(XmlReader reader) |
|||
{ |
|||
var e = (XElement)XNode.ReadFrom(reader); |
|||
|
|||
var xAxis = Vector3D.ReadFrom(e.SingleElementReader("XAxis")); |
|||
this.SetColumn(0, new[] { xAxis.X, xAxis.Y, xAxis.Z, 0 }); |
|||
|
|||
var yAxis = Vector3D.ReadFrom(e.SingleElementReader("YAxis")); |
|||
this.SetColumn(1, new[] { yAxis.X, yAxis.Y, yAxis.Z, 0 }); |
|||
|
|||
var zAxis = Vector3D.ReadFrom(e.SingleElementReader("ZAxis")); |
|||
this.SetColumn(2, new[] { zAxis.X, zAxis.Y, zAxis.Z, 0 }); |
|||
|
|||
var origin = Point3D.ReadFrom(e.SingleElementReader("Origin")); |
|||
this.SetColumn(3, new[] { origin.X, origin.Y, origin.Z, 1 }); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
void IXmlSerializable.WriteXml(XmlWriter writer) |
|||
{ |
|||
writer.WriteElement("Origin", this.Origin); |
|||
writer.WriteElement("XAxis", this.XAxis); |
|||
writer.WriteElement("YAxis", this.YAxis); |
|||
writer.WriteElement("ZAxis", this.ZAxis); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,344 @@ |
|||
using System; |
|||
using System.Diagnostics.Contracts; |
|||
using System.Xml; |
|||
using System.Xml.Linq; |
|||
using System.Xml.Schema; |
|||
using System.Xml.Serialization; |
|||
using MathNet.Numerics.Spatial.Internal; |
|||
|
|||
namespace MathNet.Numerics.Spatial.Euclidean3D |
|||
{ |
|||
/// <summary>
|
|||
/// A line between two points
|
|||
/// </summary>
|
|||
[Serializable] |
|||
public struct Line3D : IEquatable<Line3D>, IXmlSerializable |
|||
{ |
|||
/// <summary>
|
|||
/// The start point of the line
|
|||
/// </summary>
|
|||
public readonly Point3D StartPoint; |
|||
|
|||
/// <summary>
|
|||
/// The end point of the line
|
|||
/// </summary>
|
|||
public readonly Point3D EndPoint; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Line3D"/> struct.
|
|||
/// Throws an ArgumentException if the <paramref name="startPoint"/> is equal to the <paramref name="endPoint"/>.
|
|||
/// </summary>
|
|||
/// <param name="startPoint">The starting point of the line segment.</param>
|
|||
/// <param name="endPoint">The ending point of the line segment.</param>
|
|||
public Line3D(Point3D startPoint, Point3D endPoint) |
|||
{ |
|||
if (startPoint == endPoint) |
|||
{ |
|||
throw new ArgumentException("StartPoint == EndPoint"); |
|||
} |
|||
|
|||
this.StartPoint = startPoint; |
|||
this.EndPoint = endPoint; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets distance from <see cref="StartPoint"/> to <see cref="EndPoint"/>, the length of the line
|
|||
/// </summary>
|
|||
[Pure] |
|||
public double Length => this.StartPoint.DistanceTo(this.EndPoint); |
|||
|
|||
/// <summary>
|
|||
/// Gets the direction from the <see cref="StartPoint"/> to <see cref="EndPoint"/>
|
|||
/// </summary>
|
|||
[Pure] |
|||
public UnitVector3D Direction => this.StartPoint.VectorTo(this.EndPoint).Normalize(); |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether each pair of elements in two specified lines is equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first line to compare</param>
|
|||
/// <param name="right">The second line to compare</param>
|
|||
/// <returns>True if the lines are the same; otherwise false.</returns>
|
|||
public static bool operator ==(Line3D left, Line3D right) |
|||
{ |
|||
return left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether any pair of elements in two specified lines is not equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first line to compare</param>
|
|||
/// <param name="right">The second line to compare</param>
|
|||
/// <returns>True if the lines are different; otherwise false.</returns>
|
|||
public static bool operator !=(Line3D left, Line3D right) |
|||
{ |
|||
return !left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a new <see cref="Line3D"/> from a pair of strings which represent points.
|
|||
/// See <see cref="Point3D.Parse(string, IFormatProvider)" /> for details on acceptable formats.
|
|||
/// </summary>
|
|||
/// <param name="startPoint">The string representation of the first point.</param>
|
|||
/// <param name="endPoint">The string representation of the second point.</param>
|
|||
/// <returns>A line segment from the first point to the second point.</returns>
|
|||
public static Line3D Parse(string startPoint, string endPoint) |
|||
{ |
|||
return new Line3D(Point3D.Parse(startPoint), Point3D.Parse(endPoint)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the shortest line between this line and a point.
|
|||
/// </summary>
|
|||
/// <param name="p">the point to create a line to</param>
|
|||
/// <param name="mustStartBetweenStartAndEnd">If false the start point can extend beyond the start and endpoint of the line</param>
|
|||
/// <returns>The shortest line between the line and the point</returns>
|
|||
[Pure] |
|||
public Line3D LineTo(Point3D p, bool mustStartBetweenStartAndEnd) |
|||
{ |
|||
return new Line3D(this.ClosestPointTo(p, mustStartBetweenStartAndEnd), p); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the closest point on the line to the given point.
|
|||
/// </summary>
|
|||
/// <param name="p">The point that the returned point is the closest point on the line to</param>
|
|||
/// <param name="mustBeOnSegment">If true the returned point is contained by the segment ends, otherwise it can be anywhere on the projected line</param>
|
|||
/// <returns>The closest point on the line to the provided point</returns>
|
|||
[Pure] |
|||
public Point3D ClosestPointTo(Point3D p, bool mustBeOnSegment) |
|||
{ |
|||
var v = p - this.StartPoint; |
|||
var dotProduct = v.DotProduct(this.Direction); |
|||
if (mustBeOnSegment) |
|||
{ |
|||
if (dotProduct < 0) |
|||
{ |
|||
dotProduct = 0; |
|||
} |
|||
|
|||
if (dotProduct > this.Length) |
|||
{ |
|||
dotProduct = this.Length; |
|||
} |
|||
} |
|||
|
|||
var alongVector = dotProduct * this.Direction; |
|||
return this.StartPoint + alongVector; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// The line projected on a plane
|
|||
/// </summary>
|
|||
/// <param name="plane">The plane.</param>
|
|||
/// <returns>A projected line.</returns>
|
|||
[Pure] |
|||
public Line3D ProjectOn(Plane3D plane) |
|||
{ |
|||
return plane.Project(this); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Find the intersection between the line and a plane
|
|||
/// </summary>
|
|||
/// <param name="plane">The plane.</param>
|
|||
/// <param name="tolerance">A tolerance (epsilon) to compensate for floating point error</param>
|
|||
/// <returns>A point where the line and plane intersect; null if no such point exists</returns>
|
|||
[Pure] |
|||
public Point3D? IntersectionWith(Plane3D plane, double tolerance = double.Epsilon) |
|||
{ |
|||
return plane.IntersectionWith(this, tolerance); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Checks to determine whether or not two lines are parallel to each other, using the dot product within
|
|||
/// the double precision specified in the MathNet.Numerics package.
|
|||
/// </summary>
|
|||
/// <param name="other">The other line to check this one against</param>
|
|||
/// <returns>True if the lines are parallel, false if they are not</returns>
|
|||
[Pure] |
|||
public bool IsParallelTo(Line3D other) |
|||
{ |
|||
return this.Direction.IsParallelTo(other.Direction, Precision.DoublePrecision * 2); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Checks to determine whether or not two lines are parallel to each other within a specified angle tolerance
|
|||
/// </summary>
|
|||
/// <param name="other">The other line to check this one against</param>
|
|||
/// <param name="angleTolerance">If the angle between line directions is less than this value, the method returns true</param>
|
|||
/// <returns>True if the lines are parallel within the angle tolerance, false if they are not</returns>
|
|||
[Pure] |
|||
public bool IsParallelTo(Line3D other, Angle angleTolerance) |
|||
{ |
|||
return this.Direction.IsParallelTo(other.Direction, angleTolerance); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Computes the pair of points which represent the closest distance between this Line3D and another Line3D, with the first
|
|||
/// point being the point on this Line3D, and the second point being the corresponding point on the other Line3D. If the lines
|
|||
/// intersect the points will be identical, if the lines are parallel the first point will be the start point of this line.
|
|||
/// </summary>
|
|||
/// <param name="other">line to compute the closest points with</param>
|
|||
/// <returns>A tuple of two points representing the endpoints of the shortest distance between the two lines</returns>
|
|||
[Pure] |
|||
public Tuple<Point3D, Point3D> ClosestPointsBetween(Line3D other) |
|||
{ |
|||
if (this.IsParallelTo(other)) |
|||
{ |
|||
return Tuple.Create(this.StartPoint, other.ClosestPointTo(this.StartPoint, false)); |
|||
} |
|||
|
|||
// http://geomalgorithms.com/a07-_distance.html
|
|||
var point0 = this.StartPoint; |
|||
var u = this.Direction; |
|||
var point1 = other.StartPoint; |
|||
var v = other.Direction; |
|||
|
|||
var w0 = point0 - point1; |
|||
var a = u.DotProduct(u); |
|||
var b = u.DotProduct(v); |
|||
var c = v.DotProduct(v); |
|||
var d = u.DotProduct(w0); |
|||
var e = v.DotProduct(w0); |
|||
|
|||
var sc = ((b * e) - (c * d)) / ((a * c) - (b * b)); |
|||
var tc = ((a * e) - (b * d)) / ((a * c) - (b * b)); |
|||
|
|||
return Tuple.Create(point0 + (sc * u), point1 + (tc * v)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Computes the pair of points which represents the closest distance between this Line3D and another Line3D, with the option
|
|||
/// of treating the lines as segments bounded by their start and end points.
|
|||
/// </summary>
|
|||
/// <param name="other">line to compute the closest points with</param>
|
|||
/// <param name="mustBeOnSegments">if true, the lines are treated as segments bounded by the start and end point</param>
|
|||
/// <returns>A tuple of two points representing the endpoints of the shortest distance between the two lines or segments</returns>
|
|||
[Pure] |
|||
public Tuple<Point3D, Point3D> ClosestPointsBetween(Line3D other, bool mustBeOnSegments) |
|||
{ |
|||
// If the segments are parallel and the answer must be on the segments, we can skip directly to the ending
|
|||
// algorithm where the endpoints are projected onto the opposite segment and the smallest distance is
|
|||
// taken. Otherwise we must first check if the infinite length line solution is valid.
|
|||
// If the lines aren't parallel OR it doesn't have to be constrained to the segments
|
|||
if (!this.IsParallelTo(other) || !mustBeOnSegments) |
|||
{ |
|||
// Compute the unbounded result, and if mustBeOnSegments is false we can directly return the results
|
|||
// since this is the same as calling the other method.
|
|||
var result = this.ClosestPointsBetween(other); |
|||
if (!mustBeOnSegments) |
|||
{ |
|||
return result; |
|||
} |
|||
|
|||
// A point that is known to be collinear with the line start and end points is on the segment if
|
|||
// its distance to both endpoints is less than the segment length. If both projected points lie
|
|||
// within their segment, we can directly return the result.
|
|||
if (result.Item1.DistanceTo(this.StartPoint) <= this.Length && |
|||
result.Item1.DistanceTo(this.EndPoint) <= this.Length && |
|||
result.Item2.DistanceTo(other.StartPoint) <= other.Length && |
|||
result.Item2.DistanceTo(other.EndPoint) <= other.Length) |
|||
{ |
|||
return result; |
|||
} |
|||
} |
|||
|
|||
//// If we got here, we know that either we're doing a bounded distance on two parallel segments or one
|
|||
//// of the two closest span points is outside of the segment of the line it was projected on. In either
|
|||
//// case we project each of the four endpoints onto the opposite segments and select the one with the
|
|||
//// smallest projected distance.
|
|||
|
|||
var checkPoint = other.ClosestPointTo(this.StartPoint, true); |
|||
var distance = checkPoint.DistanceTo(this.StartPoint); |
|||
var closestPair = Tuple.Create(this.StartPoint, checkPoint); |
|||
var minDistance = distance; |
|||
|
|||
checkPoint = other.ClosestPointTo(this.EndPoint, true); |
|||
distance = checkPoint.DistanceTo(this.EndPoint); |
|||
if (distance < minDistance) |
|||
{ |
|||
closestPair = Tuple.Create(this.EndPoint, checkPoint); |
|||
minDistance = distance; |
|||
} |
|||
|
|||
checkPoint = this.ClosestPointTo(other.StartPoint, true); |
|||
distance = checkPoint.DistanceTo(other.StartPoint); |
|||
if (distance < minDistance) |
|||
{ |
|||
closestPair = Tuple.Create(checkPoint, other.StartPoint); |
|||
minDistance = distance; |
|||
} |
|||
|
|||
checkPoint = this.ClosestPointTo(other.EndPoint, true); |
|||
distance = checkPoint.DistanceTo(other.EndPoint); |
|||
if (distance < minDistance) |
|||
{ |
|||
closestPair = Tuple.Create(checkPoint, other.EndPoint); |
|||
} |
|||
|
|||
return closestPair; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public bool Equals(Line3D other) |
|||
{ |
|||
return this.StartPoint.Equals(other.StartPoint) && this.EndPoint.Equals(other.EndPoint); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override bool Equals(object obj) |
|||
{ |
|||
if (ReferenceEquals(null, obj)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return obj is Line3D d && this.Equals(d); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override int GetHashCode() |
|||
{ |
|||
unchecked |
|||
{ |
|||
var hashCode = this.StartPoint.GetHashCode(); |
|||
hashCode = (hashCode * 397) ^ this.EndPoint.GetHashCode(); |
|||
return hashCode; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override string ToString() |
|||
{ |
|||
return $"StartPoint: {this.StartPoint}, EndPoint: {this.EndPoint}"; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
XmlSchema IXmlSerializable.GetSchema() |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
void IXmlSerializable.ReadXml(XmlReader reader) |
|||
{ |
|||
reader.MoveToContent(); |
|||
var e = (XElement)XNode.ReadFrom(reader); |
|||
this = new Line3D( |
|||
Point3D.ReadFrom(e.SingleElement("StartPoint").CreateReader()), |
|||
Point3D.ReadFrom(e.SingleElement("EndPoint").CreateReader())); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
void IXmlSerializable.WriteXml(XmlWriter writer) |
|||
{ |
|||
writer.WriteElement("StartPoint", this.StartPoint); |
|||
writer.WriteElement("EndPoint", this.EndPoint); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,298 @@ |
|||
using System; |
|||
using System.Diagnostics.Contracts; |
|||
using MathNet.Numerics.Spatial.Internal; |
|||
|
|||
namespace MathNet.Numerics.Spatial.Euclidean3D |
|||
{ |
|||
/// <summary>
|
|||
/// This structure represents a line between two points in 3D-space. It allows for operations such as
|
|||
/// computing the length, direction, comparisons, and shifting by a vector.
|
|||
/// </summary>
|
|||
public struct LineSegment3D : IEquatable<LineSegment3D> |
|||
{ |
|||
/// <summary>
|
|||
/// The starting point of the line segment
|
|||
/// </summary>
|
|||
public readonly Point3D StartPoint; |
|||
|
|||
/// <summary>
|
|||
/// The end point of the line segment
|
|||
/// </summary>
|
|||
public readonly Point3D EndPoint; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="LineSegment3D"/> struct.
|
|||
/// Throws an ArgumentException if the <paramref name="startPoint"/> is equal to the <paramref name="endPoint"/>.
|
|||
/// </summary>
|
|||
/// <param name="startPoint">the starting point of the line segment.</param>
|
|||
/// <param name="endPoint">the ending point of the line segment</param>
|
|||
public LineSegment3D(Point3D startPoint, Point3D endPoint) |
|||
{ |
|||
if (startPoint == endPoint) |
|||
{ |
|||
throw new ArgumentException("The segment starting and ending points cannot be identical"); |
|||
} |
|||
|
|||
this.StartPoint = startPoint; |
|||
this.EndPoint = endPoint; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the distance from <see cref="StartPoint"/> to <see cref="EndPoint"/>
|
|||
/// </summary>
|
|||
[Pure] |
|||
public double Length => this.StartPoint.DistanceTo(this.EndPoint); |
|||
|
|||
/// <summary>
|
|||
/// Gets a normalized vector in the direction from <see cref="StartPoint"/> to <see cref="EndPoint"/>
|
|||
/// </summary>
|
|||
[Pure] |
|||
public UnitVector3D Direction => this.StartPoint.VectorTo(this.EndPoint).Normalize(); |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether each pair of elements in two specified lines is equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first line to compare</param>
|
|||
/// <param name="right">The second line to compare</param>
|
|||
/// <returns>True if the lines are the same; otherwise false.</returns>
|
|||
public static bool operator ==(LineSegment3D left, LineSegment3D right) |
|||
{ |
|||
return left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether any pair of elements in two specified lines is not equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first line to compare</param>
|
|||
/// <param name="right">The second line to compare</param>
|
|||
/// <returns>True if the lines are different; otherwise false.</returns>
|
|||
public static bool operator !=(LineSegment3D left, LineSegment3D right) |
|||
{ |
|||
return !left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a new <see cref="LineSegment3D"/> from a pair of strings which represent points.
|
|||
/// See <see cref="Point3D" /> for details on acceptable formats.
|
|||
/// </summary>
|
|||
/// <param name="startPointString">The string representation of the first point.</param>
|
|||
/// <param name="endPointString">The string representation of the second point.</param>
|
|||
/// <returns>A line segment from the first point to the second point.</returns>
|
|||
public static LineSegment3D Parse(string startPointString, string endPointString) |
|||
{ |
|||
return new LineSegment3D(Point3D.Parse(startPointString), Point3D.Parse(endPointString)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Translates a line according to a provided vector
|
|||
/// </summary>
|
|||
/// <param name="vector">A vector to apply</param>
|
|||
/// <returns>A new translated line segment</returns>
|
|||
public LineSegment3D TranslateBy(Vector3D vector) |
|||
{ |
|||
return new LineSegment3D(this.StartPoint + vector, this.EndPoint + vector); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the closest point on the line segment to the given point.
|
|||
/// </summary>
|
|||
/// <param name="p">The point that the returned point is the closest point on the line to</param>
|
|||
/// <returns>The closest point on the line to the provided point</returns>
|
|||
[Pure] |
|||
public Point3D ClosestPointTo(Point3D p) |
|||
{ |
|||
var v = this.StartPoint.VectorTo(p); |
|||
var dotProduct = v.DotProduct(this.Direction); |
|||
|
|||
if (dotProduct < 0) |
|||
{ |
|||
dotProduct = 0; |
|||
} |
|||
|
|||
if (dotProduct > this.Length) |
|||
{ |
|||
dotProduct = this.Length; |
|||
} |
|||
|
|||
var alongVector = dotProduct * this.Direction; |
|||
return this.StartPoint + alongVector; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a new line segment between the closest point on this line segment and a point.
|
|||
/// </summary>
|
|||
/// <param name="p">the point to create a line to</param>
|
|||
/// <returns>A line segment between the nearest point on this segment and the provided point.</returns>
|
|||
[Pure] |
|||
public LineSegment3D LineTo(Point3D p) |
|||
{ |
|||
return new LineSegment3D(this.ClosestPointTo(p), p); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Computes the pair of points which represents the closest distance between this Line3D and another Line3D, with the option
|
|||
/// of treating the lines as segments bounded by their start and end points.
|
|||
/// </summary>
|
|||
/// <param name="other">line to compute the closest points with</param>
|
|||
/// <param name="tolerance">A tolerance (epsilon) to adjust for floating point error</param>
|
|||
/// <param name="closestLine">A line representing the endpoints of the shortest distance between the two segments</param>
|
|||
/// <returns>True if a line could be found, false if the lines intersect</returns>
|
|||
[Pure] |
|||
public bool TryShortestLineTo(LineSegment3D other, Angle tolerance, out LineSegment3D closestLine) |
|||
{ |
|||
// If the segments are parallel and the answer must be on the segments, we can skip directly to the ending
|
|||
// algorithm where the endpoints are projected onto the opposite segment and the smallest distance is
|
|||
// taken. Otherwise we must first check if the infinite length line solution is valid.
|
|||
// If the lines aren't parallel
|
|||
if (!this.IsParallelTo(other, tolerance)) |
|||
{ |
|||
// Compute the unbounded result
|
|||
var result = this.ClosestPointsBetweenLines(other, tolerance); |
|||
|
|||
// A point that is known to be collinear with the line start and end points is on the segment if
|
|||
// its distance to both endpoints is less than the segment length. If both projected points lie
|
|||
// within their segment, we can directly return the result.
|
|||
if (result.Item1.DistanceTo(this.StartPoint) <= this.Length && |
|||
result.Item1.DistanceTo(this.EndPoint) <= this.Length && |
|||
result.Item2.DistanceTo(other.StartPoint) <= other.Length && |
|||
result.Item2.DistanceTo(other.EndPoint) <= other.Length) |
|||
{ |
|||
closestLine = new LineSegment3D(result.Item1, result.Item2); |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
//// If we got here, we know that either we're doing a bounded distance on two parallel segments or one
|
|||
//// of the two closest span points is outside of the segment of the line it was projected on. In either
|
|||
//// case we project each of the four endpoints onto the opposite segments and select the one with the
|
|||
//// smallest projected distance.
|
|||
|
|||
var checkPoint = other.ClosestPointTo(this.StartPoint); |
|||
var distance = checkPoint.DistanceTo(this.StartPoint); |
|||
var closestPair = Tuple.Create(this.StartPoint, checkPoint); |
|||
var minDistance = distance; |
|||
|
|||
checkPoint = other.ClosestPointTo(this.EndPoint); |
|||
distance = checkPoint.DistanceTo(this.EndPoint); |
|||
if (distance < minDistance) |
|||
{ |
|||
closestPair = Tuple.Create(this.EndPoint, checkPoint); |
|||
minDistance = distance; |
|||
} |
|||
|
|||
checkPoint = this.ClosestPointTo(other.StartPoint); |
|||
distance = checkPoint.DistanceTo(other.StartPoint); |
|||
if (distance < minDistance) |
|||
{ |
|||
closestPair = Tuple.Create(checkPoint, other.StartPoint); |
|||
minDistance = distance; |
|||
} |
|||
|
|||
checkPoint = this.ClosestPointTo(other.EndPoint); |
|||
distance = checkPoint.DistanceTo(other.EndPoint); |
|||
if (distance < minDistance) |
|||
{ |
|||
closestPair = Tuple.Create(checkPoint, other.EndPoint); |
|||
} |
|||
|
|||
if (closestPair.Item1 == closestPair.Item2) |
|||
{ |
|||
closestLine = default(LineSegment3D); |
|||
return false; |
|||
} |
|||
|
|||
closestLine = new LineSegment3D(closestPair.Item1, closestPair.Item2); |
|||
return true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Checks to determine whether or not two line segments are parallel to each other within a specified angle tolerance
|
|||
/// </summary>
|
|||
/// <param name="other">The other line to check this one against</param>
|
|||
/// <param name="tolerance">If the angle between line directions is less than this value, the method returns true</param>
|
|||
/// <returns>True if the lines are parallel within the angle tolerance, false if they are not</returns>
|
|||
[Pure] |
|||
public bool IsParallelTo(LineSegment3D other, Angle tolerance) |
|||
{ |
|||
return this.Direction.IsParallelTo(other.Direction, tolerance); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[Pure] |
|||
public override string ToString() |
|||
{ |
|||
return $"StartPoint: {this.StartPoint}, EndPoint: {this.EndPoint}"; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value to indicate if a pair of line segments are equal
|
|||
/// </summary>
|
|||
/// <param name="other">The line segment to compare against.</param>
|
|||
/// <param name="tolerance">A tolerance (epsilon) to adjust for floating point error</param>
|
|||
/// <returns>True if the line segments are equal; otherwise false</returns>
|
|||
[Pure] |
|||
public bool Equals(LineSegment3D other, double tolerance) |
|||
{ |
|||
return this.StartPoint.Equals(other.StartPoint, tolerance) && this.EndPoint.Equals(other.EndPoint, tolerance); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[Pure] |
|||
public bool Equals(LineSegment3D l) => this.StartPoint.Equals(l.StartPoint) && this.EndPoint.Equals(l.EndPoint); |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override bool Equals(object obj) => obj is LineSegment3D l && this.Equals(l); |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override int GetHashCode() => HashCode.Combine(this.StartPoint, this.EndPoint); |
|||
|
|||
/// <summary>
|
|||
/// Extends the segment to a infinite line and finds the closest point on that line to the provided point.
|
|||
/// </summary>
|
|||
/// <param name="p">a point</param>
|
|||
/// <returns>A point on the infinite line which extends the segment</returns>
|
|||
[Pure] |
|||
private Point3D ClosestLinePointTo(Point3D p) |
|||
{ |
|||
var alongVector = this.StartPoint.VectorTo(p).DotProduct(this.Direction) * this.Direction; |
|||
return this.StartPoint + alongVector; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Computes the pair of points which represent the closest distance between this Line3D and another Line3D, with the first
|
|||
/// point being the point on this Line3D, and the second point being the corresponding point on the other Line3D. If the lines
|
|||
/// intersect the points will be identical, if the lines are parallel the first point will be the start point of this line.
|
|||
/// </summary>
|
|||
/// <param name="other">line to compute the closest points with</param>
|
|||
/// <param name="tolerance">A tolerance (epsilon) to adjust for floating point error</param>
|
|||
/// <returns>A tuple of two points representing the endpoints of the shortest distance between the two lines</returns>
|
|||
[Pure] |
|||
private Tuple<Point3D, Point3D> ClosestPointsBetweenLines(LineSegment3D other, Angle tolerance) |
|||
{ |
|||
if (this.IsParallelTo(other, tolerance)) |
|||
{ |
|||
return Tuple.Create(this.StartPoint, other.ClosestLinePointTo(this.StartPoint)); |
|||
} |
|||
|
|||
// http://geomalgorithms.com/a07-_distance.html
|
|||
var point0 = this.StartPoint; |
|||
var u = this.Direction; |
|||
var point1 = other.StartPoint; |
|||
var v = other.Direction; |
|||
|
|||
var w0 = point0 - point1; |
|||
var a = u.DotProduct(u); |
|||
var b = u.DotProduct(v); |
|||
var c = v.DotProduct(v); |
|||
var d = u.DotProduct(w0); |
|||
var e = v.DotProduct(w0); |
|||
|
|||
var sc = ((b * e) - (c * d)) / ((a * c) - (b * b)); |
|||
var tc = ((a * e) - (b * d)) / ((a * c) - (b * b)); |
|||
|
|||
return Tuple.Create(point0 + (sc * u), point1 + (tc * v)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,126 @@ |
|||
using System; |
|||
using MathNet.Numerics.LinearAlgebra; |
|||
using MathNet.Numerics.LinearAlgebra.Double; |
|||
|
|||
namespace MathNet.Numerics.Spatial.Euclidean3D |
|||
{ |
|||
/// <summary>
|
|||
/// Helper class for working with 3D matrixes
|
|||
/// </summary>
|
|||
public static class Matrix3D |
|||
{ |
|||
/// <summary>
|
|||
/// Creates a rotation matrix around the X axis
|
|||
/// </summary>
|
|||
/// <param name="angle">The angle to rotate</param>
|
|||
/// <returns>A rotation matrix</returns>
|
|||
public static DenseMatrix RotationAroundXAxis(Angle angle) |
|||
{ |
|||
var rotationMatrix = new DenseMatrix(3, 3); |
|||
rotationMatrix[0, 0] = 1; |
|||
rotationMatrix[1, 1] = Math.Cos(angle.Radians); |
|||
rotationMatrix[1, 2] = -Math.Sin(angle.Radians); |
|||
rotationMatrix[2, 1] = Math.Sin(angle.Radians); |
|||
rotationMatrix[2, 2] = Math.Cos(angle.Radians); |
|||
return rotationMatrix; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a rotation matrix around the Y axis
|
|||
/// </summary>
|
|||
/// <param name="angle">The angle to rotate</param>
|
|||
/// <returns>A rotation matrix</returns>
|
|||
public static DenseMatrix RotationAroundYAxis(Angle angle) |
|||
{ |
|||
var rotationMatrix = new DenseMatrix(3, 3); |
|||
rotationMatrix[0, 0] = Math.Cos(angle.Radians); |
|||
rotationMatrix[0, 2] = Math.Sin(angle.Radians); |
|||
rotationMatrix[1, 1] = 1; |
|||
rotationMatrix[2, 0] = -Math.Sin(angle.Radians); |
|||
rotationMatrix[2, 2] = Math.Cos(angle.Radians); |
|||
return rotationMatrix; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a rotation matrix around the Z axis
|
|||
/// </summary>
|
|||
/// <param name="angle">The angle to rotate</param>
|
|||
/// <returns>A rotation matrix</returns>
|
|||
public static Matrix<double> RotationAroundZAxis(Angle angle) |
|||
{ |
|||
var rotationMatrix = new DenseMatrix(3, 3); |
|||
rotationMatrix[0, 0] = Math.Cos(angle.Radians); |
|||
rotationMatrix[0, 1] = -Math.Sin(angle.Radians); |
|||
rotationMatrix[1, 0] = Math.Sin(angle.Radians); |
|||
rotationMatrix[1, 1] = Math.Cos(angle.Radians); |
|||
rotationMatrix[2, 2] = 1; |
|||
return rotationMatrix; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sets to the matrix of rotation that would align the 'from' vector with the 'to' vector.
|
|||
/// The optional Axis argument may be used when the two vectors are parallel and in opposite directions to specify a specific solution, but is otherwise ignored.
|
|||
/// </summary>
|
|||
/// <param name="fromVector">Input Vector object to align from.</param>
|
|||
/// <param name="toVector">Input Vector object to align to.</param>
|
|||
/// <param name="axis">Input Vector object.</param>
|
|||
/// <returns>A transform matrix</returns>
|
|||
public static Matrix<double> RotationTo( |
|||
Vector3D fromVector, |
|||
Vector3D toVector, |
|||
UnitVector3D? axis = null) |
|||
{ |
|||
return RotationTo(fromVector.Normalize(), toVector.Normalize(), axis); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sets to the matrix of rotation that would align the 'from' vector with the 'to' vector.
|
|||
/// The optional Axis argument may be used when the two vectors are parallel and in opposite directions to specify a specific solution, but is otherwise ignored.
|
|||
/// </summary>
|
|||
/// <param name="fromVector">Input Vector object to align from.</param>
|
|||
/// <param name="toVector">Input Vector object to align to.</param>
|
|||
/// <param name="axis">Input Vector object. </param>
|
|||
/// <returns>A transform matrix</returns>
|
|||
public static Matrix<double> RotationTo(UnitVector3D fromVector, UnitVector3D toVector, UnitVector3D? axis = null) |
|||
{ |
|||
if (fromVector == toVector) |
|||
{ |
|||
return DenseMatrix.CreateIdentity(3); |
|||
} |
|||
|
|||
if (fromVector.IsParallelTo(toVector)) |
|||
{ |
|||
if (axis == null) |
|||
{ |
|||
axis = fromVector.Orthogonal; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
axis = fromVector.CrossProduct(toVector); |
|||
} |
|||
|
|||
var signedAngleTo = fromVector.SignedAngleTo(toVector, axis.Value); |
|||
return RotationAroundArbitraryVector(axis.Value, signedAngleTo); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a rotation matrix around an arbitrary vector
|
|||
/// </summary>
|
|||
/// <param name="aboutVector">The vector</param>
|
|||
/// <param name="angle">Angle in degrees</param>
|
|||
/// <returns>A transform matrix</returns>
|
|||
public static Matrix<double> RotationAroundArbitraryVector(UnitVector3D aboutVector, Angle angle) |
|||
{ |
|||
// http://en.wikipedia.org/wiki/Rotation_matrix
|
|||
var unitTensorProduct = aboutVector.GetUnitTensorProduct(); |
|||
var crossproductMatrix = aboutVector.CrossProductMatrix; // aboutVector.Clone().CrossProduct(aboutVector.Clone());
|
|||
|
|||
var r1 = DenseMatrix.CreateIdentity(3).Multiply(Math.Cos(angle.Radians)); |
|||
var r2 = crossproductMatrix.Multiply(Math.Sin(angle.Radians)); |
|||
var r3 = unitTensorProduct.Multiply(1 - Math.Cos(angle.Radians)); |
|||
var totalR = r1.Add(r2).Add(r3); |
|||
return totalR; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,498 @@ |
|||
using System; |
|||
using System.Diagnostics.Contracts; |
|||
using System.Xml; |
|||
using System.Xml.Linq; |
|||
using System.Xml.Schema; |
|||
using System.Xml.Serialization; |
|||
using MathNet.Numerics.LinearAlgebra.Double; |
|||
using MathNet.Numerics.Spatial.Internal; |
|||
|
|||
namespace MathNet.Numerics.Spatial.Euclidean3D |
|||
{ |
|||
/// <summary>
|
|||
/// A geometric plane
|
|||
/// </summary>
|
|||
[Serializable] |
|||
public struct Plane3D : IEquatable<Plane3D>, IXmlSerializable |
|||
{ |
|||
/// <summary>
|
|||
/// The normal vector of the Plane.
|
|||
/// </summary>
|
|||
public readonly UnitVector3D Normal; |
|||
|
|||
/// <summary>
|
|||
/// The distance to the Plane along its normal from the origin.
|
|||
/// </summary>
|
|||
public readonly double D; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Plane3D"/> struct.
|
|||
/// Constructs a Plane from the X, Y, and Z components of its normal, and its distance from the origin on that normal.
|
|||
/// </summary>
|
|||
/// <param name="x">The X-component of the normal.</param>
|
|||
/// <param name="y">The Y-component of the normal.</param>
|
|||
/// <param name="z">The Z-component of the normal.</param>
|
|||
/// <param name="d">The distance of the Plane along its normal from the origin.</param>
|
|||
public Plane3D(double x, double y, double z, double d) |
|||
: this(UnitVector3D.Create(x, y, z), -d) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Plane3D"/> struct.
|
|||
/// Constructs a Plane from the given normal and distance along the normal from the origin.
|
|||
/// </summary>
|
|||
/// <param name="normal">The Plane's normal vector.</param>
|
|||
/// <param name="offset">The Plane's distance from the origin along its normal vector.</param>
|
|||
public Plane3D(UnitVector3D normal, double offset = 0) |
|||
{ |
|||
this.Normal = normal; |
|||
this.D = -offset; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Plane3D"/> struct.
|
|||
/// Constructs a Plane from the given normal and distance along the normal from the origin.
|
|||
/// </summary>
|
|||
/// <param name="normal">The Plane's normal vector.</param>
|
|||
/// <param name="rootPoint">A point in the plane.</param>
|
|||
public Plane3D(UnitVector3D normal, Point3D rootPoint) |
|||
: this(normal, normal.DotProduct(rootPoint)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Plane3D"/> struct.
|
|||
/// Constructs a Plane from the given normal and distance along the normal from the origin.
|
|||
/// </summary>
|
|||
/// <param name="normal">The Plane's normal vector.</param>
|
|||
/// <param name="rootPoint">A point in the plane.</param>
|
|||
public Plane3D(Point3D rootPoint, UnitVector3D normal) |
|||
: this(normal, normal.DotProduct(rootPoint)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the <see cref="Normal"/> x component.
|
|||
/// </summary>
|
|||
public double A => this.Normal.X; |
|||
|
|||
/// <summary>
|
|||
/// Gets the <see cref="Normal"/> y component.
|
|||
/// </summary>
|
|||
public double B => this.Normal.Y; |
|||
|
|||
/// <summary>
|
|||
/// Gets the <see cref="Normal"/> y component.
|
|||
/// </summary>
|
|||
public double C => this.Normal.Z; |
|||
|
|||
/// <summary>
|
|||
/// Gets the point on the plane closest to origin.
|
|||
/// </summary>
|
|||
public Point3D RootPoint => (-this.D * this.Normal).ToPoint3D(); |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether each pair of elements in two specified geometric planes is equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first plane to compare.</param>
|
|||
/// <param name="right">The second plane to compare.</param>
|
|||
/// <returns>True if the geometric planes are the same; otherwise false.</returns>
|
|||
public static bool operator ==(Plane3D left, Plane3D right) |
|||
{ |
|||
return left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether any pair of elements in two specified geometric planes is not equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first plane to compare.</param>
|
|||
/// <param name="right">The second plane to compare.</param>
|
|||
/// <returns>True if the geometric planes are different; otherwise false.</returns>
|
|||
public static bool operator !=(Plane3D left, Plane3D right) |
|||
{ |
|||
return !left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Plane3D"/> struct.
|
|||
/// Creates a plane that contains the three given points.
|
|||
/// </summary>
|
|||
/// <param name="p1">The first point on the plane.</param>
|
|||
/// <param name="p2">The second point on the plane.</param>
|
|||
/// <param name="p3">The third point on the plane.</param>
|
|||
/// <returns>The plane containing the three points.</returns>
|
|||
public static Plane3D FromPoints(Point3D p1, Point3D p2, Point3D p3) |
|||
{ |
|||
// http://www.had2know.com/academics/equation-plane-through-3-points.html
|
|||
if (p1 == p2 || p1 == p3 || p2 == p3) |
|||
{ |
|||
throw new ArgumentException("Must use three different points"); |
|||
} |
|||
|
|||
var v1 = new Vector3D(p2.X - p1.X, p2.Y - p1.Y, p2.Z - p1.Z); |
|||
var v2 = new Vector3D(p3.X - p1.X, p3.Y - p1.Y, p3.Z - p1.Z); |
|||
var cross = v1.CrossProduct(v2); |
|||
|
|||
if (cross.Length <= float.Epsilon) |
|||
{ |
|||
throw new ArgumentException("The 3 points should not be on the same line"); |
|||
} |
|||
|
|||
return new Plane3D(cross.Normalize(), p1); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a point of intersection between three planes
|
|||
/// </summary>
|
|||
/// <param name="plane1">The first plane</param>
|
|||
/// <param name="plane2">The second plane</param>
|
|||
/// <param name="plane3">The third plane</param>
|
|||
/// <returns>The intersection point</returns>
|
|||
public static Point3D PointFromPlanes(Plane3D plane1, Plane3D plane2, Plane3D plane3) |
|||
{ |
|||
return Point3D.IntersectionOf(plane1, plane2, plane3); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Get the distance to the point along the <see cref="Normal"/>
|
|||
/// </summary>
|
|||
/// <param name="point">The <see cref="Point3D"/></param>
|
|||
/// <returns>The distance.</returns>
|
|||
[Pure] |
|||
public double SignedDistanceTo(Point3D point) |
|||
{ |
|||
var p = this.Project(point); |
|||
var v = p.VectorTo(point); |
|||
return v.DotProduct(this.Normal); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Get the distance to the plane along the <see cref="Normal"/>
|
|||
/// This assumes the planes are parallel
|
|||
/// </summary>
|
|||
/// <param name="other">The <see cref="Point3D"/></param>
|
|||
/// <returns>The distance.</returns>
|
|||
[Pure] |
|||
public double SignedDistanceTo(Plane3D other) |
|||
{ |
|||
if (!this.Normal.IsParallelTo(other.Normal, tolerance: 1E-15)) |
|||
{ |
|||
throw new ArgumentException("Planes are not parallel"); |
|||
} |
|||
|
|||
return this.SignedDistanceTo(other.RootPoint); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Get the distance to the ThroughPoint of <paramref name="ray"/> along the <see cref="Normal"/>
|
|||
/// This assumes the ray is parallel to the plane.
|
|||
/// </summary>
|
|||
/// <param name="ray">The <see cref="Point3D"/></param>
|
|||
/// <returns>The distance.</returns>
|
|||
[Pure] |
|||
public double SignedDistanceTo(Ray3D ray) |
|||
{ |
|||
if (Math.Abs(ray.Direction.DotProduct(this.Normal) - 0) < 1E-15) |
|||
{ |
|||
return this.SignedDistanceTo(ray.ThroughPoint); |
|||
} |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Get the distance to the point.
|
|||
/// </summary>
|
|||
/// <param name="point">The <see cref="Point3D"/></param>
|
|||
/// <returns>The distance.</returns>
|
|||
[Pure] |
|||
public double AbsoluteDistanceTo(Point3D point) |
|||
{ |
|||
return Math.Abs(this.SignedDistanceTo(point)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Projects a point onto the plane
|
|||
/// </summary>
|
|||
/// <param name="p">A point</param>
|
|||
/// <param name="projectionDirection">The direction of projection</param>
|
|||
/// <returns>a projected point</returns>
|
|||
[Pure] |
|||
public Point3D Project(Point3D p, UnitVector3D? projectionDirection = null) |
|||
{ |
|||
var dotProduct = this.Normal.DotProduct(p.ToVector3D()); |
|||
var projectiononNormal = projectionDirection == null ? this.Normal : projectionDirection.Value; |
|||
var projectionVector = (dotProduct + this.D) * projectiononNormal; |
|||
return p - projectionVector; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Projects a line onto the plane
|
|||
/// </summary>
|
|||
/// <param name="line3DToProject">The line to project</param>
|
|||
/// <returns>A projected line</returns>
|
|||
public Line3D Project(Line3D line3DToProject) |
|||
{ |
|||
var projectedStartPoint = this.Project(line3DToProject.StartPoint); |
|||
var projectedEndPoint = this.Project(line3DToProject.EndPoint); |
|||
return new Line3D(projectedStartPoint, projectedEndPoint); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Projects a line onto the plane
|
|||
/// </summary>
|
|||
/// <param name="line3DToProject">The line to project</param>
|
|||
/// <returns>A projected line</returns>
|
|||
[Pure] |
|||
public LineSegment3D Project(LineSegment3D line3DToProject) |
|||
{ |
|||
var projectedStartPoint = this.Project(line3DToProject.StartPoint); |
|||
var projectedEndPoint = this.Project(line3DToProject.EndPoint); |
|||
return new LineSegment3D(projectedStartPoint, projectedEndPoint); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Projects a ray onto the plane
|
|||
/// </summary>
|
|||
/// <param name="rayToProject">The ray to project</param>
|
|||
/// <returns>A projected ray</returns>
|
|||
[Pure] |
|||
public Ray3D Project(Ray3D rayToProject) |
|||
{ |
|||
var projectedThroughPoint = this.Project(rayToProject.ThroughPoint); |
|||
var projectedDirection = this.Project(rayToProject.Direction.ToVector3D()); |
|||
return new Ray3D(projectedThroughPoint, projectedDirection.Direction); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Project Vector3D onto this plane
|
|||
/// </summary>
|
|||
/// <param name="vector3DToProject">The Vector3D to project</param>
|
|||
/// <returns>The projected Vector3D</returns>
|
|||
[Pure] |
|||
public Ray3D Project(Vector3D vector3DToProject) |
|||
{ |
|||
var projectedEndPoint = this.Project(vector3DToProject.ToPoint3D()); |
|||
var projectedZero = this.Project(new Point3D(0, 0, 0)); |
|||
return new Ray3D(projectedZero, projectedZero.VectorTo(projectedEndPoint).Normalize()); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Project Vector3D onto this plane
|
|||
/// </summary>
|
|||
/// <param name="vector3DToProject">The Vector3D to project</param>
|
|||
/// <returns>The projected Vector3D</returns>
|
|||
[Pure] |
|||
public Ray3D Project(UnitVector3D vector3DToProject) |
|||
{ |
|||
return this.Project(vector3DToProject.ToVector3D()); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Finds the intersection of the two planes, throws if they are parallel
|
|||
/// http://mathworld.wolfram.com/Plane-PlaneIntersection.html
|
|||
/// </summary>
|
|||
/// <param name="intersectingPlane">a plane which intersects</param>
|
|||
/// <param name="tolerance">A tolerance (epsilon) to account for floating point error.</param>
|
|||
/// <returns>A ray at the intersection.</returns>
|
|||
[Pure] |
|||
public Ray3D IntersectionWith(Plane3D intersectingPlane, double tolerance = float.Epsilon) |
|||
{ |
|||
var a = new DenseMatrix(2, 3); |
|||
a.SetRow(0, this.Normal.ToVector()); |
|||
a.SetRow(1, intersectingPlane.Normal.ToVector()); |
|||
var svd = a.Svd(true); |
|||
if (svd.S[1] < tolerance) |
|||
{ |
|||
throw new ArgumentException("Planes are parallel"); |
|||
} |
|||
|
|||
var y = new DenseMatrix(2, 1) |
|||
{ |
|||
[0, 0] = -1 * this.D, |
|||
[1, 0] = -1 * intersectingPlane.D |
|||
}; |
|||
|
|||
var pointOnIntersectionLine = svd.Solve(y); |
|||
var throughPoint = Point3D.OfVector(pointOnIntersectionLine.Column(0)); |
|||
var direction = UnitVector3D.OfVector(svd.VT.Row(2)); |
|||
return new Ray3D(throughPoint, direction); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Find intersection between Line3D and Plane
|
|||
/// http://geomalgorithms.com/a05-_intersect-1.html
|
|||
/// </summary>
|
|||
/// <param name="line">A line segment</param>
|
|||
/// <param name="tolerance">A tolerance (epsilon) to account for floating point error.</param>
|
|||
/// <returns>Intersection Point or null</returns>
|
|||
public Point3D? IntersectionWith(Line3D line, double tolerance = float.Epsilon) |
|||
{ |
|||
if (line.Direction.IsPerpendicularTo(this.Normal, tolerance)) |
|||
{ |
|||
// either parallel or lies in the plane
|
|||
var projectedPoint = this.Project(line.StartPoint, line.Direction); |
|||
if (projectedPoint == line.StartPoint) |
|||
{ |
|||
throw new InvalidOperationException("Line lies in the plane"); |
|||
} |
|||
|
|||
// Line and plane are parallel
|
|||
return null; |
|||
} |
|||
|
|||
var d = this.SignedDistanceTo(line.StartPoint); |
|||
var u = line.StartPoint.VectorTo(line.EndPoint); |
|||
var t = -1 * d / u.DotProduct(this.Normal); |
|||
if (t > 1 || t < 0) |
|||
{ |
|||
// They are not intersected
|
|||
return null; |
|||
} |
|||
|
|||
return line.StartPoint + (t * u); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Find intersection between LineSegment3D and Plane
|
|||
/// http://geomalgorithms.com/a05-_intersect-1.html
|
|||
/// </summary>
|
|||
/// <param name="line">A line segment</param>
|
|||
/// <param name="tolerance">A tolerance (epsilon) to account for floating point error.</param>
|
|||
/// <returns>Intersection Point or null</returns>
|
|||
[Pure] |
|||
public Point3D? IntersectionWith(LineSegment3D line, double tolerance = float.Epsilon) |
|||
{ |
|||
if (line.Direction.IsPerpendicularTo(this.Normal, tolerance)) |
|||
{ |
|||
// either parallel or lies in the plane
|
|||
var projectedPoint = this.Project(line.StartPoint, line.Direction); |
|||
if (projectedPoint == line.StartPoint) |
|||
{ |
|||
throw new InvalidOperationException("Line lies in the plane"); |
|||
} |
|||
|
|||
// Line and plane are parallel
|
|||
return null; |
|||
} |
|||
|
|||
var d = this.SignedDistanceTo(line.StartPoint); |
|||
var u = line.StartPoint.VectorTo(line.EndPoint); |
|||
var t = -1 * d / u.DotProduct(this.Normal); |
|||
if (t > 1 || t < 0) |
|||
{ |
|||
// They are not intersected
|
|||
return null; |
|||
} |
|||
|
|||
return line.StartPoint + (t * u); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// http://www.cs.princeton.edu/courses/archive/fall00/cs426/lectures/raycast/sld017.htm
|
|||
/// </summary>
|
|||
/// <param name="ray">A ray</param>
|
|||
/// <param name="tolerance">A tolerance (epsilon) to account for floating point error.</param>
|
|||
/// <returns>The point of intersection.</returns>
|
|||
[Pure] |
|||
public Point3D IntersectionWith(Ray3D ray, double tolerance = float.Epsilon) |
|||
{ |
|||
if (this.Normal.IsPerpendicularTo(ray.Direction, tolerance)) |
|||
{ |
|||
throw new InvalidOperationException("Ray is parallel to the plane."); |
|||
} |
|||
|
|||
var d = this.SignedDistanceTo(ray.ThroughPoint); |
|||
var t = -1 * d / ray.Direction.DotProduct(this.Normal); |
|||
return ray.ThroughPoint + (t * ray.Direction); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns <paramref name="p"/> mirrored about the plane.
|
|||
/// </summary>
|
|||
/// <param name="p">The <see cref="Point3D"/></param>
|
|||
/// <returns>The mirrored point.</returns>
|
|||
[Pure] |
|||
public Point3D MirrorAbout(Point3D p) |
|||
{ |
|||
var p2 = this.Project(p); |
|||
var d = this.SignedDistanceTo(p); |
|||
return p2 - (1 * d * this.Normal); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Rotates a plane
|
|||
/// </summary>
|
|||
/// <param name="aboutVector">The vector about which to rotate</param>
|
|||
/// <param name="angle">An angle to rotate</param>
|
|||
/// <returns>A rotated plane</returns>
|
|||
[Pure] |
|||
public Plane3D Rotate(UnitVector3D aboutVector, Angle angle) |
|||
{ |
|||
var rootPoint = this.RootPoint; |
|||
var rotatedPoint = rootPoint.Rotate(aboutVector, angle); |
|||
var rotatedPlaneVector = this.Normal.Rotate(aboutVector, angle); |
|||
return new Plane3D(rotatedPlaneVector, rotatedPoint); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value to indicate if a pair of geometric planes are equal
|
|||
/// </summary>
|
|||
/// <param name="other">The geometric plane to compare against.</param>
|
|||
/// <param name="tolerance">A tolerance (epsilon) to adjust for floating point error</param>
|
|||
/// <returns>true if the geometric planes are equal; otherwise false</returns>
|
|||
[Pure] |
|||
public bool Equals(Plane3D other, double tolerance) |
|||
{ |
|||
if (tolerance < 0) |
|||
{ |
|||
throw new ArgumentException("epsilon < 0"); |
|||
} |
|||
|
|||
return Math.Abs(other.D - this.D) < tolerance && this.Normal.Equals(other.Normal, tolerance); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public bool Equals(Plane3D p) => this.D.Equals(p.D) && this.Normal.Equals(p.Normal); |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override bool Equals(object obj) => obj is Plane3D p && this.Equals(p); |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override int GetHashCode() => HashCode.Combine(this.Normal, this.D); |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override string ToString() |
|||
{ |
|||
return $"A:{Math.Round(this.A, 4)} B:{Math.Round(this.B, 4)} C:{Math.Round(this.C, 4)} D:{Math.Round(this.D, 4)}"; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
XmlSchema IXmlSerializable.GetSchema() |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
void IXmlSerializable.ReadXml(XmlReader reader) |
|||
{ |
|||
reader.MoveToContent(); |
|||
var e = (XElement)XNode.ReadFrom(reader); |
|||
this = new Plane3D( |
|||
UnitVector3D.ReadFrom(e.SingleElement("Normal").CreateReader()), |
|||
Point3D.ReadFrom(e.SingleElement("RootPoint").CreateReader())); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
void IXmlSerializable.WriteXml(XmlWriter writer) |
|||
{ |
|||
writer.WriteElement("RootPoint", this.RootPoint); |
|||
writer.WriteElement("Normal", this.Normal); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,494 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics.Contracts; |
|||
using System.Globalization; |
|||
using System.Linq; |
|||
using System.Xml; |
|||
using System.Xml.Schema; |
|||
using System.Xml.Serialization; |
|||
using MathNet.Numerics.LinearAlgebra; |
|||
using MathNet.Numerics.Spatial.Internal; |
|||
|
|||
namespace MathNet.Numerics.Spatial.Euclidean3D |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a point in 3 dimensional space
|
|||
/// </summary>
|
|||
[Serializable] |
|||
public struct Point3D : IXmlSerializable, IEquatable<Point3D>, IFormattable |
|||
{ |
|||
/// <summary>
|
|||
/// The x component.
|
|||
/// </summary>
|
|||
public readonly double X; |
|||
|
|||
/// <summary>
|
|||
/// The y component.
|
|||
/// </summary>
|
|||
public readonly double Y; |
|||
|
|||
/// <summary>
|
|||
/// The z component.
|
|||
/// </summary>
|
|||
public readonly double Z; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Point3D"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="x">The x component.</param>
|
|||
/// <param name="y">The y component.</param>
|
|||
/// <param name="z">The z component.</param>
|
|||
public Point3D(double x, double y, double z) |
|||
{ |
|||
this.X = x; |
|||
this.Y = y; |
|||
this.Z = z; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a point at the origin
|
|||
/// </summary>
|
|||
public static Point3D Origin { get; } = new Point3D(0, 0, 0); |
|||
|
|||
/// <summary>
|
|||
/// Gets a point where all values are NAN
|
|||
/// </summary>
|
|||
public static Point3D NaN { get; } = new Point3D(double.NaN, double.NaN, double.NaN); |
|||
|
|||
/// <summary>
|
|||
/// Multiplies a matrix and a vector representation of the point together
|
|||
/// </summary>
|
|||
/// <param name="left">A matrix</param>
|
|||
/// <param name="right">A point</param>
|
|||
/// <returns>A Mathnet.Numerics vector</returns>
|
|||
[Obsolete("Not sure this is nice")] |
|||
public static Vector<double> operator *(Matrix<double> left, Point3D right) |
|||
{ |
|||
return left * right.ToVector(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Multiplies a matrix and a vector representation of the point together
|
|||
/// </summary>
|
|||
/// <param name="left">A point</param>
|
|||
/// <param name="right">A matrix</param>
|
|||
/// <returns>A Mathnet.Numerics vector</returns>
|
|||
[Obsolete("Not sure this is nice")] |
|||
public static Vector<double> operator *(Point3D left, Matrix<double> right) |
|||
{ |
|||
return left.ToVector() * right; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a point and a vector together
|
|||
/// </summary>
|
|||
/// <param name="point">A point</param>
|
|||
/// <param name="vector">A vector</param>
|
|||
/// <returns>A new point at the summed location</returns>
|
|||
public static Point3D operator +(Point3D point, Vector3D vector) |
|||
{ |
|||
return new Point3D(point.X + vector.X, point.Y + vector.Y, point.Z + vector.Z); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a point and a vector together
|
|||
/// </summary>
|
|||
/// <param name="point">A point</param>
|
|||
/// <param name="vector">A vector</param>
|
|||
/// <returns>A new point at the summed location</returns>
|
|||
public static Point3D operator +(Point3D point, UnitVector3D vector) |
|||
{ |
|||
return new Point3D(point.X + vector.X, point.Y + vector.Y, point.Z + vector.Z); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Subtracts a vector from a point
|
|||
/// </summary>
|
|||
/// <param name="point">A point</param>
|
|||
/// <param name="vector">A vector</param>
|
|||
/// <returns>A new point at the difference</returns>
|
|||
public static Point3D operator -(Point3D point, Vector3D vector) |
|||
{ |
|||
return new Point3D(point.X - vector.X, point.Y - vector.Y, point.Z - vector.Z); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Subtracts a vector from a point
|
|||
/// </summary>
|
|||
/// <param name="point">A point</param>
|
|||
/// <param name="vector">A vector</param>
|
|||
/// <returns>A new point at the difference</returns>
|
|||
public static Point3D operator -(Point3D point, UnitVector3D vector) |
|||
{ |
|||
return new Point3D(point.X - vector.X, point.Y - vector.Y, point.Z - vector.Z); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Subtracts the first point from the second point
|
|||
/// </summary>
|
|||
/// <param name="left">The first point</param>
|
|||
/// <param name="right">The second point</param>
|
|||
/// <returns>A vector pointing to the difference</returns>
|
|||
public static Vector3D operator -(Point3D left, Point3D right) |
|||
{ |
|||
return new Vector3D(left.X - right.X, left.Y - right.Y, left.Z - right.Z); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether each pair of elements in two specified points is equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first point to compare</param>
|
|||
/// <param name="right">The second point to compare</param>
|
|||
/// <returns>True if the points are the same; otherwise false.</returns>
|
|||
public static bool operator ==(Point3D left, Point3D right) |
|||
{ |
|||
return left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether any pair of elements in two specified points is not equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first point to compare</param>
|
|||
/// <param name="right">The second point to compare</param>
|
|||
/// <returns>True if the points are different; otherwise false.</returns>
|
|||
public static bool operator !=(Point3D left, Point3D right) |
|||
{ |
|||
return !left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Attempts to convert a string of the form x,y,z into a point
|
|||
/// </summary>
|
|||
/// <param name="text">The string to be converted</param>
|
|||
/// <param name="result">A point with the coordinates specified</param>
|
|||
/// <returns>True if <paramref name="text"/> could be parsed.</returns>
|
|||
public static bool TryParse(string text, out Point3D result) |
|||
{ |
|||
return TryParse(text, null, out result); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Attempts to convert a string of the form x,y,z into a point
|
|||
/// </summary>
|
|||
/// <param name="text">The string to be converted</param>
|
|||
/// <param name="formatProvider">The <see cref="IFormatProvider"/></param>
|
|||
/// <param name="result">A point at the coordinates specified</param>
|
|||
/// <returns>True if <paramref name="text"/> could be parsed.</returns>
|
|||
public static bool TryParse(string text, IFormatProvider formatProvider, out Point3D result) |
|||
{ |
|||
if (Text.TryParse3D(text, formatProvider, out var x, out var y, out var z)) |
|||
{ |
|||
result = new Point3D(x, y, z); |
|||
return true; |
|||
} |
|||
|
|||
result = default(Point3D); |
|||
return false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Attempts to convert a string of the form x,y,z into a point
|
|||
/// </summary>
|
|||
/// <param name="value">The string to be converted</param>
|
|||
/// <param name="formatProvider">The <see cref="IFormatProvider"/></param>
|
|||
/// <returns>A point at the coordinates specified</returns>
|
|||
public static Point3D Parse(string value, IFormatProvider formatProvider = null) |
|||
{ |
|||
if (TryParse(value, formatProvider, out var p)) |
|||
{ |
|||
return p; |
|||
} |
|||
|
|||
throw new FormatException($"Could not parse a Point3D from the string {value}"); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Create a new <see cref="Point3D"/> from a Math.NET Numerics vector of length 3.
|
|||
/// </summary>
|
|||
/// <param name="vector"> A vector with length 2 to populate the created instance with.</param>
|
|||
/// <returns> A <see cref="Point3D"/></returns>
|
|||
public static Point3D OfVector(Vector<double> vector) |
|||
{ |
|||
if (vector.Count != 3) |
|||
{ |
|||
throw new ArgumentException("The vector length must be 3 in order to convert it to a Point3D"); |
|||
} |
|||
|
|||
return new Point3D(vector.At(0), vector.At(1), vector.At(2)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates an <see cref="Point3D"/> from an <see cref="XmlReader"/>.
|
|||
/// </summary>
|
|||
/// <param name="reader">An <see cref="XmlReader"/> positioned at the node to read into this <see cref="Point3D"/>.</param>
|
|||
/// <returns>An <see cref="Point3D"/> that contains the data read from the reader.</returns>
|
|||
public static Point3D ReadFrom(XmlReader reader) |
|||
{ |
|||
return reader.ReadElementAs<Point3D>(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the centroid of an arbitrary collection of points
|
|||
/// </summary>
|
|||
/// <param name="points">a list of points</param>
|
|||
/// <returns>The centroid of the points</returns>
|
|||
public static Point3D Centroid(IEnumerable<Point3D> points) |
|||
{ |
|||
return Centroid(points.ToArray()); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the centroid of an arbitrary collection of points
|
|||
/// </summary>
|
|||
/// <param name="points">a list of points</param>
|
|||
/// <returns>The centroid of the points</returns>
|
|||
public static Point3D Centroid(params Point3D[] points) |
|||
{ |
|||
return new Point3D( |
|||
points.Average(point => point.X), |
|||
points.Average(point => point.Y), |
|||
points.Average(point => point.Z)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the midpoint of two points
|
|||
/// </summary>
|
|||
/// <param name="p1">The first point</param>
|
|||
/// <param name="p2">The second point</param>
|
|||
/// <returns>The midpoint of the points</returns>
|
|||
public static Point3D MidPoint(Point3D p1, Point3D p2) |
|||
{ |
|||
return Centroid(p1, p2); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the point at which three planes intersect
|
|||
/// </summary>
|
|||
/// <param name="plane1">The first plane</param>
|
|||
/// <param name="plane2">The second plane</param>
|
|||
/// <param name="plane3">The third plane</param>
|
|||
/// <returns>The point of intersection</returns>
|
|||
public static Point3D IntersectionOf(Plane3D plane1, Plane3D plane2, Plane3D plane3) |
|||
{ |
|||
var ray = plane1.IntersectionWith(plane2); |
|||
return plane3.IntersectionWith(ray); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the point of intersection between a plane and a ray
|
|||
/// </summary>
|
|||
/// <param name="plane">A geometric plane</param>
|
|||
/// <param name="ray">a ray</param>
|
|||
/// <returns>The point of intersection</returns>
|
|||
public static Point3D IntersectionOf(Plane3D plane, Ray3D ray) |
|||
{ |
|||
return plane.IntersectionWith(ray); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the mirror point of this point across a plane
|
|||
/// </summary>
|
|||
/// <param name="plane">A plane</param>
|
|||
/// <returns>The mirrored point</returns>
|
|||
[Pure] |
|||
public Point3D MirrorAbout(Plane3D plane) |
|||
{ |
|||
return plane.MirrorAbout(this); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Projects a point onto a plane
|
|||
/// </summary>
|
|||
/// <param name="plane">a plane</param>
|
|||
/// <returns>The projected point</returns>
|
|||
[Pure] |
|||
public Point3D ProjectOn(Plane3D plane) |
|||
{ |
|||
return plane.Project(this); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Rotates the point about a given vector
|
|||
/// </summary>
|
|||
/// <param name="aboutVector">A vector</param>
|
|||
/// <param name="angle">The angle to rotate</param>
|
|||
/// <returns>The rotated point</returns>
|
|||
[Pure] |
|||
public Point3D Rotate(Vector3D aboutVector, Angle angle) |
|||
{ |
|||
return this.Rotate(aboutVector.Normalize(), angle); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Rotates the point about a given vector
|
|||
/// </summary>
|
|||
/// <param name="aboutVector">A vector</param>
|
|||
/// <param name="angle">The angle to rotate</param>
|
|||
/// <returns>The rotated point</returns>
|
|||
[Pure] |
|||
public Point3D Rotate(UnitVector3D aboutVector, Angle angle) |
|||
{ |
|||
var cs = CoordinateSystem3D.Rotation(angle, aboutVector); |
|||
return cs.Transform(this); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a vector from this point to another point
|
|||
/// </summary>
|
|||
/// <param name="p">The point to which the vector should go</param>
|
|||
/// <returns>A vector pointing to the other point.</returns>
|
|||
[Pure] |
|||
public Vector3D VectorTo(Point3D p) |
|||
{ |
|||
return p - this; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Finds the straight line distance to another point
|
|||
/// </summary>
|
|||
/// <param name="p">The other point</param>
|
|||
/// <returns>a distance measure</returns>
|
|||
[Pure] |
|||
public double DistanceTo(Point3D p) |
|||
{ |
|||
var vector = this.VectorTo(p); |
|||
return vector.Length; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts this point into a vector from the origin
|
|||
/// </summary>
|
|||
/// <returns>A vector equivalent to this point</returns>
|
|||
[Pure] |
|||
public Vector3D ToVector3D() |
|||
{ |
|||
return new Vector3D(this.X, this.Y, this.Z); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies a transform coordinate system to the point
|
|||
/// </summary>
|
|||
/// <param name="cs">A coordinate system</param>
|
|||
/// <returns>A new 3D point</returns>
|
|||
[Pure] |
|||
public Point3D TransformBy(CoordinateSystem3D cs) |
|||
{ |
|||
return cs.Transform(this); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies a transform matrix to the point
|
|||
/// </summary>
|
|||
/// <param name="m">A transform matrix</param>
|
|||
/// <returns>A new point</returns>
|
|||
[Pure] |
|||
public Point3D TransformBy(Matrix<double> m) |
|||
{ |
|||
return OfVector(m.Multiply(this.ToVector())); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Convert to a Math.NET Numerics dense vector of length 3.
|
|||
/// </summary>
|
|||
/// <returns>A Math.Net Numerics vector</returns>
|
|||
[Pure] |
|||
public Vector<double> ToVector() |
|||
{ |
|||
return Vector<double>.Build.Dense(new[] { this.X, this.Y, this.Z }); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override string ToString() |
|||
{ |
|||
return this.ToString(null, CultureInfo.InvariantCulture); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a string representation of this instance using the provided <see cref="IFormatProvider"/>
|
|||
/// </summary>
|
|||
/// <param name="provider">A <see cref="IFormatProvider"/></param>
|
|||
/// <returns>The string representation of this instance.</returns>
|
|||
[Pure] |
|||
public string ToString(IFormatProvider provider) |
|||
{ |
|||
return this.ToString(null, provider); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public string ToString(string format, IFormatProvider provider = null) |
|||
{ |
|||
var numberFormatInfo = provider != null ? NumberFormatInfo.GetInstance(provider) : CultureInfo.InvariantCulture.NumberFormat; |
|||
var separator = numberFormatInfo.NumberDecimalSeparator == "," ? ";" : ","; |
|||
return string.Format("({0}{1} {2}{1} {3})", this.X.ToString(format, numberFormatInfo), separator, this.Y.ToString(format, numberFormatInfo), this.Z.ToString(format, numberFormatInfo)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value to indicate if a pair of points are equal
|
|||
/// </summary>
|
|||
/// <param name="other">The point to compare against.</param>
|
|||
/// <param name="tolerance">A tolerance (epsilon) to adjust for floating point error</param>
|
|||
/// <returns>True if the points are equal; otherwise false</returns>
|
|||
[Pure] |
|||
public bool Equals(Point3D other, double tolerance) |
|||
{ |
|||
if (tolerance < 0) |
|||
{ |
|||
throw new ArgumentException("epsilon < 0"); |
|||
} |
|||
|
|||
return Math.Abs(other.X - this.X) < tolerance && |
|||
Math.Abs(other.Y - this.Y) < tolerance && |
|||
Math.Abs(other.Z - this.Z) < tolerance; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public bool Equals(Point3D other) |
|||
{ |
|||
return this.X.Equals(other.X) && this.Y.Equals(other.Y) && this.Z.Equals(other.Z); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override bool Equals(object obj) => obj is Point3D p && this.Equals(p); |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override int GetHashCode() => HashCode.Combine(this.X, this.Y, this.Z); |
|||
|
|||
/// <inheritdoc />
|
|||
XmlSchema IXmlSerializable.GetSchema() => null; |
|||
|
|||
/// <inheritdoc />
|
|||
void IXmlSerializable.ReadXml(XmlReader reader) |
|||
{ |
|||
if (reader.TryReadAttributeAsDouble("X", out var x) && |
|||
reader.TryReadAttributeAsDouble("Y", out var y) && |
|||
reader.TryReadAttributeAsDouble("Z", out var z)) |
|||
{ |
|||
reader.Skip(); |
|||
this = new Point3D(x, y, z); |
|||
return; |
|||
} |
|||
|
|||
if (reader.TryReadChildElementsAsDoubles("X", "Y", "Z", out x, out y, out z)) |
|||
{ |
|||
reader.Skip(); |
|||
this = new Point3D(x, y, z); |
|||
return; |
|||
} |
|||
|
|||
throw new XmlException($"Could not read a {this.GetType()}"); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
void IXmlSerializable.WriteXml(XmlWriter writer) |
|||
{ |
|||
writer.WriteAttribute("X", this.X); |
|||
writer.WriteAttribute("Y", this.Y); |
|||
writer.WriteAttribute("Z", this.Z); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,227 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics.Contracts; |
|||
using System.Linq; |
|||
using MathNet.Numerics.Spatial.Internal; |
|||
|
|||
namespace MathNet.Numerics.Spatial.Euclidean3D |
|||
{ |
|||
/// <summary>
|
|||
/// A PolyLine is an ordered series of line segments in space represented as list of connected Point3Ds.
|
|||
/// </summary>
|
|||
public class PolyLine3D : IEquatable<PolyLine3D> |
|||
{ |
|||
/// <summary>
|
|||
/// An internal list of points
|
|||
/// </summary>
|
|||
private readonly List<Point3D> points; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PolyLine3D"/> class.
|
|||
/// Creates a PolyLine3D from a pre-existing IEnumerable of Point3Ds
|
|||
/// </summary>
|
|||
/// <param name="points">A list of points.</param>
|
|||
public PolyLine3D(IEnumerable<Point3D> points) |
|||
{ |
|||
this.points = new List<Point3D>(points); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of vertices in the polyline.
|
|||
/// </summary>
|
|||
public int VertexCount => this.points.Count; |
|||
|
|||
/// <summary>
|
|||
/// Gets the length of the polyline, computed as the sum of the lengths of every segment
|
|||
/// </summary>
|
|||
public double Length => this.GetPolyLineLength(); |
|||
|
|||
/// <summary>
|
|||
/// Gets a list of vertices
|
|||
/// </summary>
|
|||
public IEnumerable<Point3D> Vertices |
|||
{ |
|||
get |
|||
{ |
|||
foreach (var point in this.points) |
|||
{ |
|||
yield return point; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether each pair of elements in two specified lines is equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first line to compare</param>
|
|||
/// <param name="right">The second line to compare</param>
|
|||
/// <returns>True if the lines are the same; otherwise false.</returns>
|
|||
public static bool operator ==(PolyLine3D left, PolyLine3D right) |
|||
{ |
|||
return left?.Equals(right) == true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether any pair of elements in two specified lines is not equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first line to compare</param>
|
|||
/// <param name="right">The second line to compare</param>
|
|||
/// <returns>True if the lines are different; otherwise false.</returns>
|
|||
public static bool operator !=(PolyLine3D left, PolyLine3D right) |
|||
{ |
|||
return left?.Equals(right) != true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Get the point at a fractional distance along the curve. For instance, fraction=0.5 will return
|
|||
/// the point halfway along the length of the polyline.
|
|||
/// </summary>
|
|||
/// <param name="fraction">The fractional length at which to compute the point</param>
|
|||
/// <returns>A point a fraction of the way along the line.</returns>
|
|||
public Point3D GetPointAtFractionAlongCurve(double fraction) |
|||
{ |
|||
if (fraction > 1 || fraction < 0) |
|||
{ |
|||
throw new ArgumentException("fraction must be between 0 and 1"); |
|||
} |
|||
|
|||
return this.GetPointAtLengthFromStart(fraction * this.Length); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Get the point at a specified distance along the curve. A negative argument will return the first point,
|
|||
/// an argument greater than the length of the curve will return the last point.
|
|||
/// </summary>
|
|||
/// <param name="lengthFromStart">The distance from the first point along the curve at which to return a point</param>
|
|||
/// <returns>A point which is the specified distance along the line</returns>
|
|||
public Point3D GetPointAtLengthFromStart(double lengthFromStart) |
|||
{ |
|||
var length = this.Length; |
|||
if (lengthFromStart >= length) |
|||
{ |
|||
return this.points.Last(); |
|||
} |
|||
|
|||
if (lengthFromStart <= 0) |
|||
{ |
|||
return this.points.First(); |
|||
} |
|||
|
|||
double cumulativeLength = 0; |
|||
var i = 0; |
|||
while (true) |
|||
{ |
|||
var nextLength = cumulativeLength + this.points[i].DistanceTo(this.points[i + 1]); |
|||
if (cumulativeLength <= lengthFromStart && nextLength > lengthFromStart) |
|||
{ |
|||
var leftover = lengthFromStart - cumulativeLength; |
|||
var direction = this.points[i].VectorTo(this.points[i + 1]).Normalize(); |
|||
return this.points[i] + (leftover * direction); |
|||
} |
|||
|
|||
cumulativeLength = nextLength; |
|||
i++; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the closest point on the polyline to the given point.
|
|||
/// </summary>
|
|||
/// <param name="p">A point</param>
|
|||
/// <returns>A point which is the closest to the given point but still on the line.</returns>
|
|||
public Point3D ClosestPointTo(Point3D p) |
|||
{ |
|||
var minError = double.MaxValue; |
|||
var closest = default(Point3D); |
|||
|
|||
for (var i = 0; i < this.VertexCount - 1; i++) |
|||
{ |
|||
var segment = new LineSegment3D(this.points[i], this.points[i + 1]); |
|||
var projected = segment.ClosestPointTo(p); |
|||
var error = p.DistanceTo(projected); |
|||
if (error < minError) |
|||
{ |
|||
minError = error; |
|||
closest = projected; |
|||
} |
|||
} |
|||
|
|||
return closest; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value to indicate if a pair of polylines are equal
|
|||
/// </summary>
|
|||
/// <param name="other">The polyline to compare against.</param>
|
|||
/// <param name="tolerance">A tolerance (epsilon) to adjust for floating point error</param>
|
|||
/// <returns>true if the polylines are equal; otherwise false</returns>
|
|||
[Pure] |
|||
public bool Equals(PolyLine3D other, double tolerance) |
|||
{ |
|||
if (this.VertexCount != other?.VertexCount) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
for (var i = 0; i < this.points.Count; i++) |
|||
{ |
|||
if (!this.points[i].Equals(other.points[i], tolerance)) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public bool Equals(PolyLine3D other) |
|||
{ |
|||
if (this.VertexCount != other?.VertexCount) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
for (var i = 0; i < this.points.Count; i++) |
|||
{ |
|||
if (!this.points[i].Equals(other.points[i])) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override bool Equals(object obj) |
|||
{ |
|||
return obj is PolyLine3D polyLine3D && |
|||
this.Equals(polyLine3D); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override int GetHashCode() |
|||
{ |
|||
return HashCode.CombineMany(this.points); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the length of the polyline by summing the lengths of the individual segments
|
|||
/// </summary>
|
|||
/// <returns>The length of the line.</returns>
|
|||
private double GetPolyLineLength() |
|||
{ |
|||
double length = 0; |
|||
for (var i = 0; i < this.points.Count - 1; ++i) |
|||
{ |
|||
length += this.points[i].DistanceTo(this.points[i + 1]); |
|||
} |
|||
|
|||
return length; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,196 @@ |
|||
using System; |
|||
using System.Diagnostics.Contracts; |
|||
using System.Globalization; |
|||
using System.Xml; |
|||
using System.Xml.Linq; |
|||
using System.Xml.Schema; |
|||
using System.Xml.Serialization; |
|||
using MathNet.Numerics.Spatial.Internal; |
|||
|
|||
namespace MathNet.Numerics.Spatial.Euclidean3D |
|||
{ |
|||
/// <summary>
|
|||
/// A ray in 3D space
|
|||
/// </summary>
|
|||
[Serializable] |
|||
public struct Ray3D : IEquatable<Ray3D>, IXmlSerializable, IFormattable |
|||
{ |
|||
/// <summary>
|
|||
/// The start point of the ray
|
|||
/// </summary>
|
|||
public readonly Point3D ThroughPoint; |
|||
|
|||
/// <summary>
|
|||
/// The direction of the ray
|
|||
/// </summary>
|
|||
public readonly UnitVector3D Direction; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Ray3D"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="throughPoint">The start point of the ray.</param>
|
|||
/// <param name="direction">The direction of the ray.</param>
|
|||
public Ray3D(Point3D throughPoint, UnitVector3D direction) |
|||
{ |
|||
this.ThroughPoint = throughPoint; |
|||
this.Direction = direction; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Ray3D"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="throughPoint">The start point of the ray.</param>
|
|||
/// <param name="direction">A vector indicating the direction of the ray.</param>
|
|||
public Ray3D(Point3D throughPoint, Vector3D direction) |
|||
: this(throughPoint, direction.Normalize()) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether each pair of elements in two specified rays is equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first ray to compare</param>
|
|||
/// <param name="right">The second ray to compare</param>
|
|||
/// <returns>True if the rays are the same; otherwise false.</returns>
|
|||
public static bool operator ==(Ray3D left, Ray3D right) |
|||
{ |
|||
return left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether any pair of elements in two specified rays is not equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first ray to compare</param>
|
|||
/// <param name="right">The second ray to compare</param>
|
|||
/// <returns>True if the rays are different; otherwise false.</returns>
|
|||
public static bool operator !=(Ray3D left, Ray3D right) |
|||
{ |
|||
return !left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// The intersection of the two planes
|
|||
/// </summary>
|
|||
/// <param name="plane1">The first plane</param>
|
|||
/// <param name="plane2">The second plane</param>
|
|||
/// <returns>A ray at the intersection of two planes</returns>
|
|||
public static Ray3D IntersectionOf(Plane3D plane1, Plane3D plane2) |
|||
{ |
|||
return plane1.IntersectionWith(plane2); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Parses string representation of throughpoint and direction
|
|||
/// See <see cref="Point3D.Parse(string, IFormatProvider)" /> and <see cref="UnitVector3D.Parse(string, IFormatProvider, double)" /> for details on acceptable formats.
|
|||
/// This is mainly meant for tests
|
|||
/// </summary>
|
|||
/// <param name="point">a string representing a start point for the ray.</param>
|
|||
/// <param name="direction">a string representing a direction for the ray.</param>
|
|||
/// <returns>A ray.</returns>
|
|||
public static Ray3D Parse(string point, string direction) |
|||
{ |
|||
return new Ray3D(Point3D.Parse(point), UnitVector3D.Parse(direction)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the shortest line from a point to the ray
|
|||
/// </summary>
|
|||
/// <param name="point3D">A point.</param>
|
|||
/// <returns>A line segment from the point to the closest point on the ray</returns>
|
|||
[Pure] |
|||
public LineSegment3D ShortestLineTo(Point3D point3D) |
|||
{ |
|||
var v = this.ThroughPoint.VectorTo(point3D); |
|||
var alongVector = v.ProjectOn(this.Direction); |
|||
return new LineSegment3D(this.ThroughPoint + alongVector, point3D); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the point at which this ray intersects with the plane
|
|||
/// </summary>
|
|||
/// <param name="plane">A geometric plane.</param>
|
|||
/// <returns>A point of intersection if such an intersection exists; otherwise null.</returns>
|
|||
[Pure] |
|||
public Point3D? IntersectionWith(Plane3D plane) |
|||
{ |
|||
return plane.IntersectionWith(this); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value to indicate if a pair of rays are collinear
|
|||
/// </summary>
|
|||
/// <param name="otherRay">The ray to compare against.</param>
|
|||
/// <param name="tolerance">A tolerance (epsilon) to adjust for floating point error</param>
|
|||
/// <returns>True if the rays are collinear; otherwise false.</returns>
|
|||
[Pure] |
|||
public bool IsCollinear(Ray3D otherRay, double tolerance = float.Epsilon) |
|||
{ |
|||
return this.Direction.IsParallelTo(otherRay.Direction, tolerance); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value to indicate if a pair of rays are equal
|
|||
/// </summary>
|
|||
/// <param name="other">The ray to compare against.</param>
|
|||
/// <param name="tolerance">A tolerance (epsilon) to adjust for floating point error</param>
|
|||
/// <returns>True if the rays are equal; otherwise false</returns>
|
|||
[Pure] |
|||
public bool Equals(Ray3D other, double tolerance) |
|||
{ |
|||
return this.Direction.Equals(other.Direction, tolerance) && |
|||
this.ThroughPoint.Equals(other.ThroughPoint, tolerance); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[Pure] |
|||
public bool Equals(Ray3D r) => this.Direction.Equals(r.Direction) && this.ThroughPoint.Equals(r.ThroughPoint); |
|||
|
|||
/// <inheritdoc/>
|
|||
[Pure] |
|||
public override bool Equals(object obj) => obj is Ray3D r && this.Equals(r); |
|||
|
|||
/// <inheritdoc/>
|
|||
[Pure] |
|||
public override int GetHashCode() => HashCode.Combine(this.ThroughPoint, this.Direction); |
|||
|
|||
/// <inheritdoc/>
|
|||
[Pure] |
|||
public override string ToString() |
|||
{ |
|||
return this.ToString(null, CultureInfo.InvariantCulture); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[Pure] |
|||
public string ToString(string format, IFormatProvider formatProvider) |
|||
{ |
|||
return string.Format( |
|||
"ThroughPoint: {0}, Direction: {1}", |
|||
this.ThroughPoint.ToString(format, formatProvider), |
|||
this.Direction.ToString(format, formatProvider)); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
XmlSchema IXmlSerializable.GetSchema() |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
void IXmlSerializable.ReadXml(XmlReader reader) |
|||
{ |
|||
reader.MoveToContent(); |
|||
var e = (XElement)XNode.ReadFrom(reader); |
|||
this = new Ray3D( |
|||
Point3D.ReadFrom(e.SingleElement("ThroughPoint").CreateReader()), |
|||
UnitVector3D.ReadFrom(e.SingleElement("Direction").CreateReader())); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
void IXmlSerializable.WriteXml(XmlWriter writer) |
|||
{ |
|||
writer.WriteElement("ThroughPoint", this.ThroughPoint); |
|||
writer.WriteElement("Direction", this.Direction); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,931 @@ |
|||
using System; |
|||
using System.Diagnostics.Contracts; |
|||
using System.Globalization; |
|||
using System.Xml; |
|||
using System.Xml.Schema; |
|||
using System.Xml.Serialization; |
|||
using MathNet.Numerics.LinearAlgebra; |
|||
using MathNet.Numerics.Spatial.Internal; |
|||
|
|||
namespace MathNet.Numerics.Spatial.Euclidean3D |
|||
{ |
|||
/// <summary>
|
|||
/// A unit vector, this is used to describe a direction in 3D
|
|||
/// </summary>
|
|||
[Serializable] |
|||
public struct UnitVector3D : IXmlSerializable, IEquatable<UnitVector3D>, IEquatable<Vector3D>, IFormattable |
|||
{ |
|||
/// <summary>
|
|||
/// The x component.
|
|||
/// </summary>
|
|||
public readonly double X; |
|||
|
|||
/// <summary>
|
|||
/// The y component.
|
|||
/// </summary>
|
|||
public readonly double Y; |
|||
|
|||
/// <summary>
|
|||
/// The z component.
|
|||
/// </summary>
|
|||
public readonly double Z; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="UnitVector3D"/> struct.
|
|||
/// The provided values are scaled to L2 norm == 1
|
|||
/// </summary>
|
|||
/// <param name="x">The x component.</param>
|
|||
/// <param name="y">The y component.</param>
|
|||
/// <param name="z">The z component.</param>
|
|||
private UnitVector3D(double x, double y, double z) |
|||
{ |
|||
if (double.IsNaN(x) || double.IsInfinity(x)) |
|||
{ |
|||
throw new ArgumentOutOfRangeException(nameof(x), x, "Invalid value."); |
|||
} |
|||
|
|||
if (double.IsNaN(y) || double.IsInfinity(y)) |
|||
{ |
|||
throw new ArgumentOutOfRangeException(nameof(y), y, "Invalid value."); |
|||
} |
|||
|
|||
if (double.IsNaN(z) || double.IsInfinity(z)) |
|||
{ |
|||
throw new ArgumentOutOfRangeException(nameof(z), z, "Invalid value."); |
|||
} |
|||
|
|||
var norm = Math.Sqrt((x * x) + (y * y) + (z * z)); |
|||
if (norm < float.Epsilon) |
|||
{ |
|||
throw new ArgumentException("l < float.Epsilon"); |
|||
} |
|||
|
|||
this.X = x / norm; |
|||
this.Y = y / norm; |
|||
this.Z = z / norm; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the X axis
|
|||
/// </summary>
|
|||
public static UnitVector3D XAxis { get; } = Create(1, 0, 0); |
|||
|
|||
/// <summary>
|
|||
/// Gets the Y axis
|
|||
/// </summary>
|
|||
public static UnitVector3D YAxis { get; } = Create(0, 1, 0); |
|||
|
|||
/// <summary>
|
|||
/// Gets the z Axis
|
|||
/// </summary>
|
|||
public static UnitVector3D ZAxis { get; } = Create(0, 0, 1); |
|||
|
|||
/// <summary>
|
|||
/// Gets a vector orthogonal to this
|
|||
/// </summary>
|
|||
[Pure] |
|||
public UnitVector3D Orthogonal |
|||
{ |
|||
get |
|||
{ |
|||
if (-this.X - this.Y > 0.1) |
|||
{ |
|||
return UnitVector3D.Create(this.Z, this.Z, -this.X - this.Y); |
|||
} |
|||
|
|||
return UnitVector3D.Create(-this.Y - this.Z, this.X, this.X); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the length of the vector not the count of elements
|
|||
/// </summary>
|
|||
[Pure] |
|||
public double Length => 1; |
|||
|
|||
/// <summary>
|
|||
/// Gets the cross product matrix
|
|||
/// </summary>
|
|||
[Pure] |
|||
internal Matrix<double> CrossProductMatrix => Matrix<double>.Build.Dense(3, 3, new[] { 0d, this.Z, -this.Y, -this.Z, 0d, this.X, this.Y, -this.X, 0d }); |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether each pair of elements in two specified vectors is equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first vector to compare</param>
|
|||
/// <param name="right">The second vector to compare</param>
|
|||
/// <returns>True if the vectors are the same; otherwise false.</returns>
|
|||
public static bool operator ==(UnitVector3D left, UnitVector3D right) |
|||
{ |
|||
return left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether each pair of elements in two specified vectors is equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first vector to compare</param>
|
|||
/// <param name="right">The second vector to compare</param>
|
|||
/// <returns>True if the vectors are the same; otherwise false.</returns>
|
|||
public static bool operator ==(Vector3D left, UnitVector3D right) |
|||
{ |
|||
return left.Equals((object)right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether each pair of elements in two specified vectors is equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first vector to compare</param>
|
|||
/// <param name="right">The second vector to compare</param>
|
|||
/// <returns>True if the vectors are the same; otherwise false.</returns>
|
|||
public static bool operator ==(UnitVector3D left, Vector3D right) |
|||
{ |
|||
return left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether any pair of elements in two specified vectors is not equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first vector to compare.</param>
|
|||
/// <param name="right">The second vector to compare.</param>
|
|||
/// <returns>True if the vectors are different; otherwise false.</returns>
|
|||
public static bool operator !=(UnitVector3D left, UnitVector3D right) |
|||
{ |
|||
return !left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether any pair of elements in two specified vectors is not equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first vector to compare.</param>
|
|||
/// <param name="right">The second vector to compare.</param>
|
|||
/// <returns>True if the vectors are different; otherwise false.</returns>
|
|||
public static bool operator !=(Vector3D left, UnitVector3D right) |
|||
{ |
|||
return !left.Equals((object)right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether any pair of elements in two specified vectors is not equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first vector to compare.</param>
|
|||
/// <param name="right">The second vector to compare.</param>
|
|||
/// <returns>True if the vectors are different; otherwise false.</returns>
|
|||
public static bool operator !=(UnitVector3D left, Vector3D right) |
|||
{ |
|||
return !left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds two vectors
|
|||
/// </summary>
|
|||
/// <param name="v1">The first vector</param>
|
|||
/// <param name="v2">The second vector</param>
|
|||
/// <returns>A new summed vector</returns>
|
|||
public static Vector3D operator +(UnitVector3D v1, UnitVector3D v2) |
|||
{ |
|||
return new Vector3D(v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds two vectors
|
|||
/// </summary>
|
|||
/// <param name="v1">The first vector</param>
|
|||
/// <param name="v2">The second vector</param>
|
|||
/// <returns>A new summed vector</returns>
|
|||
public static Vector3D operator +(Vector3D v1, UnitVector3D v2) |
|||
{ |
|||
return new Vector3D(v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds two vectors
|
|||
/// </summary>
|
|||
/// <param name="v1">The first vector</param>
|
|||
/// <param name="v2">The second vector</param>
|
|||
/// <returns>A new summed vector</returns>
|
|||
public static Vector3D operator +(UnitVector3D v1, Vector3D v2) |
|||
{ |
|||
return new Vector3D(v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Subtracts two vectors
|
|||
/// </summary>
|
|||
/// <param name="v1">The first vector</param>
|
|||
/// <param name="v2">The second vector</param>
|
|||
/// <returns>A new difference vector</returns>
|
|||
public static Vector3D operator -(UnitVector3D v1, UnitVector3D v2) |
|||
{ |
|||
return new Vector3D(v1.X - v2.X, v1.Y - v2.Y, v1.Z - v2.Z); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Subtracts two vectors
|
|||
/// </summary>
|
|||
/// <param name="v1">The first vector</param>
|
|||
/// <param name="v2">The second vector</param>
|
|||
/// <returns>A new difference vector</returns>
|
|||
public static Vector3D operator -(Vector3D v1, UnitVector3D v2) |
|||
{ |
|||
return new Vector3D(v1.X - v2.X, v1.Y - v2.Y, v1.Z - v2.Z); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Subtracts two vectors
|
|||
/// </summary>
|
|||
/// <param name="v1">The first vector</param>
|
|||
/// <param name="v2">The second vector</param>
|
|||
/// <returns>A new difference vector</returns>
|
|||
public static Vector3D operator -(UnitVector3D v1, Vector3D v2) |
|||
{ |
|||
return new Vector3D(v1.X - v2.X, v1.Y - v2.Y, v1.Z - v2.Z); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Negates the vector
|
|||
/// </summary>
|
|||
/// <param name="v">A vector to negate</param>
|
|||
/// <returns>A new negated vector</returns>
|
|||
public static Vector3D operator -(UnitVector3D v) |
|||
{ |
|||
return new Vector3D(-1 * v.X, -1 * v.Y, -1 * v.Z); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Multiplies a vector by a scalar
|
|||
/// </summary>
|
|||
/// <param name="d">A scalar</param>
|
|||
/// <param name="v">A vector</param>
|
|||
/// <returns>A scaled vector</returns>
|
|||
public static Vector3D operator *(double d, UnitVector3D v) |
|||
{ |
|||
return new Vector3D(d * v.X, d * v.Y, d * v.Z); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Divides a vector by a scalar
|
|||
/// </summary>
|
|||
/// <param name="v">A vector</param>
|
|||
/// <param name="d">A scalar</param>
|
|||
/// <returns>A scaled vector</returns>
|
|||
public static Vector3D operator /(UnitVector3D v, double d) |
|||
{ |
|||
return new Vector3D(v.X / d, v.Y / d, v.Z / d); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Multiplies a Matrix by a Vector
|
|||
/// </summary>
|
|||
/// <param name="left">A Matrix</param>
|
|||
/// <param name="right">A Vector</param>
|
|||
/// <returns>A new vector</returns>
|
|||
[Obsolete("Not sure this is nice")] |
|||
public static Vector<double> operator *(Matrix<double> left, UnitVector3D right) |
|||
{ |
|||
return left * right.ToVector(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Multiplies a vector by a matrix
|
|||
/// </summary>
|
|||
/// <param name="left">A Vector</param>
|
|||
/// <param name="right">A Matrix</param>
|
|||
/// <returns>A new vector</returns>
|
|||
[Obsolete("Not sure this is nice")] |
|||
public static Vector<double> operator *(UnitVector3D left, Matrix<double> right) |
|||
{ |
|||
return left.ToVector() * right; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the dot product of two vectors
|
|||
/// </summary>
|
|||
/// <param name="left">The first vector</param>
|
|||
/// <param name="right">The second vector</param>
|
|||
/// <returns>A scalar result</returns>
|
|||
public static double operator *(UnitVector3D left, UnitVector3D right) |
|||
{ |
|||
return left.DotProduct(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="UnitVector3D"/> struct.
|
|||
/// The provided values are scaled to L2 norm == 1
|
|||
/// </summary>
|
|||
/// <param name="x">The x component.</param>
|
|||
/// <param name="y">The y component.</param>
|
|||
/// <param name="z">The z component.</param>
|
|||
/// <param name="tolerance">The allowed deviation from 1 for the L2-norm of x,y,z</param>
|
|||
/// <returns>The <see cref="UnitVector3D"/></returns>
|
|||
public static UnitVector3D Create(double x, double y, double z, double tolerance = double.PositiveInfinity) |
|||
{ |
|||
var norm = Math.Sqrt((x * x) + (y * y) + (z * z)); |
|||
if (norm < float.Epsilon) |
|||
{ |
|||
throw new InvalidOperationException("The Euclidean norm of x, y, z is less than float.Epsilon"); |
|||
} |
|||
|
|||
if (Math.Abs(norm - 1) > tolerance) |
|||
{ |
|||
throw new InvalidOperationException("The Euclidean norm of x, y, z differs more than tolerance from 1"); |
|||
} |
|||
#pragma warning disable 618
|
|||
return new UnitVector3D(x / norm, y / norm, z / norm); |
|||
#pragma warning restore 618
|
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Create a new <see cref="UnitVector3D"/> from a Math.NET Numerics vector of length 3.
|
|||
/// </summary>
|
|||
/// <param name="vector"> A vector with length 2 to populate the created instance with.</param>
|
|||
/// <returns> A <see cref="UnitVector3D"/></returns>
|
|||
public static UnitVector3D OfVector(Vector<double> vector) |
|||
{ |
|||
if (vector.Count != 3) |
|||
{ |
|||
throw new ArgumentException("The vector length must be 3 in order to convert it to a Vector3D"); |
|||
} |
|||
|
|||
return Create(vector.At(0), vector.At(1), vector.At(2)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Attempts to convert a string of the form x,y,z into a vector
|
|||
/// </summary>
|
|||
/// <param name="text">The string to be converted</param>
|
|||
/// <param name="result">A vector with the coordinates specified</param>
|
|||
/// <param name="tolerance">The tolerance for how big deviation from Length = 1 is accepted</param>
|
|||
/// <returns>True if <paramref name="text"/> could be parsed.</returns>
|
|||
public static bool TryParse(string text, out UnitVector3D result, double tolerance = 0.1) |
|||
{ |
|||
return TryParse(text, null, out result, tolerance); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Attempts to convert a string of the form x,y,z into a unit vector
|
|||
/// First it is parsed to a vector then the length of the vector is compared to the tolerance and normalized if within.
|
|||
/// </summary>
|
|||
/// <param name="text">The string to be converted</param>
|
|||
/// <param name="formatProvider">The <see cref="IFormatProvider"/></param>
|
|||
/// <param name="result">A point at the coordinates specified</param>
|
|||
/// <param name="tolerance">The tolerance for how big deviation from Length = 1 is accepted</param>
|
|||
/// <returns>True if <paramref name="text"/> could be parsed.</returns>
|
|||
public static bool TryParse(string text, IFormatProvider formatProvider, out UnitVector3D result, double tolerance = 0.1) |
|||
{ |
|||
if (Text.TryParse3D(text, formatProvider, out var x, out var y, out var z)) |
|||
{ |
|||
var temp = new Vector3D(x, y, z); |
|||
if (Math.Abs(temp.Length - 1) < tolerance) |
|||
{ |
|||
result = temp.Normalize(); |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
result = default(UnitVector3D); |
|||
return false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Attempts to convert a string of the form x,y,z into a vector
|
|||
/// </summary>
|
|||
/// <param name="value">The string to be converted</param>
|
|||
/// <param name="formatProvider">The <see cref="IFormatProvider"/></param>
|
|||
/// <param name="tolerance">The tolerance for how big deviation from Length = 1 is accepted</param>
|
|||
/// <returns>A point at the coordinates specified</returns>
|
|||
public static UnitVector3D Parse(string value, IFormatProvider formatProvider = null, double tolerance = 0.1) |
|||
{ |
|||
if (TryParse(value, formatProvider, out var p, tolerance)) |
|||
{ |
|||
return p; |
|||
} |
|||
|
|||
throw new FormatException($"Could not parse a UnitVector3D from the string {value}"); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates an <see cref="UnitVector3D"/> from an <see cref="XmlReader"/>.
|
|||
/// </summary>
|
|||
/// <param name="reader">An <see cref="XmlReader"/> positioned at the node to read into this <see cref="UnitVector3D"/>.</param>
|
|||
/// <returns>An <see cref="UnitVector3D"/> that contains the data read from the reader.</returns>
|
|||
public static UnitVector3D ReadFrom(XmlReader reader) |
|||
{ |
|||
return reader.ReadElementAs<UnitVector3D>(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Scale this instance by <paramref name="factor"/>
|
|||
/// </summary>
|
|||
/// <param name="factor">The plane to project on.</param>
|
|||
/// <returns>The projected <see cref="Ray3D"/></returns>
|
|||
[Pure] |
|||
public Vector3D ScaleBy(double factor) |
|||
{ |
|||
return factor * this; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Project this instance onto the plane
|
|||
/// </summary>
|
|||
/// <param name="plane">The plane to project on.</param>
|
|||
/// <returns>The projected <see cref="Ray3D"/></returns>
|
|||
[Pure] |
|||
public Ray3D ProjectOn(Plane3D plane) |
|||
{ |
|||
return plane.Project(this.ToVector3D()); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the Dot product of the current vector and a unit vector
|
|||
/// </summary>
|
|||
/// <param name="uv">A unit vector</param>
|
|||
/// <returns>Returns a new vector</returns>
|
|||
[Pure] |
|||
public Vector3D ProjectOn(UnitVector3D uv) |
|||
{ |
|||
var pd = this.DotProduct(uv); |
|||
return pd * this; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Computes whether or not this vector is parallel to another vector using the dot product method and comparing it
|
|||
/// to within a specified tolerance.
|
|||
/// </summary>
|
|||
/// <param name="othervector">The other <see cref="Vector3D"/></param>
|
|||
/// <param name="tolerance">A tolerance value for the dot product method. Values below 2*Precision.DoublePrecision may cause issues.</param>
|
|||
/// <returns>true if the vector dot product is within the given tolerance of unity, false if it is not</returns>
|
|||
[Pure] |
|||
public bool IsParallelTo(Vector3D othervector, double tolerance = 1e-10) |
|||
{ |
|||
var other = othervector.Normalize(); |
|||
return this.IsParallelTo(other, tolerance); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Computes whether or not this vector is parallel to a unit vector using the dot product method and comparing it
|
|||
/// to within a specified tolerance.
|
|||
/// </summary>
|
|||
/// <param name="othervector">The other <see cref="UnitVector3D"/></param>
|
|||
/// <param name="tolerance">A tolerance value for the dot product method. Values below 2*Precision.DoublePrecision may cause issues.</param>
|
|||
/// <returns>true if the vector dot product is within the given tolerance of unity, false if not</returns>
|
|||
[Pure] |
|||
public bool IsParallelTo(UnitVector3D othervector, double tolerance = 1e-10) |
|||
{ |
|||
// This is the master method for all Vector3D and UnitVector3D IsParallelTo comparisons. Everything else
|
|||
// ends up here sooner or later.
|
|||
var dp = Math.Abs(this.DotProduct(othervector)); |
|||
return Math.Abs(1 - dp) <= tolerance; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Determine whether or not this vector is parallel to another vector within a given angle tolerance.
|
|||
/// </summary>
|
|||
/// <param name="othervector">The other <see cref="Vector3D"/></param>
|
|||
/// <param name="angleTolerance">The tolerance for when the vectors are considered parallel.</param>
|
|||
/// <returns>true if the vectors are parallel within the angle tolerance, false if they are not</returns>
|
|||
[Pure] |
|||
public bool IsParallelTo(UnitVector3D othervector, Angle angleTolerance) |
|||
{ |
|||
// Compute the angle between these vectors
|
|||
var angle = this.AngleTo(othervector); |
|||
|
|||
// Compute the 180° opposite of the angle
|
|||
var opposite = Angle.FromDegrees(180) - angle; |
|||
|
|||
// Check against the smaller of the two
|
|||
return ((angle < opposite) ? angle : opposite) < angleTolerance; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Determine whether or not this vector is parallel to a unit vector within a given angle tolerance.
|
|||
/// </summary>
|
|||
/// <param name="othervector">The other <see cref="UnitVector3D"/></param>
|
|||
/// <param name="angleTolerance">The tolerance for when the vectors are considered parallel.</param>
|
|||
/// <returns>true if the vectors are parallel within the angle tolerance, false if they are not</returns>
|
|||
[Pure] |
|||
public bool IsParallelTo(Vector3D othervector, Angle angleTolerance) |
|||
{ |
|||
var other = othervector.Normalize(); |
|||
return this.IsParallelTo(other, angleTolerance); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Computes whether or not this vector is perpendicular to another vector using the dot product method and
|
|||
/// comparing it to within a specified tolerance
|
|||
/// </summary>
|
|||
/// <param name="othervector">The other <see cref="Vector3D"/></param>
|
|||
/// <param name="tolerance">A tolerance value for the dot product method. Values below 2*Precision.DoublePrecision may cause issues.</param>
|
|||
/// <returns>true if the vector dot product is within the given tolerance of zero, false if not</returns>
|
|||
[Pure] |
|||
public bool IsPerpendicularTo(Vector3D othervector, double tolerance = 1e-10) |
|||
{ |
|||
var other = othervector.Normalize(); |
|||
return Math.Abs(this.DotProduct(other)) < tolerance; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Computes whether or not this vector is perpendicular to another vector using the dot product method and
|
|||
/// comparing it to within a specified tolerance
|
|||
/// </summary>
|
|||
/// <param name="othervector">The other <see cref="UnitVector3D"/></param>
|
|||
/// <param name="tolerance">A tolerance value for the dot product method. Values below 2*Precision.DoublePrecision may cause issues.</param>
|
|||
/// <returns>true if the vector dot product is within the given tolerance of zero, false if not</returns>
|
|||
[Pure] |
|||
public bool IsPerpendicularTo(UnitVector3D othervector, double tolerance = 1e-10) |
|||
{ |
|||
return Math.Abs(this.DotProduct(othervector)) < tolerance; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Inverses the direction of the vector, equivalent to multiplying by -1
|
|||
/// </summary>
|
|||
/// <returns>A <see cref="Vector3D"/> pointing in the opposite direction.</returns>
|
|||
[Pure] |
|||
public UnitVector3D Negate() |
|||
{ |
|||
return UnitVector3D.Create(-1 * this.X, -1 * this.Y, -1 * this.Z); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the dot product of two vectors.
|
|||
/// </summary>
|
|||
/// <param name="v">The second vector.</param>
|
|||
/// <returns>The dot product.</returns>
|
|||
[Pure] |
|||
public double DotProduct(Vector3D v) |
|||
{ |
|||
return (this.X * v.X) + (this.Y * v.Y) + (this.Z * v.Z); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the dot product of two vectors.
|
|||
/// </summary>
|
|||
/// <param name="v">The second vector.</param>
|
|||
/// <returns>The dot product.</returns>
|
|||
[Pure] |
|||
public double DotProduct(UnitVector3D v) |
|||
{ |
|||
var dp = (this.X * v.X) + (this.Y * v.Y) + (this.Z * v.Z); |
|||
return Math.Max(-1, Math.Min(dp, 1)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Subtracts a vector from this
|
|||
/// </summary>
|
|||
/// <param name="v">a vector to subtract</param>
|
|||
/// <returns>A new vector</returns>
|
|||
[Pure] |
|||
public Vector3D Subtract(UnitVector3D v) |
|||
{ |
|||
return new Vector3D(this.X - v.X, this.Y - v.Y, this.Z - v.Z); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a vector to this
|
|||
/// </summary>
|
|||
/// <param name="v">a vector to add</param>
|
|||
/// <returns>A new vector</returns>
|
|||
[Pure] |
|||
public Vector3D Add(UnitVector3D v) |
|||
{ |
|||
return new Vector3D(this.X + v.X, this.Y + v.Y, this.Z + v.Z); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the cross product of this vector and a vector
|
|||
/// </summary>
|
|||
/// <param name="other">A vector</param>
|
|||
/// <returns>A new vector with the cross product result</returns>
|
|||
[Pure] |
|||
public UnitVector3D CrossProduct(UnitVector3D other) |
|||
{ |
|||
var x = (this.Y * other.Z) - (this.Z * other.Y); |
|||
var y = (this.Z * other.X) - (this.X * other.Z); |
|||
var z = (this.X * other.Y) - (this.Y * other.X); |
|||
var v = Create(x, y, z); |
|||
return v; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the cross product of this vector and a unit vector
|
|||
/// </summary>
|
|||
/// <param name="inVector3D">A vector</param>
|
|||
/// <returns>A new vector with the cross product result</returns>
|
|||
[Pure] |
|||
public Vector3D CrossProduct(Vector3D inVector3D) |
|||
{ |
|||
var x = (this.Y * inVector3D.Z) - (this.Z * inVector3D.Y); |
|||
var y = (this.Z * inVector3D.X) - (this.X * inVector3D.Z); |
|||
var z = (this.X * inVector3D.Y) - (this.Y * inVector3D.X); |
|||
var v = new Vector3D(x, y, z); |
|||
return v; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a dense Matrix with the unit tensor product
|
|||
/// </summary>
|
|||
/// <returns>a dense matrix</returns>
|
|||
[Pure] |
|||
public Matrix<double> GetUnitTensorProduct() |
|||
{ |
|||
// unitTensorProduct:matrix([ux^2,ux*uy,ux*uz],[ux*uy,uy^2,uy*uz],[ux*uz,uy*uz,uz^2]),
|
|||
var xy = this.X * this.Y; |
|||
var xz = this.X * this.Z; |
|||
var yz = this.Y * this.Z; |
|||
return Matrix<double>.Build.Dense(3, 3, new[] { this.X * this.X, xy, xz, xy, this.Y * this.Y, yz, xz, yz, this.Z * this.Z }); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns signed angle
|
|||
/// </summary>
|
|||
/// <param name="v">The vector to calculate the signed angle to </param>
|
|||
/// <param name="about">The vector around which to rotate to get the correct sign</param>
|
|||
/// <returns>A signed Angle</returns>
|
|||
[Pure] |
|||
public Angle SignedAngleTo(Vector3D v, UnitVector3D about) |
|||
{ |
|||
return this.SignedAngleTo(v.Normalize(), about); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns signed angle
|
|||
/// </summary>
|
|||
/// <param name="v">The vector to calculate the signed angle to </param>
|
|||
/// <param name="about">The vector around which to rotate to get the correct sign</param>
|
|||
/// <returns>A signed Angle</returns>
|
|||
[Pure] |
|||
public Angle SignedAngleTo(UnitVector3D v, UnitVector3D about) |
|||
{ |
|||
if (this.IsParallelTo(about)) |
|||
{ |
|||
throw new ArgumentException("FromVector parallel to aboutVector"); |
|||
} |
|||
|
|||
if (v.IsParallelTo(about)) |
|||
{ |
|||
throw new ArgumentException("FromVector parallel to aboutVector"); |
|||
} |
|||
|
|||
var rp = new Plane3D(new Point3D(0, 0, 0), about); |
|||
var pfv = this.ProjectOn(rp).Direction; |
|||
var ptv = v.ProjectOn(rp).Direction; |
|||
var dp = pfv.DotProduct(ptv); |
|||
if (Math.Abs(dp - 1) < 1E-15) |
|||
{ |
|||
return Angle.FromRadians(0); |
|||
} |
|||
|
|||
if (Math.Abs(dp + 1) < 1E-15) |
|||
{ |
|||
return Angle.FromRadians(Math.PI); |
|||
} |
|||
|
|||
var angle = Math.Acos(dp); |
|||
var cpv = pfv.CrossProduct(ptv); |
|||
var sign = cpv.DotProduct(rp.Normal); |
|||
var signedAngle = sign * angle; |
|||
return Angle.FromRadians(signedAngle); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// The nearest angle between the vectors
|
|||
/// </summary>
|
|||
/// <param name="v">The other vector</param>
|
|||
/// <returns>The angle</returns>
|
|||
[Pure] |
|||
public Angle AngleTo(Vector3D v) |
|||
{ |
|||
return this.AngleTo(v.Normalize()); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compute the angle between this vector and a unit vector using the arccosine of the dot product.
|
|||
/// </summary>
|
|||
/// <param name="v">The other vector</param>
|
|||
/// <returns>The angle between the vectors, with a range between 0° and 180°</returns>
|
|||
[Pure] |
|||
public Angle AngleTo(UnitVector3D v) |
|||
{ |
|||
var dp = this.DotProduct(v); |
|||
var angle = Math.Acos(dp); |
|||
return Angle.FromRadians(angle); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a vector that is this vector rotated the signed angle around the about vector
|
|||
/// </summary>
|
|||
/// <param name="about">The vector to rotate around.</param>
|
|||
/// <param name="angle">The angle positive according to right hand rule.</param>
|
|||
/// <returns>A rotated vector.</returns>
|
|||
[Pure] |
|||
public UnitVector3D Rotate(UnitVector3D about, Angle angle) |
|||
{ |
|||
var cs = CoordinateSystem3D.Rotation(angle, about); |
|||
return cs.Transform(this).Normalize(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a point equivalent to the vector
|
|||
/// </summary>
|
|||
/// <returns>A point</returns>
|
|||
[Pure] |
|||
public Point3D ToPoint3D() |
|||
{ |
|||
return new Point3D(this.X, this.Y, this.Z); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a Vector3D equivalent to this unit vector
|
|||
/// </summary>
|
|||
/// <returns>A vector</returns>
|
|||
[Pure] |
|||
public Vector3D ToVector3D() |
|||
{ |
|||
return new Vector3D(this.X, this.Y, this.Z); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Transforms the vector by a coordinate system and returns the transformed.
|
|||
/// </summary>
|
|||
/// <param name="coordinateSystem">A coordinate system</param>
|
|||
/// <returns>A new transformed vector</returns>
|
|||
[Pure] |
|||
public Vector3D TransformBy(CoordinateSystem3D coordinateSystem) |
|||
{ |
|||
return coordinateSystem.Transform(this.ToVector3D()); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Transforms a vector by multiplying it against a provided matrix
|
|||
/// </summary>
|
|||
/// <param name="m">The matrix to multiply</param>
|
|||
/// <returns>A new transformed vector</returns>
|
|||
[Pure] |
|||
public Vector3D TransformBy(Matrix<double> m) |
|||
{ |
|||
return Vector3D.OfVector(m.Multiply(this.ToVector())); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Convert to a Math.NET Numerics dense vector of length 3.
|
|||
/// </summary>
|
|||
/// <returns>A dense vector</returns>
|
|||
[Pure] |
|||
public Vector<double> ToVector() |
|||
{ |
|||
return Vector<double>.Build.Dense(new[] { this.X, this.Y, this.Z }); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override string ToString() |
|||
{ |
|||
return this.ToString(null, CultureInfo.InvariantCulture); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a string representation of this instance using the provided <see cref="IFormatProvider"/>
|
|||
/// </summary>
|
|||
/// <param name="provider">A <see cref="IFormatProvider"/></param>
|
|||
/// <returns>The string representation of this instance.</returns>
|
|||
[Pure] |
|||
public string ToString(IFormatProvider provider) |
|||
{ |
|||
return this.ToString(null, provider); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[Pure] |
|||
public string ToString(string format, IFormatProvider provider = null) |
|||
{ |
|||
var numberFormatInfo = provider != null ? NumberFormatInfo.GetInstance(provider) : CultureInfo.InvariantCulture.NumberFormat; |
|||
var separator = numberFormatInfo.NumberDecimalSeparator == "," ? ";" : ","; |
|||
return string.Format("({0}{1} {2}{1} {3})", this.X.ToString(format, numberFormatInfo), separator, this.Y.ToString(format, numberFormatInfo), this.Z.ToString(format, numberFormatInfo)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value to indicate if a pair of vectors are equal
|
|||
/// </summary>
|
|||
/// <param name="other">The vector to compare against.</param>
|
|||
/// <param name="tolerance">A tolerance (epsilon) to adjust for floating point error</param>
|
|||
/// <returns>True if the vectors are equal; otherwise false</returns>
|
|||
[Pure] |
|||
public bool Equals(UnitVector3D other, double tolerance) |
|||
{ |
|||
if (tolerance < 0) |
|||
{ |
|||
throw new ArgumentException("epsilon < 0"); |
|||
} |
|||
|
|||
return Math.Abs(other.X - this.X) < tolerance && |
|||
Math.Abs(other.Y - this.Y) < tolerance && |
|||
Math.Abs(other.Z - this.Z) < tolerance; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value to indicate if a pair of vectors are equal
|
|||
/// </summary>
|
|||
/// <param name="other">The vector to compare against.</param>
|
|||
/// <param name="tolerance">A tolerance (epsilon) to adjust for floating point error</param>
|
|||
/// <returns>True if the vectors are equal; otherwise false</returns>
|
|||
[Pure] |
|||
public bool Equals(Vector3D other, double tolerance) |
|||
{ |
|||
if (tolerance < 0) |
|||
{ |
|||
throw new ArgumentException("epsilon < 0"); |
|||
} |
|||
|
|||
return Math.Abs(other.X - this.X) < tolerance && |
|||
Math.Abs(other.Y - this.Y) < tolerance && |
|||
Math.Abs(other.Z - this.Z) < tolerance; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public bool Equals(Vector3D other) |
|||
{ |
|||
return this.X.Equals(other.X) && this.Y.Equals(other.Y) && this.Z.Equals(other.Z); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public bool Equals(UnitVector3D other) |
|||
{ |
|||
return this.X.Equals(other.X) && this.Y.Equals(other.Y) && this.Z.Equals(other.Z); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override bool Equals(object obj) |
|||
{ |
|||
return (obj is UnitVector3D u && this.Equals(u)) || (obj is Vector3D v && this.Equals(v)); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[Pure] |
|||
public override int GetHashCode() => HashCode.Combine(this.X, this.Y, this.Z); |
|||
|
|||
/// <inheritdoc />
|
|||
XmlSchema IXmlSerializable.GetSchema() |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
void IXmlSerializable.ReadXml(XmlReader reader) |
|||
{ |
|||
bool TryGetUnitVector(double xv, double yv, double zv, out UnitVector3D result) |
|||
{ |
|||
var temp = new Vector3D(xv, yv, zv); |
|||
if (Math.Abs(temp.Length - 1) < 1E-3) |
|||
{ |
|||
result = temp.Normalize(); |
|||
return true; |
|||
} |
|||
|
|||
result = default(UnitVector3D); |
|||
return false; |
|||
} |
|||
|
|||
if (reader.TryReadAttributeAsDouble("X", out var x) && |
|||
reader.TryReadAttributeAsDouble("Y", out var y) && |
|||
reader.TryReadAttributeAsDouble("Z", out var z) && |
|||
TryGetUnitVector(x, y, z, out var uv)) |
|||
{ |
|||
reader.Skip(); |
|||
this = uv; |
|||
return; |
|||
} |
|||
|
|||
if (reader.TryReadChildElementsAsDoubles("X", "Y", "Z", out x, out y, out z) && |
|||
TryGetUnitVector(x, y, z, out uv)) |
|||
{ |
|||
reader.Skip(); |
|||
this = uv; |
|||
return; |
|||
} |
|||
|
|||
throw new XmlException($"Could not read a {this.GetType()}"); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
void IXmlSerializable.WriteXml(XmlWriter writer) |
|||
{ |
|||
writer.WriteAttribute("X", this.X); |
|||
writer.WriteAttribute("Y", this.Y); |
|||
writer.WriteAttribute("Z", this.Z); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the dot product of this vector with a second
|
|||
/// </summary>
|
|||
/// <param name="v">a second vector</param>
|
|||
/// <returns>The dot product</returns>
|
|||
[Pure] |
|||
internal double DotProduct(Point3D v) |
|||
{ |
|||
return (this.X * v.X) + (this.Y * v.Y) + (this.Z * v.Z); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,740 @@ |
|||
using System; |
|||
using System.Diagnostics.Contracts; |
|||
using System.Globalization; |
|||
using System.Xml; |
|||
using System.Xml.Schema; |
|||
using System.Xml.Serialization; |
|||
using MathNet.Numerics.LinearAlgebra; |
|||
using MathNet.Numerics.Spatial.Internal; |
|||
|
|||
namespace MathNet.Numerics.Spatial.Euclidean3D |
|||
{ |
|||
/// <summary>
|
|||
/// A struct representing a vector in 3D space
|
|||
/// </summary>
|
|||
[Serializable] |
|||
public struct Vector3D : IXmlSerializable, IEquatable<Vector3D>, IEquatable<UnitVector3D>, IFormattable |
|||
{ |
|||
/// <summary>
|
|||
/// The x component.
|
|||
/// </summary>
|
|||
public readonly double X; |
|||
|
|||
/// <summary>
|
|||
/// The y component.
|
|||
/// </summary>
|
|||
public readonly double Y; |
|||
|
|||
/// <summary>
|
|||
/// The z component.
|
|||
/// </summary>
|
|||
public readonly double Z; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Vector3D"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="x">The x component.</param>
|
|||
/// <param name="y">The y component.</param>
|
|||
/// <param name="z">The z component.</param>
|
|||
public Vector3D(double x, double y, double z) |
|||
{ |
|||
this.X = x; |
|||
this.Y = y; |
|||
this.Z = z; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets an invalid vector with no values
|
|||
/// </summary>
|
|||
public static Vector3D NaN => new Vector3D(double.NaN, double.NaN, double.NaN); |
|||
|
|||
/// <summary>
|
|||
/// Gets the Euclidean Norm.
|
|||
/// </summary>
|
|||
[Pure] |
|||
public double Length => Math.Sqrt((this.X * this.X) + (this.Y * this.Y) + (this.Z * this.Z)); |
|||
|
|||
/// <summary>
|
|||
/// Gets a unit vector orthogonal to this
|
|||
/// </summary>
|
|||
[Pure] |
|||
public UnitVector3D Orthogonal |
|||
{ |
|||
get |
|||
{ |
|||
if (-this.X - this.Y > 0.1) |
|||
{ |
|||
return UnitVector3D.Create(this.Z, this.Z, -this.X - this.Y); |
|||
} |
|||
|
|||
return UnitVector3D.Create(-this.Y - this.Z, this.X, this.X); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a dense matrix containing the cross product of this vector
|
|||
/// </summary>
|
|||
[Pure] |
|||
internal Matrix<double> CrossProductMatrix => Matrix<double>.Build.Dense(3, 3, new[] { 0d, this.Z, -this.Y, -this.Z, 0d, this.X, this.Y, -this.X, 0d }); |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether each pair of elements in two specified vectors is equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first vector to compare.</param>
|
|||
/// <param name="right">The second vector to compare.</param>
|
|||
/// <returns>True if the vectors are the same; otherwise false.</returns>
|
|||
public static bool operator ==(Vector3D left, Vector3D right) |
|||
{ |
|||
return left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether any pair of elements in two specified vectors is not equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first vector to compare.</param>
|
|||
/// <param name="right">The second vector to compare.</param>
|
|||
/// <returns>True if the vectors are different; otherwise false.</returns>
|
|||
public static bool operator !=(Vector3D left, Vector3D right) |
|||
{ |
|||
return !left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Multiplies a Matrix by a Vector
|
|||
/// </summary>
|
|||
/// <param name="left">A Matrix</param>
|
|||
/// <param name="right">A Vector</param>
|
|||
/// <returns>A new vector</returns>
|
|||
[Obsolete("Not sure this is nice")] |
|||
public static Vector<double> operator *(Matrix<double> left, Vector3D right) |
|||
{ |
|||
return left * right.ToVector(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Multiplies a vector by a matrix
|
|||
/// </summary>
|
|||
/// <param name="left">A Vector</param>
|
|||
/// <param name="right">A Matrix</param>
|
|||
/// <returns>A new vector</returns>
|
|||
[Obsolete("Not sure this is nice")] |
|||
public static Vector<double> operator *(Vector3D left, Matrix<double> right) |
|||
{ |
|||
return left.ToVector() * right; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the dot product of two vectors
|
|||
/// </summary>
|
|||
/// <param name="left">The first vector</param>
|
|||
/// <param name="right">The second vector</param>
|
|||
/// <returns>A scalar result</returns>
|
|||
public static double operator *(Vector3D left, Vector3D right) |
|||
{ |
|||
return left.DotProduct(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds two vectors
|
|||
/// </summary>
|
|||
/// <param name="left">The first vector</param>
|
|||
/// <param name="right">The second vector</param>
|
|||
/// <returns>A new summed vector</returns>
|
|||
public static Vector3D operator +(Vector3D left, Vector3D right) |
|||
{ |
|||
return new Vector3D(left.X + right.X, left.Y + right.Y, left.Z + right.Z); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Subtracts two vectors
|
|||
/// </summary>
|
|||
/// <param name="left">The first vector</param>
|
|||
/// <param name="right">The second vector</param>
|
|||
/// <returns>A new difference vector</returns>
|
|||
public static Vector3D operator -(Vector3D left, Vector3D right) |
|||
{ |
|||
return new Vector3D(left.X - right.X, left.Y - right.Y, left.Z - right.Z); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Negates the vector
|
|||
/// </summary>
|
|||
/// <param name="v">A vector to negate</param>
|
|||
/// <returns>A new negated vector</returns>
|
|||
public static Vector3D operator -(Vector3D v) |
|||
{ |
|||
return v.Negate(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Multiplies a vector by a scalar
|
|||
/// </summary>
|
|||
/// <param name="d">A scalar</param>
|
|||
/// <param name="v">A vector</param>
|
|||
/// <returns>A scaled vector</returns>
|
|||
public static Vector3D operator *(double d, Vector3D v) |
|||
{ |
|||
return new Vector3D(d * v.X, d * v.Y, d * v.Z); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Divides a vector by a scalar
|
|||
/// </summary>
|
|||
/// <param name="v">A vector</param>
|
|||
/// <param name="d">A scalar</param>
|
|||
/// <returns>A scaled vector</returns>
|
|||
public static Vector3D operator /(Vector3D v, double d) |
|||
{ |
|||
return new Vector3D(v.X / d, v.Y / d, v.Z / d); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Attempts to convert a string of the form x,y,z into a vector
|
|||
/// </summary>
|
|||
/// <param name="text">The string to be converted</param>
|
|||
/// <param name="result">A vector with the coordinates specified</param>
|
|||
/// <returns>True if <paramref name="text"/> could be parsed.</returns>
|
|||
public static bool TryParse(string text, out Vector3D result) |
|||
{ |
|||
return TryParse(text, null, out result); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Attempts to convert a string of the form x,y,z into a vector
|
|||
/// </summary>
|
|||
/// <param name="text">The string to be converted</param>
|
|||
/// <param name="formatProvider">The <see cref="IFormatProvider"/></param>
|
|||
/// <param name="result">A point at the coordinates specified</param>
|
|||
/// <returns>True if <paramref name="text"/> could be parsed.</returns>
|
|||
public static bool TryParse(string text, IFormatProvider formatProvider, out Vector3D result) |
|||
{ |
|||
if (Text.TryParse3D(text, formatProvider, out var x, out var y, out var z)) |
|||
{ |
|||
result = new Vector3D(x, y, z); |
|||
return true; |
|||
} |
|||
|
|||
result = default(Vector3D); |
|||
return false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Attempts to convert a string of the form x,y,z into a vector
|
|||
/// </summary>
|
|||
/// <param name="value">The string to be converted</param>
|
|||
/// <param name="formatProvider">The <see cref="IFormatProvider"/></param>
|
|||
/// <returns>A point at the coordinates specified</returns>
|
|||
public static Vector3D Parse(string value, IFormatProvider formatProvider = null) |
|||
{ |
|||
if (TryParse(value, formatProvider, out var p)) |
|||
{ |
|||
return p; |
|||
} |
|||
|
|||
throw new FormatException($"Could not parse a Vector3D from the string {value}"); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Create a new <see cref="Vector3D"/> from a Math.NET Numerics vector of length 3.
|
|||
/// </summary>
|
|||
/// <param name="vector"> A vector with length 2 to populate the created instance with.</param>
|
|||
/// <returns> A <see cref="Vector3D"/></returns>
|
|||
public static Vector3D OfVector(Vector<double> vector) |
|||
{ |
|||
if (vector.Count != 3) |
|||
{ |
|||
throw new ArgumentException("The vector length must be 3 in order to convert it to a Vector3D"); |
|||
} |
|||
|
|||
return new Vector3D(vector.At(0), vector.At(1), vector.At(2)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates an <see cref="Vector3D"/> from an <see cref="XmlReader"/>.
|
|||
/// </summary>
|
|||
/// <param name="reader">An <see cref="XmlReader"/> positioned at the node to read into this <see cref="Vector3D"/>.</param>
|
|||
/// <returns>An <see cref="Vector3D"/> that contains the data read from the reader.</returns>
|
|||
public static Vector3D ReadFrom(XmlReader reader) |
|||
{ |
|||
return reader.ReadElementAs<Vector3D>(); |
|||
} |
|||
|
|||
////public static explicit operator Vector3D(System.Windows.Media.Media3D.Vector3D v)
|
|||
////{
|
|||
//// return new Vector3D(v.X, v.Y, v.Z);
|
|||
////}
|
|||
|
|||
////public static explicit operator System.Windows.Media.Media3D.Vector3D(Vector3D p)
|
|||
////{
|
|||
//// return new System.Windows.Media.Media3D.Vector3D(p.X, p.Y, p.Z);
|
|||
////}
|
|||
|
|||
/// <summary>
|
|||
/// Compute and return a unit vector from this vector
|
|||
/// </summary>
|
|||
/// <returns>a normalized unit vector</returns>
|
|||
[Pure] |
|||
public UnitVector3D Normalize() |
|||
{ |
|||
return UnitVector3D.Create(this.X, this.Y, this.Z); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Multiplies the current vector by a scalar
|
|||
/// </summary>
|
|||
/// <param name="scaleFactor">a scalar</param>
|
|||
/// <returns>A new scaled vector</returns>
|
|||
[Pure] |
|||
public Vector3D ScaleBy(double scaleFactor) |
|||
{ |
|||
return scaleFactor * this; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Projects the vector onto a plane
|
|||
/// </summary>
|
|||
/// <param name="planeToProjectOn">A geometric plane</param>
|
|||
/// <returns>A ray</returns>
|
|||
[Pure] |
|||
public Ray3D ProjectOn(Plane3D planeToProjectOn) |
|||
{ |
|||
return planeToProjectOn.Project(this); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the Dot product of the current vector and a unit vector
|
|||
/// </summary>
|
|||
/// <param name="uv">A unit vector</param>
|
|||
/// <returns>Returns a new vector</returns>
|
|||
[Pure] |
|||
public Vector3D ProjectOn(UnitVector3D uv) |
|||
{ |
|||
var pd = this.DotProduct(uv); |
|||
return pd * uv; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Computes whether or not this vector is parallel to another vector using the dot product method and comparing it
|
|||
/// to within a specified tolerance.
|
|||
/// </summary>
|
|||
/// <param name="other">The other <see cref="Vector3D"/></param>
|
|||
/// <param name="tolerance">A tolerance value for the dot product method. Values below 2*Precision.DoublePrecision may cause issues.</param>
|
|||
/// <returns>true if the vector dot product is within the given tolerance of unity, false if it is not</returns>
|
|||
[Pure] |
|||
public bool IsParallelTo(Vector3D other, double tolerance = 1e-10) |
|||
{ |
|||
var @this = this.Normalize(); |
|||
return @this.IsParallelTo(other, tolerance); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Computes whether or not this vector is parallel to a unit vector using the dot product method and comparing it
|
|||
/// to within a specified tolerance.
|
|||
/// </summary>
|
|||
/// <param name="other">The other <see cref="UnitVector3D"/></param>
|
|||
/// <param name="tolerance">A tolerance value for the dot product method. Values below 2*Precision.DoublePrecision may cause issues.</param>
|
|||
/// <returns>true if the vector dot product is within the given tolerance of unity, false if not</returns>
|
|||
[Pure] |
|||
public bool IsParallelTo(UnitVector3D other, double tolerance = 1e-10) |
|||
{ |
|||
return this.Normalize().IsParallelTo(other, tolerance); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Determine whether or not this vector is parallel to another vector within a given angle tolerance.
|
|||
/// </summary>
|
|||
/// <param name="other">The other <see cref="Vector3D"/></param>
|
|||
/// <param name="tolerance">The tolerance for when the vectors are considered parallel.</param>
|
|||
/// <returns>true if the vectors are parallel within the angle tolerance, false if they are not</returns>
|
|||
[Pure] |
|||
public bool IsParallelTo(Vector3D other, Angle tolerance) |
|||
{ |
|||
return this.Normalize().IsParallelTo(other, tolerance); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Determine whether or not this vector is parallel to a unit vector within a given angle tolerance.
|
|||
/// </summary>
|
|||
/// <param name="other">The other <see cref="UnitVector3D"/></param>
|
|||
/// <param name="tolerance">The tolerance for when the vectors are considered parallel.</param>
|
|||
/// <returns>true if the vectors are parallel within the angle tolerance, false if they are not</returns>
|
|||
[Pure] |
|||
public bool IsParallelTo(UnitVector3D other, Angle tolerance) |
|||
{ |
|||
var @this = this.Normalize(); |
|||
return @this.IsParallelTo(other, tolerance); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Computes whether or not this vector is perpendicular to another vector using the dot product method and
|
|||
/// comparing it to within a specified tolerance
|
|||
/// </summary>
|
|||
/// <param name="other">The other <see cref="Vector3D"/></param>
|
|||
/// <param name="tolerance">A tolerance value for the dot product method. Values below 2*Precision.DoublePrecision may cause issues.</param>
|
|||
/// <returns>true if the vector dot product is within the given tolerance of zero, false if not</returns>
|
|||
[Pure] |
|||
public bool IsPerpendicularTo(Vector3D other, double tolerance = 1e-6) |
|||
{ |
|||
return Math.Abs(this.Normalize().DotProduct(other.Normalize())) < tolerance; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Computes whether or not this vector is perpendicular to another vector using the dot product method and
|
|||
/// comparing it to within a specified tolerance
|
|||
/// </summary>
|
|||
/// <param name="other">The other <see cref="UnitVector3D"/></param>
|
|||
/// <param name="tolerance">A tolerance value for the dot product method. Values below 2*Precision.DoublePrecision may cause issues.</param>
|
|||
/// <returns>true if the vector dot product is within the given tolerance of zero, false if not</returns>
|
|||
[Pure] |
|||
public bool IsPerpendicularTo(UnitVector3D other, double tolerance = 1e-6) |
|||
{ |
|||
return Math.Abs(this.Normalize().DotProduct(other)) < tolerance; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Inverses the direction of the vector, equivalent to multiplying by -1
|
|||
/// </summary>
|
|||
/// <returns>A <see cref="Vector3D"/> pointing in the opposite direction.</returns>
|
|||
[Pure] |
|||
public Vector3D Negate() |
|||
{ |
|||
return new Vector3D(-1 * this.X, -1 * this.Y, -1 * this.Z); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the dot product of two vectors.
|
|||
/// </summary>
|
|||
/// <param name="v">The second vector.</param>
|
|||
/// <returns>The dot product.</returns>
|
|||
[Pure] |
|||
public double DotProduct(Vector3D v) |
|||
{ |
|||
return (this.X * v.X) + (this.Y * v.Y) + (this.Z * v.Z); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the dot product of two vectors.
|
|||
/// </summary>
|
|||
/// <param name="v">The second vector.</param>
|
|||
/// <returns>The dot product.</returns>
|
|||
[Pure] |
|||
public double DotProduct(UnitVector3D v) |
|||
{ |
|||
return (this.X * v.X) + (this.Y * v.Y) + (this.Z * v.Z); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Subtracts a vector from this
|
|||
/// </summary>
|
|||
/// <param name="v">a vector to subtract</param>
|
|||
/// <returns>A new vector</returns>
|
|||
[Pure] |
|||
public Vector3D Subtract(Vector3D v) |
|||
{ |
|||
return new Vector3D(this.X - v.X, this.Y - v.Y, this.Z - v.Z); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a vector to this
|
|||
/// </summary>
|
|||
/// <param name="v">a vector to add</param>
|
|||
/// <returns>A new vector</returns>
|
|||
[Pure] |
|||
public Vector3D Add(Vector3D v) |
|||
{ |
|||
return new Vector3D(this.X + v.X, this.Y + v.Y, this.Z + v.Z); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the cross product of this vector and <paramref name="other"/>
|
|||
/// </summary>
|
|||
/// <param name="other">A vector</param>
|
|||
/// <returns>A new vector with the cross product result</returns>
|
|||
[Pure] |
|||
public Vector3D CrossProduct(Vector3D other) |
|||
{ |
|||
var x = (this.Y * other.Z) - (this.Z * other.Y); |
|||
var y = (this.Z * other.X) - (this.X * other.Z); |
|||
var z = (this.X * other.Y) - (this.Y * other.X); |
|||
var v = new Vector3D(x, y, z); |
|||
return v; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the cross product of this vector and <paramref name="other"/>
|
|||
/// </summary>
|
|||
/// <param name="other">A vector</param>
|
|||
/// <returns>A new vector with the cross product result</returns>
|
|||
[Pure] |
|||
public Vector3D CrossProduct(UnitVector3D other) |
|||
{ |
|||
var x = (this.Y * other.Z) - (this.Z * other.Y); |
|||
var y = (this.Z * other.X) - (this.X * other.Z); |
|||
var z = (this.X * other.Y) - (this.Y * other.X); |
|||
var v = new Vector3D(x, y, z); |
|||
return v; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a dense Matrix with the unit tensor product
|
|||
/// [ux^2, ux*uy, ux*uz],
|
|||
/// [ux*uy, uy^2, uy*uz],
|
|||
/// [ux*uz, uy*uz, uz^2]
|
|||
/// </summary>
|
|||
/// <returns>a dense matrix</returns>
|
|||
[Pure] |
|||
public Matrix<double> GetUnitTensorProduct() |
|||
{ |
|||
// unitTensorProduct:matrix([ux^2,ux*uy,ux*uz],[ux*uy,uy^2,uy*uz],[ux*uz,uy*uz,uz^2]),
|
|||
var xy = this.X * this.Y; |
|||
var xz = this.X * this.Z; |
|||
var yz = this.Y * this.Z; |
|||
return Matrix<double>.Build.Dense(3, 3, new[] { this.X * this.X, xy, xz, xy, this.Y * this.Y, yz, xz, yz, this.Z * this.Z }); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns signed angle
|
|||
/// </summary>
|
|||
/// <param name="v">The vector to calculate the signed angle to </param>
|
|||
/// <param name="about">The vector around which to rotate to get the correct sign</param>
|
|||
/// <returns>A signed Angle</returns>
|
|||
[Pure] |
|||
public Angle SignedAngleTo(Vector3D v, UnitVector3D about) |
|||
{ |
|||
return this.Normalize().SignedAngleTo(v.Normalize(), about); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns signed angle
|
|||
/// </summary>
|
|||
/// <param name="v">The vector to calculate the signed angle to </param>
|
|||
/// <param name="about">The vector around which to rotate to get the correct sign</param>
|
|||
/// <returns>A signed angle</returns>
|
|||
[Pure] |
|||
public Angle SignedAngleTo(UnitVector3D v, UnitVector3D about) |
|||
{ |
|||
return this.Normalize().SignedAngleTo(v, about); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compute the angle between this vector and another using the arccosine of the dot product.
|
|||
/// </summary>
|
|||
/// <param name="v">The other vector</param>
|
|||
/// <returns>The angle between the vectors, with a range between 0° and 180°</returns>
|
|||
[Pure] |
|||
public Angle AngleTo(Vector3D v) |
|||
{ |
|||
var uv1 = this.Normalize(); |
|||
var uv2 = v.Normalize(); |
|||
return uv1.AngleTo(uv2); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compute the angle between this vector and a unit vector using the arccosine of the dot product.
|
|||
/// </summary>
|
|||
/// <param name="v">The other vector</param>
|
|||
/// <returns>The angle between the vectors, with a range between 0° and 180°</returns>
|
|||
[Pure] |
|||
public Angle AngleTo(UnitVector3D v) |
|||
{ |
|||
var uv = this.Normalize(); |
|||
return uv.AngleTo(v); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a vector that is this vector rotated the signed angle around the about vector
|
|||
/// </summary>
|
|||
/// <param name="about">A vector to rotate about</param>
|
|||
/// <param name="angle">A signed angle</param>
|
|||
/// <returns>A rotated vector.</returns>
|
|||
[Pure] |
|||
public Vector3D Rotate(Vector3D about, Angle angle) |
|||
{ |
|||
return this.Rotate(about.Normalize(), angle); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a vector that is this vector rotated the signed angle around the about vector
|
|||
/// </summary>
|
|||
/// <param name="about">A unit vector to rotate about</param>
|
|||
/// <param name="angle">A signed angle</param>
|
|||
/// <returns>A rotated vector.</returns>
|
|||
[Pure] |
|||
public Vector3D Rotate(UnitVector3D about, Angle angle) |
|||
{ |
|||
var cs = CoordinateSystem3D.Rotation(angle, about); |
|||
return cs.Transform(this); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a point equivalent to the vector
|
|||
/// </summary>
|
|||
/// <returns>A point</returns>
|
|||
public Point3D ToPoint3D() |
|||
{ |
|||
return new Point3D(this.X, this.Y, this.Z); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Transforms the vector by a coordinate system and returns the transformed.
|
|||
/// </summary>
|
|||
/// <param name="coordinateSystem">A coordinate system</param>
|
|||
/// <returns>A new transformed vector</returns>
|
|||
[Pure] |
|||
public Vector3D TransformBy(CoordinateSystem3D coordinateSystem) |
|||
{ |
|||
return coordinateSystem.Transform(this); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Transforms a vector by multiplying it against a provided matrix
|
|||
/// </summary>
|
|||
/// <param name="m">The matrix to multiply</param>
|
|||
/// <returns>A new transformed vector</returns>
|
|||
[Pure] |
|||
public Vector3D TransformBy(Matrix<double> m) |
|||
{ |
|||
return Vector3D.OfVector(m.Multiply(this.ToVector())); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Convert to a Math.NET Numerics dense vector of length 3.
|
|||
/// </summary>
|
|||
/// <returns>A dense vector</returns>
|
|||
[Pure] |
|||
public Vector<double> ToVector() |
|||
{ |
|||
return Vector<double>.Build.Dense(new[] { this.X, this.Y, this.Z }); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override string ToString() |
|||
{ |
|||
return this.ToString(null, CultureInfo.InvariantCulture); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a string representation of this instance using the provided <see cref="IFormatProvider"/>
|
|||
/// </summary>
|
|||
/// <param name="provider">A <see cref="IFormatProvider"/></param>
|
|||
/// <returns>The string representation of this instance.</returns>
|
|||
[Pure] |
|||
public string ToString(IFormatProvider provider) |
|||
{ |
|||
return this.ToString(null, provider); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public string ToString(string format, IFormatProvider provider = null) |
|||
{ |
|||
var numberFormatInfo = provider != null ? NumberFormatInfo.GetInstance(provider) : CultureInfo.InvariantCulture.NumberFormat; |
|||
var separator = numberFormatInfo.NumberDecimalSeparator == "," ? ";" : ","; |
|||
return string.Format( |
|||
"({1}{0} {2}{0} {3})", |
|||
separator, |
|||
this.X.ToString(format, numberFormatInfo), |
|||
this.Y.ToString(format, numberFormatInfo), |
|||
this.Z.ToString(format, numberFormatInfo)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value to indicate if a pair of vectors are equal
|
|||
/// </summary>
|
|||
/// <param name="other">The vector to compare against.</param>
|
|||
/// <param name="tolerance">A tolerance (epsilon) to adjust for floating point error</param>
|
|||
/// <returns>true if the vectors are equal; otherwise false</returns>
|
|||
[Pure] |
|||
public bool Equals(Vector3D other, double tolerance) |
|||
{ |
|||
if (tolerance < 0) |
|||
{ |
|||
throw new ArgumentException("epsilon < 0"); |
|||
} |
|||
|
|||
return Math.Abs(other.X - this.X) < tolerance && |
|||
Math.Abs(other.Y - this.Y) < tolerance && |
|||
Math.Abs(other.Z - this.Z) < tolerance; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value to indicate if this vector is equivalent to a given unit vector
|
|||
/// </summary>
|
|||
/// <param name="other">The unit vector to compare against.</param>
|
|||
/// <param name="tolerance">A tolerance (epsilon) to adjust for floating point error</param>
|
|||
/// <returns>true if the vectors are equal; otherwise false</returns>
|
|||
[Pure] |
|||
public bool Equals(UnitVector3D other, double tolerance) |
|||
{ |
|||
if (tolerance < 0) |
|||
{ |
|||
throw new ArgumentException("epsilon < 0"); |
|||
} |
|||
|
|||
return Math.Abs(other.X - this.X) < tolerance && |
|||
Math.Abs(other.Y - this.Y) < tolerance && |
|||
Math.Abs(other.Z - this.Z) < tolerance; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public bool Equals(Vector3D other) |
|||
{ |
|||
return this.X.Equals(other.X) && this.Y.Equals(other.Y) && this.Z.Equals(other.Z); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public bool Equals(UnitVector3D other) |
|||
{ |
|||
return this.X.Equals(other.X) && this.Y.Equals(other.Y) && this.Z.Equals(other.Z); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override bool Equals(object obj) |
|||
{ |
|||
return (obj is UnitVector3D u && this.Equals(u)) || (obj is Vector3D v && this.Equals(v)); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
[Pure] |
|||
public override int GetHashCode() => HashCode.Combine(this.X, this.Y, this.Z); |
|||
|
|||
/// <inheritdoc />
|
|||
XmlSchema IXmlSerializable.GetSchema() |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
void IXmlSerializable.ReadXml(XmlReader reader) |
|||
{ |
|||
if (reader.TryReadAttributeAsDouble("X", out var x) && |
|||
reader.TryReadAttributeAsDouble("Y", out var y) && |
|||
reader.TryReadAttributeAsDouble("Z", out var z)) |
|||
{ |
|||
reader.Skip(); |
|||
this = new Vector3D(x, y, z); |
|||
return; |
|||
} |
|||
|
|||
if (reader.TryReadChildElementsAsDoubles("X", "Y", "Z", out x, out y, out z)) |
|||
{ |
|||
reader.Skip(); |
|||
this = new Vector3D(x, y, z); |
|||
return; |
|||
} |
|||
|
|||
throw new XmlException($"Could not read a {this.GetType()}"); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
void IXmlSerializable.WriteXml(XmlWriter writer) |
|||
{ |
|||
writer.WriteAttribute("X", this.X); |
|||
writer.WriteAttribute("Y", this.Y); |
|||
writer.WriteAttribute("Z", this.Z); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,109 @@ |
|||
using System.Diagnostics.CodeAnalysis; |
|||
|
|||
namespace MathNet.Numerics.Spatial.Internal.AvlTreeSet |
|||
{ |
|||
/// <summary>
|
|||
/// A node of the Avl Tree
|
|||
/// </summary>
|
|||
/// <typeparam name="T">Any type</typeparam>
|
|||
[SuppressMessage("Microsoft.StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "By design")] |
|||
internal sealed class AvlNode<T> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the parent node
|
|||
/// </summary>
|
|||
public AvlNode<T> Parent; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the left node
|
|||
/// </summary>
|
|||
public AvlNode<T> Left; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the right node
|
|||
/// </summary>
|
|||
public AvlNode<T> Right; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the item
|
|||
/// </summary>
|
|||
public T Item; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the Avl balance
|
|||
/// </summary>
|
|||
public int Balance; |
|||
|
|||
/// <summary>
|
|||
/// Non recursive function that return the next ordered node
|
|||
/// </summary>
|
|||
/// <returns>The next node</returns>
|
|||
public AvlNode<T> GetNextNode() |
|||
{ |
|||
AvlNode<T> current; |
|||
|
|||
if (this.Right != null) |
|||
{ |
|||
current = this.Right; |
|||
while (current.Left != null) |
|||
{ |
|||
current = current.Left; |
|||
} |
|||
|
|||
return current; |
|||
} |
|||
|
|||
current = this; |
|||
while (current.Parent != null) |
|||
{ |
|||
if (current.Parent.Left == current) |
|||
{ |
|||
return current.Parent; |
|||
} |
|||
|
|||
current = current.Parent; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Non recursive function that return the previous ordered node
|
|||
/// </summary>
|
|||
/// <returns>The previous node</returns>
|
|||
public AvlNode<T> GetPreviousNode() |
|||
{ |
|||
AvlNode<T> current; |
|||
|
|||
if (this.Left != null) |
|||
{ |
|||
current = this.Left; |
|||
while (current.Right != null) |
|||
{ |
|||
current = current.Right; |
|||
} |
|||
|
|||
return current; |
|||
} |
|||
|
|||
current = this; |
|||
while (current.Parent != null) |
|||
{ |
|||
if (current.Parent.Right == current) |
|||
{ |
|||
return current.Parent; |
|||
} |
|||
|
|||
current = current.Parent; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() |
|||
{ |
|||
return $"AvlNode [{this.Item}], balance: {this.Balance}, Parent: {this.Parent?.Item.ToString()}, Left: {this.Left?.Item.ToString()}, Right: {this.Right?.Item.ToString()},"; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,92 @@ |
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace MathNet.Numerics.Spatial.Internal.AvlTreeSet |
|||
{ |
|||
/// <summary>
|
|||
/// An enumerator for AvlNodeItems
|
|||
/// </summary>
|
|||
/// <typeparam name="T">Any type which is also a node type</typeparam>
|
|||
internal class AvlNodeItemEnumerator<T> : IEnumerator<T> |
|||
{ |
|||
/// <summary>
|
|||
/// A reference to the tree
|
|||
/// </summary>
|
|||
private readonly AvlTreeSet<T> avlTree; |
|||
|
|||
/// <summary>
|
|||
/// The current node
|
|||
/// </summary>
|
|||
private AvlNode<T> current = null; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="AvlNodeItemEnumerator{T}"/> class.
|
|||
/// </summary>
|
|||
/// <param name="avlTree">The tree to enumerate</param>
|
|||
public AvlNodeItemEnumerator(AvlTreeSet<T> avlTree) |
|||
{ |
|||
this.avlTree = avlTree ?? throw new ArgumentNullException("avlTree can't be null"); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the current node
|
|||
/// </summary>
|
|||
public T Current |
|||
{ |
|||
get |
|||
{ |
|||
if (this.current == null) |
|||
{ |
|||
throw new InvalidOperationException("Current is invalid"); |
|||
} |
|||
|
|||
return this.current.Item; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the current node
|
|||
/// </summary>
|
|||
object IEnumerator.Current => this.Current; |
|||
|
|||
/// <summary>
|
|||
/// Dispose of the enumerator
|
|||
/// </summary>
|
|||
public void Dispose() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Moves to the next node
|
|||
/// </summary>
|
|||
/// <returns>True if the move to the next node was successful; otherwise false</returns>
|
|||
public bool MoveNext() |
|||
{ |
|||
if (this.current == null) |
|||
{ |
|||
this.current = this.avlTree.GetFirstNode(); |
|||
} |
|||
else |
|||
{ |
|||
this.current = this.current.GetNextNode(); |
|||
} |
|||
|
|||
// Should check for an empty tree too :-)
|
|||
if (this.current == null) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Resets the enumerator
|
|||
/// </summary>
|
|||
public void Reset() |
|||
{ |
|||
this.current = null; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,943 @@ |
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.Threading; |
|||
|
|||
namespace MathNet.Numerics.Spatial.Internal.AvlTreeSet |
|||
{ |
|||
/// <summary>
|
|||
/// 2016-12-08, Eric Ouellet
|
|||
/// The code is an adapted version of BitLush AvlTree: https://bitlush.com/blog/efficient-avl-tree-in-c-sharp
|
|||
/// </summary>
|
|||
/// <typeparam name="T">Any type</typeparam>
|
|||
internal class AvlTreeSet<T> : IEnumerable<T>, IEnumerable, ICollection<T>, ICollection |
|||
{ |
|||
/// <summary>
|
|||
/// A comparer
|
|||
/// </summary>
|
|||
private readonly IComparer<T> comparer; |
|||
|
|||
/// <summary>
|
|||
/// The root node of the tree
|
|||
/// </summary>
|
|||
private AvlNode<T> root; |
|||
|
|||
/// <summary>
|
|||
/// a sync object
|
|||
/// </summary>
|
|||
private object syncRoot; |
|||
|
|||
/// <summary>
|
|||
/// A count of nodes
|
|||
/// </summary>
|
|||
private int count = 0; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="AvlTreeSet{T}"/> class.
|
|||
/// </summary>
|
|||
/// <param name="comparer">a comparer for nodes</param>
|
|||
public AvlTreeSet(IComparer<T> comparer) |
|||
{ |
|||
this.comparer = comparer; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="AvlTreeSet{T}"/> class.
|
|||
/// </summary>
|
|||
public AvlTreeSet() |
|||
: this(Comparer<T>.Default) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the root node
|
|||
/// </summary>
|
|||
public AvlNode<T> Root => this.root; |
|||
|
|||
/// <summary>
|
|||
/// Gets the count of nodes
|
|||
/// </summary>
|
|||
public int Count => this.count; |
|||
|
|||
/// <summary>
|
|||
/// Gets the sync object
|
|||
/// </summary>
|
|||
public object SyncRoot |
|||
{ |
|||
get |
|||
{ |
|||
if (this.syncRoot == null) |
|||
{ |
|||
Interlocked.CompareExchange(ref this.syncRoot, new object(), (object)null); |
|||
} |
|||
|
|||
return this.syncRoot; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the tree is synchronized;
|
|||
/// </summary>
|
|||
public bool IsSynchronized => true; |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the tree is read only;
|
|||
/// </summary>
|
|||
public bool IsReadOnly => false; |
|||
|
|||
/// <summary>
|
|||
/// Gets an enumerator for the tree
|
|||
/// </summary>
|
|||
/// <returns>An enumerator</returns>
|
|||
public IEnumerator<T> GetEnumerator() |
|||
{ |
|||
return new AvlNodeItemEnumerator<T>(this); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the tree contains an item
|
|||
/// </summary>
|
|||
/// <param name="item">The item to find</param>
|
|||
/// <returns>True if the item is found; otherwise false;</returns>
|
|||
public bool Contains(T item) |
|||
{ |
|||
AvlNode<T> node = this.root; |
|||
|
|||
while (node != null) |
|||
{ |
|||
int compareResult = this.comparer.Compare(item, node.Item); |
|||
if (compareResult < 0) |
|||
{ |
|||
node = node.Left; |
|||
} |
|||
else if (compareResult > 0) |
|||
{ |
|||
node = node.Right; |
|||
} |
|||
else |
|||
{ |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds an item to the tree
|
|||
/// </summary>
|
|||
/// <param name="item">The item to add</param>
|
|||
/// <returns>True if adding was successful; otherwise false</returns>
|
|||
public virtual bool Add(T item) |
|||
{ |
|||
AvlNode<T> node = this.root; |
|||
|
|||
while (node != null) |
|||
{ |
|||
int compare = this.comparer.Compare(item, node.Item); |
|||
|
|||
if (compare < 0) |
|||
{ |
|||
AvlNode<T> left = node.Left; |
|||
|
|||
if (left == null) |
|||
{ |
|||
node.Left = new AvlNode<T> { Item = item, Parent = node }; |
|||
this.AddBalance(node, 1); |
|||
return true; |
|||
} |
|||
else |
|||
{ |
|||
node = left; |
|||
} |
|||
} |
|||
else if (compare > 0) |
|||
{ |
|||
AvlNode<T> right = node.Right; |
|||
|
|||
if (right == null) |
|||
{ |
|||
node.Right = new AvlNode<T> { Item = item, Parent = node }; |
|||
this.AddBalance(node, -1); |
|||
return true; |
|||
} |
|||
else |
|||
{ |
|||
node = right; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
this.root = new AvlNode<T> { Item = item }; |
|||
this.count++; |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Clear() |
|||
{ |
|||
this.root = null; |
|||
this.count = 0; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets an enumerator
|
|||
/// </summary>
|
|||
/// <returns>an enumerator</returns>
|
|||
IEnumerator IEnumerable.GetEnumerator() |
|||
{ |
|||
return this.GetEnumerator(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Copies to an array
|
|||
/// </summary>
|
|||
/// <param name="array">The array to copy to</param>
|
|||
/// <param name="index">start point</param>
|
|||
/// <param name="count">number of items to copy</param>
|
|||
public void CopyTo(T[] array, int index, int count) |
|||
{ |
|||
if (array == null) |
|||
{ |
|||
throw new ArgumentNullException("'array' can't be null"); |
|||
} |
|||
|
|||
if (index < 0) |
|||
{ |
|||
throw new ArgumentException("'index' can't be null"); |
|||
} |
|||
|
|||
if (count < 0) |
|||
{ |
|||
throw new ArgumentOutOfRangeException("'count' should be greater or equal to 0"); |
|||
} |
|||
|
|||
if (index > array.Length || count > array.Length - index) |
|||
{ |
|||
throw new ArgumentException("The array size is not big enough to get all items"); |
|||
} |
|||
|
|||
if (count == 0) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
int indexIter = 0; |
|||
int indexArray = 0; |
|||
|
|||
AvlNode<T> current = this.GetFirstNode(); |
|||
while (current.GetNextNode() != null) |
|||
{ |
|||
if (indexIter >= index) |
|||
{ |
|||
array[indexArray] = current.Item; |
|||
indexArray++; |
|||
count--; |
|||
if (count == 0) |
|||
{ |
|||
return; |
|||
} |
|||
} |
|||
|
|||
indexIter++; |
|||
} |
|||
|
|||
/* |
|||
foreach (AvlNode<T> node in this.Nodes()) |
|||
{ |
|||
if (indexIter >= index) |
|||
{ |
|||
array[indexArray] = node.Item; |
|||
indexArray++; |
|||
count--; |
|||
if (count == 0) |
|||
{ |
|||
return; |
|||
} |
|||
} |
|||
|
|||
indexIter++; |
|||
}*/ |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void CopyTo(T[] array, int arrayIndex) |
|||
{ |
|||
this.CopyTo(array, arrayIndex, this.Count); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void CopyTo(Array array, int index) |
|||
{ |
|||
this.CopyTo(array as T[], index, this.Count); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
void ICollection<T>.Add(T item) |
|||
{ |
|||
this.Add(item); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the first item
|
|||
/// </summary>
|
|||
/// <returns>The first item</returns>
|
|||
public T GetFirstItem() |
|||
{ |
|||
AvlNode<T> node = this.GetFirstNode(); |
|||
if (node != null) |
|||
{ |
|||
return node.Item; |
|||
} |
|||
|
|||
return default(T); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the first node
|
|||
/// </summary>
|
|||
/// <returns>The first node</returns>
|
|||
public AvlNode<T> GetFirstNode() |
|||
{ |
|||
if (this.Root != null) |
|||
{ |
|||
AvlNode<T> current = this.Root; |
|||
while (current.Left != null) |
|||
{ |
|||
current = current.Left; |
|||
} |
|||
|
|||
return current; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// gets the last item
|
|||
/// </summary>
|
|||
/// <returns>The last item</returns>
|
|||
public T GetLastItem() |
|||
{ |
|||
AvlNode<T> node = this.GetLastNode(); |
|||
if (node != null) |
|||
{ |
|||
return node.Item; |
|||
} |
|||
|
|||
return default(T); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the last node
|
|||
/// </summary>
|
|||
/// <returns>returns the last node</returns>
|
|||
public AvlNode<T> GetLastNode() |
|||
{ |
|||
if (this.Root != null) |
|||
{ |
|||
AvlNode<T> current = this.Root; |
|||
while (current.Right != null) |
|||
{ |
|||
current = current.Right; |
|||
} |
|||
|
|||
return current; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Removes a node
|
|||
/// </summary>
|
|||
/// <param name="item">item to remove</param>
|
|||
/// <returns>True if successful; otherwise false</returns>
|
|||
public virtual bool Remove(T item) |
|||
{ |
|||
AvlNode<T> node = this.root; |
|||
|
|||
while (node != null) |
|||
{ |
|||
if (this.comparer.Compare(item, node.Item) < 0) |
|||
{ |
|||
node = node.Left; |
|||
} |
|||
else if (this.comparer.Compare(item, node.Item) > 0) |
|||
{ |
|||
node = node.Right; |
|||
} |
|||
else |
|||
{ |
|||
this.RemoveNode(node); |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a node with the provided item
|
|||
/// </summary>
|
|||
/// <param name="item">An item to find</param>
|
|||
/// <returns>The node with that item</returns>
|
|||
protected AvlNode<T> GetNode(T item) |
|||
{ |
|||
AvlNode<T> node = this.root; |
|||
|
|||
while (node != null) |
|||
{ |
|||
int compareResult = this.comparer.Compare(item, node.Item); |
|||
if (compareResult < 0) |
|||
{ |
|||
node = node.Left; |
|||
} |
|||
else if (compareResult > 0) |
|||
{ |
|||
node = node.Right; |
|||
} |
|||
else |
|||
{ |
|||
return node; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Should always be called for any inserted node
|
|||
/// </summary>
|
|||
/// <param name="node">A node</param>
|
|||
/// <param name="balance">The balance measure</param>
|
|||
//// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|||
protected void AddBalance(AvlNode<T> node, int balance) |
|||
{ |
|||
this.count++; |
|||
|
|||
while (node != null) |
|||
{ |
|||
balance = node.Balance += balance; |
|||
|
|||
if (balance == 0) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
if (balance == 2) |
|||
{ |
|||
if (node.Left.Balance == 1) |
|||
{ |
|||
this.RotateRight(node); |
|||
} |
|||
else |
|||
{ |
|||
this.RotateLeftRight(node); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
|
|||
if (balance == -2) |
|||
{ |
|||
if (node.Right.Balance == -1) |
|||
{ |
|||
this.RotateLeft(node); |
|||
} |
|||
else |
|||
{ |
|||
this.RotateRightLeft(node); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
|
|||
AvlNode<T> parent = node.Parent; |
|||
|
|||
if (parent != null) |
|||
{ |
|||
balance = parent.Left == node ? 1 : -1; |
|||
} |
|||
|
|||
node = parent; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Rotate the tree node left
|
|||
/// </summary>
|
|||
/// <param name="node">A node</param>
|
|||
/// <returns>The rotated node</returns>
|
|||
protected AvlNode<T> RotateLeft(AvlNode<T> node) |
|||
{ |
|||
AvlNode<T> right = node.Right; |
|||
AvlNode<T> rightLeft = right.Left; |
|||
AvlNode<T> parent = node.Parent; |
|||
|
|||
right.Parent = parent; |
|||
right.Left = node; |
|||
node.Right = rightLeft; |
|||
node.Parent = right; |
|||
|
|||
if (rightLeft != null) |
|||
{ |
|||
rightLeft.Parent = node; |
|||
} |
|||
|
|||
if (node == this.root) |
|||
{ |
|||
this.root = right; |
|||
} |
|||
else if (parent.Right == node) |
|||
{ |
|||
parent.Right = right; |
|||
} |
|||
else |
|||
{ |
|||
parent.Left = right; |
|||
} |
|||
|
|||
right.Balance++; |
|||
node.Balance = -right.Balance; |
|||
|
|||
return right; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Rotate a tree node right
|
|||
/// </summary>
|
|||
/// <param name="node">a node</param>
|
|||
/// <returns>The rotated tree node</returns>
|
|||
protected AvlNode<T> RotateRight(AvlNode<T> node) |
|||
{ |
|||
AvlNode<T> left = node.Left; |
|||
AvlNode<T> leftRight = left.Right; |
|||
AvlNode<T> parent = node.Parent; |
|||
|
|||
left.Parent = parent; |
|||
left.Right = node; |
|||
node.Left = leftRight; |
|||
node.Parent = left; |
|||
|
|||
if (leftRight != null) |
|||
{ |
|||
leftRight.Parent = node; |
|||
} |
|||
|
|||
if (node == this.root) |
|||
{ |
|||
this.root = left; |
|||
} |
|||
else if (parent.Left == node) |
|||
{ |
|||
parent.Left = left; |
|||
} |
|||
else |
|||
{ |
|||
parent.Right = left; |
|||
} |
|||
|
|||
left.Balance--; |
|||
node.Balance = -left.Balance; |
|||
|
|||
return left; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Rotate a tree node leftright
|
|||
/// </summary>
|
|||
/// <param name="node">a node</param>
|
|||
/// <returns>a rotated tree node</returns>
|
|||
protected AvlNode<T> RotateLeftRight(AvlNode<T> node) |
|||
{ |
|||
AvlNode<T> left = node.Left; |
|||
AvlNode<T> leftRight = left.Right; |
|||
AvlNode<T> parent = node.Parent; |
|||
AvlNode<T> leftRightRight = leftRight.Right; |
|||
AvlNode<T> leftRightLeft = leftRight.Left; |
|||
|
|||
leftRight.Parent = parent; |
|||
node.Left = leftRightRight; |
|||
left.Right = leftRightLeft; |
|||
leftRight.Left = left; |
|||
leftRight.Right = node; |
|||
left.Parent = leftRight; |
|||
node.Parent = leftRight; |
|||
|
|||
if (leftRightRight != null) |
|||
{ |
|||
leftRightRight.Parent = node; |
|||
} |
|||
|
|||
if (leftRightLeft != null) |
|||
{ |
|||
leftRightLeft.Parent = left; |
|||
} |
|||
|
|||
if (node == this.root) |
|||
{ |
|||
this.root = leftRight; |
|||
} |
|||
else if (parent.Left == node) |
|||
{ |
|||
parent.Left = leftRight; |
|||
} |
|||
else |
|||
{ |
|||
parent.Right = leftRight; |
|||
} |
|||
|
|||
if (leftRight.Balance == -1) |
|||
{ |
|||
node.Balance = 0; |
|||
left.Balance = 1; |
|||
} |
|||
else if (leftRight.Balance == 0) |
|||
{ |
|||
node.Balance = 0; |
|||
left.Balance = 0; |
|||
} |
|||
else |
|||
{ |
|||
node.Balance = -1; |
|||
left.Balance = 0; |
|||
} |
|||
|
|||
leftRight.Balance = 0; |
|||
|
|||
return leftRight; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Rotate a tree node rightleft
|
|||
/// </summary>
|
|||
/// <param name="node">a node</param>
|
|||
/// <returns>a rotated tree node</returns>
|
|||
protected AvlNode<T> RotateRightLeft(AvlNode<T> node) |
|||
{ |
|||
AvlNode<T> right = node.Right; |
|||
AvlNode<T> rightLeft = right.Left; |
|||
AvlNode<T> parent = node.Parent; |
|||
AvlNode<T> rightLeftLeft = rightLeft.Left; |
|||
AvlNode<T> rightLeftRight = rightLeft.Right; |
|||
|
|||
rightLeft.Parent = parent; |
|||
node.Right = rightLeftLeft; |
|||
right.Left = rightLeftRight; |
|||
rightLeft.Right = right; |
|||
rightLeft.Left = node; |
|||
right.Parent = rightLeft; |
|||
node.Parent = rightLeft; |
|||
|
|||
if (rightLeftLeft != null) |
|||
{ |
|||
rightLeftLeft.Parent = node; |
|||
} |
|||
|
|||
if (rightLeftRight != null) |
|||
{ |
|||
rightLeftRight.Parent = right; |
|||
} |
|||
|
|||
if (node == this.root) |
|||
{ |
|||
this.root = rightLeft; |
|||
} |
|||
else if (parent.Right == node) |
|||
{ |
|||
parent.Right = rightLeft; |
|||
} |
|||
else |
|||
{ |
|||
parent.Left = rightLeft; |
|||
} |
|||
|
|||
if (rightLeft.Balance == 1) |
|||
{ |
|||
node.Balance = 0; |
|||
right.Balance = -1; |
|||
} |
|||
else if (rightLeft.Balance == 0) |
|||
{ |
|||
node.Balance = 0; |
|||
right.Balance = 0; |
|||
} |
|||
else |
|||
{ |
|||
node.Balance = 1; |
|||
right.Balance = 0; |
|||
} |
|||
|
|||
rightLeft.Balance = 0; |
|||
|
|||
return rightLeft; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Removes a node
|
|||
/// </summary>
|
|||
/// <param name="node">node to remove</param>
|
|||
protected void RemoveNode(AvlNode<T> node) |
|||
{ |
|||
this.count--; |
|||
|
|||
AvlNode<T> left = node.Left; |
|||
AvlNode<T> right = node.Right; |
|||
|
|||
if (left == null) |
|||
{ |
|||
if (right == null) |
|||
{ |
|||
if (node == this.root) |
|||
{ |
|||
this.root = null; |
|||
} |
|||
else |
|||
{ |
|||
if (node.Parent.Left == node) |
|||
{ |
|||
node.Parent.Left = null; |
|||
|
|||
this.RemoveBalance(node.Parent, -1); |
|||
} |
|||
else if (node.Parent.Right == node) |
|||
{ |
|||
node.Parent.Right = null; |
|||
|
|||
this.RemoveBalance(node.Parent, 1); |
|||
} |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
Replace(node, right); |
|||
|
|||
this.RemoveBalance(node, 0); |
|||
} |
|||
} |
|||
else if (right == null) |
|||
{ |
|||
Replace(node, left); |
|||
|
|||
this.RemoveBalance(node, 0); |
|||
} |
|||
else |
|||
{ |
|||
AvlNode<T> successor = right; |
|||
|
|||
if (successor.Left == null) |
|||
{ |
|||
AvlNode<T> parent = node.Parent; |
|||
|
|||
successor.Parent = parent; |
|||
successor.Left = left; |
|||
successor.Balance = node.Balance; |
|||
|
|||
left.Parent = successor; |
|||
|
|||
if (node == this.root) |
|||
{ |
|||
this.root = successor; |
|||
} |
|||
else |
|||
{ |
|||
if (parent.Left == node) |
|||
{ |
|||
parent.Left = successor; |
|||
} |
|||
else |
|||
{ |
|||
parent.Right = successor; |
|||
} |
|||
} |
|||
|
|||
this.RemoveBalance(successor, 1); |
|||
} |
|||
else |
|||
{ |
|||
while (successor.Left != null) |
|||
{ |
|||
successor = successor.Left; |
|||
} |
|||
|
|||
AvlNode<T> parent = node.Parent; |
|||
AvlNode<T> successorParent = successor.Parent; |
|||
AvlNode<T> successorRight = successor.Right; |
|||
|
|||
if (successorParent.Left == successor) |
|||
{ |
|||
successorParent.Left = successorRight; |
|||
} |
|||
else |
|||
{ |
|||
successorParent.Right = successorRight; |
|||
} |
|||
|
|||
if (successorRight != null) |
|||
{ |
|||
successorRight.Parent = successorParent; |
|||
} |
|||
|
|||
successor.Parent = parent; |
|||
successor.Left = left; |
|||
successor.Balance = node.Balance; |
|||
successor.Right = right; |
|||
right.Parent = successor; |
|||
|
|||
left.Parent = successor; |
|||
|
|||
if (node == this.root) |
|||
{ |
|||
this.root = successor; |
|||
} |
|||
else |
|||
{ |
|||
if (parent.Left == node) |
|||
{ |
|||
parent.Left = successor; |
|||
} |
|||
else |
|||
{ |
|||
parent.Right = successor; |
|||
} |
|||
} |
|||
|
|||
this.RemoveBalance(successorParent, -1); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Should always be called for any removed node
|
|||
/// </summary>
|
|||
/// <param name="node">a node</param>
|
|||
/// <param name="balance">the node balance</param>
|
|||
protected void RemoveBalance(AvlNode<T> node, int balance) |
|||
{ |
|||
while (node != null) |
|||
{ |
|||
balance = node.Balance += balance; |
|||
|
|||
if (balance == 2) |
|||
{ |
|||
if (node.Left.Balance >= 0) |
|||
{ |
|||
node = this.RotateRight(node); |
|||
|
|||
if (node.Balance == -1) |
|||
{ |
|||
return; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
node = this.RotateLeftRight(node); |
|||
} |
|||
} |
|||
else if (balance == -2) |
|||
{ |
|||
if (node.Right.Balance <= 0) |
|||
{ |
|||
node = this.RotateLeft(node); |
|||
|
|||
if (node.Balance == 1) |
|||
{ |
|||
return; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
node = this.RotateRightLeft(node); |
|||
} |
|||
} |
|||
else if (balance != 0) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
AvlNode<T> parent = node.Parent; |
|||
|
|||
if (parent != null) |
|||
{ |
|||
balance = parent.Left == node ? -1 : 1; |
|||
} |
|||
|
|||
node = parent; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Replace a node
|
|||
/// </summary>
|
|||
/// <param name="target">The node to replace</param>
|
|||
/// <param name="source">The replacing node</param>
|
|||
private static void Replace(AvlNode<T> target, AvlNode<T> source) |
|||
{ |
|||
AvlNode<T> left = source.Left; |
|||
AvlNode<T> right = source.Right; |
|||
|
|||
target.Balance = source.Balance; |
|||
target.Item = source.Item; |
|||
target.Left = left; |
|||
target.Right = right; |
|||
|
|||
if (left != null) |
|||
{ |
|||
left.Parent = target; |
|||
} |
|||
|
|||
if (right != null) |
|||
{ |
|||
right.Parent = target; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Counts the nodes recursively
|
|||
/// </summary>
|
|||
/// <param name="node">A node in the tree</param>
|
|||
/// <returns>A count of nodes</returns>
|
|||
private int RecursiveCount(AvlNode<T> node) |
|||
{ |
|||
if (node == null) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
return 1 + this.RecursiveCount(node.Left) + this.RecursiveCount(node.Right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Find child max height
|
|||
/// </summary>
|
|||
/// <param name="node">A node in the tree</param>
|
|||
/// <returns>The height</returns>
|
|||
private int RecursiveGetChildMaxHeight(AvlNode<T> node) |
|||
{ |
|||
if (node == null) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
int leftHeight = 0; |
|||
if (node.Left != null) |
|||
{ |
|||
leftHeight = this.RecursiveGetChildMaxHeight(node.Left); |
|||
} |
|||
|
|||
int rightHeight = 0; |
|||
if (node.Right != null) |
|||
{ |
|||
rightHeight = this.RecursiveGetChildMaxHeight(node.Right); |
|||
} |
|||
|
|||
return 1 + Math.Max(leftHeight, rightHeight); |
|||
} |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,65 @@ |
|||
using System; |
|||
|
|||
namespace MathNet.Numerics.Spatial.Internal.ConvexHull |
|||
{ |
|||
/// <summary>
|
|||
/// Provides a working surface for points for ConvextHull - do not use otherwise
|
|||
/// </summary>
|
|||
internal struct MutablePoint : IEquatable<MutablePoint> |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="MutablePoint"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="x">The x value</param>
|
|||
/// <param name="y">The y value</param>
|
|||
internal MutablePoint(double x, double y) |
|||
{ |
|||
this.X = x; |
|||
this.Y = y; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the X value
|
|||
/// </summary>
|
|||
internal double X { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the Y Value
|
|||
/// </summary>
|
|||
internal double Y { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether each pair of elements in two specified points is equal.
|
|||
/// </summary>
|
|||
/// <param name="p1">The first point to compare</param>
|
|||
/// <param name="p2">The second point to compare</param>
|
|||
/// <returns>True if the points are the same; otherwise false.</returns>
|
|||
public static bool operator ==(MutablePoint p1, MutablePoint p2) => p1.Equals(p2); |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether any pair of elements in two specified points is not equal.
|
|||
/// </summary>
|
|||
/// <param name="p1">The first point to compare</param>
|
|||
/// <param name="p2">The second point to compare</param>
|
|||
/// <returns>True if the points are different; otherwise false.</returns>
|
|||
public static bool operator !=(MutablePoint p1, MutablePoint p2) => !p1.Equals(p2); |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool Equals(MutablePoint other) |
|||
{ |
|||
return this.X == other.X && this.Y == other.Y; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object obj) |
|||
{ |
|||
return base.Equals(obj); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int GetHashCode() |
|||
{ |
|||
return base.GetHashCode(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace MathNet.Numerics.Spatial.Internal.ConvexHull |
|||
{ |
|||
/// <summary>
|
|||
/// A comparer for convex hull's use of an Avl tree
|
|||
/// </summary>
|
|||
internal class QComparer : IComparer<MutablePoint> |
|||
{ |
|||
/// <summary>
|
|||
/// A function to compare with
|
|||
/// </summary>
|
|||
private readonly Func<MutablePoint, MutablePoint, int> comparer; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="QComparer"/> class.
|
|||
/// </summary>
|
|||
/// <param name="comparer">a function to use for comparing</param>
|
|||
public QComparer(Func<MutablePoint, MutablePoint, int> comparer) |
|||
{ |
|||
this.comparer = comparer; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compares two points using the provided function
|
|||
/// </summary>
|
|||
/// <param name="pt1">the first point</param>
|
|||
/// <param name="pt2">the second point</param>
|
|||
/// <returns>A value of -1 if less than, a value of 1 is greater than; otherwise a value of 0</returns>
|
|||
public int Compare(MutablePoint pt1, MutablePoint pt2) |
|||
{ |
|||
return this.comparer(pt1, pt2); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,188 @@ |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics.CodeAnalysis; |
|||
using System.Linq; |
|||
using MathNet.Numerics.Spatial.Internal.AvlTreeSet; |
|||
|
|||
namespace MathNet.Numerics.Spatial.Internal.ConvexHull |
|||
{ |
|||
/// <summary>
|
|||
/// An avl node for convex hull representing a quadrant
|
|||
/// </summary>
|
|||
[SuppressMessage("Microsoft.StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "By design")] |
|||
[SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1306:FieldNamesMustBeginWithLowerCaseLetter", Justification = "Reviewed.")] |
|||
internal abstract class Quadrant : AvlTreeSet<MutablePoint> |
|||
{ |
|||
/// <summary>
|
|||
/// The first point
|
|||
/// </summary>
|
|||
public MutablePoint FirstPoint; |
|||
|
|||
/// <summary>
|
|||
/// The last point
|
|||
/// </summary>
|
|||
public MutablePoint LastPoint; |
|||
|
|||
/// <summary>
|
|||
/// The root point
|
|||
/// </summary>
|
|||
public MutablePoint RootPoint; |
|||
|
|||
/// <summary>
|
|||
/// The current node
|
|||
/// </summary>
|
|||
protected AvlNode<MutablePoint> CurrentNode = null; |
|||
|
|||
/// <summary>
|
|||
/// A list of points
|
|||
/// </summary>
|
|||
protected MutablePoint[] ListOfPoint; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Quadrant"/> class.
|
|||
/// </summary>
|
|||
/// <param name="listOfPoint">a list of points</param>
|
|||
/// <param name="comparer">Comparer is only used to add the second point (the last point, which is compared against the first one).</param>
|
|||
internal Quadrant(MutablePoint[] listOfPoint, IComparer<MutablePoint> comparer) |
|||
: base(comparer) |
|||
{ |
|||
this.ListOfPoint = listOfPoint; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// An enum for the side
|
|||
/// </summary>
|
|||
internal enum Side |
|||
{ |
|||
/// <summary>
|
|||
/// Unknown side
|
|||
/// </summary>
|
|||
Unknown = 0, |
|||
|
|||
/// <summary>
|
|||
/// left side
|
|||
/// </summary>
|
|||
Left = 1, |
|||
|
|||
/// <summary>
|
|||
/// right side
|
|||
/// </summary>
|
|||
Right = 2 |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Prepares the node
|
|||
/// </summary>
|
|||
public void Prepare() |
|||
{ |
|||
if (!this.ListOfPoint.Any()) |
|||
{ |
|||
// There is no points at all. Hey don't try to crash me.
|
|||
return; |
|||
} |
|||
|
|||
// Begin : General Init
|
|||
this.Add(this.FirstPoint); |
|||
if (this.FirstPoint.Equals(this.LastPoint)) |
|||
{ |
|||
return; // Case where for weird distribution like triangle or diagonal. This quadrant will have no point
|
|||
} |
|||
|
|||
this.Add(this.LastPoint); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Tell if should try to add and where. -1 ==> Should not add.
|
|||
/// </summary>
|
|||
/// <param name="point">A point</param>
|
|||
internal abstract void ProcessPoint(ref MutablePoint point); |
|||
|
|||
/// <summary>
|
|||
/// Initialize every values needed to extract values that are parts of the convex hull.
|
|||
/// This is where the first pass of all values is done the get maximum in every directions (x and y).
|
|||
/// </summary>
|
|||
protected abstract void SetQuadrantLimits(); |
|||
|
|||
/// <summary>
|
|||
/// To know if to the right. It is meaningful when p1 is first and p2 is next.
|
|||
/// </summary>
|
|||
/// <param name="p1">The first point</param>
|
|||
/// <param name="p2">The second point</param>
|
|||
/// <param name="pointtToCheck">The point to check</param>
|
|||
/// <returns>Equivalent of tracing a line from p1 to p2 and tell if ptToCheck
|
|||
/// is to the right or left of that line taking p1 as reference point.</returns>
|
|||
//// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|||
protected bool IsPointToTheRightOfOthers(MutablePoint p1, MutablePoint p2, MutablePoint pointtToCheck) |
|||
{ |
|||
return ((p2.X - p1.X) * (pointtToCheck.Y - p1.Y)) - ((p2.Y - p1.Y) * (pointtToCheck.X - p1.X)) < 0; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Checks if point should be in quadrant
|
|||
/// </summary>
|
|||
/// <param name="pt">a point</param>
|
|||
/// <returns>True if it is a good quadrant for the point; otherwise false</returns>
|
|||
//// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|||
protected abstract bool IsGoodQuadrantForPoint(MutablePoint pt); |
|||
|
|||
/// <summary>
|
|||
/// Called after insertion in order to see if the newly added point invalidate one
|
|||
/// or more neighbors and if so, remove it/them from the tree.
|
|||
/// </summary>
|
|||
/// <param name="pointPrevious">Previous point</param>
|
|||
/// <param name="pointNew">New point</param>
|
|||
/// <param name="pointNext">Next point</param>
|
|||
//// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|||
[SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1002:SemicolonsMustBeSpacedCorrectly", Justification = "Reviewed.")] |
|||
protected void InvalidateNeighbors(AvlNode<MutablePoint> pointPrevious, AvlNode<MutablePoint> pointNew, AvlNode<MutablePoint> pointNext) |
|||
{ |
|||
bool invalidPoint; |
|||
|
|||
if (pointPrevious != null) |
|||
{ |
|||
AvlNode<MutablePoint> previousPrevious = pointPrevious.GetPreviousNode(); |
|||
for (; ;) |
|||
{ |
|||
if (previousPrevious == null) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
invalidPoint = !this.IsPointToTheRightOfOthers(previousPrevious.Item, pointNew.Item, pointPrevious.Item); |
|||
if (!invalidPoint) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
MutablePoint pointPrevPrev = previousPrevious.Item; |
|||
this.RemoveNode(pointPrevious); |
|||
pointPrevious = this.GetNode(pointPrevPrev); |
|||
previousPrevious = pointPrevious.GetPreviousNode(); |
|||
} |
|||
} |
|||
|
|||
// Invalidate next(s)
|
|||
if (pointNext != null) |
|||
{ |
|||
AvlNode<MutablePoint> nextNext = pointNext.GetNextNode(); |
|||
for (; ;) |
|||
{ |
|||
if (nextNext == null) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
invalidPoint = !this.IsPointToTheRightOfOthers(pointNew.Item, nextNext.Item, pointNext.Item); |
|||
if (!invalidPoint) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
MutablePoint pointNextNext = nextNext.Item; |
|||
this.RemoveNode(pointNext); |
|||
pointNext = this.GetNode(pointNextNext); |
|||
nextNext = pointNext.GetNextNode(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,240 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using MathNet.Numerics.Spatial.Internal.AvlTreeSet; |
|||
|
|||
namespace MathNet.Numerics.Spatial.Internal.ConvexHull |
|||
{ |
|||
/// <summary>
|
|||
/// Class to process quadrant 1
|
|||
/// </summary>
|
|||
internal class QuadrantSpecific1 : Quadrant |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="QuadrantSpecific1"/> class.
|
|||
/// </summary>
|
|||
/// <param name="listOfPoint">a list of points</param>
|
|||
/// <param name="comparer">a comparer</param>
|
|||
internal QuadrantSpecific1(MutablePoint[] listOfPoint, Func<MutablePoint, MutablePoint, int> comparer) |
|||
: base(listOfPoint, new QComparer(comparer)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Check if we can quickly reject a point
|
|||
/// </summary>
|
|||
/// <param name="point">a point</param>
|
|||
/// <param name="pointHull">a point on the hull</param>
|
|||
/// <returns>True if can quickly reject; otherwise false</returns>
|
|||
//// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|||
internal static bool CanQuickReject(ref MutablePoint point, ref MutablePoint pointHull) |
|||
{ |
|||
return point.X <= pointHull.X && point.Y <= pointHull.Y; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Iterate over each points to see if we can add it has a ConvexHull point.
|
|||
/// It is specific by Quadrant to improve efficiency.
|
|||
/// </summary>
|
|||
/// <param name="point">a point</param>
|
|||
//// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|||
internal override void ProcessPoint(ref MutablePoint point) |
|||
{ |
|||
this.CurrentNode = this.Root; |
|||
AvlNode<MutablePoint> currentPrevious = null; |
|||
AvlNode<MutablePoint> currentNext = null; |
|||
|
|||
while (this.CurrentNode != null) |
|||
{ |
|||
var insertionSide = Side.Unknown; |
|||
if (point.X > this.CurrentNode.Item.X) |
|||
{ |
|||
if (this.CurrentNode.Left != null) |
|||
{ |
|||
this.CurrentNode = this.CurrentNode.Left; |
|||
continue; |
|||
} |
|||
|
|||
currentPrevious = this.CurrentNode.GetPreviousNode(); |
|||
if (CanQuickReject(ref point, ref currentPrevious.Item)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (!this.IsPointToTheRightOfOthers(currentPrevious.Item, this.CurrentNode.Item, point)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
// Ensure to have no duplicate
|
|||
if (this.CurrentNode.Item == point) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
insertionSide = Side.Left; |
|||
} |
|||
else if (point.X < this.CurrentNode.Item.X) |
|||
{ |
|||
if (this.CurrentNode.Right != null) |
|||
{ |
|||
this.CurrentNode = this.CurrentNode.Right; |
|||
continue; |
|||
} |
|||
|
|||
currentNext = this.CurrentNode.GetNextNode(); |
|||
if (CanQuickReject(ref point, ref currentNext.Item)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (!this.IsPointToTheRightOfOthers(this.CurrentNode.Item, currentNext.Item, point)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
// Ensure to have no duplicate
|
|||
if (this.CurrentNode.Item == point) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
insertionSide = Side.Right; |
|||
} |
|||
else |
|||
{ |
|||
if (point.Y <= this.CurrentNode.Item.Y) |
|||
{ |
|||
return; // invalid point
|
|||
} |
|||
|
|||
// Replace CurrentNode point with point
|
|||
this.CurrentNode.Item = point; |
|||
|
|||
this.InvalidateNeighbors(this.CurrentNode.GetPreviousNode(), this.CurrentNode, this.CurrentNode.GetNextNode()); |
|||
return; |
|||
} |
|||
|
|||
// We should insert the point
|
|||
// Try to optimize and verify if can replace a node instead insertion to minimize tree balancing
|
|||
if (insertionSide == Side.Right) |
|||
{ |
|||
currentPrevious = this.CurrentNode.GetPreviousNode(); |
|||
if (currentPrevious != null && !this.IsPointToTheRightOfOthers(currentPrevious.Item, point, this.CurrentNode.Item)) |
|||
{ |
|||
this.CurrentNode.Item = point; |
|||
this.InvalidateNeighbors(currentPrevious, this.CurrentNode, currentNext); |
|||
return; |
|||
} |
|||
|
|||
var nextNext = currentNext.GetNextNode(); |
|||
if (nextNext != null && !this.IsPointToTheRightOfOthers(point, nextNext.Item, currentNext.Item)) |
|||
{ |
|||
currentNext.Item = point; |
|||
this.InvalidateNeighbors(null, currentNext, nextNext); |
|||
return; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
// Left
|
|||
currentNext = this.CurrentNode.GetNextNode(); |
|||
if (currentNext != null && !this.IsPointToTheRightOfOthers(point, currentNext.Item, this.CurrentNode.Item)) |
|||
{ |
|||
this.CurrentNode.Item = point; |
|||
this.InvalidateNeighbors(currentPrevious, this.CurrentNode, currentNext); |
|||
return; |
|||
} |
|||
|
|||
var previousPrevious = currentPrevious.GetPreviousNode(); |
|||
if (previousPrevious != null && !this.IsPointToTheRightOfOthers(previousPrevious.Item, point, currentPrevious.Item)) |
|||
{ |
|||
currentPrevious.Item = point; |
|||
this.InvalidateNeighbors(previousPrevious, currentPrevious, null); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
// Should insert but no invalidation is required. (That's why we need to insert... can't replace an adjacent neighbor)
|
|||
AvlNode<MutablePoint> newNode = new AvlNode<MutablePoint>(); |
|||
if (insertionSide == Side.Right) |
|||
{ |
|||
newNode.Parent = this.CurrentNode; |
|||
newNode.Item = point; |
|||
this.CurrentNode.Right = newNode; |
|||
this.AddBalance(newNode.Parent, -1); |
|||
} |
|||
else |
|||
{ |
|||
// Left
|
|||
newNode.Parent = this.CurrentNode; |
|||
newNode.Item = point; |
|||
this.CurrentNode.Left = newNode; |
|||
this.AddBalance(newNode.Parent, 1); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override void SetQuadrantLimits() |
|||
{ |
|||
MutablePoint firstPoint = this.ListOfPoint.First(); |
|||
|
|||
double rightX = firstPoint.X; |
|||
double rightY = firstPoint.Y; |
|||
|
|||
double topX = rightX; |
|||
double topY = rightY; |
|||
|
|||
foreach (var point in this.ListOfPoint) |
|||
{ |
|||
if (point.X >= rightX) |
|||
{ |
|||
if (point.X == rightX) |
|||
{ |
|||
if (point.Y > rightY) |
|||
{ |
|||
rightY = point.Y; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
rightX = point.X; |
|||
rightY = point.Y; |
|||
} |
|||
} |
|||
|
|||
if (point.Y >= topY) |
|||
{ |
|||
if (point.Y == topY) |
|||
{ |
|||
if (point.X > topX) |
|||
{ |
|||
topX = point.X; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
topX = point.X; |
|||
topY = point.Y; |
|||
} |
|||
} |
|||
} |
|||
|
|||
this.FirstPoint = new MutablePoint(rightX, rightY); |
|||
this.LastPoint = new MutablePoint(topX, topY); |
|||
this.RootPoint = new MutablePoint(topX, rightY); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
//// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|||
protected override bool IsGoodQuadrantForPoint(MutablePoint pt) |
|||
{ |
|||
if (pt.X > this.RootPoint.X && pt.Y > this.RootPoint.Y) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,241 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using MathNet.Numerics.Spatial.Internal.AvlTreeSet; |
|||
|
|||
namespace MathNet.Numerics.Spatial.Internal.ConvexHull |
|||
{ |
|||
/// <summary>
|
|||
/// Class to process quadrant 2
|
|||
/// </summary>
|
|||
internal class QuadrantSpecific2 : Quadrant |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="QuadrantSpecific2"/> class.
|
|||
/// </summary>
|
|||
/// <param name="listOfPoint">a list of points</param>
|
|||
/// <param name="comparer">a comparer</param>
|
|||
internal QuadrantSpecific2(MutablePoint[] listOfPoint, Func<MutablePoint, MutablePoint, int> comparer) |
|||
: base(listOfPoint, new QComparer(comparer)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Check if we can quickly reject a point
|
|||
/// </summary>
|
|||
/// <param name="point">a point</param>
|
|||
/// <param name="pointHull">a point on the hull</param>
|
|||
/// <returns>True if can quickly reject; otherwise false</returns>
|
|||
//// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|||
internal static bool CanQuickReject(ref MutablePoint point, ref MutablePoint pointHull) |
|||
{ |
|||
return point.X >= pointHull.X && point.Y <= pointHull.Y; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Iterate over each points to see if we can add it has a ConvexHull point.
|
|||
/// It is specific by Quadrant to improve efficiency.
|
|||
/// </summary>
|
|||
/// <param name="point">a point</param>
|
|||
//// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|||
internal override void ProcessPoint(ref MutablePoint point) |
|||
{ |
|||
this.CurrentNode = this.Root; |
|||
AvlNode<MutablePoint> currentPrevious = null; |
|||
AvlNode<MutablePoint> currentNext = null; |
|||
|
|||
while (this.CurrentNode != null) |
|||
{ |
|||
var insertionSide = Side.Unknown; |
|||
if (point.X > this.CurrentNode.Item.X) |
|||
{ |
|||
if (this.CurrentNode.Left != null) |
|||
{ |
|||
this.CurrentNode = this.CurrentNode.Left; |
|||
continue; |
|||
} |
|||
|
|||
currentPrevious = this.CurrentNode.GetPreviousNode(); |
|||
if (CanQuickReject(ref point, ref currentPrevious.Item)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (!this.IsPointToTheRightOfOthers(currentPrevious.Item, this.CurrentNode.Item, point)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
// Ensure to have no duplicate
|
|||
if (this.CurrentNode.Item == point) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
insertionSide = Side.Left; |
|||
} |
|||
else if (point.X < this.CurrentNode.Item.X) |
|||
{ |
|||
if (this.CurrentNode.Right != null) |
|||
{ |
|||
this.CurrentNode = this.CurrentNode.Right; |
|||
continue; |
|||
} |
|||
|
|||
currentNext = this.CurrentNode.GetNextNode(); |
|||
if (CanQuickReject(ref point, ref currentNext.Item)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (!this.IsPointToTheRightOfOthers(this.CurrentNode.Item, currentNext.Item, point)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
// Ensure to have no duplicate
|
|||
if (this.CurrentNode.Item == point) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
insertionSide = Side.Right; |
|||
} |
|||
else |
|||
{ |
|||
if (point.Y <= this.CurrentNode.Item.Y) |
|||
{ |
|||
return; // invalid point
|
|||
} |
|||
|
|||
// Replace CurrentNode point with point
|
|||
this.CurrentNode.Item = point; |
|||
this.InvalidateNeighbors(this.CurrentNode.GetPreviousNode(), this.CurrentNode, this.CurrentNode.GetNextNode()); |
|||
return; |
|||
} |
|||
|
|||
// We should insert the point
|
|||
// Try to optimize and verify if can replace a node instead insertion to minimize tree balancing
|
|||
if (insertionSide == Side.Right) |
|||
{ |
|||
currentPrevious = this.CurrentNode.GetPreviousNode(); |
|||
if (currentPrevious != null && !this.IsPointToTheRightOfOthers(currentPrevious.Item, point, this.CurrentNode.Item)) |
|||
{ |
|||
this.CurrentNode.Item = point; |
|||
this.InvalidateNeighbors(currentPrevious, this.CurrentNode, currentNext); |
|||
return; |
|||
} |
|||
|
|||
var nextNext = currentNext.GetNextNode(); |
|||
if (nextNext != null && !this.IsPointToTheRightOfOthers(point, nextNext.Item, currentNext.Item)) |
|||
{ |
|||
currentNext.Item = point; |
|||
this.InvalidateNeighbors(null, currentNext, nextNext); |
|||
return; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
// Left
|
|||
currentNext = this.CurrentNode.GetNextNode(); |
|||
if (currentNext != null && !this.IsPointToTheRightOfOthers(point, currentNext.Item, this.CurrentNode.Item)) |
|||
{ |
|||
this.CurrentNode.Item = point; |
|||
this.InvalidateNeighbors(currentPrevious, this.CurrentNode, currentNext); |
|||
return; |
|||
} |
|||
|
|||
var previousPrevious = currentPrevious.GetPreviousNode(); |
|||
if (previousPrevious != null && !this.IsPointToTheRightOfOthers(previousPrevious.Item, point, currentPrevious.Item)) |
|||
{ |
|||
currentPrevious.Item = point; |
|||
this.InvalidateNeighbors(previousPrevious, currentPrevious, null); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
// Should insert but no invalidation is required. (That's why we need to insert... can't replace an adjacent neighbor)
|
|||
AvlNode<MutablePoint> newNode = new AvlNode<MutablePoint>(); |
|||
if (insertionSide == Side.Right) |
|||
{ |
|||
newNode.Parent = this.CurrentNode; |
|||
newNode.Item = point; |
|||
this.CurrentNode.Right = newNode; |
|||
this.AddBalance(newNode.Parent, -1); |
|||
} |
|||
else |
|||
{ |
|||
// Left
|
|||
newNode.Parent = this.CurrentNode; |
|||
newNode.Item = point; |
|||
this.CurrentNode.Left = newNode; |
|||
this.AddBalance(newNode.Parent, 1); |
|||
} |
|||
|
|||
return; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override void SetQuadrantLimits() |
|||
{ |
|||
MutablePoint firstPoint = this.ListOfPoint.First(); |
|||
|
|||
double leftX = firstPoint.X; |
|||
double leftY = firstPoint.Y; |
|||
|
|||
double topX = leftX; |
|||
double topY = leftY; |
|||
|
|||
foreach (var point in this.ListOfPoint) |
|||
{ |
|||
if (point.X <= leftX) |
|||
{ |
|||
if (point.X == leftX) |
|||
{ |
|||
if (point.Y > leftY) |
|||
{ |
|||
leftY = point.Y; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
leftX = point.X; |
|||
leftY = point.Y; |
|||
} |
|||
} |
|||
|
|||
if (point.Y >= topY) |
|||
{ |
|||
if (point.Y == topY) |
|||
{ |
|||
if (point.X < topX) |
|||
{ |
|||
topX = point.X; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
topX = point.X; |
|||
topY = point.Y; |
|||
} |
|||
} |
|||
} |
|||
|
|||
this.FirstPoint = new MutablePoint(topX, topY); |
|||
this.LastPoint = new MutablePoint(leftX, leftY); |
|||
this.RootPoint = new MutablePoint(topX, leftY); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
//// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|||
protected override bool IsGoodQuadrantForPoint(MutablePoint pt) |
|||
{ |
|||
if (pt.X < this.RootPoint.X && pt.Y > this.RootPoint.Y) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,243 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using MathNet.Numerics.Spatial.Internal.AvlTreeSet; |
|||
|
|||
namespace MathNet.Numerics.Spatial.Internal.ConvexHull |
|||
{ |
|||
/// <summary>
|
|||
/// Class to process quadrant 3
|
|||
/// </summary>
|
|||
internal class QuadrantSpecific3 : Quadrant |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="QuadrantSpecific3"/> class.
|
|||
/// </summary>
|
|||
/// <param name="listOfPoint">a list of points</param>
|
|||
/// <param name="comparer">a comparer</param>
|
|||
internal QuadrantSpecific3(MutablePoint[] listOfPoint, Func<MutablePoint, MutablePoint, int> comparer) |
|||
: base(listOfPoint, new QComparer(comparer)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Check if we can quickly reject a point
|
|||
/// </summary>
|
|||
/// <param name="point">a point</param>
|
|||
/// <param name="pointHull">a point on the hull</param>
|
|||
/// <returns>True if can quickly reject; otherwise false</returns>
|
|||
//// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|||
internal static bool CanQuickReject(ref MutablePoint point, ref MutablePoint pointHull) |
|||
{ |
|||
return point.X >= pointHull.X && point.Y >= pointHull.Y; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Iterate over each points to see if we can add it has a ConvexHull point.
|
|||
/// It is specific by Quadrant to improve efficiency.
|
|||
/// </summary>
|
|||
/// <param name="point">a point</param>
|
|||
//// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|||
internal override void ProcessPoint(ref MutablePoint point) |
|||
{ |
|||
this.CurrentNode = this.Root; |
|||
AvlNode<MutablePoint> currentPrevious = null; |
|||
AvlNode<MutablePoint> currentNext = null; |
|||
|
|||
while (this.CurrentNode != null) |
|||
{ |
|||
var insertionSide = Side.Unknown; |
|||
if (point.X > this.CurrentNode.Item.X) |
|||
{ |
|||
if (this.CurrentNode.Right != null) |
|||
{ |
|||
this.CurrentNode = this.CurrentNode.Right; |
|||
continue; |
|||
} |
|||
|
|||
currentNext = this.CurrentNode.GetNextNode(); |
|||
if (CanQuickReject(ref point, ref currentNext.Item)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (!this.IsPointToTheRightOfOthers(this.CurrentNode.Item, currentNext.Item, point)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
// Ensure to have no duplicate
|
|||
if (this.CurrentNode.Item == point) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
insertionSide = Side.Right; |
|||
} |
|||
else if (point.X < this.CurrentNode.Item.X) |
|||
{ |
|||
if (this.CurrentNode.Left != null) |
|||
{ |
|||
this.CurrentNode = this.CurrentNode.Left; |
|||
continue; |
|||
} |
|||
|
|||
currentPrevious = this.CurrentNode.GetPreviousNode(); |
|||
if (CanQuickReject(ref point, ref currentPrevious.Item)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (!this.IsPointToTheRightOfOthers(currentPrevious.Item, this.CurrentNode.Item, point)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
// Ensure to have no duplicate
|
|||
if (this.CurrentNode.Item == point) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
insertionSide = Side.Left; |
|||
} |
|||
else |
|||
{ |
|||
if (point.Y >= this.CurrentNode.Item.Y) |
|||
{ |
|||
return; // invalid point
|
|||
} |
|||
|
|||
// Replace CurrentNode point with point
|
|||
this.CurrentNode.Item = point; |
|||
this.InvalidateNeighbors(this.CurrentNode.GetPreviousNode(), this.CurrentNode, this.CurrentNode.GetNextNode()); |
|||
return; |
|||
} |
|||
|
|||
// We should insert the point
|
|||
// Try to optimize and verify if can replace a node instead insertion to minimize tree balancing
|
|||
if (insertionSide == Side.Right) |
|||
{ |
|||
currentPrevious = this.CurrentNode.GetPreviousNode(); |
|||
if (currentPrevious != null && !this.IsPointToTheRightOfOthers(currentPrevious.Item, point, this.CurrentNode.Item)) |
|||
{ |
|||
this.CurrentNode.Item = point; |
|||
this.InvalidateNeighbors(currentPrevious, this.CurrentNode, currentNext); |
|||
return; |
|||
} |
|||
|
|||
var nextNext = currentNext.GetNextNode(); |
|||
if (nextNext != null && !this.IsPointToTheRightOfOthers(point, nextNext.Item, currentNext.Item)) |
|||
{ |
|||
currentNext.Item = point; |
|||
this.InvalidateNeighbors(null, currentNext, nextNext); |
|||
return; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
// Left
|
|||
currentNext = this.CurrentNode.GetNextNode(); |
|||
if (currentNext != null && !this.IsPointToTheRightOfOthers(point, currentNext.Item, this.CurrentNode.Item)) |
|||
{ |
|||
this.CurrentNode.Item = point; |
|||
this.InvalidateNeighbors(currentPrevious, this.CurrentNode, currentNext); |
|||
return; |
|||
} |
|||
|
|||
var previousPrevious = currentPrevious.GetPreviousNode(); |
|||
if (previousPrevious != null && !this.IsPointToTheRightOfOthers(previousPrevious.Item, point, currentPrevious.Item)) |
|||
{ |
|||
currentPrevious.Item = point; |
|||
this.InvalidateNeighbors(previousPrevious, currentPrevious, null); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
// Should insert but no invalidation is required. (That's why we need to insert... can't replace an adjacent neighbor)
|
|||
AvlNode<MutablePoint> newNode = new AvlNode<MutablePoint>(); |
|||
if (insertionSide == Side.Right) |
|||
{ |
|||
newNode.Parent = this.CurrentNode; |
|||
newNode.Item = point; |
|||
this.CurrentNode.Right = newNode; |
|||
this.AddBalance(newNode.Parent, -1); |
|||
} |
|||
else |
|||
{ |
|||
// Left
|
|||
newNode.Parent = this.CurrentNode; |
|||
newNode.Item = point; |
|||
this.CurrentNode.Left = newNode; |
|||
this.AddBalance(newNode.Parent, 1); |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override void SetQuadrantLimits() |
|||
{ |
|||
MutablePoint firstPoint = this.ListOfPoint.First(); |
|||
|
|||
double leftX = firstPoint.X; |
|||
double leftY = firstPoint.Y; |
|||
|
|||
double bottomX = leftX; |
|||
double bottomY = leftY; |
|||
|
|||
foreach (var point in this.ListOfPoint) |
|||
{ |
|||
if (point.X <= leftX) |
|||
{ |
|||
if (point.X == leftX) |
|||
{ |
|||
if (point.Y < leftY) |
|||
{ |
|||
leftY = point.Y; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
leftX = point.X; |
|||
leftY = point.Y; |
|||
} |
|||
} |
|||
|
|||
if (point.Y <= bottomY) |
|||
{ |
|||
if (point.Y == bottomY) |
|||
{ |
|||
if (point.X < bottomX) |
|||
{ |
|||
bottomX = point.X; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
bottomX = point.X; |
|||
bottomY = point.Y; |
|||
} |
|||
} |
|||
} |
|||
|
|||
this.FirstPoint = new MutablePoint(leftX, leftY); |
|||
this.LastPoint = new MutablePoint(bottomX, bottomY); |
|||
this.RootPoint = new MutablePoint(bottomX, leftY); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
//// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|||
protected override bool IsGoodQuadrantForPoint(MutablePoint pt) |
|||
{ |
|||
if (pt.X < this.RootPoint.X && pt.Y < this.RootPoint.Y) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,243 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using MathNet.Numerics.Spatial.Internal.AvlTreeSet; |
|||
|
|||
namespace MathNet.Numerics.Spatial.Internal.ConvexHull |
|||
{ |
|||
/// <summary>
|
|||
/// Class to process quadrant 4
|
|||
/// </summary>
|
|||
internal class QuadrantSpecific4 : Quadrant |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="QuadrantSpecific4"/> class.
|
|||
/// </summary>
|
|||
/// <param name="listOfPoint">a list of points</param>
|
|||
/// <param name="comparer">a comparer</param>
|
|||
internal QuadrantSpecific4(MutablePoint[] listOfPoint, Func<MutablePoint, MutablePoint, int> comparer) |
|||
: base(listOfPoint, new QComparer(comparer)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Check if we can quickly reject a point
|
|||
/// </summary>
|
|||
/// <param name="point">a point</param>
|
|||
/// <param name="pointHull">a point on the hull</param>
|
|||
/// <returns>True if can quickly reject; otherwise false</returns>
|
|||
//// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|||
internal static bool CanQuickReject(ref MutablePoint point, ref MutablePoint pointHull) |
|||
{ |
|||
return point.X <= pointHull.X && point.Y >= pointHull.Y; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Iterate over each points to see if we can add it has a ConvexHull point.
|
|||
/// It is specific by Quadrant to improve efficiency.
|
|||
/// </summary>
|
|||
/// <param name="point">a point</param>
|
|||
//// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|||
internal override void ProcessPoint(ref MutablePoint point) |
|||
{ |
|||
this.CurrentNode = this.Root; |
|||
AvlNode<MutablePoint> currentPrevious = null; |
|||
AvlNode<MutablePoint> currentNext = null; |
|||
|
|||
while (this.CurrentNode != null) |
|||
{ |
|||
var insertionSide = Side.Unknown; |
|||
if (point.X > this.CurrentNode.Item.X) |
|||
{ |
|||
if (this.CurrentNode.Right != null) |
|||
{ |
|||
this.CurrentNode = this.CurrentNode.Right; |
|||
continue; |
|||
} |
|||
|
|||
currentNext = this.CurrentNode.GetNextNode(); |
|||
if (CanQuickReject(ref point, ref currentNext.Item)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (!this.IsPointToTheRightOfOthers(this.CurrentNode.Item, currentNext.Item, point)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
// Ensure to have no duplicate
|
|||
if (this.CurrentNode.Item == point) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
insertionSide = Side.Right; |
|||
} |
|||
else if (point.X < this.CurrentNode.Item.X) |
|||
{ |
|||
if (this.CurrentNode.Left != null) |
|||
{ |
|||
this.CurrentNode = this.CurrentNode.Left; |
|||
continue; |
|||
} |
|||
|
|||
currentPrevious = this.CurrentNode.GetPreviousNode(); |
|||
if (CanQuickReject(ref point, ref currentPrevious.Item)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (!this.IsPointToTheRightOfOthers(currentPrevious.Item, this.CurrentNode.Item, point)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
// Ensure to have no duplicate
|
|||
if (this.CurrentNode.Item == point) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
insertionSide = Side.Left; |
|||
} |
|||
else |
|||
{ |
|||
if (point.Y >= this.CurrentNode.Item.Y) |
|||
{ |
|||
return; // invalid point
|
|||
} |
|||
|
|||
// Replace CurrentNode point with point
|
|||
this.CurrentNode.Item = point; |
|||
this.InvalidateNeighbors(this.CurrentNode.GetPreviousNode(), this.CurrentNode, this.CurrentNode.GetNextNode()); |
|||
return; |
|||
} |
|||
|
|||
// We should insert the point
|
|||
// Try to optimize and verify if can replace a node instead insertion to minimize tree balancing
|
|||
if (insertionSide == Side.Right) |
|||
{ |
|||
currentPrevious = this.CurrentNode.GetPreviousNode(); |
|||
if (currentPrevious != null && !this.IsPointToTheRightOfOthers(currentPrevious.Item, point, this.CurrentNode.Item)) |
|||
{ |
|||
this.CurrentNode.Item = point; |
|||
this.InvalidateNeighbors(currentPrevious, this.CurrentNode, currentNext); |
|||
return; |
|||
} |
|||
|
|||
var nextNext = currentNext.GetNextNode(); |
|||
if (nextNext != null && !this.IsPointToTheRightOfOthers(point, nextNext.Item, currentNext.Item)) |
|||
{ |
|||
currentNext.Item = point; |
|||
this.InvalidateNeighbors(null, currentNext, nextNext); |
|||
return; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
// Left
|
|||
currentNext = this.CurrentNode.GetNextNode(); |
|||
if (currentNext != null && !this.IsPointToTheRightOfOthers(point, currentNext.Item, this.CurrentNode.Item)) |
|||
{ |
|||
this.CurrentNode.Item = point; |
|||
this.InvalidateNeighbors(currentPrevious, this.CurrentNode, currentNext); |
|||
return; |
|||
} |
|||
|
|||
var previousPrevious = currentPrevious.GetPreviousNode(); |
|||
if (previousPrevious != null && !this.IsPointToTheRightOfOthers(previousPrevious.Item, point, currentPrevious.Item)) |
|||
{ |
|||
currentPrevious.Item = point; |
|||
this.InvalidateNeighbors(previousPrevious, currentPrevious, null); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
// Should insert but no invalidation is required. (That's why we need to insert... can't replace an adjacent neighbor)
|
|||
AvlNode<MutablePoint> newNode = new AvlNode<MutablePoint>(); |
|||
if (insertionSide == Side.Right) |
|||
{ |
|||
newNode.Parent = this.CurrentNode; |
|||
newNode.Item = point; |
|||
this.CurrentNode.Right = newNode; |
|||
this.AddBalance(newNode.Parent, -1); |
|||
} |
|||
else |
|||
{ |
|||
// Left
|
|||
newNode.Parent = this.CurrentNode; |
|||
newNode.Item = point; |
|||
this.CurrentNode.Left = newNode; |
|||
this.AddBalance(newNode.Parent, 1); |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override void SetQuadrantLimits() |
|||
{ |
|||
MutablePoint firstPoint = this.ListOfPoint.First(); |
|||
|
|||
double rightX = firstPoint.X; |
|||
double rightY = firstPoint.Y; |
|||
|
|||
double bottomX = rightX; |
|||
double bottomY = rightY; |
|||
|
|||
foreach (var point in this.ListOfPoint) |
|||
{ |
|||
if (point.X >= rightX) |
|||
{ |
|||
if (point.X == rightX) |
|||
{ |
|||
if (point.Y < rightY) |
|||
{ |
|||
rightY = point.Y; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
rightX = point.X; |
|||
rightY = point.Y; |
|||
} |
|||
} |
|||
|
|||
if (point.Y <= bottomY) |
|||
{ |
|||
if (point.Y == bottomY) |
|||
{ |
|||
if (point.X > bottomX) |
|||
{ |
|||
bottomX = point.X; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
bottomX = point.X; |
|||
bottomY = point.Y; |
|||
} |
|||
} |
|||
} |
|||
|
|||
this.FirstPoint = new MutablePoint(bottomX, bottomY); |
|||
this.LastPoint = new MutablePoint(rightX, rightY); |
|||
this.RootPoint = new MutablePoint(bottomX, rightY); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
//// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|||
protected override bool IsGoodQuadrantForPoint(MutablePoint pt) |
|||
{ |
|||
if (pt.X > this.RootPoint.X && pt.Y < this.RootPoint.Y) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,380 @@ |
|||
using System.Collections.Generic; |
|||
using System.Runtime.CompilerServices; |
|||
using MathNet.Numerics.Random; |
|||
|
|||
namespace MathNet.Numerics.Spatial.Internal |
|||
{ |
|||
// The contents of this file is taken from https://github.com/dotnet/coreclr/blob/master/src/mscorlib/shared/System/HashCode.cs
|
|||
// To be replaced by the framework implementation when released for the appropriate builds
|
|||
#pragma warning disable SA1203, SA1101, SA1600, SA1512, SA1515, SA1503, SA1028, SA1308, SA1512, SA1202, SA1311, SA1028, SA1132, SA1309, SA1108, SA1520
|
|||
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
|||
// See the LICENSE file in the project root for more information.
|
|||
|
|||
/* |
|||
|
|||
The xxHash32 implementation is based on the code published by Yann Collet: |
|||
https://raw.githubusercontent.com/Cyan4973/xxHash/5c174cfa4e45a42f94082dc0d4539b39696afea1/xxhash.c
|
|||
|
|||
xxHash - Fast Hash algorithm |
|||
Copyright (C) 2012-2016, Yann Collet |
|||
|
|||
BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
|
|||
|
|||
Redistribution and use in source and binary forms, with or without |
|||
modification, are permitted provided that the following conditions are |
|||
met: |
|||
|
|||
* Redistributions of source code must retain the above copyright |
|||
notice, this list of conditions and the following disclaimer. |
|||
* Redistributions in binary form must reproduce the above |
|||
copyright notice, this list of conditions and the following disclaimer |
|||
in the documentation and/or other materials provided with the |
|||
distribution. |
|||
|
|||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|||
|
|||
You can contact the author at : |
|||
- xxHash homepage: http://www.xxhash.com
|
|||
- xxHash source repository : https://github.com/Cyan4973/xxHash
|
|||
|
|||
*/ |
|||
// xxHash32 is used for the hash code.
|
|||
// https://github.com/Cyan4973/xxHash
|
|||
|
|||
/// <summary>
|
|||
/// Generates a hashcode
|
|||
/// </summary>
|
|||
internal static class HashCode |
|||
{ |
|||
private static readonly uint Seed = GenerateGlobalSeed(); |
|||
|
|||
private const uint Prime1 = 2654435761U; |
|||
private const uint Prime2 = 2246822519U; |
|||
private const uint Prime3 = 3266489917U; |
|||
private const uint Prime4 = 668265263U; |
|||
private const uint Prime5 = 374761393U; |
|||
|
|||
private static uint GenerateGlobalSeed() |
|||
{ |
|||
// NOTE: Modified from original unsafe implementation.
|
|||
int seed = RandomSeed.Robust(); |
|||
return unchecked((uint)seed); |
|||
} |
|||
|
|||
public static int Combine<T1>(T1 value1) |
|||
{ |
|||
// Provide a way of diffusing bits from something with a limited
|
|||
// input hash space. For example, many enums only have a few
|
|||
// possible hashes, only using the bottom few bits of the code. Some
|
|||
// collections are built on the assumption that hashes are spread
|
|||
// over a larger space, so diffusing the bits may help the
|
|||
// collection work more efficiently.
|
|||
|
|||
var hc1 = (uint)(value1?.GetHashCode() ?? 0); |
|||
|
|||
uint hash = MixEmptyState(); |
|||
hash += 4; |
|||
|
|||
hash = QueueRound(hash, hc1); |
|||
|
|||
hash = MixFinal(hash); |
|||
return (int)hash; |
|||
} |
|||
|
|||
public static int CombineMany<T>(T[] items) |
|||
{ |
|||
if (items == null) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
unchecked |
|||
{ |
|||
int hashcode = 0; |
|||
for (var i = 0; i < items.Length; i++) |
|||
{ |
|||
// HashCode.Combine(single) is partially diffuse so should be ok for this.
|
|||
hashcode += Combine(items[i]); |
|||
} |
|||
|
|||
return hashcode; |
|||
} |
|||
} |
|||
|
|||
public static int CombineMany<T>(List<T> items) |
|||
{ |
|||
if (items == null) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
unchecked |
|||
{ |
|||
int hashcode = 0; |
|||
for (var i = 0; i < items.Count; i++) |
|||
{ |
|||
// HashCode.Combine(single) is partially diffuse so should be ok for this.
|
|||
hashcode += Combine(items[i]); |
|||
} |
|||
|
|||
return hashcode; |
|||
} |
|||
} |
|||
|
|||
public static int CombineMany<T>(IEnumerable<T> items) |
|||
{ |
|||
if (items == null) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
unchecked |
|||
{ |
|||
int hashcode = 0; |
|||
foreach (var item in items) |
|||
{ |
|||
// HashCode.Combine(single) is partially diffuse so should be ok for this.
|
|||
hashcode += Combine(item); |
|||
} |
|||
|
|||
return hashcode; |
|||
} |
|||
} |
|||
|
|||
public static int Combine<T1, T2>(T1 value1, T2 value2) |
|||
{ |
|||
var hc1 = (uint)(value1?.GetHashCode() ?? 0); |
|||
var hc2 = (uint)(value2?.GetHashCode() ?? 0); |
|||
|
|||
uint hash = MixEmptyState(); |
|||
hash += 8; |
|||
|
|||
hash = QueueRound(hash, hc1); |
|||
hash = QueueRound(hash, hc2); |
|||
|
|||
hash = MixFinal(hash); |
|||
return (int)hash; |
|||
} |
|||
|
|||
public static int Combine<T1, T2, T3>(T1 value1, T2 value2, T3 value3) |
|||
{ |
|||
var hc1 = (uint)(value1?.GetHashCode() ?? 0); |
|||
var hc2 = (uint)(value2?.GetHashCode() ?? 0); |
|||
var hc3 = (uint)(value3?.GetHashCode() ?? 0); |
|||
|
|||
uint hash = MixEmptyState(); |
|||
hash += 12; |
|||
|
|||
hash = QueueRound(hash, hc1); |
|||
hash = QueueRound(hash, hc2); |
|||
hash = QueueRound(hash, hc3); |
|||
|
|||
hash = MixFinal(hash); |
|||
return (int)hash; |
|||
} |
|||
|
|||
public static int Combine<T1, T2, T3, T4>(T1 value1, T2 value2, T3 value3, T4 value4) |
|||
{ |
|||
var hc1 = (uint)(value1?.GetHashCode() ?? 0); |
|||
var hc2 = (uint)(value2?.GetHashCode() ?? 0); |
|||
var hc3 = (uint)(value3?.GetHashCode() ?? 0); |
|||
var hc4 = (uint)(value4?.GetHashCode() ?? 0); |
|||
|
|||
Initialize(out uint v1, out uint v2, out uint v3, out uint v4); |
|||
|
|||
v1 = Round(v1, hc1); |
|||
v2 = Round(v2, hc2); |
|||
v3 = Round(v3, hc3); |
|||
v4 = Round(v4, hc4); |
|||
|
|||
uint hash = MixState(v1, v2, v3, v4); |
|||
hash += 16; |
|||
|
|||
hash = MixFinal(hash); |
|||
return (int)hash; |
|||
} |
|||
|
|||
public static int Combine<T1, T2, T3, T4, T5>(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5) |
|||
{ |
|||
var hc1 = (uint)(value1?.GetHashCode() ?? 0); |
|||
var hc2 = (uint)(value2?.GetHashCode() ?? 0); |
|||
var hc3 = (uint)(value3?.GetHashCode() ?? 0); |
|||
var hc4 = (uint)(value4?.GetHashCode() ?? 0); |
|||
var hc5 = (uint)(value5?.GetHashCode() ?? 0); |
|||
|
|||
Initialize(out uint v1, out uint v2, out uint v3, out uint v4); |
|||
|
|||
v1 = Round(v1, hc1); |
|||
v2 = Round(v2, hc2); |
|||
v3 = Round(v3, hc3); |
|||
v4 = Round(v4, hc4); |
|||
|
|||
uint hash = MixState(v1, v2, v3, v4); |
|||
hash += 20; |
|||
|
|||
hash = QueueRound(hash, hc5); |
|||
|
|||
hash = MixFinal(hash); |
|||
return (int)hash; |
|||
} |
|||
|
|||
public static int Combine<T1, T2, T3, T4, T5, T6>(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6) |
|||
{ |
|||
var hc1 = (uint)(value1?.GetHashCode() ?? 0); |
|||
var hc2 = (uint)(value2?.GetHashCode() ?? 0); |
|||
var hc3 = (uint)(value3?.GetHashCode() ?? 0); |
|||
var hc4 = (uint)(value4?.GetHashCode() ?? 0); |
|||
var hc5 = (uint)(value5?.GetHashCode() ?? 0); |
|||
var hc6 = (uint)(value6?.GetHashCode() ?? 0); |
|||
|
|||
Initialize(out uint v1, out uint v2, out uint v3, out uint v4); |
|||
|
|||
v1 = Round(v1, hc1); |
|||
v2 = Round(v2, hc2); |
|||
v3 = Round(v3, hc3); |
|||
v4 = Round(v4, hc4); |
|||
|
|||
uint hash = MixState(v1, v2, v3, v4); |
|||
hash += 24; |
|||
|
|||
hash = QueueRound(hash, hc5); |
|||
hash = QueueRound(hash, hc6); |
|||
|
|||
hash = MixFinal(hash); |
|||
return (int)hash; |
|||
} |
|||
|
|||
public static int Combine<T1, T2, T3, T4, T5, T6, T7>(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7) |
|||
{ |
|||
var hc1 = (uint)(value1?.GetHashCode() ?? 0); |
|||
var hc2 = (uint)(value2?.GetHashCode() ?? 0); |
|||
var hc3 = (uint)(value3?.GetHashCode() ?? 0); |
|||
var hc4 = (uint)(value4?.GetHashCode() ?? 0); |
|||
var hc5 = (uint)(value5?.GetHashCode() ?? 0); |
|||
var hc6 = (uint)(value6?.GetHashCode() ?? 0); |
|||
var hc7 = (uint)(value7?.GetHashCode() ?? 0); |
|||
|
|||
Initialize(out uint v1, out uint v2, out uint v3, out uint v4); |
|||
|
|||
v1 = Round(v1, hc1); |
|||
v2 = Round(v2, hc2); |
|||
v3 = Round(v3, hc3); |
|||
v4 = Round(v4, hc4); |
|||
|
|||
uint hash = MixState(v1, v2, v3, v4); |
|||
hash += 28; |
|||
|
|||
hash = QueueRound(hash, hc5); |
|||
hash = QueueRound(hash, hc6); |
|||
hash = QueueRound(hash, hc7); |
|||
|
|||
hash = MixFinal(hash); |
|||
return (int)hash; |
|||
} |
|||
|
|||
public static int Combine<T1, T2, T3, T4, T5, T6, T7, T8>(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8) |
|||
{ |
|||
var hc1 = (uint)(value1?.GetHashCode() ?? 0); |
|||
var hc2 = (uint)(value2?.GetHashCode() ?? 0); |
|||
var hc3 = (uint)(value3?.GetHashCode() ?? 0); |
|||
var hc4 = (uint)(value4?.GetHashCode() ?? 0); |
|||
var hc5 = (uint)(value5?.GetHashCode() ?? 0); |
|||
var hc6 = (uint)(value6?.GetHashCode() ?? 0); |
|||
var hc7 = (uint)(value7?.GetHashCode() ?? 0); |
|||
var hc8 = (uint)(value8?.GetHashCode() ?? 0); |
|||
|
|||
Initialize(out uint v1, out uint v2, out uint v3, out uint v4); |
|||
|
|||
v1 = Round(v1, hc1); |
|||
v2 = Round(v2, hc2); |
|||
v3 = Round(v3, hc3); |
|||
v4 = Round(v4, hc4); |
|||
|
|||
v1 = Round(v1, hc5); |
|||
v2 = Round(v2, hc6); |
|||
v3 = Round(v3, hc7); |
|||
v4 = Round(v4, hc8); |
|||
|
|||
uint hash = MixState(v1, v2, v3, v4); |
|||
hash += 32; |
|||
|
|||
hash = MixFinal(hash); |
|||
return (int)hash; |
|||
} |
|||
|
|||
#if !NET40
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
#endif
|
|||
private static uint Rol(uint value, int count) |
|||
=> (value << count) | (value >> (32 - count)); |
|||
|
|||
#if !NET40
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
#endif
|
|||
private static void Initialize(out uint v1, out uint v2, out uint v3, out uint v4) |
|||
{ |
|||
v1 = Seed + Prime1 + Prime2; |
|||
v2 = Seed + Prime2; |
|||
v3 = Seed; |
|||
v4 = Seed - Prime1; |
|||
} |
|||
|
|||
#if !NET40
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
#endif
|
|||
private static uint Round(uint hash, uint input) |
|||
{ |
|||
hash += input * Prime2; |
|||
hash = Rol(hash, 13); |
|||
hash *= Prime1; |
|||
return hash; |
|||
} |
|||
|
|||
#if !NET40
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
#endif
|
|||
private static uint QueueRound(uint hash, uint queuedValue) |
|||
{ |
|||
hash += queuedValue * Prime3; |
|||
return Rol(hash, 17) * Prime4; |
|||
} |
|||
|
|||
#if !NET40
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
#endif
|
|||
private static uint MixState(uint v1, uint v2, uint v3, uint v4) |
|||
{ |
|||
return Rol(v1, 1) + Rol(v2, 7) + Rol(v3, 12) + Rol(v4, 18); |
|||
} |
|||
|
|||
private static uint MixEmptyState() |
|||
{ |
|||
return Seed + Prime5; |
|||
} |
|||
|
|||
#if !NET40
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
#endif
|
|||
private static uint MixFinal(uint hash) |
|||
{ |
|||
hash ^= hash >> 15; |
|||
hash *= Prime2; |
|||
hash ^= hash >> 13; |
|||
hash *= Prime3; |
|||
hash ^= hash >> 16; |
|||
return hash; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
|
|||
namespace MathNet.Numerics.Spatial.Internal |
|||
{ |
|||
/// <summary>
|
|||
/// Internal implementation of an immutable list
|
|||
/// </summary>
|
|||
internal static class ImmutableList |
|||
{ |
|||
/// <summary>
|
|||
/// Factory Construction
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The list type</typeparam>
|
|||
/// <param name="data">A list of items to initialize with</param>
|
|||
/// <returns>An immutable list</returns>
|
|||
internal static ImmutableList<T> Create<T>(IEnumerable<T> data) |
|||
{ |
|||
return ImmutableList<T>.Empty.AddRange(data.AsCollection()); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the passed source as a collection
|
|||
/// </summary>
|
|||
/// <typeparam name="T">the list type</typeparam>
|
|||
/// <param name="source">the list source</param>
|
|||
/// <returns>A collection</returns>
|
|||
private static ICollection<T> AsCollection<T>(this IEnumerable<T> source) |
|||
{ |
|||
if (source is ICollection<T> collection) |
|||
{ |
|||
return collection; |
|||
} |
|||
|
|||
if (source is ImmutableList<T> list) |
|||
{ |
|||
return list.GetRawData(); |
|||
} |
|||
|
|||
return source.ToList(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,113 @@ |
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics.Contracts; |
|||
|
|||
namespace MathNet.Numerics.Spatial.Internal |
|||
{ |
|||
/// <summary>
|
|||
/// An internal implementation of ImmutableList
|
|||
/// </summary>
|
|||
/// <typeparam name="T">A type for the list</typeparam>
|
|||
internal sealed class ImmutableList<T> : IEnumerable<T> |
|||
{ |
|||
/// <summary>
|
|||
/// An empty list
|
|||
/// </summary>
|
|||
internal static readonly ImmutableList<T> Empty = new ImmutableList<T>(new T[0]); |
|||
|
|||
/// <summary>
|
|||
/// The list data
|
|||
/// </summary>
|
|||
private readonly T[] data; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ImmutableList{T}"/> class.
|
|||
/// </summary>
|
|||
/// <param name="data">The data to initialize the list with</param>
|
|||
private ImmutableList(T[] data) |
|||
{ |
|||
this.data = data; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of items in the list
|
|||
/// </summary>
|
|||
internal int Count => this.data.Length; |
|||
|
|||
/// <summary>
|
|||
/// An 0 based index into the list
|
|||
/// </summary>
|
|||
/// <param name="index">the index</param>
|
|||
/// <returns>A list item</returns>
|
|||
internal T this[int index] => this.data[index]; |
|||
|
|||
/// <inheritdoc />
|
|||
public IEnumerator<T> GetEnumerator() => ((IList<T>)this.data).GetEnumerator(); |
|||
|
|||
/// <inheritdoc />
|
|||
IEnumerator IEnumerable.GetEnumerator() => this.data.GetEnumerator(); |
|||
|
|||
/// <summary>
|
|||
/// Adds an item to the list
|
|||
/// </summary>
|
|||
/// <param name="value">An item</param>
|
|||
/// <returns>A new list with the item added</returns>
|
|||
[Pure] |
|||
internal ImmutableList<T> Add(T value) |
|||
{ |
|||
var newData = new T[this.data.Length + 1]; |
|||
Array.Copy(this.data, newData, this.data.Length); |
|||
newData[this.data.Length] = value; |
|||
return new ImmutableList<T>(newData); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a range of items to the list
|
|||
/// </summary>
|
|||
/// <param name="values">The items to add</param>
|
|||
/// <returns>A new list with the items added</returns>
|
|||
[Pure] |
|||
internal ImmutableList<T> AddRange(ICollection<T> values) |
|||
{ |
|||
var newData = new T[this.data.Length + values.Count]; |
|||
Array.Copy(this.data, newData, this.data.Length); |
|||
values.CopyTo(newData, this.data.Length); |
|||
return new ImmutableList<T>(newData); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Removes an item from the list
|
|||
/// </summary>
|
|||
/// <param name="value">The item to remove</param>
|
|||
/// <returns>A new list with the item removed</returns>
|
|||
[Pure] |
|||
internal ImmutableList<T> Remove(T value) |
|||
{ |
|||
var i = Array.IndexOf(this.data, value); |
|||
if (i < 0) |
|||
{ |
|||
return this; |
|||
} |
|||
|
|||
var length = this.data.Length; |
|||
if (length == 1) |
|||
{ |
|||
return Empty; |
|||
} |
|||
|
|||
var newData = new T[length - 1]; |
|||
|
|||
Array.Copy(this.data, 0, newData, 0, i); |
|||
Array.Copy(this.data, i + 1, newData, i, length - i - 1); |
|||
|
|||
return new ImmutableList<T>(newData); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// An internal method to access the underlying data. To be used with care.
|
|||
/// </summary>
|
|||
/// <returns>The backing data array</returns>
|
|||
internal T[] GetRawData() => this.data; |
|||
} |
|||
} |
|||
@ -0,0 +1,429 @@ |
|||
using System; |
|||
using System.Globalization; |
|||
using System.Text.RegularExpressions; |
|||
|
|||
namespace MathNet.Numerics.Spatial.Internal |
|||
{ |
|||
/// <summary>
|
|||
/// Internal text processing
|
|||
/// </summary>
|
|||
internal static class Text |
|||
{ |
|||
/// <summary>
|
|||
/// regex pattern with period
|
|||
/// </summary>
|
|||
private const string DoublePatternPointProvider = "[+-]?\\d*(?:[.]\\d+)?(?:[eE][+-]?\\d+)?"; |
|||
|
|||
/// <summary>
|
|||
/// regex pattern with comma
|
|||
/// </summary>
|
|||
private const string DoublePatternCommaProvider = "[+-]?\\d*(?:[,]\\d+)?(?:[eE][+-]?\\d+)?"; |
|||
|
|||
/// <summary>
|
|||
/// Separator with period
|
|||
/// </summary>
|
|||
private const string SeparatorPatternPointProvider = " ?[,;]?( |\u00A0)?"; |
|||
|
|||
/// <summary>
|
|||
/// Separator with comma
|
|||
/// </summary>
|
|||
private const string SeparatorPatternCommaProvider = " ?[;]?( |\u00A0)?"; |
|||
|
|||
/// <summary>
|
|||
/// Attempts to parse a string into x, y coordinates
|
|||
/// </summary>
|
|||
/// <param name="text">a string</param>
|
|||
/// <param name="provider">a format provider</param>
|
|||
/// <param name="x">The x value</param>
|
|||
/// <param name="y">The y value</param>
|
|||
/// <returns>True if successful; otherwise false</returns>
|
|||
internal static bool TryParse2D(string text, IFormatProvider provider, out double x, out double y) |
|||
{ |
|||
x = 0; |
|||
y = 0; |
|||
if (string.IsNullOrWhiteSpace(text)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (Regex2D.TryMatch(text, provider, out var match) && |
|||
match.Groups.Count == 3 && |
|||
match.Groups[0].Captures.Count == 1 && |
|||
match.Groups[1].Captures.Count == 1 && |
|||
match.Groups[2].Captures.Count == 1) |
|||
{ |
|||
return TryParseDouble(match.Groups["x"].Value, provider, out x) && |
|||
TryParseDouble(match.Groups["y"].Value, provider, out y); |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Attempts to parse a string into x, y, z coordinates
|
|||
/// </summary>
|
|||
/// <param name="text">A string</param>
|
|||
/// <param name="provider">A format provider</param>
|
|||
/// <param name="x">The x value</param>
|
|||
/// <param name="y">The y value</param>
|
|||
/// <param name="z">The z value</param>
|
|||
/// <returns>True if successful; otherwise false</returns>
|
|||
internal static bool TryParse3D(string text, IFormatProvider provider, out double x, out double y, out double z) |
|||
{ |
|||
x = 0; |
|||
y = 0; |
|||
z = 0; |
|||
if (string.IsNullOrWhiteSpace(text)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (Regex3D.TryMatch(text, provider, out var match) && |
|||
match.Groups.Count == 4 && |
|||
match.Groups[0].Captures.Count == 1 && |
|||
match.Groups[1].Captures.Count == 1 && |
|||
match.Groups[2].Captures.Count == 1 && |
|||
match.Groups[3].Captures.Count == 1) |
|||
{ |
|||
return TryParseDouble(match.Groups["x"].Value, provider, out x) && |
|||
TryParseDouble(match.Groups["y"].Value, provider, out y) && |
|||
TryParseDouble(match.Groups["z"].Value, provider, out z); |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Attempts to parse a string into an Angle
|
|||
/// </summary>
|
|||
/// <param name="text">A string</param>
|
|||
/// <param name="provider">A format provider</param>
|
|||
/// <param name="a">An angle</param>
|
|||
/// <returns>True if successful; otherwise false</returns>
|
|||
internal static bool TryParseAngle(string text, IFormatProvider provider, out Angle a) |
|||
{ |
|||
a = default(Angle); |
|||
if (string.IsNullOrWhiteSpace(text)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (RegexAngle.TryMatchDegrees(text, provider, out var value)) |
|||
{ |
|||
a = Angle.FromDegrees(value); |
|||
return true; |
|||
} |
|||
|
|||
if (RegexAngle.TryMatchRadians(text, provider, out value)) |
|||
{ |
|||
a = Angle.FromRadians(value); |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Attempts to parse a double
|
|||
/// </summary>
|
|||
/// <param name="s">A string</param>
|
|||
/// <param name="formatProvider">A format provider</param>
|
|||
/// <param name="result">A double</param>
|
|||
/// <returns>True if successful; otherwise false</returns>
|
|||
private static bool TryParseDouble(string s, IFormatProvider formatProvider, out double result) |
|||
{ |
|||
if (formatProvider == null) |
|||
{ |
|||
// This is for legacy reasons, we allow any culture, not nice.
|
|||
// Fixing would break
|
|||
return double.TryParse(s.Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out result); |
|||
} |
|||
|
|||
return double.TryParse(s, NumberStyles.Float, formatProvider, out result); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// A class providing regex matching for 2D values
|
|||
/// </summary>
|
|||
private static class Regex2D |
|||
{ |
|||
/// <summary>
|
|||
/// The pattern for this regex
|
|||
/// </summary>
|
|||
private const string Pattern2D = "^ *\\(?(?<x>{0}){1}(?<y>{0})\\)? *$"; |
|||
|
|||
/// <summary>
|
|||
/// The standard options
|
|||
/// </summary>
|
|||
private const RegexOptions RegexOptions = System.Text.RegularExpressions.RegexOptions.ExplicitCapture | System.Text.RegularExpressions.RegexOptions.Compiled | System.Text.RegularExpressions.RegexOptions.Singleline; |
|||
|
|||
/// <summary>
|
|||
/// A regex containing a .
|
|||
/// </summary>
|
|||
private static readonly Regex Point = new Regex( |
|||
string.Format(Pattern2D, DoublePatternPointProvider, SeparatorPatternPointProvider), |
|||
RegexOptions); |
|||
|
|||
/// <summary>
|
|||
/// a regex containing a ,
|
|||
/// </summary>
|
|||
private static readonly Regex Comma = new Regex( |
|||
string.Format(Pattern2D, DoublePatternCommaProvider, SeparatorPatternCommaProvider), |
|||
RegexOptions); |
|||
|
|||
/// <summary>
|
|||
/// Attempts to match a string
|
|||
/// </summary>
|
|||
/// <param name="text">a string</param>
|
|||
/// <param name="formatProvider">a format provider</param>
|
|||
/// <param name="match">the match</param>
|
|||
/// <returns>True if successful; Otherwise false</returns>
|
|||
internal static bool TryMatch(string text, IFormatProvider formatProvider, out Match match) |
|||
{ |
|||
if (formatProvider != null && |
|||
NumberFormatInfo.GetInstance(formatProvider) is NumberFormatInfo formatInfo) |
|||
{ |
|||
if (formatInfo.NumberDecimalSeparator == ".") |
|||
{ |
|||
match = Point.Match(text); |
|||
return match.Success; |
|||
} |
|||
|
|||
if (formatInfo.NumberDecimalSeparator == ",") |
|||
{ |
|||
match = Comma.Match(text); |
|||
return match.Success; |
|||
} |
|||
} |
|||
|
|||
match = Point.Match(text); |
|||
if (match.Success) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
match = Comma.Match(text); |
|||
return match.Success; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// A class providing regex matching for 3D values
|
|||
/// </summary>
|
|||
private static class Regex3D |
|||
{ |
|||
/// <summary>
|
|||
/// The pattern for this regex
|
|||
/// </summary>
|
|||
private const string Pattern3D = "^ *\\(?(?<x>{0}){1}(?<y>{0}){1}(?<z>{0})\\)? *$"; |
|||
|
|||
/// <summary>
|
|||
/// The standard options
|
|||
/// </summary>
|
|||
private const RegexOptions RegexOptions = System.Text.RegularExpressions.RegexOptions.ExplicitCapture | System.Text.RegularExpressions.RegexOptions.Compiled | System.Text.RegularExpressions.RegexOptions.Singleline; |
|||
|
|||
/// <summary>
|
|||
/// A regex containing a .
|
|||
/// </summary>
|
|||
private static readonly Regex Point = new Regex( |
|||
string.Format(Pattern3D, DoublePatternPointProvider, SeparatorPatternPointProvider), |
|||
RegexOptions); |
|||
|
|||
/// <summary>
|
|||
/// A regex containing a ,
|
|||
/// </summary>
|
|||
private static readonly Regex Comma = new Regex( |
|||
string.Format(Pattern3D, DoublePatternCommaProvider, SeparatorPatternCommaProvider), |
|||
RegexOptions); |
|||
|
|||
/// <summary>
|
|||
/// Attempts to match a string
|
|||
/// </summary>
|
|||
/// <param name="text">a string</param>
|
|||
/// <param name="formatProvider">a format provider</param>
|
|||
/// <param name="match">the match</param>
|
|||
/// <returns>True if successful; Otherwise false</returns>
|
|||
internal static bool TryMatch(string text, IFormatProvider formatProvider, out Match match) |
|||
{ |
|||
if (formatProvider != null && |
|||
NumberFormatInfo.GetInstance(formatProvider) is NumberFormatInfo formatInfo) |
|||
{ |
|||
if (formatInfo.NumberDecimalSeparator == ".") |
|||
{ |
|||
match = Point.Match(text); |
|||
return match.Success; |
|||
} |
|||
|
|||
if (formatInfo.NumberDecimalSeparator == ",") |
|||
{ |
|||
match = Comma.Match(text); |
|||
return match.Success; |
|||
} |
|||
} |
|||
|
|||
match = Point.Match(text); |
|||
if (match.Success) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
match = Comma.Match(text); |
|||
return match.Success; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// A class providing regex matching for angle values
|
|||
/// </summary>
|
|||
private static class RegexAngle |
|||
{ |
|||
/// <summary>
|
|||
/// A regex for radians angles
|
|||
/// </summary>
|
|||
private const string RadiansPattern = "^(?<value>{0})( |\u00A0)?(°|rad|radians) *$"; |
|||
|
|||
/// <summary>
|
|||
/// A regex for degrees angles
|
|||
/// </summary>
|
|||
private const string DegreesPattern = "^(?<value>{0})( |\u00A0)?(°|deg|degrees) *$"; |
|||
|
|||
/// <summary>
|
|||
/// Standard regex options
|
|||
/// </summary>
|
|||
private const RegexOptions RegexOptions = System.Text.RegularExpressions.RegexOptions.ExplicitCapture | System.Text.RegularExpressions.RegexOptions.Compiled | System.Text.RegularExpressions.RegexOptions.Singleline | System.Text.RegularExpressions.RegexOptions.IgnoreCase; |
|||
|
|||
/// <summary>
|
|||
/// Radians with a point
|
|||
/// </summary>
|
|||
private static readonly Regex RadiansPoint = new Regex( |
|||
string.Format(RadiansPattern, DoublePatternPointProvider), |
|||
RegexOptions); |
|||
|
|||
/// <summary>
|
|||
/// Radians with a comma
|
|||
/// </summary>
|
|||
private static readonly Regex RadiansComma = new Regex( |
|||
string.Format(RadiansPattern, DoublePatternCommaProvider), |
|||
RegexOptions); |
|||
|
|||
/// <summary>
|
|||
/// Degrees with a point
|
|||
/// </summary>
|
|||
private static readonly Regex DegreesPoint = new Regex( |
|||
string.Format(DegreesPattern, DoublePatternPointProvider), |
|||
RegexOptions); |
|||
|
|||
/// <summary>
|
|||
/// Degrees with a comma
|
|||
/// </summary>
|
|||
private static readonly Regex DegreesComma = new Regex( |
|||
string.Format(DegreesPattern, DoublePatternCommaProvider), |
|||
RegexOptions); |
|||
|
|||
/// <summary>
|
|||
/// Attempts to match Degrees
|
|||
/// </summary>
|
|||
/// <param name="text">a string</param>
|
|||
/// <param name="provider">a format provider</param>
|
|||
/// <param name="value">a double</param>
|
|||
/// <returns>True if successful; otherwise false</returns>
|
|||
internal static bool TryMatchDegrees(string text, IFormatProvider provider, out double value) |
|||
{ |
|||
if (TryMatchDegrees(text, provider, out Match match)) |
|||
{ |
|||
return TryParseDouble(match.Groups["value"].Value, provider, out value); |
|||
} |
|||
|
|||
value = 0; |
|||
return false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Attempts to match Radians
|
|||
/// </summary>
|
|||
/// <param name="text">a string</param>
|
|||
/// <param name="provider">a format provider</param>
|
|||
/// <param name="value">a double</param>
|
|||
/// <returns>True if successful; otherwise false</returns>
|
|||
internal static bool TryMatchRadians(string text, IFormatProvider provider, out double value) |
|||
{ |
|||
if (TryMatchRadians(text, provider, out Match match)) |
|||
{ |
|||
return TryParseDouble(match.Groups["value"].Value, provider, out value); |
|||
} |
|||
|
|||
value = 0; |
|||
return false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Attempts to match Radians with either . or , separators
|
|||
/// </summary>
|
|||
/// <param name="text">a string</param>
|
|||
/// <param name="formatProvider">a format provider</param>
|
|||
/// <param name="match">a list of matches</param>
|
|||
/// <returns>True if successful; otherwise false</returns>
|
|||
private static bool TryMatchRadians(string text, IFormatProvider formatProvider, out Match match) |
|||
{ |
|||
if (formatProvider != null && |
|||
NumberFormatInfo.GetInstance(formatProvider) is NumberFormatInfo formatInfo) |
|||
{ |
|||
if (formatInfo.NumberDecimalSeparator == ".") |
|||
{ |
|||
match = RadiansPoint.Match(text); |
|||
return match.Success; |
|||
} |
|||
|
|||
if (formatInfo.NumberDecimalSeparator == ",") |
|||
{ |
|||
match = RadiansComma.Match(text); |
|||
return match.Success; |
|||
} |
|||
} |
|||
|
|||
match = RadiansPoint.Match(text); |
|||
if (match.Success) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
match = RadiansComma.Match(text); |
|||
return match.Success; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Attempts to match Degrees with either . or , separators
|
|||
/// </summary>
|
|||
/// <param name="text">a string</param>
|
|||
/// <param name="provider">a format provider</param>
|
|||
/// <param name="match">a list of matches</param>
|
|||
/// <returns>True if successful; otherwise false</returns>
|
|||
private static bool TryMatchDegrees(string text, IFormatProvider provider, out Match match) |
|||
{ |
|||
if (provider != null && NumberFormatInfo.GetInstance(provider) is NumberFormatInfo formatInfo) |
|||
{ |
|||
if (formatInfo.NumberDecimalSeparator == ".") |
|||
{ |
|||
match = DegreesPoint.Match(text); |
|||
return match.Success; |
|||
} |
|||
|
|||
if (formatInfo.NumberDecimalSeparator == ",") |
|||
{ |
|||
match = DegreesComma.Match(text); |
|||
return match.Success; |
|||
} |
|||
} |
|||
|
|||
match = DegreesPoint.Match(text); |
|||
if (match.Success) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
match = DegreesComma.Match(text); |
|||
return match.Success; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,261 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using System.Xml; |
|||
using System.Xml.Linq; |
|||
using System.Xml.Serialization; |
|||
|
|||
namespace MathNet.Numerics.Spatial.Internal |
|||
{ |
|||
/// <summary>
|
|||
/// An extension class for XmlReader
|
|||
/// </summary>
|
|||
internal static class XmlReaderExt |
|||
{ |
|||
/// <summary>
|
|||
/// Creates a default(T) and calls ReadXml(reader) on it.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The type of the instance to read from the current position of the reader.</typeparam>
|
|||
/// <param name="reader">A <see cref="XmlReader"/></param>
|
|||
/// <returns> A new instance of {T} with values from <paramref name="reader"/></returns>
|
|||
internal static T ReadElementAs<T>(this XmlReader reader) |
|||
where T : struct, IXmlSerializable |
|||
{ |
|||
if (reader == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(reader)); |
|||
} |
|||
|
|||
var instance = default(T); |
|||
instance.ReadXml(reader); |
|||
return instance; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the attribute named <paramref name="localName"/> if it exists on the current element.
|
|||
/// This is not a proper try method as it checks if <paramref name="reader"/> is null and throws.
|
|||
/// </summary>
|
|||
/// <param name="reader">A <see cref="XmlReader"/></param>
|
|||
/// <param name="localName">The name of the attribute to read the value of.</param>
|
|||
/// <param name="value">The value read from <paramref name="reader"/></param>
|
|||
/// <returns>True if the attribute was found.</returns>
|
|||
internal static bool TryReadAttributeAsDouble(this XmlReader reader, string localName, out double value) |
|||
{ |
|||
if (reader == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(reader)); |
|||
} |
|||
|
|||
if (reader.MoveToContent() == XmlNodeType.Element && |
|||
reader.HasAttributes && |
|||
reader.MoveToAttribute(localName)) |
|||
{ |
|||
value = XmlConvert.ToDouble(reader.Value); |
|||
return true; |
|||
} |
|||
|
|||
value = 0; |
|||
return false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the values of the elements named <paramref name="localName"/> and <paramref name="localName"/> if they exist on the current element.
|
|||
/// This is not a proper try method as it checks if <paramref name="reader"/> is null and throws.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Calling this method has side effects as it changes the position of the reader.
|
|||
/// </remarks>
|
|||
/// <param name="reader">A <see cref="XmlReader"/></param>
|
|||
/// <param name="localName">The local name of the element to read value from.</param>
|
|||
/// <param name="value">The value read from <paramref name="reader"/></param>
|
|||
/// <returns>True if both elements were found.</returns>
|
|||
internal static bool TryReadElementContentAsDouble(this XmlReader reader, string localName, out double value) |
|||
{ |
|||
if (reader == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(reader)); |
|||
} |
|||
|
|||
if (reader.MoveToContent() == XmlNodeType.Element && |
|||
!reader.IsEmptyElement && |
|||
reader.LocalName == localName) |
|||
{ |
|||
value = reader.ReadElementContentAsDouble(); |
|||
return true; |
|||
} |
|||
|
|||
value = 0; |
|||
return false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the values of the elements named <paramref name="xName"/> and <paramref name="xName"/> if they exist on the current element.
|
|||
/// This is not a proper try method as it checks if <paramref name="reader"/> is null and throws.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Calling this method has side effects as it changes the position of the reader.
|
|||
/// </remarks>
|
|||
/// <param name="reader">A <see cref="XmlReader"/></param>
|
|||
/// <param name="xName">The local name of the x element.</param>
|
|||
/// <param name="yName">The local name of the y element.</param>
|
|||
/// <param name="x">The x value read from <paramref name="reader"/></param>
|
|||
/// <param name="y">The y value read from <paramref name="reader"/></param>
|
|||
/// <returns>True if both elements were found.</returns>
|
|||
internal static bool TryReadChildElementsAsDoubles(this XmlReader reader, string xName, string yName, out double x, out double y) |
|||
{ |
|||
if (reader == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(reader)); |
|||
} |
|||
|
|||
x = 0; |
|||
y = 0; |
|||
if (reader.MoveToContent() == XmlNodeType.Element && |
|||
!reader.IsEmptyElement && |
|||
!reader.HasValue) |
|||
{ |
|||
var subtree = reader.ReadSubtree(); |
|||
if (subtree.ReadToFirstDescendant()) |
|||
{ |
|||
if (subtree.TryReadSiblingElementsAsDoubles(xName, yName, out x, out y)) |
|||
{ |
|||
subtree.Skip(); |
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the values of the elements named <paramref name="xName"/> and <paramref name="xName"/> if they exist on the current element.
|
|||
/// This is not a proper try method as it checks if <paramref name="reader"/> is null and throws.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Calling this method has side effects as it changes the position of the reader.
|
|||
/// </remarks>
|
|||
/// <param name="reader">A <see cref="XmlReader"/></param>
|
|||
/// <param name="xName">The local name of the x element.</param>
|
|||
/// <param name="yName">The local name of the y element.</param>
|
|||
/// <param name="zName">The local name of the z element.</param>
|
|||
/// <param name="x">The x value read from <paramref name="reader"/></param>
|
|||
/// <param name="y">The y value read from <paramref name="reader"/></param>
|
|||
/// <param name="z">The z value read from <paramref name="reader"/></param>
|
|||
/// <returns>True if both elements were found.</returns>
|
|||
internal static bool TryReadChildElementsAsDoubles(this XmlReader reader, string xName, string yName, string zName, out double x, out double y, out double z) |
|||
{ |
|||
if (reader == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(reader)); |
|||
} |
|||
|
|||
x = 0; |
|||
y = 0; |
|||
z = 0; |
|||
if (reader.MoveToContent() == XmlNodeType.Element && |
|||
!reader.IsEmptyElement && |
|||
!reader.HasValue) |
|||
{ |
|||
var subtree = reader.ReadSubtree(); |
|||
if (subtree.ReadToFirstDescendant()) |
|||
{ |
|||
if (subtree.TryReadElementContentAsDouble(xName, out x)) |
|||
{ |
|||
if (subtree.TryReadSiblingElementsAsDoubles(yName, zName, out y, out z)) |
|||
{ |
|||
subtree.Skip(); |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
if (subtree.TryReadElementContentAsDouble(yName, out y)) |
|||
{ |
|||
if (subtree.TryReadSiblingElementsAsDoubles(xName, zName, out x, out z)) |
|||
{ |
|||
subtree.Skip(); |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
if (subtree.TryReadElementContentAsDouble(zName, out z)) |
|||
{ |
|||
if (subtree.TryReadSiblingElementsAsDoubles(xName, yName, out x, out y)) |
|||
{ |
|||
subtree.Skip(); |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads until first descendant
|
|||
/// </summary>
|
|||
/// <param name="reader">An xml reader</param>
|
|||
/// <returns>True if successful; otherwise false</returns>
|
|||
internal static bool ReadToFirstDescendant(this XmlReader reader) |
|||
{ |
|||
var depth = reader.Depth; |
|||
if (reader.MoveToContent() != XmlNodeType.Element) |
|||
{ |
|||
return reader.Depth > depth && |
|||
reader.NodeType == XmlNodeType.Element; |
|||
} |
|||
|
|||
while (reader.Read() && |
|||
reader.Depth <= depth) |
|||
{ |
|||
} |
|||
|
|||
return reader.Depth > depth && |
|||
reader.NodeType == XmlNodeType.Element; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Attempts to read sibling elements as a pair of doubles
|
|||
/// </summary>
|
|||
/// <param name="subtree">an xml reader</param>
|
|||
/// <param name="xName">The name of the x element</param>
|
|||
/// <param name="yName">The name of the y element</param>
|
|||
/// <param name="x">The x value</param>
|
|||
/// <param name="y">The y value</param>
|
|||
/// <returns>True if successful; otherwise false</returns>
|
|||
private static bool TryReadSiblingElementsAsDoubles(this XmlReader subtree, string xName, string yName, out double x, out double y) |
|||
{ |
|||
if (subtree.TryReadElementContentAsDouble(xName, out x) && |
|||
subtree.TryReadElementContentAsDouble(yName, out y)) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (subtree.TryReadElementContentAsDouble(yName, out y) && |
|||
subtree.TryReadElementContentAsDouble(xName, out x)) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
public static XElement SingleElement(this XElement e, string localName) |
|||
{ |
|||
return e.Elements() |
|||
.Single(x => x.Name.LocalName == localName); |
|||
} |
|||
|
|||
public static XmlReader SingleElementReader(this XElement e, string localName) |
|||
{ |
|||
return e.SingleElement(localName) |
|||
.CreateReader(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,58 @@ |
|||
using System.Globalization; |
|||
using System.Xml; |
|||
using System.Xml.Serialization; |
|||
|
|||
namespace MathNet.Numerics.Spatial.Internal |
|||
{ |
|||
/// <summary>
|
|||
/// An extension class for XmlWriter
|
|||
/// </summary>
|
|||
internal static class XmlWriterExt |
|||
{ |
|||
/// <summary>
|
|||
/// Writes an element
|
|||
/// </summary>
|
|||
/// <param name="writer">An Xml Writer</param>
|
|||
/// <param name="name">The element name</param>
|
|||
/// <param name="value">The value</param>
|
|||
internal static void WriteElement(this XmlWriter writer, string name, IXmlSerializable value) |
|||
{ |
|||
writer.WriteStartElement(name); |
|||
value.WriteXml(writer); |
|||
writer.WriteEndElement(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes an element
|
|||
/// </summary>
|
|||
/// <param name="writer">An Xml Writer</param>
|
|||
/// <param name="name">The element name</param>
|
|||
/// <param name="value">The value</param>
|
|||
internal static void WriteElement(this XmlWriter writer, string name, double value) |
|||
{ |
|||
writer.WriteStartElement(name); |
|||
writer.WriteValue(value); |
|||
writer.WriteEndElement(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes an element
|
|||
/// </summary>
|
|||
/// <param name="writer">An Xml Writer</param>
|
|||
/// <param name="name">The element name</param>
|
|||
/// <param name="value">The value</param>
|
|||
/// <param name="format">a format to apply to the value</param>
|
|||
internal static void WriteElement(this XmlWriter writer, string name, double value, string format) |
|||
{ |
|||
writer.WriteElementString(name, value.ToString(format, CultureInfo.InvariantCulture)); |
|||
} |
|||
|
|||
internal static XmlWriter WriteAttribute<T>(this XmlWriter writer, string name, T value) |
|||
{ |
|||
writer.WriteStartAttribute(name); |
|||
writer.WriteValue(value); |
|||
writer.WriteEndAttribute(); |
|||
return writer; |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue