예제 #1
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 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>();
                var animated   = true;

                // 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;
                    animated   = false;
                }

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

                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.ToRadians(-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; }),
                // Removed Animation_Skin_03 due to a change in the spec that disallows this situation.
                // Left commented out because this will likely be re-added as a negative test in the future.
                // 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);
                    }
                    nodes[0].Skin.InverseBindMatrices = new[]
                    {
                        Matrix4x4.Identity,
                        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 = distantCamera; }),
                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.InverseBindMatrices = new[]
                    {
                        nodes[0].Skin.InverseBindMatrices.First(),
                        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.ToRadians(-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.ToRadians(-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 <Matrix4x4> inverseBindMatrixList = (List <Matrix4x4>)nodes[0].Skin.InverseBindMatrices;
                    for (var i = 1; i < inverseBindMatrixList.Count; i++)
                    {
                        var translationInverseBindMatrix = inverseBindMatrixList[i];
                        Matrix4x4.Invert(Matrix4x4.CreateRotationX(rotationRadian * (i + 1)), out Matrix4x4 invertedRotation);
                        inverseBindMatrixList[i] = 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; }),
                CreateModel((properties, animations, nodes) =>
                {
                    var skinA1 = Nodes.CreateFoldingPlaneSkin("skinA", 2, 3);
                    var skinA2 = Nodes.CreateFoldingPlaneSkin("skinA", 2, 3);

                    // Set the same mesh on both nodes.
                    skinA2[0].Mesh = skinA1[0].Mesh;

                    // Offset one of the models so they aren't overlapping.
                    Vector3 translation   = skinA2[1].Translation.Value;
                    skinA2[1].Translation = new Vector3(translation.X + 0.6f, translation.Y, translation.Z);

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

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

                    properties.Add(new Property(PropertyName.Description, "Two instances of `skinA` sharing a mesh but with separate skins."));
                }, (model) => { model.Camera = distantCamera; }),
            };

            GenerateUsedPropertiesList();
        }
        public Animation_SkinType(List <string> imageList)
        {
            // There are no common properties in this model group that are reported in the readme.

            Model CreateModel(Action <List <Property>, Runtime.MeshPrimitive> setProperties)
            {
                var properties            = new List <Property>();
                List <Runtime.Node> nodes = Nodes.CreateFoldingPlaneSkin("skinA", 2, 3);
                var animations            = new List <Runtime.Animation>();

                Runtime.MeshPrimitive   meshPrimitive       = nodes[0].Mesh.MeshPrimitives.First();
                JointComponentTypeEnum  jointComponentType  = meshPrimitive.JointComponentType;
                WeightComponentTypeEnum weightComponentType = meshPrimitive.WeightComponentType;
                var closeCameraTranslation = new Manifest.Camera(new Vector3(0.5f, 0.0f, 0.6f));

                // Apply the common properties to the gltf.
                AnimateWithRotation(animations, nodes);

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

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

            void AnimateWithRotation(List <Runtime.Animation> animations, List <Runtime.Node> nodes)
            {
                animations = new List <Runtime.Animation>
                {
                    new Runtime.Animation
                    {
                        Channels = new List <Runtime.AnimationChannel>
                        {
                            new Runtime.AnimationChannel
                            {
                                Target = new Runtime.AnimationChannelTarget
                                {
                                    Node = nodes[1].Children.First(),
                                    Path = Runtime.AnimationChannelTarget.PathEnum.ROTATION,
                                }
                            }
                        }
                    }
                };
                float quarterTurn = (FloatMath.Pi / 2.0f);

                animations[0].Channels.First().Sampler = new Runtime.LinearAnimationSampler <Quaternion>(
                    new[]
                {
                    0.0f,
                    1.0f,
                    2.0f,
                },
                    new[]
                {
                    Quaternion.Identity,
                    Quaternion.CreateFromYawPitchRoll(0.0f, quarterTurn, 0.0f),
                    Quaternion.Identity,
                });
            }

            void JointsAreByte(Runtime.MeshPrimitive meshPrimitive)
            {
                meshPrimitive.JointComponentType = JointComponentTypeEnum.UNSIGNED_BYTE;
            }

            void JointsAreShort(Runtime.MeshPrimitive meshPrimitive)
            {
                meshPrimitive.JointComponentType = JointComponentTypeEnum.UNSIGNED_SHORT;
            }

            void WeightsAreFloat(Runtime.MeshPrimitive meshPrimitive)
            {
                meshPrimitive.WeightComponentType = WeightComponentTypeEnum.FLOAT;
            }

            void WeightsAreByte(Runtime.MeshPrimitive meshPrimitive)
            {
                meshPrimitive.WeightComponentType = WeightComponentTypeEnum.NORMALIZED_UNSIGNED_BYTE;
            }

            void WeightsAreShort(Runtime.MeshPrimitive meshPrimitive)
            {
                meshPrimitive.WeightComponentType = WeightComponentTypeEnum.NORMALIZED_UNSIGNED_SHORT;
            }

            Models = new List <Model>
            {
                CreateModel((properties, meshPrimitive) => {
                    JointsAreByte(meshPrimitive);
                    WeightsAreFloat(meshPrimitive);
                    properties.Add(new Property(PropertyName.JointsComponentType, "Byte"));
                    properties.Add(new Property(PropertyName.WeightComponentType, "Float"));
                }),
                CreateModel((properties, meshPrimitive) => {
                    JointsAreByte(meshPrimitive);
                    WeightsAreByte(meshPrimitive);
                    properties.Add(new Property(PropertyName.JointsComponentType, "Byte"));
                    properties.Add(new Property(PropertyName.WeightComponentType, "Byte"));
                }),
                CreateModel((properties, meshPrimitive) => {
                    JointsAreByte(meshPrimitive);
                    WeightsAreShort(meshPrimitive);
                    properties.Add(new Property(PropertyName.JointsComponentType, "Byte"));
                    properties.Add(new Property(PropertyName.WeightComponentType, "Short"));
                }),
                CreateModel((properties, meshPrimitive) => {
                    JointsAreShort(meshPrimitive);
                    WeightsAreFloat(meshPrimitive);
                    properties.Add(new Property(PropertyName.JointsComponentType, "Short"));
                    properties.Add(new Property(PropertyName.WeightComponentType, "Float"));
                }),
            };

            GenerateUsedPropertiesList();
        }
예제 #3
0
        public Instancing(List <string> imageList)
        {
            Runtime.Image baseColorTextureImageA    = UseTexture(imageList, "BaseColor_A");
            Runtime.Image baseColorTextureImageB    = UseTexture(imageList, "BaseColor_B");
            Runtime.Image baseColorTextureImageCube = UseTexture(imageList, "BaseColor_Cube");
            var           distantCamera             = new Manifest.Camera(new Vector3(0.0f, 0.0f, 2.7f));

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

            Model CreateModel(Action <List <Property>, List <Runtime.Node>, List <Runtime.Animation> > setProperties, Action <Model> setCamera)
            {
                var properties = new List <Property>();
                var animations = new List <Runtime.Animation>();
                var animated   = true;
                var nodes      = new List <Runtime.Node>();

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

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

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

                setCamera(model);

                return(model);
            }

            var SamplerInputLinear = new[]
            {
                0.0f,
                1.0f,
                2.0f,
                3.0f,
                4.0f,
            };

            var SamplerInputCurve = new[]
            {
                0.0f,
                0.5f,
                1.0f,
                2.0f,
                4.0f,
            };

            var SamplerOutput = new[]
            {
                Quaternion.CreateFromYawPitchRoll(0.0f, FloatMath.ToRadians(90.0f), 0.0f),
                Quaternion.Identity,
                Quaternion.CreateFromYawPitchRoll(0.0f, FloatMath.ToRadians(-90.0f), 0.0f),
                Quaternion.Identity,
                Quaternion.CreateFromYawPitchRoll(0.0f, FloatMath.ToRadians(90.0f), 0.0f),
            };

            var SamplerOutputReverse = new[]
            {
                Quaternion.CreateFromYawPitchRoll(0.0f, FloatMath.ToRadians(-90.0f), 0.0f),
                Quaternion.Identity,
                Quaternion.CreateFromYawPitchRoll(0.0f, FloatMath.ToRadians(90.0f), 0.0f),
                Quaternion.Identity,
                Quaternion.CreateFromYawPitchRoll(0.0f, FloatMath.ToRadians(-90.0f), 0.0f),
            };

            Runtime.Texture CreateTexture(Runtime.Image image)
            {
                return(new Runtime.Texture {
                    Source = image
                });
            }

            Runtime.Material CreateMaterial(Runtime.Texture texture)
            {
                return(new Runtime.Material
                {
                    MetallicRoughnessMaterial = new Runtime.PbrMetallicRoughness
                    {
                        BaseColorTexture = texture
                    }
                });
            }

            void AddMeshPrimitivesToSingleNode(List <Runtime.Node> nodes, List <Runtime.MeshPrimitive> meshPrimitives)
            {
                // If there are multiple mesh primitives, offset their position so they don't overlap.
                if (meshPrimitives.Count > 1)
                {
                    meshPrimitives[0].Positions = meshPrimitives[0].Positions.Select(position => { return(new Vector3(position.X - 0.6f, position.Y, position.Z)); });
                    meshPrimitives[1].Positions = meshPrimitives[1].Positions.Select(position => { return(new Vector3(position.X + 0.6f, position.Y, position.Z)); });
                }

                nodes.Add(
                    new Runtime.Node
                {
                    Mesh = new Runtime.Mesh
                    {
                        MeshPrimitives = meshPrimitives
                    }
                }
                    );
            }

            void AddMeshPrimitivesToMultipleNodes(List <Runtime.Node> nodes, Runtime.MeshPrimitive meshPrimitives0, Runtime.MeshPrimitive meshPrimitives1)
            {
                nodes.AddRange(new[]
                {
                    new Runtime.Node
                    {
                        Translation = new Vector3(-0.6f, 0.0f, 0.0f),
                        Mesh        = new Runtime.Mesh
                        {
                            MeshPrimitives = new List <Runtime.MeshPrimitive>
                            {
                                meshPrimitives0
                            }
                        }
                    },
                    new Runtime.Node
                    {
                        Translation = new Vector3(0.6f, 0.0f, 0.0f),
                        Mesh        = new Runtime.Mesh
                        {
                            MeshPrimitives = new List <Runtime.MeshPrimitive>
                            {
                                meshPrimitives1
                            }
                        }
                    }
                }
                               );
            }

            void AddAnimation(List <Runtime.Animation> animations, List <Runtime.Node> nodes, Runtime.AnimationSampler sampler0, Runtime.AnimationSampler sampler1, bool samplerInstanced)
            {
                animations.Add(new Runtime.Animation
                {
                    Channels = new List <Runtime.AnimationChannel>
                    {
                        new Runtime.AnimationChannel
                        {
                            Target = new Runtime.AnimationChannelTarget
                            {
                                Node = nodes[0],
                                Path = ROTATION,
                            },
                            Sampler = sampler0
                        },
                        new Runtime.AnimationChannel
                        {
                            Target = new Runtime.AnimationChannelTarget
                            {
                                Node = nodes[1],
                                Path = ROTATION,
                            },
                            Sampler = sampler1
                        },
                    }
                });
            }

            Models = new List <Model>
            {
                CreateModel((properties, nodes, animations) =>
                {
                    var meshPrimitives = new List <Runtime.MeshPrimitive>
                    {
                        MeshPrimitive.CreateSinglePlane(includeTextureCoords: false),
                        MeshPrimitive.CreateSinglePlane(includeTextureCoords: false)
                    };

                    foreach (Runtime.MeshPrimitive meshPrimitive in meshPrimitives)
                    {
                        // This non-standard set of texture coordinates is larger than the texture but not an exact multiple, so it allows texture sampler settings to be visible.
                        meshPrimitive.TextureCoordSets = new List <List <Vector2> >
                        {
                            new List <Vector2>
                            {
                                new Vector2(1.3f, 1.3f),
                                new Vector2(-0.3f, 1.3f),
                                new Vector2(-0.3f, -0.3f),
                                new Vector2(1.3f, -0.3f),
                            }
                        };
                    }

                    meshPrimitives[0].Material = CreateMaterial(CreateTexture(baseColorTextureImageA));
                    meshPrimitives[1].Material = CreateMaterial(CreateTexture(baseColorTextureImageA));

                    meshPrimitives[0].Material.MetallicRoughnessMaterial.BaseColorTexture.Sampler = new Runtime.Sampler
                    {
                        WrapT = WrapTEnum.CLAMP_TO_EDGE,
                        WrapS = WrapSEnum.CLAMP_TO_EDGE
                    };
                    meshPrimitives[1].Material.MetallicRoughnessMaterial.BaseColorTexture.Sampler = new Runtime.Sampler
                    {
                        WrapT = WrapTEnum.MIRRORED_REPEAT,
                        WrapS = WrapSEnum.MIRRORED_REPEAT
                    };

                    AddMeshPrimitivesToSingleNode(nodes, meshPrimitives);

                    properties.Add(new Property(PropertyName.Description, "Two textures using the same image as their source."));
                    properties.Add(new Property(PropertyName.Difference, "The texture sampler `WrapT` and `WrapS` are set to `CLAMP_TO_EDGE` for one and `MIRRORED_REPEAT` for the other."));
                }, (model) => { model.Camera = distantCamera; }),
                CreateModel((properties, nodes, animations) =>
                {
                    var meshPrimitives = new List <Runtime.MeshPrimitive>
                    {
                        MeshPrimitive.CreateSinglePlane(includeTextureCoords: false),
                        MeshPrimitive.CreateSinglePlane(includeTextureCoords: false)
                    };

                    meshPrimitives[0].Material = CreateMaterial(CreateTexture(baseColorTextureImageA));
                    meshPrimitives[1].Material = CreateMaterial(CreateTexture(baseColorTextureImageB));

                    var sampler = new Runtime.Sampler
                    {
                        WrapT = WrapTEnum.CLAMP_TO_EDGE,
                        WrapS = WrapSEnum.CLAMP_TO_EDGE
                    };
                    foreach (Runtime.MeshPrimitive meshPrimitive in meshPrimitives)
                    {
                        meshPrimitive.Material.MetallicRoughnessMaterial.BaseColorTexture.Sampler = sampler;
                        // This non-standard set of texture coordinates is larger than the texture but not an exact multiple, so it allows texture sampler settings to be visible.
                        meshPrimitive.TextureCoordSets = new List <List <Vector2> >
                        {
                            new List <Vector2>
                            {
                                new Vector2(1.3f, 1.3f),
                                new Vector2(-0.3f, 1.3f),
                                new Vector2(-0.3f, -0.3f),
                                new Vector2(1.3f, -0.3f),
                            }
                        };
                    }

                    AddMeshPrimitivesToSingleNode(nodes, meshPrimitives);

                    properties.Add(new Property(PropertyName.Description, "Two textures using the same sampler."));
                    properties.Add(new Property(PropertyName.Difference, "One texture uses image A while the other uses image B."));
                }, (model) => { model.Camera = distantCamera; }),
                CreateModel((properties, nodes, animations) =>
                {
                    var meshPrimitives = new List <Runtime.MeshPrimitive>
                    {
                        MeshPrimitive.CreateSinglePlane(),
                        MeshPrimitive.CreateSinglePlane()
                    };

                    var texture = CreateTexture(baseColorTextureImageA);
                    foreach (Runtime.MeshPrimitive meshPrimitive in meshPrimitives)
                    {
                        meshPrimitive.Material = CreateMaterial(texture);
                    }
                    meshPrimitives[0].Material.MetallicRoughnessMaterial.BaseColorTexture = meshPrimitives[1].Material.MetallicRoughnessMaterial.BaseColorTexture;
                    meshPrimitives[1].Material.MetallicRoughnessMaterial.BaseColorFactor  = new Vector4(0.5f, 0.5f, 1.0f, 1.0f);

                    AddMeshPrimitivesToSingleNode(nodes, meshPrimitives);

                    properties.Add(new Property(PropertyName.Description, "Two textures using the same source image."));
                    properties.Add(new Property(PropertyName.Difference, "One material does not have a baseColorFactor and the other has a blue baseColorFactor."));
                }, (model) => { model.Camera = distantCamera; }),
                CreateModel((properties, nodes, animations) =>
                {
                    var meshPrimitives = new List <Runtime.MeshPrimitive>
                    {
                        MeshPrimitive.CreateSinglePlane(),
                        MeshPrimitive.CreateSinglePlane(includeTextureCoords: false)
                    };
                    var material = CreateMaterial(CreateTexture(baseColorTextureImageA));
                    foreach (Runtime.MeshPrimitive meshPrimitive in meshPrimitives)
                    {
                        meshPrimitive.Material = material;
                    }

                    // One of the primitives has a 'zoomed in' texture coordinate set.
                    meshPrimitives[1].TextureCoordSets = new List <List <Vector2> >
                    {
                        new List <Vector2>
                        {
                            new Vector2(0.9f, 0.9f),
                            new Vector2(0.1f, 0.9f),
                            new Vector2(0.1f, 0.1f),
                            new Vector2(0.9f, 0.1f),
                        }
                    };

                    AddMeshPrimitivesToSingleNode(nodes, meshPrimitives);

                    properties.Add(new Property(PropertyName.Description, "Two primitives using the same material."));
                    properties.Add(new Property(PropertyName.Difference, "One primitive has texture coordinates that displays all of texture A, while the other primitive has textures coordinates that don't display the border."));
                }, (model) => { model.Camera = distantCamera; }),
                CreateModel((properties, nodes, animations) =>
                {
                    var meshPrimitive0      = MeshPrimitive.CreateSinglePlane();
                    var meshPrimitive1      = MeshPrimitive.CreateSinglePlane();
                    meshPrimitive0.Material = CreateMaterial(CreateTexture(baseColorTextureImageA));
                    meshPrimitive1.Material = CreateMaterial(CreateTexture(baseColorTextureImageB));

                    meshPrimitive0.Positions = meshPrimitive1.Positions;

                    AddMeshPrimitivesToMultipleNodes(nodes, meshPrimitive0, meshPrimitive1);

                    properties.Add(new Property(PropertyName.Description, "Two primitives using the same accessors for the `POSITION` attribute."));
                    properties.Add(new Property(PropertyName.Difference, "One primitive uses texture A while the other primitive uses texture B."));
                }, (model) => { model.Camera = distantCamera; }),
                CreateModel((properties, nodes, animations) => {
                    var meshPrimitive0      = MeshPrimitive.CreateSinglePlane(includeIndices: false);
                    var meshPrimitive1      = MeshPrimitive.CreateSinglePlane();
                    meshPrimitive0.Material = CreateMaterial(CreateTexture(baseColorTextureImageA));
                    meshPrimitive1.Material = CreateMaterial(CreateTexture(baseColorTextureImageB));

                    meshPrimitive0.Indices = meshPrimitive1.Indices;

                    AddMeshPrimitivesToMultipleNodes(nodes, meshPrimitive0, meshPrimitive1);

                    properties.Add(new Property(PropertyName.Description, "Two primitives using the same accessors for indices."));
                    properties.Add(new Property(PropertyName.Difference, "One primitive uses texture A while the other primitive uses texture B."));
                }, (model) => { model.Camera = distantCamera; }),
                CreateModel((properties, nodes, animations) =>
                {
                    var meshPrimitive      = MeshPrimitive.CreateSinglePlane();
                    meshPrimitive.Material = CreateMaterial(CreateTexture(baseColorTextureImageA));

                    AddMeshPrimitivesToMultipleNodes(nodes, meshPrimitive, meshPrimitive);
                    nodes[1].Mesh = nodes[0].Mesh;

                    properties.Add(new Property(PropertyName.Description, "Two nodes using the same mesh."));
                    properties.Add(new Property(PropertyName.Difference, "The two nodes have different translations."));
                }, (model) => { model.Camera = distantCamera; }),
                CreateModel((properties, nodes, animations) => {
                    nodes.AddRange(Nodes.CreateFoldingPlaneSkin("skinA", 2, 3));
                    nodes[0].Name = "plane0";
                    nodes[0].Mesh.MeshPrimitives.ElementAt(0).Material         = CreateMaterial(CreateTexture(baseColorTextureImageA));
                    nodes[0].Mesh.MeshPrimitives.ElementAt(0).TextureCoordSets = Nodes.GetSkinATextureCoordSets();

                    // Adds just the node containing the mesh, dropping the data for a second set of joints.
                    nodes.Add(Nodes.CreateFoldingPlaneSkin("skinA", 2, 3)[0]);
                    nodes[2].Name = "plane1";
                    nodes[2].Mesh.MeshPrimitives.ElementAt(0).Material         = CreateMaterial(CreateTexture(baseColorTextureImageB));
                    nodes[2].Mesh.MeshPrimitives.ElementAt(0).TextureCoordSets = Nodes.GetSkinATextureCoordSets();
                    nodes[2].Skin = nodes[0].Skin;

                    // Offsets the position of both meshes so they don't overlap.
                    nodes[0].Mesh.MeshPrimitives.ElementAt(0).Positions = nodes[0].Mesh.MeshPrimitives.ElementAt(0).Positions.Select(position => { return(new Vector3(position.X - 0.3f, position.Y, position.Z)); });
                    nodes[2].Mesh.MeshPrimitives.ElementAt(0).Positions = nodes[2].Mesh.MeshPrimitives.ElementAt(0).Positions.Select(position => { return(new Vector3(position.X + 0.3f, position.Y, position.Z)); });

                    properties.Add(new Property(PropertyName.Description, "Two nodes using the same skin."));
                    properties.Add(new Property(PropertyName.Difference, "The two mesh primitives have different `POSITION` values."));
                }, (model) => { model.Camera = null; }),
                CreateModel((properties, nodes, animations) =>
                {
                    nodes.AddRange(Nodes.CreateFoldingPlaneSkin("skinA", 2, 3));
                    nodes[0].Name = "plane0";
                    nodes[0].Mesh.MeshPrimitives.ElementAt(0).Material         = CreateMaterial(CreateTexture(baseColorTextureImageA));
                    nodes[0].Mesh.MeshPrimitives.ElementAt(0).TextureCoordSets = Nodes.GetSkinATextureCoordSets();

                    // Adds just the node containing the mesh, dropping the data for a second set of joints.
                    nodes.Add(Nodes.CreateFoldingPlaneSkin("skinA", 2, 3)[0]);
                    nodes[2].Name = "plane1";
                    nodes[2].Mesh.MeshPrimitives.ElementAt(0).Material         = CreateMaterial(CreateTexture(baseColorTextureImageB));
                    nodes[2].Mesh.MeshPrimitives.ElementAt(0).TextureCoordSets = Nodes.GetSkinATextureCoordSets();
                    nodes[2].Skin.Joints = nodes[0].Skin.Joints;

                    // Creates new inverseBindMatrices for the second skin, rotating the flap further than the default value would.
                    nodes[2].Skin.InverseBindMatrices = new[]
                    {
                        nodes[2].Skin.InverseBindMatrices.First(),
                        Matrix4x4.Multiply(nodes[2].Skin.InverseBindMatrices.ElementAt(1), Matrix4x4.CreateRotationX(FloatMath.ToRadians(-30))),
                    };

                    // Offsets the position of both meshes so they don't overlap.
                    nodes[0].Mesh.MeshPrimitives.ElementAt(0).Positions = nodes[0].Mesh.MeshPrimitives.ElementAt(0).Positions.Select(position => { return(new Vector3(position.X - 0.3f, position.Y, position.Z)); });
                    nodes[2].Mesh.MeshPrimitives.ElementAt(0).Positions = nodes[2].Mesh.MeshPrimitives.ElementAt(0).Positions.Select(position => { return(new Vector3(position.X + 0.3f, position.Y, position.Z)); });

                    properties.Add(new Property(PropertyName.Description, "Two skins using the same joints."));
                    properties.Add(new Property(PropertyName.Difference, "The skin with texture B has inverseBindMatrices that fold twice as far as the skin with texture A."));
                }, (model) => { model.Camera = null; }),
                CreateModel((properties, nodes, animations) =>
                {
                    nodes.AddRange(Nodes.CreateFoldingPlaneSkin("skinA", 2, 3));
                    nodes[0].Name = "plane0";
                    nodes[0].Mesh.MeshPrimitives.ElementAt(0).Material         = CreateMaterial(CreateTexture(baseColorTextureImageA));
                    nodes[0].Mesh.MeshPrimitives.ElementAt(0).TextureCoordSets = Nodes.GetSkinATextureCoordSets();
                    nodes[1].Translation = Vector3.Add((Vector3)nodes[1].Translation, new Vector3(-0.3f, 0.0f, 0.0f));

                    nodes.AddRange(Nodes.CreateFoldingPlaneSkin("skinA", 2, 3));
                    nodes[2].Name = "plane1";
                    nodes[2].Mesh.MeshPrimitives.ElementAt(0).Material         = CreateMaterial(CreateTexture(baseColorTextureImageB));
                    nodes[2].Mesh.MeshPrimitives.ElementAt(0).TextureCoordSets = Nodes.GetSkinATextureCoordSets();
                    nodes[3].Translation = Vector3.Add((Vector3)nodes[3].Translation, new Vector3(0.3f, 0.0f, 0.0f));

                    nodes[2].Skin.InverseBindMatrices = nodes[0].Skin.InverseBindMatrices;

                    properties.Add(new Property(PropertyName.Description, "Two skins using the same inverseBindMatrices."));
                    properties.Add(new Property(PropertyName.Difference, "The base joint for the two skins have different translations."));
                }, (model) => { model.Camera = null; }),
                CreateModel((properties, nodes, animations) =>
                {
                    var meshPrimitive0      = MeshPrimitive.CreateCube();
                    var meshPrimitive1      = MeshPrimitive.CreateCube();
                    meshPrimitive0.Material = CreateMaterial(CreateTexture(baseColorTextureImageCube));
                    meshPrimitive1.Material = CreateMaterial(CreateTexture(baseColorTextureImageCube));
                    AddMeshPrimitivesToMultipleNodes(nodes, meshPrimitive0, meshPrimitive1);

                    var sampler = new Runtime.LinearAnimationSampler <Quaternion>(SamplerInputLinear, SamplerOutput);
                    AddAnimation(animations, nodes, sampler, sampler, true);

                    properties.Add(new Property(PropertyName.Description, "Two animation channels using the same sampler."));
                    properties.Add(new Property(PropertyName.Difference, "The two animation channels target different nodes."));
                }, (model) => { model.Camera = distantCamera; }),
                CreateModel((properties, nodes, animations) =>
                {
                    var meshPrimitive0      = MeshPrimitive.CreateCube();
                    var meshPrimitive1      = MeshPrimitive.CreateCube();
                    meshPrimitive0.Material = CreateMaterial(CreateTexture(baseColorTextureImageCube));
                    meshPrimitive1.Material = CreateMaterial(CreateTexture(baseColorTextureImageCube));
                    AddMeshPrimitivesToMultipleNodes(nodes, meshPrimitive0, meshPrimitive1);

                    var inputKeys = SamplerInputLinear;
                    var sampler0  = new Runtime.LinearAnimationSampler <Quaternion>(inputKeys, SamplerOutput);
                    var sampler1  = new Runtime.LinearAnimationSampler <Quaternion>(inputKeys, SamplerOutputReverse);
                    AddAnimation(animations, nodes, sampler0, sampler1, false);

                    properties.Add(new Property(PropertyName.Description, "Two animation samplers using the same input accessors."));
                    properties.Add(new Property(PropertyName.Difference, "The two animation samplers have different output values."));
                }, (model) => { model.Camera = distantCamera; }),
                CreateModel((properties, nodes, animations) =>
                {
                    var meshPrimitive0      = MeshPrimitive.CreateCube();
                    var meshPrimitive1      = MeshPrimitive.CreateCube();
                    meshPrimitive0.Material = CreateMaterial(CreateTexture(baseColorTextureImageCube));
                    meshPrimitive1.Material = CreateMaterial(CreateTexture(baseColorTextureImageCube));
                    AddMeshPrimitivesToMultipleNodes(nodes, meshPrimitive0, meshPrimitive1);

                    var output   = SamplerOutput;
                    var sampler0 = new Runtime.LinearAnimationSampler <Quaternion>(SamplerInputLinear, output);
                    var sampler1 = new Runtime.LinearAnimationSampler <Quaternion>(SamplerInputCurve, output);
                    AddAnimation(animations, nodes, sampler0, sampler1, false);

                    properties.Add(new Property(PropertyName.Description, "Two animation samplers using the same output accessors."));
                    properties.Add(new Property(PropertyName.Difference, "The two animation samplers have different input values."));
                }, (model) => { model.Camera = distantCamera; }),
                // New model

                // To be implemented later. Needs to work as a type of interleaving.
                //CreateModel((properties, nodes, animations) =>
                //{
                //    var meshPrimitives = new List<Runtime.MeshPrimitive>
                //    {
                //        MeshPrimitive.CreateSinglePlane(includeTextureCoords: false),
                //        MeshPrimitive.CreateSinglePlane(includeTextureCoords: false)
                //    };
                //    meshPrimitives[0].TextureCoordSets = meshPrimitives[1].TextureCoordSets = MeshPrimitive.GetSinglePlaneTextureCoordSets();
                //    meshPrimitives[0].Normals = meshPrimitives[1].Normals = MeshPrimitive.GetSinglePlaneNormals();

                //   foreach (Runtime.MeshPrimitive meshPrimitive in meshPrimitives)
                //    {
                //        meshPrimitive.BufferViewsInstanced = true;
                //        meshPrimitive.Material = CreateMaterial();
                //    }
                //    AddMeshPrimitivesToSingleNode(nodes, meshPrimitives);

                //    properties.Add(new Property(PropertyName.Description, "Two accessors using the same buffer view."));
                //}, (model) => { model.Camera = null; }),
            };

            GenerateUsedPropertiesList();
        }