Browse Source

fixes and renamings

Vicente Penades 6 years ago
parent
commit
2387b70c46

+ 1 - 1
src/SharpGLTF.Toolkit/Collections/VertexColumn.cs → src/SharpGLTF.Toolkit/Collections/VertexList.cs

@@ -5,7 +5,7 @@ using System.Text;
 
 
 namespace SharpGLTF.Collections
 namespace SharpGLTF.Collections
 {
 {
-    public class VertexColumn<T> : IReadOnlyList<T>
+    public class VertexList<T> : IReadOnlyList<T>
         where T : struct
         where T : struct
     {
     {
         #region data
         #region data

+ 1 - 1
src/SharpGLTF.Toolkit/Geometry/MeshBuilder.cs

@@ -61,7 +61,7 @@ namespace SharpGLTF.Geometry
 
 
         private readonly TMaterial _Material;
         private readonly TMaterial _Material;
 
 
-        private readonly VertexColumn<(TvP, TvM, TvJ)> _Vertices = new VertexColumn<(TvP, TvM, TvJ)>();
+        private readonly VertexList<(TvP, TvM, TvJ)> _Vertices = new VertexList<(TvP, TvM, TvJ)>();
         private readonly List<int> _Indices = new List<int>();
         private readonly List<int> _Indices = new List<int>();
 
 
         #endregion
         #endregion

+ 53 - 17
src/SharpGLTF.Toolkit/Geometry/StaticMeshBuilder.cs

@@ -6,28 +6,65 @@ using System.Text;
 
 
 namespace SharpGLTF.Geometry
 namespace SharpGLTF.Geometry
 {
 {
+    using VertexTypes;
+
+    /// <summary>
+    /// Represents an utility class to help build meshes by adding primitives associated with a given material.
+    /// </summary>
+    /// <typeparam name="TvP">
+    /// The vertex fragment type with Position, Normal and Tangent.
+    /// Valid types are:
+    /// <see cref="VertexPosition"/>,
+    /// <see cref="VertexPositionNormal"/>,
+    /// <see cref="VertexPositionNormalTangent"/>.
+    /// </typeparam>
+    /// <typeparam name="TvM">
+    /// The vertex fragment type with Colors and Texture Coordinates.
+    /// Valid types are:
+    /// <see cref="VertexEmpty"/>,
+    /// <see cref="VertexColor1"/>,
+    /// <see cref="VertexTexture1"/>,
+    /// <see cref="VertexColor1Texture1"/>.
+    /// </typeparam>
+    /// <typeparam name="TvJ">
+    /// The vertex fragment type with Skin Joint Weights.
+    /// Valid types are:
+    /// <see cref="VertexEmpty"/>,
+    /// <see cref="VertexJoints8x4"/>,
+    /// <see cref="VertexJoints8x8"/>,
+    /// <see cref="VertexJoints16x4"/>,
+    /// <see cref="VertexJoints16x8"/>.
+    /// </typeparam>
+    public class MeshBuilder<TvP, TvM, TvJ> : MeshBuilder<Materials.MaterialBuilder,TvP,TvM,TvJ>
+        where TvP : struct, IVertexPosition
+        where TvM : struct, IVertexMaterial
+        where TvJ : struct, IVertexJoints
+    {
+        public MeshBuilder(string name = null)
+            : base(name) { }
+    }
+
     /// <summary>
     /// <summary>
     /// Represents an utility class to help build meshes by adding primitives associated with a given material.
     /// Represents an utility class to help build meshes by adding primitives associated with a given material.
     /// </summary>
     /// </summary>
-    /// <typeparam name="TMaterial">The material type used by this <see cref="PrimitiveBuilder{TMaterial, TvP, TvM, TvJ}"/> instance.</typeparam>
     /// <typeparam name="TvP">
     /// <typeparam name="TvP">
     /// The vertex fragment type with Position, Normal and Tangent.
     /// The vertex fragment type with Position, Normal and Tangent.
     /// Valid types are:
     /// Valid types are:
-    /// <see cref="VertexTypes.VertexPosition"/>,
-    /// <see cref="VertexTypes.VertexPositionNormal"/>,
-    /// <see cref="VertexTypes.VertexPositionNormalTangent"/>.
+    /// <see cref="VertexPosition"/>,
+    /// <see cref="VertexPositionNormal"/>,
+    /// <see cref="VertexPositionNormalTangent"/>.
     /// </typeparam>
     /// </typeparam>
     /// <typeparam name="TvM">
     /// <typeparam name="TvM">
     /// The vertex fragment type with Colors and Texture Coordinates.
     /// The vertex fragment type with Colors and Texture Coordinates.
     /// Valid types are:
     /// Valid types are:
-    /// <see cref="VertexTypes.VertexEmpty"/>,
-    /// <see cref="VertexTypes.VertexColor1"/>,
-    /// <see cref="VertexTypes.VertexTexture1"/>,
-    /// <see cref="VertexTypes.VertexColor1Texture1"/>.
+    /// <see cref="VertexEmpty"/>,
+    /// <see cref="VertexColor1"/>,
+    /// <see cref="VertexTexture1"/>,
+    /// <see cref="VertexColor1Texture1"/>.
     /// </typeparam>
     /// </typeparam>
-    public class MeshBuilder<TMaterial, TvP, TvM> : MeshBuilder<TMaterial, TvP, TvM, VertexTypes.VertexEmpty>
-        where TvP : struct, VertexTypes.IVertexPosition
-        where TvM : struct, VertexTypes.IVertexMaterial
+    public class MeshBuilder<TvP, TvM> : MeshBuilder<Materials.MaterialBuilder, TvP, TvM, VertexEmpty>
+        where TvP : struct, IVertexPosition
+        where TvM : struct, IVertexMaterial
     {
     {
         public MeshBuilder(string name = null)
         public MeshBuilder(string name = null)
             : base(name) { }
             : base(name) { }
@@ -36,16 +73,15 @@ namespace SharpGLTF.Geometry
     /// <summary>
     /// <summary>
     /// Represents an utility class to help build meshes by adding primitives associated with a given material.
     /// Represents an utility class to help build meshes by adding primitives associated with a given material.
     /// </summary>
     /// </summary>
-    /// <typeparam name="TMaterial">The material type used by this <see cref="PrimitiveBuilder{TMaterial, TvP, TvM, TvJ}"/> instance.</typeparam>
     /// <typeparam name="TvP">
     /// <typeparam name="TvP">
     /// The vertex fragment type with Position, Normal and Tangent.
     /// The vertex fragment type with Position, Normal and Tangent.
     /// Valid types are:
     /// Valid types are:
-    /// <see cref="VertexTypes.VertexPosition"/>,
-    /// <see cref="VertexTypes.VertexPositionNormal"/>,
-    /// <see cref="VertexTypes.VertexPositionNormalTangent"/>.
+    /// <see cref="VertexPosition"/>,
+    /// <see cref="VertexPositionNormal"/>,
+    /// <see cref="VertexPositionNormalTangent"/>.
     /// </typeparam>
     /// </typeparam>
-    public class MeshBuilder<TMaterial, TvP> : MeshBuilder<TMaterial, TvP, VertexTypes.VertexEmpty, VertexTypes.VertexEmpty>
-       where TvP : struct, VertexTypes.IVertexPosition
+    public class MeshBuilder<TvP> : MeshBuilder<Materials.MaterialBuilder, TvP, VertexEmpty, VertexEmpty>
+        where TvP : struct, IVertexPosition
     {
     {
         public MeshBuilder(string name = null)
         public MeshBuilder(string name = null)
             : base(name) { }
             : base(name) { }

+ 1 - 1
src/SharpGLTF.Toolkit/IO/WavefrontWriter.cs

@@ -32,7 +32,7 @@ namespace SharpGLTF.IO
             public BYTES DiffuseTexture;
             public BYTES DiffuseTexture;
         }
         }
 
 
-        private readonly Geometry.MeshBuilder<Material, POSITION, TEXCOORD> _Mesh = new Geometry.MeshBuilder<Material, POSITION, TEXCOORD>();
+        private readonly Geometry.MeshBuilder<Material, POSITION, TEXCOORD, VEMPTY> _Mesh = new Geometry.MeshBuilder<Material, POSITION, TEXCOORD, VEMPTY>();
 
 
         #endregion
         #endregion
 
 

+ 1 - 1
src/SharpGLTF.Toolkit/Schema2/MaterialExtensions.cs

@@ -227,7 +227,7 @@ namespace SharpGLTF.Schema2
         {
         {
             if (srcChannel == null) return;
             if (srcChannel == null) return;
 
 
-            dstChannel.SetFactor(dstChannel.Factor);
+            dstChannel.SetFactor(srcChannel.Factor);
 
 
             var srcTex = srcChannel.Texture;
             var srcTex = srcChannel.Texture;
 
 

+ 4 - 4
tests/SharpGLTF.Tests/Memory/MemoryArrayTests.cs

@@ -59,11 +59,11 @@ namespace SharpGLTF.Memory
 
 
             var v4n = new Vector4Array(bytes, 0, Schema2.EncodingType.UNSIGNED_BYTE, true);
             var v4n = new Vector4Array(bytes, 0, Schema2.EncodingType.UNSIGNED_BYTE, true);
             v4n[1] = v1;
             v4n[1] = v1;
-            VectorAssert.AreEqual(v4n[1], v1, 0.1f);
+            VectorUtils.AreEqual(v4n[1], v1, 0.1f);
 
 
             var v4u = new Vector4Array(bytes, 0, Schema2.EncodingType.UNSIGNED_BYTE, false);
             var v4u = new Vector4Array(bytes, 0, Schema2.EncodingType.UNSIGNED_BYTE, false);
             v4u[1] = v2;
             v4u[1] = v2;
-            VectorAssert.AreEqual(v4u[1], v2);
+            VectorUtils.AreEqual(v4u[1], v2);
         }
         }
 
 
         [Test]
         [Test]
@@ -78,11 +78,11 @@ namespace SharpGLTF.Memory
             var v4u = new Vector4Array(bytes, 4, 5, 8, Schema2.EncodingType.UNSIGNED_BYTE, false);
             var v4u = new Vector4Array(bytes, 4, 5, 8, Schema2.EncodingType.UNSIGNED_BYTE, false);
 
 
             v4n[1] = v1;
             v4n[1] = v1;
-            VectorAssert.AreEqual(v4n[1], v1, 0.1f);
+            VectorUtils.AreEqual(v4n[1], v1, 0.1f);
 
 
             
             
             v4u[1] = v2;
             v4u[1] = v2;
-            VectorAssert.AreEqual(v4u[1], v2);
+            VectorUtils.AreEqual(v4u[1], v2);
         }
         }
     }
     }
 }
 }

+ 50 - 7
tests/SharpGLTF.Tests/Schema2/Authoring/CreateModelTests.cs

@@ -2,13 +2,14 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Numerics;
 using System.Numerics;
 using System.Text;
 using System.Text;
+using System.Linq;
 
 
 using NUnit.Framework;
 using NUnit.Framework;
 
 
-
 namespace SharpGLTF.Schema2.Authoring
 namespace SharpGLTF.Schema2.Authoring
 {
 {
     using Geometry;
     using Geometry;
+    using Materials;
 
 
     using VEMPTY = Geometry.VertexTypes.VertexEmpty;
     using VEMPTY = Geometry.VertexTypes.VertexEmpty;
     using VPOSNRM = Geometry.VertexTypes.VertexPositionNormal;
     using VPOSNRM = Geometry.VertexTypes.VertexPositionNormal;
@@ -16,6 +17,7 @@ namespace SharpGLTF.Schema2.Authoring
     using VCLR1 = Geometry.VertexTypes.VertexColor1;
     using VCLR1 = Geometry.VertexTypes.VertexColor1;
     using VTEX1 = Geometry.VertexTypes.VertexTexture1;
     using VTEX1 = Geometry.VertexTypes.VertexTexture1;
     using VSKIN4 = Geometry.VertexTypes.VertexJoints8x4;
     using VSKIN4 = Geometry.VertexTypes.VertexJoints8x4;
+    
 
 
     [TestFixture]
     [TestFixture]
     public class CreateModelTests
     public class CreateModelTests
@@ -222,7 +224,7 @@ namespace SharpGLTF.Schema2.Authoring
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachGltfValidatorLink();
             TestContext.CurrentContext.AttachGltfValidatorLink();
 
 
-            var meshBuilder = new MeshBuilder<Vector4, VPOSNRM>("mesh1");
+            var meshBuilder = new MeshBuilder<Vector4, VPOSNRM, VEMPTY, VEMPTY>("mesh1");
 
 
             var v1 = new VPOSNRM(-10, 10, 0, -10, 10, 15);
             var v1 = new VPOSNRM(-10, 10, 0, -10, 10, 15);
             var v2 = new VPOSNRM( 10, 10, 0, 10, 10, 15);
             var v2 = new VPOSNRM( 10, 10, 0, 10, 10, 15);
@@ -250,10 +252,10 @@ namespace SharpGLTF.Schema2.Authoring
             TestContext.CurrentContext.AttachGltfValidatorLink();
             TestContext.CurrentContext.AttachGltfValidatorLink();
 
 
             // create several meshes
             // create several meshes
-            var meshBuilder1 = new MeshBuilder<Vector4, VPOSNRM>("mesh1");
-            var meshBuilder2 = new MeshBuilder<Vector4, VPOSNRM>("mesh2");
-            var meshBuilder3 = new MeshBuilder<Vector4, VPOSNRM>("mesh3");
-            var meshBuilder4 = new MeshBuilder<Vector4, VPOSNRM>("mesh4");
+            var meshBuilder1 = new MeshBuilder<Vector4, VPOSNRM, VEMPTY, VEMPTY>("mesh1");
+            var meshBuilder2 = new MeshBuilder<Vector4, VPOSNRM, VEMPTY, VEMPTY>("mesh2");
+            var meshBuilder3 = new MeshBuilder<Vector4, VPOSNRM, VEMPTY, VEMPTY>("mesh3");
+            var meshBuilder4 = new MeshBuilder<Vector4, VPOSNRM, VEMPTY, VEMPTY>("mesh4");
 
 
             meshBuilder1.AddCube(new Vector4(1, 1, 0, 1), Matrix4x4.Identity);
             meshBuilder1.AddCube(new Vector4(1, 1, 0, 1), Matrix4x4.Identity);
             meshBuilder2.AddCube(new Vector4(1, 0, 1, 1), Matrix4x4.Identity);
             meshBuilder2.AddCube(new Vector4(1, 0, 1, 1), Matrix4x4.Identity);
@@ -306,7 +308,7 @@ namespace SharpGLTF.Schema2.Authoring
             };
             };
 
 
             // create a mesh
             // create a mesh
-            var meshBuilder = new MeshBuilder<Vector4, VPOSNRM>("mesh1");
+            var meshBuilder = new MeshBuilder<Vector4, VPOSNRM, VEMPTY, VEMPTY>("mesh1");
             meshBuilder.AddCube(Vector4.One, Matrix4x4.Identity);
             meshBuilder.AddCube(Vector4.One, Matrix4x4.Identity);
             meshBuilder.Validate();
             meshBuilder.Validate();
 
 
@@ -441,6 +443,47 @@ namespace SharpGLTF.Schema2.Authoring
             model.AttachToCurrentTest("terrain.glb");
             model.AttachToCurrentTest("terrain.glb");
         }
         }
 
 
