diff --git a/src/Numerics.Tests/Numerics.Tests.csproj b/src/Numerics.Tests/Numerics.Tests.csproj
index 3cfa17d8..9b4c2a4e 100644
--- a/src/Numerics.Tests/Numerics.Tests.csproj
+++ b/src/Numerics.Tests/Numerics.Tests.csproj
@@ -27,5 +27,8 @@
+
+
+
diff --git a/src/Numerics.Tests/Spatial/AngleTests.cs b/src/Numerics.Tests/Spatial/AngleTests.cs
new file mode 100644
index 00000000..7b43e664
--- /dev/null
+++ b/src/Numerics.Tests/Spatial/AngleTests.cs
@@ -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(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(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(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(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);
+
+ // ReSharper disable once RedundantToStringCall
+ angle = Angle.Parse(s.ToString());
+ Assert.AreEqual(expected, angle.Radians, Tolerance);
+ Assert.IsInstanceOf(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(() => 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(@"")]
+ [TestCase(@"1")]
+ 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°", @"")]
+ 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
+ {
+ Value1 = Angle.FromRadians(1),
+ Value2 = Angle.FromRadians(2),
+ };
+ var expected = "\r\n" +
+ " \r\n" +
+ " \r\n" +
+ "";
+ 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
+ {
+ Value1 = Angle.FromRadians(1),
+ Value2 = Angle.FromRadians(2),
+ };
+ var xml = "\r\n" +
+ " 1\r\n" +
+ " 2\r\n" +
+ "";
+ var serializer = new XmlSerializer(typeof(AssertXml.Container));
+ using (var reader = new StringReader(xml))
+ {
+ var deserialized = (AssertXml.Container)serializer.Deserialize(reader);
+ Assert.AreEqual(container.Value1, deserialized.Value1);
+ Assert.AreEqual(container.Value2, deserialized.Value2);
+ }
+ }
+
+ [TestCase("15°", @"0.261799387799149")]
+ [TestCase("15°", @"0.261799387799149")]
+ [TestCase("15°", @"15")]
+ [TestCase("15°", @"")]
+ [TestCase("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, test }
+ };
+
+ Assert.AreEqual(test, lookup[angle]);
+ }
+ }
+}
diff --git a/src/Numerics.Tests/Spatial/AssertGeometry.cs b/src/Numerics.Tests/Spatial/AssertGeometry.cs
new file mode 100644
index 00000000..1582333f
--- /dev/null
+++ b/src/Numerics.Tests/Spatial/AssertGeometry.cs
@@ -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 expected, Matrix 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);
+ }
+ }
+}
diff --git a/src/Numerics.Tests/Spatial/AssertXml.cs b/src/Numerics.Tests/Spatial/AssertXml.cs
new file mode 100644
index 00000000..f9e54c3f
--- /dev/null
+++ b/src/Numerics.Tests/Spatial/AssertXml.cs
@@ -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);
+ }
+
+ ///
+ /// Serializes using XmlSerializer & DataContractSerializer
+ /// Compares the generated xml
+ /// Then asserts that the deserialized is the same as input (item)
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static void XmlRoundTrips(T item, string expectedXml, Action assert)
+ {
+ var roundtrips = new[]
+ {
+ XmlSerializerRoundTrip(item, expectedXml)
+ };
+ foreach (var roundtrip in roundtrips)
+ {
+ assert(item, roundtrip);
+ }
+ }
+
+ public static T XmlSerializerRoundTrip(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);
+ }
+
+ ///
+ /// Core recursion function
+ ///
+ ///
+ ///
+ 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
+ {
+ public T Value1 { get; set; }
+
+ public T Value2 { get; set; }
+ }
+ }
+}
diff --git a/src/Numerics.Tests/Spatial/AssertXmlTests.cs b/src/Numerics.Tests/Spatial/AssertXmlTests.cs
new file mode 100644
index 00000000..b008cbf0
--- /dev/null
+++ b/src/Numerics.Tests/Spatial/AssertXmlTests.cs
@@ -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, @"Meh");
+ 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 item,
+ TProperty value,
+ Expression> fieldExpression)
+ {
+ string name = ((MemberExpression)fieldExpression.Body).Member.Name;
+ GetAllFields(item.GetType())
+ .Single(x => x.Name == name)
+ .SetValue(item, value);
+ }
+
+ private static IEnumerable GetAllFields(Type t)
+ {
+ if (t == null)
+ {
+ return Enumerable.Empty();
+ }
+
+ BindingFlags bindingAttr = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static |
+ BindingFlags.Public | BindingFlags.NonPublic;
+ return t.GetFields(bindingAttr)
+ .Concat(GetAllFields(t.BaseType));
+ }
+ }
+ }
+
+#endif
+
+}
diff --git a/src/Numerics.Tests/Spatial/Euclidean2D/Circle2DTests.cs b/src/Numerics.Tests/Spatial/Euclidean2D/Circle2DTests.cs
new file mode 100644
index 00000000..bfa438ee
--- /dev/null
+++ b/src/Numerics.Tests/Spatial/Euclidean2D/Circle2DTests.cs
@@ -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(() => { Circle2D.FromPoints(p1, p2, p3); });
+ }
+ }
+}
diff --git a/src/Numerics.Tests/Spatial/Euclidean2D/Line2DTests.cs b/src/Numerics.Tests/Spatial/Euclidean2D/Line2DTests.cs
new file mode 100644
index 00000000..415a33f2
--- /dev/null
+++ b/src/Numerics.Tests/Spatial/Euclidean2D/Line2DTests.cs
@@ -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(() => 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);
+ }
+ }
+}
diff --git a/src/Numerics.Tests/Spatial/Euclidean2D/LineSegment2DTests.cs b/src/Numerics.Tests/Spatial/Euclidean2D/LineSegment2DTests.cs
new file mode 100644
index 00000000..de335bf3
--- /dev/null
+++ b/src/Numerics.Tests/Spatial/Euclidean2D/LineSegment2DTests.cs
@@ -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(() => 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);
+ }
+ }
+}
diff --git a/src/Numerics.Tests/Spatial/Euclidean2D/Point2DTests.cs b/src/Numerics.Tests/Spatial/Euclidean2D/Point2DTests.cs
new file mode 100644
index 00000000..aa84aabc
--- /dev/null
+++ b/src/Numerics.Tests/Spatial/Euclidean2D/Point2DTests.cs
@@ -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(() => 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(() => Point2D.Parse(text));
+ }
+
+ [TestCase(@"")]
+ [TestCase(@"12")]
+ 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(() => 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, @"", (e, a) => AssertGeometry.AreEqual(e, a));
+ }
+
+ [Test]
+ public void XmlContainerRoundtrip()
+ {
+ var container = new AssertXml.Container
+ {
+ Value1 = new Point2D(1, 2),
+ Value2 = new Point2D(3, 4)
+ };
+ var expected = "\r\n" +
+ " \r\n" +
+ " \r\n" +
+ "";
+ 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(@"12")));
+ }
+
+ [Test]
+ public void XmlContainerElements()
+ {
+ var container = new AssertXml.Container
+ {
+ Value1 = new Point2D(1, 2),
+ Value2 = new Point2D(3, 4)
+ };
+ var xml = "\r\n" +
+ " 12\r\n" +
+ " 34\r\n" +
+ "";
+ var serializer = new XmlSerializer(typeof(AssertXml.Container));
+ var deserialized = (AssertXml.Container)serializer.Deserialize(new StringReader(xml));
+ AssertGeometry.AreEqual(container.Value1, deserialized.Value1);
+ AssertGeometry.AreEqual(container.Value2, deserialized.Value2);
+ }
+ }
+}
diff --git a/src/Numerics.Tests/Spatial/Euclidean2D/PolyLine2DTests.cs b/src/Numerics.Tests/Spatial/Euclidean2D/PolyLine2DTests.cs
new file mode 100644
index 00000000..5a63925e
--- /dev/null
+++ b/src/Numerics.Tests/Spatial/Euclidean2D/PolyLine2DTests.cs
@@ -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(() => { 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));
+ }
+ }
+}
diff --git a/src/Numerics.Tests/Spatial/Euclidean2D/Polygon2DTests.cs b/src/Numerics.Tests/Spatial/Euclidean2D/Polygon2DTests.cs
new file mode 100644
index 00000000..ea6b7608
--- /dev/null
+++ b/src/Numerics.Tests/Spatial/Euclidean2D/Polygon2DTests.cs
@@ -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 { 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 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 { 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);
+ }
+
+ [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);
+ 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.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, thinned);
+ }
+
+ 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);
+ }
+ }
+}
diff --git a/src/Numerics.Tests/Spatial/Euclidean2D/Vector2DTests.cs b/src/Numerics.Tests/Spatial/Euclidean2D/Vector2DTests.cs
new file mode 100644
index 00000000..376869a7
--- /dev/null
+++ b/src/Numerics.Tests/Spatial/Euclidean2D/Vector2DTests.cs
@@ -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(() => Vector2D.FromPolar(-1.0, Angle.FromRadians(0)));
+ }
+
+ [Test]
+ public void OfVector()
+ {
+ var v = Vector2D.OfVector(Vector.Build.Dense(new[] { 1.0, 2 }));
+ Assert.AreEqual(1, v.X);
+ Assert.AreEqual(2, v.Y);
+
+ Assert.Throws(() => Vector2D.OfVector(Vector.Build.Dense(new[] { 1.0 })));
+ Assert.Throws(() => Vector2D.OfVector(Vector.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(() => Vector2D.Parse(text));
+ }
+
+ [TestCase(@"")]
+ [TestCase(@"")]
+ [TestCase(@"12")]
+ [TestCase(@"21")]
+ 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(() => 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, @"", (e, a) => AssertGeometry.AreEqual(e, a));
+ }
+
+ [Test]
+ public void XmlContainerRoundtrip()
+ {
+ var container = new AssertXml.Container
+ {
+ Value1 = new Vector2D(1, 2),
+ Value2 = new Vector2D(3, 4)
+ };
+ var expected = "\r\n" +
+ " \r\n" +
+ " \r\n" +
+ "";
+ 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(@"12")));
+ }
+
+ [Test]
+ public void XmlContainerElements()
+ {
+ var xml = "\r\n" +
+ " 12\r\n" +
+ " 34\r\n" +
+ "";
+ var serializer = new XmlSerializer(typeof(AssertXml.Container));
+ var deserialized = (AssertXml.Container)serializer.Deserialize(new StringReader(xml));
+ AssertGeometry.AreEqual(new Vector2D(1, 2), deserialized.Value1);
+ AssertGeometry.AreEqual(new Vector2D(3, 4), deserialized.Value2);
+ }
+ }
+}
diff --git a/src/Numerics.Tests/Spatial/Euclidean3D/Circle3DTests.cs b/src/Numerics.Tests/Spatial/Euclidean3D/Circle3DTests.cs
new file mode 100644
index 00000000..d6d9da5a
--- /dev/null
+++ b/src/Numerics.Tests/Spatial/Euclidean3D/Circle3DTests.cs
@@ -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(() => Circle3D.FromPoints(p1, p2, p3));
+ }
+ }
+}
diff --git a/src/Numerics.Tests/Spatial/Euclidean3D/CoordinateSystem3DTests.cs b/src/Numerics.Tests/Spatial/Euclidean3D/CoordinateSystem3DTests.cs
new file mode 100644
index 00000000..2953af1f
--- /dev/null
+++ b/src/Numerics.Tests/Spatial/Euclidean3D/CoordinateSystem3DTests.cs
@@ -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 = @"
+
+
+
+
+
+";
+ AssertXml.XmlRoundTrips(cs, expected, (e, a) => AssertGeometry.AreEqual(e, a));
+ }
+ }
+}
diff --git a/src/Numerics.Tests/Spatial/Euclidean3D/Line3DTests.cs b/src/Numerics.Tests/Spatial/Euclidean3D/Line3DTests.cs
new file mode 100644
index 00000000..8b5f45a6
--- /dev/null
+++ b/src/Numerics.Tests/Spatial/Euclidean3D/Line3DTests.cs
@@ -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(() => 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", @"")]
+ 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);
+ }
+ }
+}
diff --git a/src/Numerics.Tests/Spatial/Euclidean3D/LineSegment3DTests.cs b/src/Numerics.Tests/Spatial/Euclidean3D/LineSegment3DTests.cs
new file mode 100644
index 00000000..ff7dbe25
--- /dev/null
+++ b/src/Numerics.Tests/Spatial/Euclidean3D/LineSegment3DTests.cs
@@ -0,0 +1,143 @@
+using System;
+using MathNet.Numerics.Spatial;
+using MathNet.Numerics.Spatial.Euclidean3D;
+using NUnit.Framework;
+
+namespace MathNet.Numerics.Tests.Spatial.Euclidean3D
+{
+ ///
+ /// Tests for LineSegment3D
+ ///
+ [TestFixture]
+ public class LineSegment3DTests
+ {
+ [Test]
+ public void Ctor()
+ {
+ Assert.Throws(() => 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);
+ }
+ }
+}
diff --git a/src/Numerics.Tests/Spatial/Euclidean3D/Plane3DTests.cs b/src/Numerics.Tests/Spatial/Euclidean3D/Plane3DTests.cs
new file mode 100644
index 00000000..f152715d
--- /dev/null
+++ b/src/Numerics.Tests/Spatial/Euclidean3D/Plane3DTests.cs
@@ -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(() => plane1.IntersectionWith(plane2));
+ Assert.Throws(() => 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", @"")]
+ 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]);
+ }
+ }
+}
diff --git a/src/Numerics.Tests/Spatial/Euclidean3D/Point3DTests.cs b/src/Numerics.Tests/Spatial/Euclidean3D/Point3DTests.cs
new file mode 100644
index 00000000..7cfce8a5
--- /dev/null
+++ b/src/Numerics.Tests/Spatial/Euclidean3D/Point3DTests.cs
@@ -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(() => Point3D.Parse(text));
+ }
+
+ [TestCase("")]
+ [TestCase("")]
+ [TestCase("")]
+ [TestCase("1-23")]
+ [TestCase("-231")]
+ [TestCase("31-2")]
+ 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 = @"";
+ AssertXml.XmlRoundTrips(p, xml, (expected, actual) => AssertGeometry.AreEqual(expected, actual));
+ }
+
+ [Test]
+ public void XmlContainerRoundtrip()
+ {
+ var container = new AssertXml.Container
+ {
+ Value1 = new Point3D(1, 2, 3),
+ Value2 = new Point3D(4, 5, 6)
+ };
+ var expected = "\r\n" +
+ " \r\n" +
+ " \r\n" +
+ "";
+ 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(@"123")));
+ }
+
+ [Test]
+ public void XmlContainerElements()
+ {
+ var xml = "\r\n" +
+ " 123\r\n" +
+ " 456\r\n" +
+ "";
+ var serializer = new XmlSerializer(typeof(AssertXml.Container));
+ var deserialized = (AssertXml.Container)serializer.Deserialize(new StringReader(xml));
+ AssertGeometry.AreEqual(new Point3D(1, 2, 3), deserialized.Value1);
+ AssertGeometry.AreEqual(new Point3D(4, 5, 6), deserialized.Value2);
+ }
+ }
+}
diff --git a/src/Numerics.Tests/Spatial/Euclidean3D/PolyLine3DTests.cs b/src/Numerics.Tests/Spatial/Euclidean3D/PolyLine3DTests.cs
new file mode 100644
index 00000000..2b43c719
--- /dev/null
+++ b/src/Numerics.Tests/Spatial/Euclidean3D/PolyLine3DTests.cs
@@ -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(() => { 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);
+ }
+ }
+}
diff --git a/src/Numerics.Tests/Spatial/Euclidean3D/Ray3DTests.cs b/src/Numerics.Tests/Spatial/Euclidean3D/Ray3DTests.cs
new file mode 100644
index 00000000..3a602caf
--- /dev/null
+++ b/src/Numerics.Tests/Spatial/Euclidean3D/Ray3DTests.cs
@@ -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, @"")]
+ 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));
+ }
+ }
+}
diff --git a/src/Numerics.Tests/Spatial/Euclidean3D/UnitVector3DTests.cs b/src/Numerics.Tests/Spatial/Euclidean3D/UnitVector3DTests.cs
new file mode 100644
index 00000000..789ca124
--- /dev/null
+++ b/src/Numerics.Tests/Spatial/Euclidean3D/UnitVector3DTests.cs
@@ -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(() => UnitVector3D.Create(double.NaN, 2, 3));
+ Assert.Throws(() => UnitVector3D.Create(double.PositiveInfinity, 2, 3));
+ Assert.Throws(() => 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(() => 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("")]
+ [TestCase("")]
+ [TestCase("")]
+ [TestCase("0.2672612419124244-0.534522483824848790.80178372573727319")]
+ [TestCase("-0.534522483824848790.801783725737273190.2672612419124244")]
+ [TestCase("0.801783725737273190.2672612419124244-0.53452248382484879")]
+ 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 = "";
+ AssertXml.XmlRoundTrips(uv, xml, (expected, actual) => AssertGeometry.AreEqual(expected, actual));
+ }
+
+ [Test]
+ public void XmlContainerRoundtrip()
+ {
+ var container = new AssertXml.Container
+ {
+ Value1 = UnitVector3D.Create(0.2672612419124244, -0.53452248382484879, 0.80178372573727319),
+ Value2 = UnitVector3D.Create(1, 0, 0)
+ };
+ var expected = "\r\n" +
+ " \r\n" +
+ " \r\n" +
+ "";
+ 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("0.2672612419124244-0.534522483824848790.80178372573727319"))
+ {
+ AssertGeometry.AreEqual(v, (UnitVector3D)serializer.Deserialize(reader));
+ }
+ }
+
+ [Test]
+ public void XmlContainerElements()
+ {
+ var xml = "\r\n" +
+ " 0.2672612419124244-0.534522483824848790.80178372573727319\r\n" +
+ " 100\r\n" +
+ "";
+ var serializer = new XmlSerializer(typeof(AssertXml.Container));
+ var deserialized = (AssertXml.Container)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);
+ }
+ }
+}
diff --git a/src/Numerics.Tests/Spatial/Euclidean3D/Vector3DTests.cs b/src/Numerics.Tests/Spatial/Euclidean3D/Vector3DTests.cs
new file mode 100644
index 00000000..500938a8
--- /dev/null
+++ b/src/Numerics.Tests/Spatial/Euclidean3D/Vector3DTests.cs
@@ -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(() => Vector3D.Parse(text));
+ }
+
+ [TestCase("")]
+ [TestCase("")]
+ [TestCase("")]
+ [TestCase("1-23")]
+ [TestCase("-231")]
+ [TestCase("31-2")]
+ 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(() => { 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(() => 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 = @"";
+ AssertXml.XmlRoundTrips(p, xml, (expected, actual) => AssertGeometry.AreEqual(expected, actual));
+ }
+
+ [Test]
+ public void XmlContainerRoundtrip()
+ {
+ var container = new AssertXml.Container
+ {
+ Value1 = new Vector3D(1, 2, 3),
+ Value2 = new Vector3D(4, 5, 6)
+ };
+ var expected = "\r\n" +
+ " \r\n" +
+ " \r\n" +
+ "";
+ 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(@"123")));
+ }
+
+ [Test]
+ public void XmlContainerElements()
+ {
+ var xml = "\r\n" +
+ " 123\r\n" +
+ " 456\r\n" +
+ "";
+ var serializer = new XmlSerializer(typeof(AssertXml.Container));
+ var deserialized = (AssertXml.Container)serializer.Deserialize(new StringReader(xml));
+ AssertGeometry.AreEqual(new Vector3D(1, 2, 3), deserialized.Value1);
+ AssertGeometry.AreEqual(new Vector3D(4, 5, 6), deserialized.Value2);
+ }
+ }
+}