Ejemplo n.º 1
0
            public static List <Runtime.Node> CreatePlaneWithSkinB()
            {
                var colorInner = new Vector4(0.8f, 0.8f, 0.8f, 1.0f);
                var colorOuter = new Vector4(0.0f, 0.0f, 1.0f, 1.0f);

                var nodeInnerPrism = new Runtime.Node
                {
                    Name = "innerPrism",
                    Skin = new Runtime.Skin()
                    {
                        Name = "innerPrismSkinB",
                    },
                    Mesh = Mesh.CreatePrism(colorInner),
                };

                var nodeOuterPrism = new Runtime.Node
                {
                    Name = "outerPrism",
                    Skin = new Runtime.Skin()
                    {
                        Name = "outerPrismSkinB",
                    },
                    Mesh = Mesh.CreatePrism(colorOuter, Scale: new Vector3(1.6f, 1.6f, 0.3f)),
                };

                Matrix4x4 rotation = Matrix4x4.CreateFromYawPitchRoll(0.0f, FloatMath.ConvertDegreesToRadians(90.0f), 0.0f);
                var       translationVectorJoint1 = new Vector3(0.0f, 0.0f, -0.6f);
                var       translationVectorJoint0 = new Vector3(0.0f, 0.0f, 0.3f);
                Matrix4x4 matrixJoint1            = Matrix4x4.CreateTranslation(translationVectorJoint1);
                Matrix4x4 matrixJoint0            = Matrix4x4.CreateTranslation(translationVectorJoint0);

                matrixJoint1 = Matrix4x4.Multiply(matrixJoint0, matrixJoint1);
                Matrix4x4.Invert(matrixJoint1, out Matrix4x4 invertedJoint1);
                Matrix4x4.Invert(matrixJoint0, out Matrix4x4 invertedJoint0);

                var nodeJoint1 = new Runtime.Node
                {
                    Name        = "joint1",
                    Translation = translationVectorJoint1,
                };
                var nodeJoint0 = new Runtime.Node
                {
                    Name        = "joint0",
                    Rotation    = Quaternion.CreateFromRotationMatrix(rotation),
                    Translation = new Vector3(0.0f, -0.3f, 0.0f),
                    Children    = new[]
                    {
                        nodeJoint1
                    },
                };

                var joint1 = new Runtime.SkinJoint
                             (
                    inverseBindMatrix: invertedJoint1,
                    node: nodeJoint1
                             );
                var joint0 = new Runtime.SkinJoint
                             (
                    inverseBindMatrix: invertedJoint0,
                    node: nodeJoint0
                             );

                nodeInnerPrism.Skin.SkinJoints = new[]
                {
                    joint0,
                    joint1,
                };
                nodeOuterPrism.Skin.SkinJoints = new[]
                {
                    joint0,
                    joint1,
                };

                var weightsListInnerPrism = new List <List <Runtime.JointWeight> >();
                var weightsListOuterPrism = new List <List <Runtime.JointWeight> >();

                for (var vertexIndex = 0; vertexIndex < 3; vertexIndex++)
                {
                    var weight = new List <Runtime.JointWeight>()
                    {
                        new Runtime.JointWeight
                        {
                            Joint  = joint0,
                            Weight = 1,
                        },
                        new Runtime.JointWeight
                        {
                            Joint  = joint1,
                            Weight = 0,
                        },
                    };
                    weightsListInnerPrism.Add(weight);
                    weightsListOuterPrism.Add(weight);
                }
                for (var vertexIndex = 0; vertexIndex < 3; vertexIndex++)
                {
                    var weight = new List <Runtime.JointWeight>()
                    {
                        new Runtime.JointWeight
                        {
                            Joint  = joint0,
                            Weight = 0,
                        },
                        new Runtime.JointWeight
                        {
                            Joint  = joint1,
                            Weight = 1,
                        },
                    };
                    weightsListInnerPrism.Add(weight);
                    weightsListOuterPrism.Add(weight);
                }
                nodeInnerPrism.Mesh.MeshPrimitives.First().VertexJointWeights = weightsListInnerPrism;
                nodeOuterPrism.Mesh.MeshPrimitives.First().VertexJointWeights = weightsListOuterPrism;

                return(new List <Runtime.Node>
                {
                    nodeInnerPrism,
                    nodeJoint0,
                    nodeOuterPrism
                });
            }