+        [Test]
+        public void CreateRandomCubesScene()
+        {
+            TestContext.CurrentContext.AttachShowDirLink();
+            TestContext.CurrentContext.AttachGltfValidatorLink();
+
+            var rnd = new Random();
+
+            var materials = Enumerable
+                .Range(0, 10)
+                .Select(idx => new MaterialBuilder()
+                .WithChannelColor("BaseColor", new Vector4(rnd.NextVector3(),1)))
+                .ToList();
+
+            // create a mesh
+            var cubes = new MeshBuilder<VPOSNRM>("cube");
+
+            for(int i=0; i < 100; ++i)
+            {
+                var r = rnd.NextVector3() * 5;
+                var m = materials[rnd.Next(0, 10)];
+                var xform = Matrix4x4.CreateFromYawPitchRoll(r.X,r.Y,r.Z) * Matrix4x4.CreateTranslation(rnd.NextVector3() * 25);
+                cubes.AddCube(m, xform);
+            }
+
+            cubes.Validate();
+
+            // create a new gltf model
+            var model = ModelRoot.CreateModel();
+
+            // add all meshes (just one in this case) to the model
+            model.CreateMeshes(cubes);
+
+            // create a scene, a node, and assign the first mesh (the terrain)
+            model.UseScene("Default")
+                .CreateNode().WithMesh(model.LogicalMeshes[0]);
+
+            // save the model as GLB
+            model.AttachToCurrentTest("cubes.glb");
+        }
+
         
         
     }
     }
 }
 }

