Procházet zdrojové kódy

more skinning improvements

Vicente Penades před 6 roky
rodič
revize
810d3dbc8f

binární
examples/Example1.glb


binární
examples/InfiniteSkinnedTentacle.glb


+ 37 - 14
examples/InfiniteSkinnedTentacle/Program.cs

@@ -38,8 +38,7 @@ namespace InfiniteSkinnedTentacle
         // │   ├── Bone2
         // │   └── Bone3
         // └── SkinnedMesh3─> Mesh1, Skin3
-
-        private static readonly Random _Randomizer = new Random(17);
+        // ...
 
         static void Main(string[] args)
         {
@@ -50,27 +49,43 @@ namespace InfiniteSkinnedTentacle
                 .CreateMeshes(CreateMesh(10))
                 .First();
 
-            AddTentacleSkeleton(scene, mesh, Matrix4x4.CreateTranslation(-100, 0, 0), Quaternion.CreateFromYawPitchRoll(0f, 0.2f, 0f));
-            AddTentacleSkeleton(scene, mesh, Matrix4x4.CreateTranslation(   0, 0, 0), Quaternion.CreateFromYawPitchRoll(0.2f, 0f, 0f));
-            AddTentacleSkeleton(scene, mesh, Matrix4x4.CreateTranslation( 100, 0, 0), Quaternion.CreateFromYawPitchRoll(0f, 0f, 0.2f));
+            RecusiveTentacle(scene, Matrix4x4.CreateTranslation(+25, 0, +25), mesh, Quaternion.CreateFromYawPitchRoll(0f, 0.2f, 0f), 2);
+            RecusiveTentacle(scene, Matrix4x4.CreateTranslation(-25, 0, +25), mesh, Quaternion.CreateFromYawPitchRoll(0.2f, 0f, 0f), 2);
+            RecusiveTentacle(scene, Matrix4x4.CreateTranslation(-25, 0, -25), mesh, Quaternion.CreateFromYawPitchRoll(0f, 0f, 0.2f), 2);
+            RecusiveTentacle(scene, Matrix4x4.CreateTranslation(+25, 0, -25), mesh, Quaternion.CreateFromYawPitchRoll(0.2f, 0f, 0f), 2);            
+
+            model.SaveGLB("recursive tentacles.glb");
+        }
+        
+        static void RecusiveTentacle(IVisualNodeContainer parent, Matrix4x4 offset, Mesh mesh, Quaternion anim, int repeat)
+        {
+            parent = parent
+                .CreateNode()
+                .WithLocalTransform(offset);
+
+            parent = AddTentacleSkeleton(parent as Node, mesh, anim);
+
+            if (repeat == 0) return;
+
+            var scale = Matrix4x4.CreateScale(0.2f);
 
-            model.SaveGLB("tentacle.glb");
+            RecusiveTentacle(parent, Matrix4x4.CreateTranslation(+15, 0, +15) * scale, mesh, Quaternion.CreateFromYawPitchRoll(0f, 0.2f, 0f), repeat - 1);
+            RecusiveTentacle(parent, Matrix4x4.CreateTranslation(-15, 0, +15) * scale, mesh, Quaternion.CreateFromYawPitchRoll(0.2f, 0f, 0f), repeat - 1);
+            RecusiveTentacle(parent, Matrix4x4.CreateTranslation(-15, 0, -15) * scale, mesh, Quaternion.CreateFromYawPitchRoll(0f, 0f, 0.2f), repeat - 1);
+            RecusiveTentacle(parent, Matrix4x4.CreateTranslation(+15, 0, -15) * scale, mesh, Quaternion.CreateFromYawPitchRoll(0.2f, 0f, 0f), repeat - 1);
         }
 
