First pass increasing test coverage (#209)
* point tests * Mesh Tests and removal of unused functions * Vector tests * more tests
This commit is contained in:
+1
-2
@@ -254,8 +254,7 @@ dotnet_diagnostic.ca1506.severity = warning # Avoid excessive class coupling
|
||||
dotnet_diagnostic.ca1507.severity = warning # Use nameof in place of string
|
||||
dotnet_diagnostic.ca1508.severity = warning # Avoid dead conditional code
|
||||
dotnet_diagnostic.ca1509.severity = warning # Invalid entry in code metrics configuration file
|
||||
dotnet_diagnostic.ca1861.severity = none # Prefer 'static readonly' fields over constant array arguments if the called method is called repeatedly and is not mutating the passed array (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861)
|
||||
|
||||
dotnet_diagnostic.ca1861.severity = suggestion # Prefer 'static readonly' fields over constant array arguments if the called method is called repeatedly and is not mutating the passed array (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861)
|
||||
|
||||
|
||||
# Performance rules
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=QL/@EntryIndexedValue">QL</s:String></wpf:ResourceDictionary>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=QL/@EntryIndexedValue">QL</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=XYZ/@EntryIndexedValue">XYZ</s:String></wpf:ResourceDictionary>
|
||||
@@ -1,24 +1,46 @@
|
||||
using System.Diagnostics.Contracts;
|
||||
using Speckle.Newtonsoft.Json;
|
||||
using Speckle.Objects.Other;
|
||||
using Speckle.Objects.Utils;
|
||||
using Speckle.Sdk;
|
||||
using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Geometry;
|
||||
|
||||
/// <remarks><a href="https://speckle.notion.site/Objects-Geometry-Mesh-9b0bf5ab92bf42f58bf2fe3922d2efca">More docs on notion</a></remarks>
|
||||
[SpeckleType("Objects.Geometry.Mesh")]
|
||||
public class Mesh : Base, IHasBoundingBox, IHasVolume, IHasArea, ITransformable<Mesh>
|
||||
{
|
||||
/// <summary>
|
||||
/// Flat list of vertex data (flat <c>x,y,z,x,y,z...</c> list)
|
||||
/// </summary>
|
||||
[DetachProperty, Chunkable(31250)]
|
||||
public required List<double> vertices { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Flat list of face data<br/>
|
||||
/// Each face starts with the length of the face (e.g. 3 in the case of triangles), followed by that many indices
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// N-gons are supported, but large values of n (> ~50) tend to cause significant performance problems for consumers (e.g. HostApps and <see cref="MeshTriangulationHelper"/>.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code>[
|
||||
/// 3, 0, 1, 2, //first face, a triangle (3-gon)
|
||||
/// 4, 1, 2, 3, 4, //second face, a quad (4-gon)
|
||||
/// 6, 4, 5, 6, 7, 8, 9, //third face, an n-gon (6-gon)
|
||||
/// ];</code></example>
|
||||
[DetachProperty, Chunkable(62500)]
|
||||
public required List<int> faces { get; set; }
|
||||
|
||||
/// <summary> Vertex colors as ARGB <see cref="int"/>s</summary>
|
||||
/// <summary>Vertex colors as ARGB <see cref="int"/>s</summary>
|
||||
/// <remarks>Expected that there are either 1 color per vertex, or an empty <see cref="List{T}"/></remarks>
|
||||
[DetachProperty, Chunkable(62500)]
|
||||
public List<int> colors { get; set; } = new();
|
||||
|
||||
/// <summary>Flat list of texture coordinates (flat <c>u,v,u,v,u,v...</c> list)</summary>
|
||||
/// <remarks>Expected that there are either 1 texture coordinate per vertex, or an empty <see cref="List{T}"/></remarks>
|
||||
[DetachProperty, Chunkable(31250)]
|
||||
public List<double> textureCoordinates { get; set; } = new();
|
||||
|
||||
@@ -85,8 +107,6 @@ public class Mesh : Base, IHasBoundingBox, IHasVolume, IHasArea, ITransformable<
|
||||
return res;
|
||||
}
|
||||
|
||||
#region Convenience Methods
|
||||
|
||||
[JsonIgnore]
|
||||
public int VerticesCount => vertices.Count / 3;
|
||||
|
||||
@@ -98,6 +118,8 @@ public class Mesh : Base, IHasBoundingBox, IHasVolume, IHasArea, ITransformable<
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the vertex</param>
|
||||
/// <returns>Vertex as a <see cref="Point"/></returns>
|
||||
/// <remarks>It is usually recommended to instead consume the <see cref="vertices"/> list manually for better performance</remarks>
|
||||
[Pure]
|
||||
public Point GetPoint(int index)
|
||||
{
|
||||
index *= 3;
|
||||
@@ -106,6 +128,8 @@ public class Mesh : Base, IHasBoundingBox, IHasVolume, IHasArea, ITransformable<
|
||||
|
||||
/// <returns><see cref="vertices"/> as list of <see cref="Point"/>s</returns>
|
||||
/// <exception cref="SpeckleException">when list is malformed</exception>
|
||||
/// <remarks>It is usually recommended to instead consume the <see cref="vertices"/> list manually for better performance</remarks>
|
||||
[Pure]
|
||||
public List<Point> GetPoints()
|
||||
{
|
||||
if (vertices.Count % 3 != 0)
|
||||
@@ -129,78 +153,10 @@ public class Mesh : Base, IHasBoundingBox, IHasVolume, IHasArea, ITransformable<
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the texture coordinate</param>
|
||||
/// <returns>Texture coordinate as a <see cref="ValueTuple{T1, T2}"/></returns>
|
||||
[Pure]
|
||||
public (double, double) GetTextureCoordinate(int index)
|
||||
{
|
||||
index *= 2;
|
||||
return (textureCoordinates[index], textureCoordinates[index + 1]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If not already so, this method will align <see cref="Mesh.vertices"/>
|
||||
/// such that a vertex and its corresponding texture coordinates have the same index.
|
||||
/// This alignment is what is expected by most applications.<br/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the calling application expects
|
||||
/// <code>vertices.count == textureCoordinates.count</code>
|
||||
/// Then this method should be called by the <c>MeshToNative</c> method before parsing <see cref="Mesh.vertices"/> and <see cref="Mesh.faces"/>
|
||||
/// to ensure compatibility with geometry originating from applications that map <see cref="Mesh.vertices"/> to <see cref="Mesh.textureCoordinates"/> using vertex instance index (rather than vertex index)
|
||||
/// <br/>
|
||||
/// <see cref="Mesh.vertices"/>, <see cref="Mesh.colors"/>, and <see cref="faces"/> lists will be modified to contain no shared vertices (vertices shared between polygons)
|
||||
/// </remarks>
|
||||
public void AlignVerticesWithTexCoordsByIndex()
|
||||
{
|
||||
if (textureCoordinates.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (TextureCoordinatesCount == VerticesCount)
|
||||
{
|
||||
return; //Tex-coords already aligned as expected
|
||||
}
|
||||
|
||||
var facesUnique = new List<int>(faces.Count);
|
||||
var verticesUnique = new List<double>(TextureCoordinatesCount * 3);
|
||||
bool hasColors = colors.Count > 0;
|
||||
var colorsUnique = hasColors ? new List<int>(TextureCoordinatesCount) : null;
|
||||
|
||||
int nIndex = 0;
|
||||
while (nIndex < faces.Count)
|
||||
{
|
||||
int n = faces[nIndex];
|
||||
if (n < 3)
|
||||
{
|
||||
n += 3; // 0 -> 3, 1 -> 4
|
||||
}
|
||||
|
||||
if (nIndex + n >= faces.Count)
|
||||
{
|
||||
break; //Malformed face list
|
||||
}
|
||||
|
||||
facesUnique.Add(n);
|
||||
for (int i = 1; i <= n; i++)
|
||||
{
|
||||
int vertIndex = faces[nIndex + i];
|
||||
int newVertIndex = verticesUnique.Count / 3;
|
||||
|
||||
var (x, y, z) = GetPoint(vertIndex);
|
||||
verticesUnique.Add(x);
|
||||
verticesUnique.Add(y);
|
||||
verticesUnique.Add(z);
|
||||
|
||||
colorsUnique?.Add(colors[vertIndex]);
|
||||
facesUnique.Add(newVertIndex);
|
||||
}
|
||||
|
||||
nIndex += n + 1;
|
||||
}
|
||||
|
||||
vertices = verticesUnique;
|
||||
colors = colorsUnique ?? colors;
|
||||
faces = facesUnique;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -86,15 +86,9 @@ public class Vector : Base, IHasBoundingBox, ITransformable<Vector>
|
||||
/// Returns the coordinates of this <see cref="Vector"/> as a list of numbers
|
||||
/// </summary>
|
||||
/// <returns>A list of coordinates {x, y, z} </returns>
|
||||
public List<double> ToList()
|
||||
{
|
||||
return new List<double> { x, y, z };
|
||||
}
|
||||
public List<double> ToList() => [x, y, z];
|
||||
|
||||
public Point ToPoint()
|
||||
{
|
||||
return new Point(x, y, z, units, applicationId);
|
||||
}
|
||||
public Point ToPoint() => new(x, y, z, units, applicationId);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new vector based on a list of coordinates and the unit they're drawn in.
|
||||
@@ -176,11 +170,6 @@ public class Vector : Base, IHasBoundingBox, ITransformable<Vector>
|
||||
return new Vector(x, y, z, units: u.units);
|
||||
}
|
||||
|
||||
public static double Angle(Vector u, Vector v)
|
||||
{
|
||||
return Math.Acos(DotProduct(u, v) / (u.Length * v.Length));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute and return a unit vector from this vector
|
||||
/// </summary>
|
||||
@@ -205,23 +194,6 @@ public class Vector : Base, IHasBoundingBox, ITransformable<Vector>
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a normalized copy of this vector.
|
||||
/// </summary>
|
||||
/// <returns>A copy of this vector unitized.</returns>
|
||||
public Vector Unit()
|
||||
{
|
||||
return this / Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new <see cref="Vector"/> from a <see cref="Point"/>
|
||||
/// </summary>
|
||||
/// <param name="point">The point whose coordinates will be used</param>
|
||||
/// <param name="applicationId">The unique application ID of the object.</param>
|
||||
[Obsolete($"Use {nameof(Point.ToVector)}", true)]
|
||||
public Vector(Point point, string? applicationId = null) { }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the coordinates of the vector
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Diagnostics.Contracts;
|
||||
using Speckle.Sdk.Dependencies;
|
||||
|
||||
namespace Speckle.Sdk.Common;
|
||||
|
||||
@@ -19,7 +20,7 @@ public static class Units
|
||||
/// <summary>US Survey foot, now not supported by Speckle, kept privately for backwards compatibility</summary>
|
||||
private const string USFeet = "us_ft";
|
||||
|
||||
internal static readonly List<string> SupportedUnits = new()
|
||||
internal static readonly IReadOnlyCollection<string> SupportedUnits = new[]
|
||||
{
|
||||
Millimeters,
|
||||
Centimeters,
|
||||
@@ -30,7 +31,7 @@ public static class Units
|
||||
Yards,
|
||||
Miles,
|
||||
None,
|
||||
};
|
||||
}.Freeze();
|
||||
|
||||
/// <param name="unit"></param>
|
||||
/// <returns><see langword="true"/> if <paramref name="unit"/> is a recognised/supported unit string, otherwise <see langword="false"/></returns>
|
||||
|
||||
@@ -6,22 +6,7 @@ namespace Speckle.Objects.Tests.Unit.Geometry;
|
||||
|
||||
public class MeshTests
|
||||
{
|
||||
private static readonly Mesh[] TestCaseSource = { CreateBlenderStylePolygon(), CreateRhinoStylePolygon() };
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(GetTestCaseSource))]
|
||||
public void CanAlignVertices(Mesh inPolygon)
|
||||
{
|
||||
inPolygon.AlignVerticesWithTexCoordsByIndex();
|
||||
|
||||
inPolygon.VerticesCount.Should().Be(inPolygon.TextureCoordinatesCount);
|
||||
|
||||
var expectedPolygon = CreateRhinoStylePolygon();
|
||||
|
||||
inPolygon.vertices.Should().BeEquivalentTo(expectedPolygon.vertices);
|
||||
inPolygon.faces.Should().BeEquivalentTo(expectedPolygon.faces);
|
||||
inPolygon.textureCoordinates.Should().BeEquivalentTo(expectedPolygon.textureCoordinates);
|
||||
}
|
||||
private static readonly Mesh[] TestCaseSource = { CreateRhinoStylePolygon(), CreateEmpty() };
|
||||
|
||||
public static IEnumerable<object[]> GetTestCaseSource() => TestCaseSource.Select(mesh => new object[] { mesh });
|
||||
|
||||
@@ -36,14 +21,51 @@ public class MeshTests
|
||||
};
|
||||
}
|
||||
|
||||
private static Mesh CreateBlenderStylePolygon()
|
||||
private static Mesh CreateEmpty()
|
||||
{
|
||||
return new Mesh
|
||||
{
|
||||
vertices = new List<double> { 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0 },
|
||||
faces = new List<int> { 3, 0, 1, 2, 3, 0, 2, 3 },
|
||||
textureCoordinates = new List<double> { 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0 },
|
||||
vertices = [],
|
||||
faces = [],
|
||||
textureCoordinates = [],
|
||||
units = Units.Meters,
|
||||
};
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(GetTestCaseSource))]
|
||||
public void GetTextureCoordinate_ReturnsCorrectUVValue(Mesh testCase)
|
||||
{
|
||||
for (int i = 0, j = 0; i < testCase.textureCoordinates.Count; i += 2, j++)
|
||||
{
|
||||
var (u, v) = testCase.GetTextureCoordinate(j);
|
||||
|
||||
u.Should().Be(testCase.textureCoordinates[i]);
|
||||
v.Should().Be(testCase.textureCoordinates[i + 1]);
|
||||
}
|
||||
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => testCase.GetTextureCoordinate(testCase.textureCoordinates.Count));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(GetTestCaseSource))]
|
||||
public void GetPoints_ReturnsVerticesAsPoints(Mesh testCase)
|
||||
{
|
||||
testCase.VerticesCount.Should().Be(testCase.vertices.Count / 3);
|
||||
|
||||
var getPoints = testCase.GetPoints();
|
||||
var getPoint = Enumerable.Range(0, testCase.VerticesCount).Select(i => testCase.GetPoint(i));
|
||||
|
||||
//Test each point has correct units
|
||||
getPoints.Select(x => x.units).Should().AllBe(testCase.units).And.HaveCount(testCase.VerticesCount);
|
||||
getPoints.Select(x => x.units).Should().AllBe(testCase.units).And.HaveCount(testCase.VerticesCount);
|
||||
|
||||
//Convert back to flat list
|
||||
var expected = testCase.vertices;
|
||||
var getPointsList = getPoints.SelectMany(x => x.ToList());
|
||||
var getPointList = getPoint.SelectMany(x => x.ToList());
|
||||
|
||||
getPointsList.Should().BeEquivalentTo(expected);
|
||||
getPointList.Should().BeEquivalentTo(expected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using FluentAssertions;
|
||||
using Speckle.DoubleNumerics;
|
||||
using Speckle.Objects.Geometry;
|
||||
using Speckle.Objects.Other;
|
||||
using Speckle.Sdk.Common;
|
||||
|
||||
namespace Speckle.Objects.Tests.Unit.Geometry;
|
||||
@@ -64,4 +66,131 @@ public class PointTests
|
||||
|
||||
(p1 == p2).Should().Be(expectedResult);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestDistanceTo()
|
||||
{
|
||||
//Arrange
|
||||
var p1 = new Point(1, 0, 0, units: Units.Meters);
|
||||
var p2 = new Point(0, 0, 0, units: Units.Meters);
|
||||
|
||||
//Act
|
||||
var result = p1.DistanceTo(p2);
|
||||
|
||||
//Assert
|
||||
result.Should().Be(1);
|
||||
}
|
||||
|
||||
private static IReadOnlyList<Matrix4x4> MatrixTestData =>
|
||||
[
|
||||
Matrix4x4.Identity,
|
||||
Matrix4x4.CreateScale(1, 2, 3),
|
||||
Matrix4x4.CreateTranslation(100, 10, 0),
|
||||
Matrix4x4.CreateRotationZ(1),
|
||||
];
|
||||
|
||||
private static IReadOnlyList<Point> PointTestData =>
|
||||
[
|
||||
new(1, 2, 3, Units.Meters),
|
||||
new(0.5, 100.5, 123.123, Units.Meters),
|
||||
new(1, 2, 3, Units.Meters, applicationId: "Test me!"),
|
||||
new(0, 0, 0, Units.Feet),
|
||||
];
|
||||
|
||||
public static TheoryData<Point> PointTestCases() => new(PointTestData);
|
||||
|
||||
public static TheoryData<Matrix4x4, Point> TransformTestCases()
|
||||
{
|
||||
TheoryData<Matrix4x4, Point> testCases = new();
|
||||
for (int i = 0; i < PointTestData.Count; i++)
|
||||
{
|
||||
testCases.Add(MatrixTestData[i], PointTestData[i]);
|
||||
}
|
||||
return testCases;
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(TransformTestCases))]
|
||||
public void TransformPoint_SameUnits(Matrix4x4 matrix, Point point)
|
||||
{
|
||||
//Arrange
|
||||
Transform t = new() { matrix = Matrix4x4.Transpose(matrix), units = point.units };
|
||||
|
||||
Vector3 expectedVector = Vector3.Transform(new(point.x, point.y, point.z), matrix);
|
||||
var expectedResult = (expectedVector.X, expectedVector.Y, expectedVector.Z);
|
||||
|
||||
//Act
|
||||
point.TransformTo(t, out Point transformedPoint);
|
||||
var actualResult = (transformedPoint.x, transformedPoint.y, transformedPoint.z);
|
||||
|
||||
//Assert
|
||||
actualResult.Should().Be(expectedResult);
|
||||
transformedPoint.applicationId.Should().Be(point.applicationId);
|
||||
|
||||
transformedPoint.applicationId.Should().Be(point.applicationId);
|
||||
transformedPoint.id.Should().Be(point.id);
|
||||
transformedPoint.units.Should().Be(point.units);
|
||||
}
|
||||
|
||||
[Fact(Skip = "Something clearly wrong with units!!!")]
|
||||
public void TransformingPoint_ChangeOfUnits()
|
||||
{
|
||||
//Arrange
|
||||
Point point = new(0, 0, 10, Units.Meters);
|
||||
Transform t = new()
|
||||
{
|
||||
matrix = Matrix4x4.Transpose(Matrix4x4.CreateTranslation(1000, 0, 0)),
|
||||
units = Units.Millimeters,
|
||||
};
|
||||
Vector3 expected = new(1, 0, 10);
|
||||
|
||||
//Act
|
||||
point.TransformTo(t, out Point transformedPoint);
|
||||
|
||||
//Assert
|
||||
(double x, double y, double z) = transformedPoint;
|
||||
transformedPoint.units.Should().Be(point.units);
|
||||
transformedPoint.applicationId.Should().Be(point.applicationId);
|
||||
|
||||
new Vector3(x, y, z).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(PointTestCases))]
|
||||
public void ToVector(Point testCase)
|
||||
{
|
||||
var expectedXYZ = (testCase.x, testCase.y, testCase.z);
|
||||
var expectedUnits = testCase.units;
|
||||
var expectedApplicationId = testCase.applicationId;
|
||||
|
||||
var asVector = testCase.ToVector();
|
||||
var resultXYZ = (asVector.x, asVector.y, asVector.z);
|
||||
|
||||
resultXYZ.Should().Be(expectedXYZ);
|
||||
asVector.units.Should().Be(expectedUnits);
|
||||
asVector.applicationId.Should().Be(expectedApplicationId);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(PointTestCases))]
|
||||
public void Deconstruct_Double_Double_Double_String(Point testCase)
|
||||
{
|
||||
(double x, double y, double z, string? units) = testCase;
|
||||
|
||||
x.Should().Be(testCase.x);
|
||||
y.Should().Be(testCase.y);
|
||||
z.Should().Be(testCase.z);
|
||||
units.Should().Be(testCase.units);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(PointTestCases))]
|
||||
public void Deconstruct_Double_Double_Double(Point testCase)
|
||||
{
|
||||
(double x, double y, double z) = testCase;
|
||||
|
||||
x.Should().Be(testCase.x);
|
||||
y.Should().Be(testCase.y);
|
||||
z.Should().Be(testCase.z);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
using FluentAssertions;
|
||||
using Speckle.DoubleNumerics;
|
||||
using Speckle.Objects.Geometry;
|
||||
|
||||
namespace Speckle.Objects.Tests.Unit.Geometry;
|
||||
|
||||
public class VectorTests
|
||||
{
|
||||
private const float FLOAT_TOLERANCE = 1e-6f;
|
||||
|
||||
public static TheoryData<double, double, double, string> TestCases() =>
|
||||
new()
|
||||
{
|
||||
{ 0d, 0d, 0d, "m" },
|
||||
{ 1d, 2d, 3d, "ft" },
|
||||
{ 0d, 0d, -1d, "km" },
|
||||
{ 100d, 0d, -200d, "in" },
|
||||
{ 123.123d, 456.456d, 5789.789d, "cm" },
|
||||
{ -123.123d, -456.456d, -5789.789d, "mm" },
|
||||
};
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(TestCases))]
|
||||
public void Constructors_AreTheSame(double x, double y, double z, string units)
|
||||
{
|
||||
const string appId = "asdfasdfasdf";
|
||||
var pctor = new Vector(x, y, z, units, applicationId: appId);
|
||||
|
||||
pctor.x.Should().Be(x);
|
||||
pctor.y.Should().Be(y);
|
||||
pctor.z.Should().Be(z);
|
||||
pctor.units.Should().Be(units);
|
||||
pctor.applicationId.Should().Be(appId);
|
||||
|
||||
var init = new Vector
|
||||
{
|
||||
x = x,
|
||||
y = y,
|
||||
z = z,
|
||||
units = units,
|
||||
applicationId = appId,
|
||||
};
|
||||
|
||||
Assert.Equal(pctor.x, init.x);
|
||||
Assert.Equal(pctor.y, init.y);
|
||||
Assert.Equal(pctor.z, init.z);
|
||||
Assert.Equal(pctor.units, init.units);
|
||||
Assert.Equal(pctor.applicationId, init.applicationId);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(1d, 0d, 0d, 1d)]
|
||||
[InlineData(0d, 2d, 0d, 2d)]
|
||||
[InlineData(0d, 0d, -3d, 3d)]
|
||||
[InlineData(1d, 1d, 0d, 1.4142135623730951d)]
|
||||
public void LengthCalculated(double x, double y, double z, double expected)
|
||||
{
|
||||
var testCase = new Vector(x, y, z, "");
|
||||
var actual = testCase.Length;
|
||||
actual.Should().Be(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(TestCases))]
|
||||
public void EncodingToAndFromList(double x, double y, double z, string units)
|
||||
{
|
||||
var testCase = new Vector(x, y, z, units);
|
||||
|
||||
var encoded = testCase.ToList();
|
||||
encoded.Should().BeEquivalentTo([x, y, z]);
|
||||
|
||||
const string NEW_UNIT = "something different...";
|
||||
var decoded = Vector.FromList(encoded, NEW_UNIT);
|
||||
|
||||
decoded.x.Should().Be(x);
|
||||
decoded.y.Should().Be(y);
|
||||
decoded.z.Should().Be(z);
|
||||
decoded.units.Should().Be(NEW_UNIT);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(TestCases))]
|
||||
public void ToPoint(double x, double y, double z, string units)
|
||||
{
|
||||
var testCase = new Vector(x, y, z, units, "asdfasdf");
|
||||
|
||||
var asPoint = testCase.ToPoint();
|
||||
|
||||
asPoint.x.Should().Be(x);
|
||||
asPoint.y.Should().Be(y);
|
||||
asPoint.z.Should().Be(z);
|
||||
asPoint.units.Should().Be(units);
|
||||
asPoint.applicationId.Should().Be("asdfasdf");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(TestCases))]
|
||||
public void Normalize(double x, double y, double z, string units)
|
||||
{
|
||||
var sut = new Vector(x, y, z, units);
|
||||
var originalLength = sut.Length;
|
||||
sut.Normalize();
|
||||
|
||||
if (!(originalLength > 0))
|
||||
{
|
||||
sut.Length.Should().Be(double.NaN);
|
||||
return;
|
||||
}
|
||||
|
||||
sut.Length.Should().BeApproximately(1, FLOAT_TOLERANCE);
|
||||
|
||||
var rescaled = sut * originalLength;
|
||||
|
||||
rescaled.x.Should().BeApproximately(x, FLOAT_TOLERANCE);
|
||||
rescaled.y.Should().BeApproximately(y, FLOAT_TOLERANCE);
|
||||
rescaled.z.Should().BeApproximately(z, FLOAT_TOLERANCE);
|
||||
rescaled.units.Should().Be(units);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(TestCases))]
|
||||
public void Negate(double x, double y, double z, string units)
|
||||
{
|
||||
var sut = new Vector(x, y, z, units);
|
||||
var originalLength = sut.Length;
|
||||
sut.Negate();
|
||||
|
||||
sut.Length.Should().Be(originalLength);
|
||||
var rescaled = sut.Negate();
|
||||
|
||||
rescaled.x.Should().BeApproximately(x, FLOAT_TOLERANCE);
|
||||
rescaled.y.Should().BeApproximately(y, FLOAT_TOLERANCE);
|
||||
rescaled.z.Should().BeApproximately(z, FLOAT_TOLERANCE);
|
||||
rescaled.units.Should().Be(units);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(TestCases))]
|
||||
public void TestAddition(double x, double y, double z, string units)
|
||||
{
|
||||
var operand1 = new Vector(x, y, z, units);
|
||||
var operand2 = new Vector(x, y, z, units);
|
||||
|
||||
var result = operand1 + operand2;
|
||||
double[] expected = [x + x, y + y, z + z];
|
||||
|
||||
result.ToList().Should().BeEquivalentTo(expected);
|
||||
result.units.Should().BeEquivalentTo(units);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(TestCases))]
|
||||
public void TestSubtraction(double x, double y, double z, string units)
|
||||
{
|
||||
var operand1 = new Vector(x, y, z, units);
|
||||
var operand2 = new Vector(x, y, z, units);
|
||||
|
||||
var result = operand1 - operand2;
|
||||
double[] expected = [x - x, y - y, z - z];
|
||||
|
||||
result.ToList().Should().BeEquivalentTo(expected);
|
||||
result.units.Should().BeEquivalentTo(units);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(TestCases))]
|
||||
public void TestDivision(double x, double y, double z, string units)
|
||||
{
|
||||
var operand1 = new Vector(x, y, z, units);
|
||||
const int OPERAND2 = 2;
|
||||
|
||||
var result = operand1 / OPERAND2;
|
||||
double[] expected = [x / OPERAND2, y / OPERAND2, z / OPERAND2];
|
||||
|
||||
result.ToList().Should().BeEquivalentTo(expected);
|
||||
result.units.Should().BeEquivalentTo(units);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(TestCases))]
|
||||
public void TestMultiplication(double x, double y, double z, string units)
|
||||
{
|
||||
var operand1 = new Vector(x, y, z, units);
|
||||
const int OPERAND2 = 2;
|
||||
|
||||
var result = operand1 * OPERAND2;
|
||||
double[] expected = [x * OPERAND2, y * OPERAND2, z * OPERAND2];
|
||||
|
||||
result.ToList().Should().BeEquivalentTo(expected);
|
||||
result.units.Should().BeEquivalentTo(units);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(TestCases))]
|
||||
public void TestDotProduct(double x, double y, double z, string units)
|
||||
{
|
||||
var operand1 = new Vector(x, y, z, units);
|
||||
var operand2 = new Vector(x, y, z, units);
|
||||
|
||||
var result = Vector.DotProduct(operand1, operand2);
|
||||
double expected = Vector3.Dot(new Vector3(x, y, z), new Vector3(x, y, z));
|
||||
|
||||
result.Should().BeApproximately(expected, FLOAT_TOLERANCE);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(TestCases))]
|
||||
public void TestCrossProduct(double x, double y, double z, string units)
|
||||
{
|
||||
var operand1 = new Vector(x, y, z, units);
|
||||
var operand2 = new Vector(x, y, z, units);
|
||||
|
||||
var result = Vector.CrossProduct(operand1, operand2);
|
||||
var expected = Vector3.Cross(new Vector3(x, y, z), new Vector3(x, y, z));
|
||||
|
||||
result.x.Should().BeApproximately(expected.X, FLOAT_TOLERANCE);
|
||||
result.y.Should().BeApproximately(expected.Y, FLOAT_TOLERANCE);
|
||||
result.z.Should().BeApproximately(expected.Z, FLOAT_TOLERANCE);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(TestCases))]
|
||||
[Obsolete("Tests Obsolete legacy behaviour to maintain backwards json compatibility with ~2.13? data")]
|
||||
public void TestLegacyValueProp(double x, double y, double z, string _)
|
||||
{
|
||||
var vector = Activator.CreateInstance<Vector>();
|
||||
vector.value = [x, y, z];
|
||||
|
||||
vector.x.Should().Be(x);
|
||||
vector.y.Should().Be(y);
|
||||
vector.z.Should().Be(z);
|
||||
}
|
||||
}
|
||||
@@ -8,11 +8,9 @@ public class UnitsTest
|
||||
{
|
||||
private const double EPS = 0.00022;
|
||||
|
||||
public static List<string> OfficiallySupportedUnits => Units.SupportedUnits;
|
||||
|
||||
public static List<string> NotSupportedUnits => ["feeters", "liters", "us_ft"];
|
||||
|
||||
public static List<string?> ConversionSupport => Units.SupportedUnits.Concat([null]).ToList();
|
||||
public static IReadOnlyCollection<string> OfficiallySupportedUnits => Units.SupportedUnits;
|
||||
public static IReadOnlyCollection<string> NotSupportedUnits => ["feeters", "liters", "us_ft"];
|
||||
public static IReadOnlyCollection<string?> ConversionSupport => [.. Units.SupportedUnits, null];
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConversionSupportGenerator))]
|
||||
|
||||
Reference in New Issue
Block a user