+ 12 - 10
tests/SharpGLTF.Tests/Schema2/Authoring/SolidMeshUtils.cs

@@ -5,16 +5,16 @@ using System.Text;
 
 
 namespace SharpGLTF.Schema2.Authoring
 namespace SharpGLTF.Schema2.Authoring
 {
 {
-    using Geometry;    
-    
-    using VPOS = Geometry.VertexTypes.VertexPosition;
-    using VTEX1 = Geometry.VertexTypes.VertexTexture1;
+    using Geometry;
 
 
+    using VEMPTY = Geometry.VertexTypes.VertexEmpty;
+    using VPOS = Geometry.VertexTypes.VertexPosition;
     using VPOSNRM = Geometry.VertexTypes.VertexPositionNormal;
     using VPOSNRM = Geometry.VertexTypes.VertexPositionNormal;
+    using VTEX1 = Geometry.VertexTypes.VertexTexture1;    
 
 
     static class SolidMeshUtils
     static class SolidMeshUtils
     {
     {
-        public static void AddCube<TMaterial>(this MeshBuilder<TMaterial, VPOSNRM> meshBuilder, TMaterial material, Matrix4x4 xform)
+        public static void AddCube<TMaterial>(this MeshBuilder<TMaterial, VPOSNRM, VEMPTY, VEMPTY> meshBuilder, TMaterial material, Matrix4x4 xform)
         {
         {
             meshBuilder._AddCubeFace(material, Vector3.UnitX, Vector3.UnitY, Vector3.UnitZ, xform);
             meshBuilder._AddCubeFace(material, Vector3.UnitX, Vector3.UnitY, Vector3.UnitZ, xform);
             meshBuilder._AddCubeFace(material, -Vector3.UnitX, Vector3.UnitZ, Vector3.UnitY, xform);
             meshBuilder._AddCubeFace(material, -Vector3.UnitX, Vector3.UnitZ, Vector3.UnitY, xform);
@@ -26,7 +26,7 @@ namespace SharpGLTF.Schema2.Authoring
             meshBuilder._AddCubeFace(material, -Vector3.UnitZ, Vector3.UnitY, Vector3.UnitX, xform);
             meshBuilder._AddCubeFace(material, -Vector3.UnitZ, Vector3.UnitY, Vector3.UnitX, xform);
         }
         }
 
 
-        private static void _AddCubeFace<TMaterial>(this MeshBuilder<TMaterial, VPOSNRM> meshBuilder, TMaterial material, Vector3 origin, Vector3 axisX, Vector3 axisY, Matrix4x4 xform)
+        private static void _AddCubeFace<TMaterial>(this MeshBuilder<TMaterial, VPOSNRM, VEMPTY, VEMPTY> meshBuilder, TMaterial material, Vector3 origin, Vector3 axisX, Vector3 axisY, Matrix4x4 xform)
         {
         {
             var p1 = Vector3.Transform(origin - axisX - axisY, xform);
             var p1 = Vector3.Transform(origin - axisX - axisY, xform);
             var p2 = Vector3.Transform(origin + axisX - axisY, xform);
             var p2 = Vector3.Transform(origin + axisX - axisY, xform);
@@ -44,7 +44,7 @@ namespace SharpGLTF.Schema2.Authoring
                 );
                 );
         }
         }
 
 
-        public static void AddSphere<TMaterial>(this MeshBuilder<TMaterial, VPOSNRM> meshBuilder, TMaterial material, Single radius, Matrix4x4 xform)
+        public static void AddSphere<TMaterial>(this MeshBuilder<TMaterial, VPOSNRM, VEMPTY, VEMPTY> meshBuilder, TMaterial material, Single radius, Matrix4x4 xform)
         {
         {
             // http://blog.andreaskahler.com/2009/06/creating-icosphere-mesh-in-code.html
             // http://blog.andreaskahler.com/2009/06/creating-icosphere-mesh-in-code.html
 
 
@@ -94,7 +94,7 @@ namespace SharpGLTF.Schema2.Authoring
             meshBuilder._AddSphereTriangle(material, xform, v9, v8, v1, 3);
             meshBuilder._AddSphereTriangle(material, xform, v9, v8, v1, 3);
         }
         }
 
 
-        private static void _AddSphereTriangle<TMaterial>(this MeshBuilder<TMaterial, VPOSNRM> meshBuilder, TMaterial material, Matrix4x4 xform, Vector3 a, Vector3 b, Vector3 c, int iterations = 0)
+        private static void _AddSphereTriangle<TMaterial>(this MeshBuilder<TMaterial, VPOSNRM, VEMPTY, VEMPTY> meshBuilder, TMaterial material, Matrix4x4 xform, Vector3 a, Vector3 b, Vector3 c, int iterations = 0)
         {
         {
             if (iterations <=0)
             if (iterations <=0)
             {
             {
@@ -121,14 +121,14 @@ namespace SharpGLTF.Schema2.Authoring
             _AddSphereTriangle(meshBuilder, material, xform, c, ca, bc, iterations);
             _AddSphereTriangle(meshBuilder, material, xform, c, ca, bc, iterations);
         }
         }
 
 
-        public static MeshBuilder<Materials.MaterialBuilder, VPOS, VTEX1> CreateTerrainMesh(int width, int length, Func<int,int,float> heightFunction, string terrainColorImagePath)
+        public static MeshBuilder<VPOS, VTEX1> CreateTerrainMesh(int width, int length, Func<int,int,float> heightFunction, string terrainColorImagePath)
         {
         {
             // we create a new material to use with the terrain mesh
             // we create a new material to use with the terrain mesh
             var material = new Materials.MaterialBuilder("TerrainMaterial")
             var material = new Materials.MaterialBuilder("TerrainMaterial")
                 .WithChannelTexture("BaseColor", 0, terrainColorImagePath);
                 .WithChannelTexture("BaseColor", 0, terrainColorImagePath);
 
 
             // we create a MeshBuilder
             // we create a MeshBuilder
-            var terrainMesh = new MeshBuilder<Materials.MaterialBuilder, VPOS, VTEX1>("terrain");
+            var terrainMesh = new MeshBuilder<VPOS, VTEX1>("terrain");
 
 
             var texScale = new Vector2(width, length);
             var texScale = new Vector2(width, length);
 
 
@@ -163,6 +163,8 @@ namespace SharpGLTF.Schema2.Authoring
                 }
                 }
             }
             }
 
 
+            terrainMesh.Validate();
+
             return terrainMesh;
             return terrainMesh;
         }
         }
     }
     }