Ejemplo n.º 2
0
        public Animation_Skin(List <string> imageList)
        {
            UseFigure(imageList, "skinA");
            UseFigure(imageList, "skinB");
            UseFigure(imageList, "skinC");
            UseFigure(imageList, "skinD");
            UseFigure(imageList, "skinE");
            UseFigure(imageList, "skinF");
            var closeCamera   = new Manifest.Camera(new Vector3(0.5f, 0.0f, 0.6f));
            var midCamera     = new Manifest.Camera(new Vector3(0.8f, 0.0f, 1.0f));
            var distantCamera = new Manifest.Camera(new Vector3(1.5f, 0.0f, 1.0f));
            var skinBCamera   = new Manifest.Camera(new Vector3(0.5f, 0.6f, 1.1f));

            // There are no common properties in this model group that are reported in the readme.

            Model CreateModel(Action <List <Property>, List <Runtime.Animation>, List <Runtime.Node> > setProperties, Action <Model> setCamera, Action <glTFLoader.Schema.Gltf> postRuntimeChanges = null)
            {
                var properties = new List <Property>();
                var nodes      = new List <Runtime.Node>();
                var animations = new List <Runtime.Animation>();

                // There are no common properties in this model group.

                // Apply the properties that are specific to this gltf.
                setProperties(properties, animations, nodes);

                // If no animations are used, null out that property.
                if (!animations.Any())
                {
                    animations = null;
                }

                // Create the gltf object.
                var model = new Model
                {
                    Properties = properties,
                    GLTF       = CreateGLTF(() => new Runtime.Scene()
                    {
                        Nodes = nodes
                    }, animations: animations),
                };

                if (postRuntimeChanges != null)
                {
                    model.PostRuntimeChanges = postRuntimeChanges;
                }

                setCamera(model);

                return(model);
            }

            void AddRotationAnimationChannel(List <Runtime.AnimationChannel> channelList, Runtime.Node targetNode, Quaternion pitchValue, Quaternion restValue)
            {
                channelList.Add(
                    new Runtime.AnimationChannel
                {
                    Target = new Runtime.AnimationChannelTarget
                    {
                        Node = targetNode,
                        Path = Runtime.AnimationChannelTarget.PathEnum.ROTATION,
                    },
                    Sampler = new Runtime.LinearAnimationSampler <Quaternion>(
                        new[]
                    {
                        0.0f,
                        1.0f,
                        2.0f,
                    },
                        new[]
                    {
                        restValue,
                        pitchValue,
                        restValue,
                    })
                });
            }

            Runtime.Animation CreateFoldingAnimation(Runtime.Node jointRootNode, List <Runtime.AnimationChannel> channelList = null)
            {
                if (channelList == null)
                {
                    channelList = new List <Runtime.AnimationChannel>();
                }

                Runtime.Node nodeCheck  = jointRootNode;
                float        pitchValue = FloatMath.ConvertDegreesToRadians(-90.0f);
                var          nodeList   = new List <Runtime.Node>()
                {
                    jointRootNode,
                };

                while (nodeCheck.Children != null)
                {
                    foreach (var node in nodeCheck.Children)
                    {
                        nodeList.Add(node);
                    }
                    nodeCheck = nodeCheck.Children.First();
                }

                for (var nodeIndex = 1; nodeIndex < nodeList.Count(); nodeIndex++)
                {
                    float rotateValueModifier = 1.0f;
                    if (nodeIndex == 1)
                    {
                        rotateValueModifier = 0.5f;
                    }
                    else if (nodeIndex % 2 == 0)
                    {
                        rotateValueModifier = -1.0f;
                    }
                    AddRotationAnimationChannel(channelList, nodeList[nodeIndex], Quaternion.CreateFromYawPitchRoll(0.0f, pitchValue * rotateValueModifier, 0.0f), Quaternion.Identity);
                }
                return(new Runtime.Animation
                {
                    Channels = channelList
                });
            }

            Models = new List <Model>
            {
                CreateModel((properties, animations, nodes) => {
                    foreach (Runtime.Node node in Nodes.CreateFoldingPlaneSkin("skinA", 2, 3))
                    {
                        nodes.Add(node);
                    }

                    properties.Add(new Property(PropertyName.Description, "`skinA`."));
                }, (model) => { model.Camera = closeCamera; }),
                CreateModel((properties, animations, nodes) => {
                    foreach (Runtime.Node node in Nodes.CreateFoldingPlaneSkin("skinA", 2, 3))
                    {
                        nodes.Add(node);
                    }
                    animations.Add(CreateFoldingAnimation(nodes[1]));

                    properties.Add(new Property(PropertyName.Description, "`skinA` where `joint1` is animating with a rotation."));
                }, (model) => { model.Camera = closeCamera; }),
                CreateModel((properties, animations, nodes) => {
                    var tempNodeList = Nodes.CreateFoldingPlaneSkin("skinA", 2, 3);

                    // Give the skin node a rotation
                    tempNodeList[0].Rotation = Quaternion.CreateFromYawPitchRoll((FloatMath.Pi / 4.0f), 0.0f, 0.0f);

                    // Create a new parent node and give it a rotation
                    tempNodeList[0] = new Runtime.Node
                    {
                        Name     = "jointParent",
                        Rotation = Quaternion.CreateFromYawPitchRoll((FloatMath.Pi / 4.0f), 0.0f, 0.0f),
                        Children = new List <Runtime.Node>
                        {
                            tempNodeList[0]
                        }
                    };

                    foreach (Runtime.Node node in tempNodeList)
                    {
                        nodes.Add(node);
                    }

                    properties.Add(new Property(PropertyName.Description, "`skinA` where the skinned node has a transform and a parent node with a transform. Both transforms should be ignored."));
                }, (model) => { model.Camera = closeCamera; }),
                CreateModel((properties, animations, nodes) => {
                    foreach (Runtime.Node node in Nodes.CreateFoldingPlaneSkin("skinA", 2, 3))
                    {
                        nodes.Add(node);
                    }

                    properties.Add(new Property(PropertyName.Description, "`skinA`. The skin joints are not referenced by the scene nodes."));
                }, (model) => { model.Camera = closeCamera; }, (gltf) => { gltf.Scenes.First().Nodes = new [] { 0, }; }),
                CreateModel((properties, animations, nodes) => {
                    foreach (Runtime.Node node in Nodes.CreateFoldingPlaneSkin("skinA", 2, 3))
                    {
                        nodes.Add(node);
                    }
                    foreach (var joint in nodes[0].Skin.SkinJoints)
                    {
                        joint.InverseBindMatrix = Matrix4x4.Identity;
                    }

                    properties.Add(new Property(PropertyName.Description, "`skinA` without inverse bind matrices."));
                }, (model) => { model.Camera = closeCamera; }),
                CreateModel((properties, animations, nodes) => {
                    foreach (Runtime.Node node in Nodes.CreateFoldingPlaneSkin("skinA", 2, 3))
                    {
                        nodes.Add(node);
                    }
                    animations.Add(CreateFoldingAnimation(nodes[1]));

                    // Attach a node with a mesh to the end of the joint hierarchy
                    Runtime.Node nodeCheck = nodes[1];
                    while (nodeCheck.Children != null)
                    {
                        nodeCheck = nodeCheck.Children.First();
                    }

                    nodeCheck.Children = new List <Runtime.Node>
                    {
                        new Runtime.Node
                        {
                            Mesh = Mesh.CreateTriangle()
                        }
                    };

                    properties.Add(new Property(PropertyName.Description, "`skinA` where `joint1` is animated with a rotation and `joint1` has a triangle mesh attached to it."));
                }, (model) => { model.Camera = closeCamera; }),
                CreateModel((properties, animations, nodes) => {
                    foreach (Runtime.Node node in Nodes.CreateFoldingPlaneSkin("skinA", 2, 3))
                    {
                        nodes.Add(node);
                    }

                    // Create a set of positions for the second mesh that are offset from the first mesh.
                    Runtime.MeshPrimitive originalMeshPrimitive = nodes[0].Mesh.MeshPrimitives.First();
                    var offsetPositions = new List <Vector3>();
                    foreach (Vector3 position in originalMeshPrimitive.Positions)
                    {
                        var offsetPosition = position;
                        offsetPosition.X  += 0.6f;
                        offsetPositions.Add(offsetPosition);
                    }

                    // Create a second mesh
                    nodes.Add(new Runtime.Node
                    {
                        Name = "plane2",
                        Skin = nodes[0].Skin,
                        Mesh = new Runtime.Mesh
                        {
                            MeshPrimitives = new[]
                            {
                                new Runtime.MeshPrimitive
                                {
                                    VertexJointWeights = originalMeshPrimitive.VertexJointWeights,
                                    Positions          = offsetPositions,
                                    Indices            = originalMeshPrimitive.Indices,
                                    Material           = new Runtime.Material
                                    {
                                        DoubleSided = true,
                                        MetallicRoughnessMaterial = new Runtime.PbrMetallicRoughness
                                        {
                                            BaseColorFactor = new Vector4(0.0f, 0.0f, 1.0f, 1.0f)
                                        }
                                    }
                                }
                            }
                        }
                    });

                    properties.Add(new Property(PropertyName.Description, "`skinA` where there are two meshes sharing a single skin."));
                }, (model) => { model.Camera = midCamera; }),
                CreateModel((properties, animations, nodes) => {
                    foreach (Runtime.Node node in Nodes.CreateFoldingPlaneSkin("skinA", 2, 3))
                    {
                        nodes.Add(node);
                    }

                    // Make joint1 a root joint
                    nodes.Add(nodes[1].Children.First());
                    nodes[1].Children = null;

                    // Compensate for no longer inheriting from joint0
                    nodes[2].Rotation    = Quaternion.Multiply((Quaternion)nodes[2].Rotation, (Quaternion)nodes[1].Rotation);
                    nodes[2].Translation = null;
                    nodes[0].Skin.SkinJoints.ElementAt(1).InverseBindMatrix = Matrix4x4.Identity;

                    properties.Add(new Property(PropertyName.Description, "`skinA` where `joint1` is a root node and not a child of `joint0`."));
                }, (model) => { model.Camera = closeCamera; }),
                CreateModel((properties, animations, nodes) => {
                    foreach (Runtime.Node node in Nodes.CreatePlaneWithSkinB())
                    {
                        nodes.Add(node);
                    }

                    // Animate the joints
                    Runtime.Node nodeJoint0 = nodes[1];
                    Runtime.Node nodeJoint1 = nodeJoint0.Children.First();
                    var channelList         = new List <Runtime.AnimationChannel>();
                    float rotationValue     = FloatMath.ConvertDegreesToRadians(-15.0f);
                    AddRotationAnimationChannel(channelList, nodeJoint1, Quaternion.CreateFromYawPitchRoll(0.0f, 0.0f, rotationValue), Quaternion.CreateFromYawPitchRoll(0.0f, 0.0f, 0.0f));
                    animations.Add(new Runtime.Animation
                    {
                        Channels = channelList
                    });

                    properties.Add(new Property(PropertyName.Description, "`skinB` which is made up of two skins. `joint1` is referenced by both skins and is animating with a rotation."));
                }, (model) => { model.Camera = skinBCamera; }),
                CreateModel((properties, animations, nodes) => {
                    foreach (Runtime.Node node in Nodes.CreateFoldingPlaneSkin("skinC", 5, 5))
                    {
                        nodes.Add(node);
                    }

                    // Rotate each joint node, except the root which already has the desired rotation
                    Runtime.Node nodeCheck = nodes[1].Children.First();
                    float rotationRadian   = FloatMath.ConvertDegreesToRadians(-10.0f);
                    Quaternion rotation    = Quaternion.CreateFromYawPitchRoll(0.0f, rotationRadian, 0.0f);
                    nodeCheck.Rotation     = rotation;
                    while (nodeCheck.Children != null)
                    {
                        foreach (var node in nodeCheck.Children)
                        {
                            node.Rotation = rotation;
                        }
                        nodeCheck = nodeCheck.Children.First();
                    }

                    // Rebuild the inverseBindMatrix for each joint (except the root) to work with the new rotation
                    List <Runtime.SkinJoint> skinJointList = (List <Runtime.SkinJoint>)nodes[0].Skin.SkinJoints;
                    for (var skinJointIndex = 1; skinJointIndex < skinJointList.Count(); skinJointIndex++)
                    {
                        var translationInverseBindMatrix = skinJointList[skinJointIndex].InverseBindMatrix;
                        Matrix4x4.Invert(Matrix4x4.CreateRotationX(rotationRadian * (skinJointIndex + 1)), out Matrix4x4 invertedRotation);
                        skinJointList[skinJointIndex].InverseBindMatrix = Matrix4x4.Multiply(translationInverseBindMatrix, invertedRotation);
                    }

                    properties.Add(new Property(PropertyName.Description, "`skinC` where all of the joints have a local rotation of -10 degrees, except the root which is rotated -90 degrees."));
                }, (model) => { model.Camera = distantCamera; }),
                CreateModel((properties, animations, nodes) => {
                    foreach (Runtime.Node node in Nodes.CreateFoldingPlaneSkin("skinD", 5, 6, 3, false))
                    {
                        nodes.Add(node);
                    }
                    animations.Add(CreateFoldingAnimation(nodes[1]));

                    // Remove animation for the transform node
                    animations[0].Channels = new List <Runtime.AnimationChannel>()
                    {
                        animations[0].Channels.First(),
                        animations[0].Channels.ElementAt(1),
                        animations[0].Channels.ElementAt(3),
                    };

                    // Add the mesh to the transform node
                    nodes[1].Children.First().Children.First().Children.First().Mesh = Mesh.CreateTriangle();

                    properties.Add(new Property(PropertyName.Description, "`skinD` where each joint is animating with a rotation. There is a transform node in the joint hierarchy that is not a joint. That node has a mesh attached to it in order to show its location."));
                }, (model) => { model.Camera = distantCamera; }),
                CreateModel((properties, animations, nodes) => {
                    foreach (Runtime.Node node in Nodes.CreatePlaneWithSkinE())
                    {
                        nodes.Add(node);
                    }

                    properties.Add(new Property(PropertyName.Description, "`skinE`."));
                }, (model) => { model.Camera = distantCamera; }),
                // Removing this model for now, since no viewer currently supports models that have >4 jointweights per vertex.
                //CreateModel((properties, animations, nodes) => {
                //    foreach (Runtime.Node node in Nodes.CreateFoldingPlaneSkin("skinF", 8, 9, vertexVerticalSpacingMultiplier: 0.5f))
                //    {
                //        nodes.Add(node);
                //    }

                //    // Rotate each joint node, except the root which already has the desired rotation
                //    Runtime.Node nodeCheck = nodes[1].Children.First();
                //    float rotationRadian = FloatMath.ConvertDegreesToRadians(-10.0f);
                //    Quaternion rotationQuaternion = Quaternion.CreateFromYawPitchRoll(0.0f, rotationRadian, 0.0f);
                //    nodeCheck.Rotation = rotationQuaternion;
                //    while (nodeCheck.Children != null)
                //    {
                //        foreach (Runtime.Node node in nodeCheck.Children)
                //        {
                //            node.Rotation = rotationQuaternion;
                //        }
                //        nodeCheck = nodeCheck.Children.First();
                //    }

                //    // Rebuild the inverseBindMatrix for each joint (except the root) to work with the new rotation
                //    var skinJointList = (List<Runtime.SkinJoint>)nodes[0].Skin.SkinJoints;
                //    for (var skinJointIndex = 1; skinJointIndex < skinJointList.Count(); skinJointIndex++)
                //    {
                //        Matrix4x4 translationInverseBindMatrix = skinJointList.ElementAt(skinJointIndex).InverseBindMatrix;
                //        Matrix4x4.Invert(Matrix4x4.CreateRotationX(rotationRadian * (skinJointIndex + 1)) , out Matrix4x4 invertedRotation);
                //        skinJointList.ElementAt(skinJointIndex).InverseBindMatrix = Matrix4x4.Multiply(translationInverseBindMatrix, invertedRotation);
                //    }

                //    // Rebuild weights to include every joint instead of just the ones with a weight > 0
                //    var weightList = (List<List<Runtime.JointWeight>>)nodes[0].Mesh.MeshPrimitives.First().VertexJointWeights;
                //    for (var weightIndex = 0; weightIndex < weightList.Count(); weightIndex++)
                //    {
                //        var jointWeight = new List<Runtime.JointWeight>();

                //        for (var skinJointIndex = 0; skinJointIndex < skinJointList.Count; skinJointIndex++)
                //        {
                //            int weightToUse = 0;
                //            // Set the weight to 1 if the skinJoint is at the same level as the vertex.
                //            // Or Set the weight to 1 if the vertex is further out than the last skinjoint and the last skinjoint is being set.
                //            if (skinJointIndex == (weightIndex / 2) || (((weightIndex / 2) > skinJointList.Count - 1) && (skinJointIndex == skinJointList.Count - 1)) )
                //            {
                //                weightToUse = 1;
                //            }

                //            jointWeight.Add(new Runtime.JointWeight
                //            {
                //                Joint = skinJointList[skinJointIndex],
                //                Weight = weightToUse,
                //            });
                //        }

                //        weightList[weightIndex] = jointWeight;
                //    }

                //    properties.Add(new Property(PropertyName.Description, "`skinF`. Each vertex has weights for more than four joints."));
                //}, (model) => { model.Camera = distantCamera; }),
            };

            GenerateUsedPropertiesList();
        }