-        static void AddTentacleSkeleton(Scene scene, Mesh mesh, Matrix4x4 origin, Quaternion anim)
+        static Node AddTentacleSkeleton(Node skeleton, Mesh mesh, Quaternion anim)
         {
             var bindings = new List<Node>();
 
-            Node skeleton = scene.CreateNode();
-            skeleton.LocalTransform = origin;
-
-            Node bone = skeleton;
+            Node bone = null;
 
             for (int i = 0; i < 10; ++i)
             {
                 if (bone == null)
                 {
-                    bone = skeleton.CreateNode();
+                    bone = skeleton;
                 }
                 else
                 {
@@ -83,9 +98,11 @@ namespace InfiniteSkinnedTentacle
                 bindings.Add(bone);                
             }
 
-            scene.CreateNode()
+            skeleton.VisualScene.CreateNode()
                 .WithMesh(mesh)
                 .WithSkinBinding(bindings.ToArray());
+
+            return bindings.Last();
         }
 
         static MESH CreateMesh(int boneCount)
@@ -105,7 +122,11 @@ namespace InfiniteSkinnedTentacle
                 var b2 = new VERTEX(new Vector3(+5, i * 10, +5), Vector4.One, (i, 1));
                 var b3 = new VERTEX(new Vector3(-5, i * 10, +5), Vector4.One, (i, 1));
 
-                if (i > 0)
+                if (i == 0)
+                {
+                    prim.AddPolygon(b0, b1, b2, b3);
+                }
+                else
                 {
                     prim.AddPolygon(b0, b1, a1, a0);
                     prim.AddPolygon(b1, b2, a2, a1);
@@ -119,6 +140,8 @@ namespace InfiniteSkinnedTentacle
                 a3 = b3;
             }
 
+            prim.AddPolygon(a3, a2, a1, a0);
+
             return mesh;
         }
     }

binární
examples/PointCloudGalaxy.glb


+ 9 - 0
examples/README.MD

@@ -0,0 +1,9 @@
+### Showcase
+
+In here I'll by uploading projects levaraging SharpGLTF.
+
+|Project|Description|Result|
+|-|-|
+|Example1|A simple scene with two colored polygons.|[Example1.glb](Example1.glb)|
+|Point Cloud Galaxy|A procedurally generated galaxy with 100.000 stars.|[PointCloudGalaxy.glb](PointCloudGalaxy.glb)|
+|Infinite Skinned Tentacle|A complex animated scene with 84 skinned nodes sharing a single mesh.|[InfiniteSkinnedTentacle.glb](InfiniteSkinnedTentacle.glb)|

+ 16 - 0
src/Shared/_Extensions.cs

@@ -54,6 +54,15 @@ namespace SharpGLTF
             return v.X._IsReal() & v.Y._IsReal() & v.Z._IsReal() & v.W._IsReal();
         }
 
+        internal static bool _IsReal(this Matrix4x4 v)
+        {
+            if (!(v.M11._IsReal() & v.M12._IsReal() & v.M13._IsReal() & v.M14._IsReal())) return false;
+            if (!(v.M21._IsReal() & v.M22._IsReal() & v.M23._IsReal() & v.M24._IsReal())) return false;
+            if (!(v.M31._IsReal() & v.M32._IsReal() & v.M33._IsReal() & v.M34._IsReal())) return false;
+            if (!(v.M41._IsReal() & v.M42._IsReal() & v.M43._IsReal() & v.M44._IsReal())) return false;
+            return true;
+        }
+
         internal static bool _IsReal(this Quaternion v)
         {
             return v.X._IsReal() & v.Y._IsReal() & v.Z._IsReal() & v.W._IsReal();
@@ -144,6 +153,13 @@ namespace SharpGLTF
             return new Vector3(tangent.X, tangent.Y, tangent.Z).IsValidNormal();
         }
 
+        internal static Matrix4x4 Inverse(this Matrix4x4 src)
+        {
+            if (!Matrix4x4.Invert(src, out Matrix4x4 dst)) Guard.IsTrue(false, nameof(src), "Matrix cannot be inverted.");
+
+            return dst;
+        }
+
         #endregion
 
         #region linq

+ 0 - 1
src/SharpGLTF.Core/Schema2/gltf.Node.cs