+ 0 - 20
tests/SharpGLTF.Tests/VectorAssert.cs

@@ -1,20 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Numerics;
-using System.Text;
-
-using NUnit.Framework;
-
-namespace SharpGLTF
-{
-    public static class VectorAssert
-    {
-        public static void AreEqual(Vector4 a, Vector4 b, double delta = 0)
-        {
-            Assert.AreEqual(a.X, b.X, delta);
-            Assert.AreEqual(a.Y, b.Y, delta);
-            Assert.AreEqual(a.Z, b.Z, delta);
-            Assert.AreEqual(a.W, b.W, delta);
-        }
-    }
-}

+ 40 - 0
tests/SharpGLTF.Tests/VectorUtils.cs

@@ -0,0 +1,40 @@
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+using System.Text;
+
+using NUnit.Framework;
+
+namespace SharpGLTF
+{
+    public static class VectorUtils
+    {
+        public static Single NextSingle(this Random rnd)
+        {
+            return (Single)rnd.NextDouble();
+        }
+
+        public static Vector2 NextVector2(this Random rnd)
+        {
+            return new Vector2(rnd.NextSingle(), rnd.NextSingle());
+        }
+
+        public static Vector3 NextVector3(this Random rnd)
+        {
+            return new Vector3(rnd.NextSingle(), rnd.NextSingle(), rnd.NextSingle());
+        }
+
+        public static Vector4 NextVector4(this Random rnd)
+        {
+            return new Vector4(rnd.NextSingle(), rnd.NextSingle(), rnd.NextSingle(), rnd.NextSingle());
+        }
+
+        public static void AreEqual(Vector4 a, Vector4 b, double delta = 0)
+        {
+            Assert.AreEqual(a.X, b.X, delta);
+            Assert.AreEqual(a.Y, b.Y, delta);
+            Assert.AreEqual(a.Z, b.Z, delta);
+            Assert.AreEqual(a.W, b.W, delta);
+        }
+    }
+}