Ejemplo n.º 3
0
            private static List <Runtime.Node> CreateJointsAndWeightsForCommonRoot(Runtime.Node nodePlane)
            {
                Matrix4x4 baseRotation                    = Matrix4x4.CreateFromYawPitchRoll(0.0f, FloatMath.ConvertDegreesToRadians(-90.0f), 0.0f);
                Matrix4x4 jointRotation                   = Matrix4x4.CreateFromYawPitchRoll(0.0f, FloatMath.ConvertDegreesToRadians(-15.0f), 0.0f);
                var       translationVectorJoint3         = new Vector3(0.1875f, 0.0f, 0.25f);
                var       translationVectorJoint2         = new Vector3(-0.1875f, 0.0f, 0.25f);
                var       translationVectorJoint1         = new Vector3(0.0f, 0.0f, 0.25f);
                var       translationVectorJoint0         = new Vector3(0.0f, -0.25f, 0.0f);
                Matrix4x4 invertedTranslationMatrixJoint3 = Matrix4x4.CreateTranslation(-translationVectorJoint3);
                Matrix4x4 invertedTranslationMatrixJoint2 = Matrix4x4.CreateTranslation(-translationVectorJoint2);

                var matrixJoint1 = jointRotation;

                Matrix4x4.Invert(matrixJoint1, out Matrix4x4 invertedJoint1);

                var matrixJoint0 = Matrix4x4.CreateTranslation(new Vector3(0, 0.0f, -0.25f));

                Matrix4x4.Invert(matrixJoint0, out Matrix4x4 invertedJoint0);

                var nodeJoint3 = new Runtime.Node
                {
                    Name        = "joint3",
                    Rotation    = Quaternion.CreateFromRotationMatrix(jointRotation),
                    Translation = translationVectorJoint3,
                };
                var nodeJoint2 = new Runtime.Node
                {
                    Name        = "joint2",
                    Rotation    = Quaternion.CreateFromRotationMatrix(jointRotation),
                    Translation = translationVectorJoint2,
                };
                var nodeJoint1 = new Runtime.Node
                {
                    Name        = "joint1",
                    Rotation    = Quaternion.CreateFromRotationMatrix(jointRotation),
                    Translation = translationVectorJoint1,
                    Children    = new[]
                    {
                        nodeJoint2,
                        nodeJoint3
                    }
                };
                var nodeJoint0 = new Runtime.Node
                {
                    Name        = "joint0",
                    Rotation    = Quaternion.CreateFromRotationMatrix(baseRotation),
                    Translation = translationVectorJoint0,
                    Children    = new[]
                    {
                        nodeJoint1
                    }
                };

                var joint3 = new Runtime.SkinJoint
                             (
                    inverseBindMatrix: invertedTranslationMatrixJoint3,
                    node: nodeJoint3
                             );
                var joint2 = new Runtime.SkinJoint
                             (
                    inverseBindMatrix: invertedTranslationMatrixJoint2,
                    node: nodeJoint2
                             );
                var joint1 = new Runtime.SkinJoint
                             (
                    inverseBindMatrix: invertedJoint1,
                    node: nodeJoint1
                             );
                var joint0 = new Runtime.SkinJoint
                             (
                    inverseBindMatrix: invertedJoint0,
                    node: nodeJoint0
                             );

                nodePlane.Skin.SkinJoints = new[]
                {
                    joint0,
                    joint1,
                    joint2,
                    joint3
                };

                // Top four vertexes of each arm have a weight for the relevant joint. Otherwise the vertex has a weight from the root
                var jointWeights = new List <List <Runtime.JointWeight> >();

                // Base of trunk
                for (int vertexIndex = 0; vertexIndex < 2; vertexIndex++)
                {
                    jointWeights.Add(new List <Runtime.JointWeight>()
                    {
                        new Runtime.JointWeight
                        {
                            Joint  = joint0,
                            Weight = 1,
                        },
                        new Runtime.JointWeight
                        {
                            Joint  = joint1,
                            Weight = 0,
                        },
                        new Runtime.JointWeight
                        {
                            Joint  = joint2,
                            Weight = 0,
                        },
                        new Runtime.JointWeight
                        {
                            Joint  = joint3,
                            Weight = 0,
                        }
                    });
                }
                // Top of trunk
                for (int vertexIndex = 0; vertexIndex < 3; vertexIndex++)
                {
                    jointWeights.Add(new List <Runtime.JointWeight>()
                    {
                        new Runtime.JointWeight
                        {
                            Joint  = joint0,
                            Weight = 0,
                        },
                        new Runtime.JointWeight
                        {
                            Joint  = joint1,
                            Weight = 1,
                        },
                        new Runtime.JointWeight
                        {
                            Joint  = joint2,
                            Weight = 0,
                        },
                        new Runtime.JointWeight
                        {
                            Joint  = joint3,
                            Weight = 0,
                        }
                    });
                }
                // Left arm
                for (int vertexIndex = 0; vertexIndex < 4; vertexIndex++)
                {
                    jointWeights.Add(new List <Runtime.JointWeight>()
                    {
                        new Runtime.JointWeight
                        {
                            Joint  = joint0,
                            Weight = 0,
                        },
                        new Runtime.JointWeight
                        {
                            Joint  = joint1,
                            Weight = 0,
                        },
                        new Runtime.JointWeight
                        {
                            Joint  = joint2,
                            Weight = 1,
                        },
                        new Runtime.JointWeight
                        {
                            Joint  = joint3,
                            Weight = 0,
                        }
                    });
                }
                // Right arm
                for (int vertexIndex = 0; vertexIndex < 4; vertexIndex++)
                {
                    jointWeights.Add(new List <Runtime.JointWeight>()
                    {
                        new Runtime.JointWeight
                        {
                            Joint  = joint0,
                            Weight = 0,
                        },
                        new Runtime.JointWeight
                        {
                            Joint  = joint1,
                            Weight = 0,
                        },
                        new Runtime.JointWeight
                        {
                            Joint  = joint2,
                            Weight = 0,
                        },
                        new Runtime.JointWeight
                        {
                            Joint  = joint3,
                            Weight = 1,
                        }
                    });
                }
                nodePlane.Mesh.MeshPrimitives.First().VertexJointWeights = jointWeights;

                return(new List <Runtime.Node>
                {
                    nodePlane,
                    nodeJoint0
                });
            }
            public static List <Runtime.Node> CreateFoldingPlaneSkin(string skinName, int numberOfNodesInJointHierarchy, float numberOfVertexPairs, int?indexOfTransformNode = null, bool rotationJoint1 = true, float vertexVerticalSpacingMultiplier = 1.0f)
            {
                var   color              = new Vector4(0.8f, 0.8f, 0.8f, 1.0f);
                var   positions          = new List <Vector3>();
                var   indices            = new List <int>();
                float vertexHeightOffset = 0.2f * vertexVerticalSpacingMultiplier;

                float startHeight = -((numberOfVertexPairs - 1) * vertexHeightOffset / 2);
                float positionZ   = startHeight;
                int   index1      = 0;
                int   index2      = 1;
                int   index3      = 2;

                // Create positions and indices. Pattern for indices is as follows:
                // 0, 1, 2,
                // 2, 1, 3,
                // 2, 3, 4,
                // 4, 3, 5,
                // 4, 5, 6,
                // 6, 5, 7,
                // 6, 7, 8,
                // 8, 7, 9,
                // 8, 9, 10,
                // 10, 9, 11
                for (int vertexPairIndex = 0; vertexPairIndex < numberOfVertexPairs; vertexPairIndex++)
                {
                    positions.Add(new Vector3(-0.25f, 0.0f, positionZ));
                    positions.Add(new Vector3(0.25f, 0.0f, positionZ));
                    positionZ = positionZ + vertexHeightOffset;

                    if (vertexPairIndex > 0)
                    {
                        indices.Add(index1);
                        indices.Add(index2);
                        indices.Add(index3);
                        indices.Add(index1 + 2);
                        indices.Add(index2);
                        indices.Add(index3 + 1);
                        index1 += 2;
                        index2 += 2;
                        index3 += 2;
                    }
                }

                Matrix4x4  baseRotation            = Matrix4x4.CreateFromYawPitchRoll(0.0f, FloatMath.ConvertDegreesToRadians(-90.0f), 0.0f);
                Quaternion jointRotation           = Quaternion.CreateFromRotationMatrix(Matrix4x4.CreateFromYawPitchRoll(0.0f, FloatMath.ConvertDegreesToRadians(-30.0f), 0.0f));
                var        translationVector       = new Vector3(0.0f, 0.0f, vertexHeightOffset);
                var        translationVectorJoint0 = new Vector3(0.0f, startHeight, 0.0f);
                var        matrixJoint0            = Matrix4x4.CreateTranslation(new Vector3(0.0f, 0.0f, startHeight));

                Matrix4x4.Invert(matrixJoint0, out Matrix4x4 invertedJoint0);
                Matrix4x4 invertedTranslationMatrix = Matrix4x4.CreateTranslation(-translationVector);

                var jointHierarchyNodes = new List <Runtime.Node>();

                // Create the root node, since it is a special case
                jointHierarchyNodes.Add(new Runtime.Node
                {
                    Name        = "joint0",
                    Rotation    = Quaternion.CreateFromRotationMatrix(baseRotation),
                    Translation = translationVectorJoint0,
                });
                // Create the child nodes in the joint hierarchy
                for (int nodeIndex = 1, jointIndex = 1; nodeIndex < numberOfNodesInJointHierarchy; nodeIndex++)
                {
                    string name;
                    if (nodeIndex == indexOfTransformNode)
                    {
                        name = "transformNode";
                    }
                    else
                    {
                        name = $"joint{jointIndex}";
                        jointIndex++;
                    }

                    Quaternion rotationToUse = Quaternion.Identity;
                    if (nodeIndex == 1 && rotationJoint1)
                    {
                        rotationToUse = jointRotation;
                    }

                    jointHierarchyNodes.Add(new Runtime.Node
                    {
                        Name        = name,
                        Rotation    = rotationToUse,
                        Translation = translationVector,
                    });

                    // Add as a child of the previous node
                    jointHierarchyNodes[nodeIndex - 1].Children = new[] { jointHierarchyNodes[nodeIndex] };
                }

                var skinJoints = new List <Runtime.SkinJoint>();

                // Create the skinjoint for the root node, since it is a special case
                skinJoints.Add(new Runtime.SkinJoint
                               (
                                   inverseBindMatrix: invertedJoint0,
                                   node: jointHierarchyNodes[0]
                               ));
                // Create the skinjoints for the rest of the joints
                for (int nodeIndex = 1, translationMultiplier = 1; nodeIndex < numberOfNodesInJointHierarchy; nodeIndex++)
                {
                    // Create the skinjoint. Transform node is skipped.
                    if (nodeIndex != indexOfTransformNode)
                    {
                        skinJoints.Add(new Runtime.SkinJoint
                                       (
                                           inverseBindMatrix: Matrix4x4.CreateTranslation(new Vector3(0.0f, 0.0f, -positions[nodeIndex * 2].Z)),
                                           node: jointHierarchyNodes[nodeIndex]
                                       ));
                    }
                    translationMultiplier--;
                }

                // Assign weights
                var weights = new List <List <Runtime.JointWeight> >();

                for (int vertexPairIndex = 0, jointIndex = 0; vertexPairIndex < numberOfVertexPairs; vertexPairIndex++)
                {
                    Runtime.SkinJoint jointToUse;
                    // If there is a transform node, use the joint from the node before it.
                    // Else if there are more vertex pairs than joints, then the last ones use the last joint.
                    // Otherwise, use the joint with the same index as the vertex pair.
                    if (vertexPairIndex == indexOfTransformNode)
                    {
                        jointToUse = skinJoints[jointIndex - 1];
                    }
                    else if (vertexPairIndex > skinJoints.Count - 1)
                    {
                        jointToUse = skinJoints[skinJoints.Count - 1];
                    }
                    else
                    {
                        jointToUse = skinJoints[jointIndex];
                    }

                    weights.Add(new List <Runtime.JointWeight>()
                    {
                        new Runtime.JointWeight
                        {
                            Joint  = jointToUse,
                            Weight = 1,
                        },
                    });
                    weights.Add(new List <Runtime.JointWeight>()
                    {
                        new Runtime.JointWeight
                        {
                            Joint  = jointToUse,
                            Weight = 1,
                        },
                    });
                    jointIndex++;
                }

                var nodePlane = new Runtime.Node
                {
                    Name = "plane",
                    Skin = new Runtime.Skin()
                    {
                        Name       = skinName,
                        SkinJoints = skinJoints
                    },
                    Mesh = new Runtime.Mesh
                    {
                        MeshPrimitives = new[]
                        {
                            new Runtime.MeshPrimitive
                            {
                                VertexJointWeights = weights,
                                Positions          = positions,
                                Indices            = indices,
                                Material           = new Runtime.Material
                                {
                                    DoubleSided = true,
                                    MetallicRoughnessMaterial = new Runtime.PbrMetallicRoughness
                                    {
                                        BaseColorFactor = color
                                    }
                                }
                            }
                        }
                    },
                };

                return(new List <Runtime.Node>
                {
                    nodePlane,
                    jointHierarchyNodes[0]
                });
            }