@@ -262,7 +262,6 @@ namespace SharpGLTF.Schema2
 
         internal bool _ContainsVisualNode(Node node, bool recursive)
         {
-            Guard.NotNull(node, nameof(node));
             Guard.MustShareLogicalParent(this, node, nameof(node));
 
             if (!recursive) return VisualChildren.Any(item => item == node);

+ 116 - 68
src/SharpGLTF.Core/Schema2/gltf.Skin.cs

@@ -14,7 +14,7 @@ namespace SharpGLTF.Schema2
         // https://github.com/AnalyticalGraphicsInc/cesium/blob/master/Source/Scene/Model.js#L2526
 
         // max shader joints
-        // https://github.com/KhronosGroup/glTF/issues/283
+        // https://github.com/KhronosGroup/COLLADA2GLTF/issues/110
 
         #region lifecycle
 
@@ -59,36 +59,6 @@ namespace SharpGLTF.Schema2
 
         #region API
 
-        /// <summary>
-        /// Finds all the skins that are using the given <see cref="Node"/> as a joint.
-        /// </summary>
-        /// <param name="jointNode">A <see cref="Node"/> joint.</param>
-        /// <returns>A collection of <see cref="Skin"/> instances.</returns>
-        internal static IEnumerable<Skin> FindSkinsUsingJoint(Node jointNode)
-        {
-            var idx = jointNode.LogicalIndex;
-
-            return jointNode
-                .LogicalParent
-                .LogicalSkins
-                .Where(s => s._joints.Contains(idx));
-        }
-
-        /// <summary>
-        /// Finds all the skins that are using the given <see cref="Node"/> as a skeleton.
-        /// </summary>
-        /// <param name="skeletonNode">A <see cref="Node"/> skeleton.</param>
-        /// <returns>A collection of <see cref="Skin"/> instances.</returns>
-        internal static IEnumerable<Skin> FindSkinsUsingSkeleton(Node skeletonNode)
-        {
-            var idx = skeletonNode.LogicalIndex;
-
-            return skeletonNode
-                .LogicalParent
-                .LogicalSkins
-                .Where(s => s._skeleton == idx);
-        }
-
         public Accessor GetInverseBindMatricesAccessor()
         {
             if (!this._inverseBindMatrices.HasValue) return null;
@@ -109,29 +79,66 @@ namespace SharpGLTF.Schema2
             return new KeyValuePair<Node, Matrix4x4>(node, matrix);
         }
 
-        internal override void Validate(Validation.ValidationContext result)
+        public void BindJoints(params Node[] joints)
         {
-            base.Validate(result);
+            var rootJoint = _FindCommonAncestor(joints);
 
-            // note: this check will fail if the buffers are not set
+            BindJoints(rootJoint.WorldMatrix, joints);
+        }
 
-            for (int i = 0; i < _joints.Count; ++i)
-            {
-                var j = GetJoint(i);
+        public void BindJoints(Matrix4x4 meshBingTransform, params Node[] joints)
+        {
+            var meshBingInverse = meshBingTransform.Inverse();
+
+            var pairs = new KeyValuePair<Node, Matrix4x4>[joints.Length];
 
-                var invXform = j.Value;
+            for (int i = 0; i < pairs.Length; ++i)
+            {
+                var xform = (joints[i].WorldMatrix * meshBingInverse).Inverse();
 
-                if (invXform.M44 != 1) result.AddError(this, $"Joint {i} has invalid inverse matrix");
+                pairs[i] = new KeyValuePair<Node, Matrix4x4>(joints[i], xform);
             }
+
+            BindJoints(pairs);
         }
 
+        public void BindJoints(KeyValuePair<Node, Matrix4x4>[] joints)
+        {
+            _FindCommonAncestor(joints.Select(item => item.Key));
+
+            foreach (var j in joints) { Guard.IsTrue(j.Value._IsReal(), nameof(joints)); }
+
+            // inverse bind matrices accessor
+
+            var data = new Byte[joints.Length * 16 * 4];
+
+            var matrices = new Memory.Matrix4x4Array(data.Slice(0), 0, EncodingType.FLOAT, false);
+
+            matrices.Fill(joints.Select(item => item.Value));
+
+            var accessor = LogicalParent.CreateAccessor("Bind Matrices");
+
+            accessor.SetData( LogicalParent.UseBufferView(data), 0, joints.Length, DimensionType.MAT4, EncodingType.FLOAT, false);
+
+            this._inverseBindMatrices = accessor.LogicalIndex;
+
+            // joints
+
+            _joints.Clear();
+            _joints.AddRange(joints.Select(item => item.Key.LogicalIndex));
+        }
+
+        #endregion
+
+        #region helpers
+
         /// <summary>
         /// Returns true if this <see cref="Skin"/> matches the input values.
         /// </summary>
         /// <param name="skeleton">A <see cref="Node"/> instance that represents the skeleton root.</param>
         /// <param name="joints">A key value pair collection of <see cref="Node"/> joints and their binding matrices.</param>
         /// <returns>True if the input values match this <see cref="Skin"/>.</returns>
-        public bool IsMatch(Node skeleton, KeyValuePair<Node, Matrix4x4>[] joints)
+        internal bool IsMatch(Node skeleton, KeyValuePair<Node, Matrix4x4>[] joints)
         {
             if (!ReferenceEquals(skeleton, this.Skeleton)) return false;
 
@@ -149,56 +156,97 @@ namespace SharpGLTF.Schema2
             return true;
         }
 
-        public void BindJoints(params Node[] joints)
+        /// <summary>
+        /// Finds all the skins that are using the given <see cref="Node"/> as a joint.
+        /// </summary>
+        /// <param name="jointNode">A <see cref="Node"/> joint.</param>
+        /// <returns>A collection of <see cref="Skin"/> instances.</returns>
+        internal static IEnumerable<Skin> FindSkinsUsingJoint(Node jointNode)
         {
-            foreach (var j in joints) Guard.MustShareLogicalParent(this, j, nameof(joints));
+            var idx = jointNode.LogicalIndex;
 
-            var rootJoint = joints.Select(item => item.VisualRoot).Distinct().ToList();
+            return jointNode
+                .LogicalParent
+                .LogicalSkins
+                .Where(s => s._joints.Contains(idx));
+        }
 
-            Guard.IsTrue(rootJoint.Count == 1, nameof(joints), "All joints must have only one root parent");
+        /// <summary>
+        /// Finds all the skins that are using the given <see cref="Node"/> as a skeleton.
+        /// </summary>
+        /// <param name="skeletonNode">A <see cref="Node"/> skeleton.</param>
+        /// <returns>A collection of <see cref="Skin"/> instances.</returns>
+        internal static IEnumerable<Skin> FindSkinsUsingSkeleton(Node skeletonNode)
+        {
+            var idx = skeletonNode.LogicalIndex;
 
-            var skeleton = rootJoint[0];
+            return skeletonNode
+                .LogicalParent
+                .LogicalSkins
+                .Where(s => s._skeleton == idx);
+        }
 
-            this.Skeleton = skeleton;
+        /// <summary>
+        /// Validates the node tree, ensuring that all nodes share a common ancestor node, and returns it.
+        /// </summary>
+        /// <param name="nodes">A collection of <see cref="Node"/> joints arranged in a tree.</param>
+        /// <returns>The <see cref="Node"/> root of the tree.</returns>
+        private Node _FindCommonAncestor(IEnumerable<Node> nodes)
+        {
+            foreach (var j in nodes) Guard.MustShareLogicalParent(this, j, nameof(nodes));
 
-            Matrix4x4.Invert(skeleton.WorldMatrix, out Matrix4x4 skeletonBindXform);
+            var workingNodes = nodes.ToList();
 
-            var pairs = new KeyValuePair<Node, Matrix4x4>[joints.Length];
+            var rootJoint = workingNodes.First();
 
-            for (int i = 0; i < pairs.Length; ++i)
+            while (true)
             {
-                var xform = joints[i].WorldMatrix * skeletonBindXform;
+                if (workingNodes.All(j => rootJoint == j || rootJoint._ContainsVisualNode(j, true))) return rootJoint;
 
-                Matrix4x4.Invert(xform, out Matrix4x4 ixform);
+                if (rootJoint.VisualParent == null) break;
 
-                pairs[i] = new KeyValuePair<Node, Matrix4x4>(joints[i], ixform);
+                rootJoint = rootJoint.VisualParent;
             }
 
-            BindJoints(pairs);
+            Guard.IsTrue(false, nameof(nodes), "Common ancestor not found");
+            return null;
         }
 
-        public void BindJoints(KeyValuePair<Node, Matrix4x4>[] joints)
-        {
-            foreach (var j in joints) Guard.MustShareLogicalParent(this, j.Key, nameof(joints));
-
-            // inverse bind matrices accessor
-
-            var data = new Byte[joints.Length * 16 * 4];
+        #endregion
 
-            var matrices = new Memory.Matrix4x4Array(data.Slice(0), 0, EncodingType.FLOAT, false);
+        #region validation
 
-            matrices.Fill(joints.Select(item => item.Value));
+        internal override void Validate(Validation.ValidationContext result)
+        {
+            base.Validate(result);
 
-            var accessor = LogicalParent.CreateAccessor("Bind Matrices");
+            var ibxAccessor = GetInverseBindMatricesAccessor();
 
-            accessor.SetData( LogicalParent.UseBufferView(data), 0, joints.Length, DimensionType.MAT4, EncodingType.FLOAT, false);
+            var logicalNodeCount = this.LogicalParent.LogicalNodes.Count;
 
-            this._inverseBindMatrices = accessor.LogicalIndex;
+            if (_skeleton.HasValue)
+            {
+                if (_skeleton.Value < 0 || _skeleton.Value >= logicalNodeCount) result.AddError(this, $"Skeleton Node index reference is out of bounds.");
+            }
 
-            // joints
+            if (_joints == null || _joints.Count < _jointsMinItems)
+            {
+                result.AddError(this, $"Expected at least {_jointsMinItems} Joints");
+                return;
+            }
 
-            _joints.Clear();
-            _joints.AddRange(joints.Select(item => item.Key.LogicalIndex));
+            for (int i = 0; i < _joints.Count; ++i)
+            {
+                var jidx = _joints[i];
+                if (jidx < 0 || jidx >= logicalNodeCount) result.AddError(this, $"Joint {i} Node index reference is out of bounds.");
+
+                if (ibxAccessor != null)
+                {
+                    var ibxform = ibxAccessor.AsMatrix4x4Array()[i];
+                    try { ibxform.Inverse(); }
+                    catch { result.AddError(this, $"Joint {i} has invalid bind matrix"); }
+                }
+            }
         }
 
         #endregion

+ 2 - 4
src/SharpGLTF.Core/Transforms/AffineTransform.cs

@@ -123,11 +123,9 @@ namespace SharpGLTF.Transforms
 
         public static Matrix4x4 WorldToLocal(Matrix4x4 parentWorld, Matrix4x4 childWorld)
         {
-            Matrix4x4.Invert(parentWorld, out Matrix4x4 invWorld);
-
-            return childWorld * invWorld;
+            return childWorld * parentWorld.Inverse();
         }
-        
+
         #endregion
     }
 }

+ 15 - 0
src/SharpGLTF.Toolkit/Schema2/SceneExtensions.cs

@@ -10,6 +10,12 @@ namespace SharpGLTF.Schema2
     {
         #region fluent creation
 
+        public static Node WithLocalTransform(this Node node, Transforms.AffineTransform xform)
+        {
+            node.LocalTransform = xform;
+            return node;
+        }
+
         public static Node WithLocalTranslation(this Node node, Vector3 translation)
         {
             var xform = node.LocalTransform;
@@ -58,6 +64,15 @@ namespace SharpGLTF.Schema2
             return node;
         }
 
+        public static Node WithSkinBinding(this Node node, Matrix4x4 meshPoseTransform, params Node[] joints)
+        {
+            var skin = node.LogicalParent.CreateSkin();
+            skin.BindJoints(meshPoseTransform, joints);
+
+            node.Skin = skin;
+            return node;
+        }
+
         #endregion
 
         #region evaluation