Exemplo n.º 1
0
        } // ColladaModel(setFilename)

        #endregion

        #region FromVertices
        /// <summary>
        /// Creates a static mesh directly from vertices (triangles only)
        /// </summary>
        public static SkeletalMesh FromVertices(Vector3[] vertices)
        {
            SkinnedTangentVertex[] verts = new SkinnedTangentVertex[vertices.Length];
            for (int i = 0; i < verts.Length; i++)
            {
                verts[i] = new SkinnedTangentVertex(
                    vertices[i], Vector2.Zero, new Vector3(0, 1, 0),
                    Vector3.Zero, Vector3.Zero,
                    Vector4.Zero, Vector4.One);
            }

            return(FromVertices(verts));
        }
Exemplo n.º 2
0
        /// <summary>
        /// Load
        /// </summary>
        public override void Deserialize(BinaryReader reader)
        {
            base.Deserialize(reader);

            // Read mesh data
            numOfVertices = reader.ReadInt32();
            vertices      = new SkinnedTangentVertex[numOfVertices];
            for (int i = 0; i < vertices.Length; i++)
            {
                vertices[i] = new SkinnedTangentVertex(
                    new Vector3(reader.ReadSingle(), reader.ReadSingle(),
                                reader.ReadSingle()),
                    new Vector2(reader.ReadSingle(), reader.ReadSingle()),
                    new Vector3(reader.ReadSingle(), reader.ReadSingle(),
                                reader.ReadSingle()),
                    new Vector3(reader.ReadSingle(), reader.ReadSingle(),
                                reader.ReadSingle()),
                    new Vector3(reader.ReadSingle(), reader.ReadSingle(),
                                reader.ReadSingle()),
                    new Vector4(reader.ReadSingle(), reader.ReadSingle(),
                                reader.ReadSingle(), reader.ReadSingle()),
                    new Vector4(reader.ReadSingle(), reader.ReadSingle(),
                                reader.ReadSingle(), reader.ReadSingle()));
            } // for (int)

            numOfIndices  = reader.ReadInt32();
            objectIndices = new uint[numOfIndices];
            for (int i = 0; i < objectIndices.Length; i++)
            {
                objectIndices[i] = reader.ReadUInt32();
            } // for (int)

            boundingSphere = new BoundingSphere(
                new Vector3(reader.ReadSingle(), reader.ReadSingle(),
                            reader.ReadSingle()),
                reader.ReadSingle());

            // Read animation data
            runtimeAnimations.Clear();

            int numAnim = reader.ReadInt32();

            for (int i = 0; i < numAnim; i++)
            {
                RuntimeAnimation anim = new RuntimeAnimation();
                anim.Name  = reader.ReadString();
                anim.Start = reader.ReadInt32();
                anim.End   = reader.ReadInt32();
                runtimeAnimations.Add(anim);
            } // for (int)

            string semantic = reader.ReadString();
            string defaultDiffuseTexture = reader.ReadString();

            semantic = reader.ReadString();
            string defaultNormalTexture = reader.ReadString();

            objectMatrix = ReadMatrixHelper(reader);

            numOfAnimations = reader.ReadInt32();

            int boneCnt = reader.ReadInt32();

            bones = new RuntimeBone[boneCnt];
            // Precreate empty bones
            for (int i = 0; i < boneCnt; i++)
            {
                bones[i] = new RuntimeBone();
            } // for (int)
              // Now read bones
            for (int i = 0; i < boneCnt; i++)
            {
                bones[i].id     = reader.ReadString();
                bones[i].parent = ReadBoneRef(reader);
                int childCnt = reader.ReadInt32();
                bones[i].children = new RuntimeBone[childCnt];
                for (int j = 0; j < childCnt; j++)
                {
                    bones[i].children[j] = ReadBoneRef(reader);
                } // for (int)
                bones[i].initialMatrix     = ReadMatrixHelper(reader);
                bones[i].invBoneSkinMatrix = ReadMatrixHelper(reader);
                int aninMatCnt = reader.ReadInt32();
                bones[i].animationMatrices = new Matrix[aninMatCnt];
                for (int j = 0; j < aninMatCnt; j++)
                {
                    bones[i].animationMatrices[j] =
                        ReadMatrixHelper(reader);
                } // for (int)
            }     // for (int)

            for (int i = 0; i < bones.Length; i++)
            {
                RuntimeBone bone = bones[i];

                // Just assign the final matrix from the animation matrices.
                bone.finalMatrix = bone.animationMatrices[0];

                // Also use parent matrix if we got one
                // This will always work because all the bones are in order.
                if (bone.parent != null)
                {
                    bone.finalMatrix *=
                        bone.parent.finalMatrix;
                }
            } // for (int)

            sockets = new RuntimeSocket[reader.ReadInt32()];
            for (int i = 0; i < sockets.Length; i++)
            {
                sockets[i] = new RuntimeSocket();
                sockets[i].LoadFromStream(reader, bones);
            } // for (int)

            GenerateVertexAndIndexBuffers();

            CreateBoundingSphere();

            ValidateAnimations();
        } // Load(packageName, assetName, stream)
        private static List <SkeletalMesh> LoadMesh(Package packageToSaveIn, Gltf model, List <byte[]> rawBuffers, Mesh mesh)
        {
            List <SkeletalMesh> meshes = new List <SkeletalMesh>();

            for (int p = 0; p < mesh.Primitives.Length; p++)
            {
                var prim = mesh.Primitives[p];

                if (prim.Mode != MeshPrimitive.ModeEnum.TRIANGLES)
                {
                    throw new NotImplementedException("Modes other than TRIANGLES are not implemented (yet)");
                }

                Accessor positionAccessor = GetAccessorByType("POSITION", model, prim);
                if (positionAccessor.Type != Accessor.TypeEnum.VEC3)
                {
                    throw new InvalidDataException("POSITION accessor must have type VEC3");
                }

                Accessor normalAccessor = GetAccessorByType("NORMAL", model, prim);
                if (normalAccessor != null &&
                    normalAccessor.Type != Accessor.TypeEnum.VEC3)
                {
                    throw new InvalidDataException("NORMAL accessor must have type VEC3");
                }

                Accessor texcoordAccessor = GetAccessorByType("TEXCOORD_0", model, prim);
                if (texcoordAccessor != null &&
                    texcoordAccessor.Type != Accessor.TypeEnum.VEC2)
                {
                    throw new InvalidDataException("TEXCOORD accessor must have type VEC2");
                }

                Accessor tangentAccessor = GetAccessorByType("TANGENT", model, prim);
                if (tangentAccessor != null &&
                    tangentAccessor.Type != Accessor.TypeEnum.VEC4)
                {
                    throw new InvalidDataException("TANGENT accessor must have type VEC4");
                }

                Accessor weightsAccessor = GetAccessorByType("WEIGHTS_0", model, prim);
                if (weightsAccessor != null &&
                    weightsAccessor.Type != Accessor.TypeEnum.VEC4)
                {
                    throw new InvalidDataException("WEIGHTS_0 accessor must have type VEC4");
                }

                Accessor jointsAccessor = GetAccessorByType("JOINTS_0", model, prim);
                if (jointsAccessor != null &&
                    jointsAccessor.Type != Accessor.TypeEnum.VEC4)
                {
                    throw new InvalidDataException("JOINTS_0 accessor must have type VEC4");
                }

                SkinnedTangentVertex[] vertices           = new SkinnedTangentVertex[positionAccessor.Count];
                BufferView             positionBufferView = model.BufferViews[positionAccessor.BufferView.Value];
                BufferView             normalsBufferView  = null;
                if (normalAccessor != null)
                {
                    normalsBufferView = model.BufferViews[normalAccessor.BufferView.Value];
                }
                BufferView texCoordBufferView = null;
                if (texcoordAccessor != null)
                {
                    texCoordBufferView = model.BufferViews[texcoordAccessor.BufferView.Value];
                }
                BufferView tangentBufferView = null;
                if (tangentAccessor != null)
                {
                    tangentBufferView = model.BufferViews[tangentAccessor.BufferView.Value];
                }
                BufferView weightsBufferView = null;
                if (weightsAccessor != null)
                {
                    weightsBufferView = model.BufferViews[weightsAccessor.BufferView.Value];
                }
                BufferView jointsBufferView = null;
                if (jointsAccessor != null)
                {
                    jointsBufferView = model.BufferViews[jointsAccessor.BufferView.Value];
                }

                ReadOnlySpan <Vector3> positionSpan = MemoryMarshal.Cast <byte, Vector3>(
                    new ReadOnlySpan <byte>(rawBuffers[positionBufferView.Buffer], positionBufferView.ByteOffset + positionAccessor.ByteOffset, positionAccessor.Count * 12));
                ReadOnlySpan <Vector3> normalsSpan = null;
                if (normalsBufferView != null)
                {
                    normalsSpan = MemoryMarshal.Cast <byte, Vector3>(
                        new ReadOnlySpan <byte>(rawBuffers[normalsBufferView.Buffer], normalsBufferView.ByteOffset + normalAccessor.ByteOffset, normalAccessor.Count * 12));
                }
                ReadOnlySpan <Vector2> texCoordSpan = null;
                if (texCoordBufferView != null)
                {
                    texCoordSpan = MemoryMarshal.Cast <byte, Vector2>(
                        new ReadOnlySpan <byte>(rawBuffers[texCoordBufferView.Buffer], texCoordBufferView.ByteOffset + texcoordAccessor.ByteOffset, texcoordAccessor.Count * 8));
                }
                ReadOnlySpan <Vector4> tangentSpan = null;
                if (tangentBufferView != null)
                {
                    tangentSpan = MemoryMarshal.Cast <byte, Vector4>(
                        new ReadOnlySpan <byte>(rawBuffers[tangentBufferView.Buffer], tangentBufferView.ByteOffset + tangentAccessor.ByteOffset, tangentAccessor.Count * 16));
                }
                ReadOnlySpan <Vector4> weightsSpan = null;
                if (weightsBufferView != null)
                {
                    weightsSpan = MemoryMarshal.Cast <byte, Vector4>(
                        new ReadOnlySpan <byte>(rawBuffers[weightsBufferView.Buffer], weightsBufferView.ByteOffset + weightsAccessor.ByteOffset, weightsAccessor.Count * 16));
                }
                ReadOnlySpan <Vector4> jointsSpan = null;
                if (jointsBufferView != null)
                {
                    jointsSpan = MemoryMarshal.Cast <byte, Vector4>(
                        new ReadOnlySpan <byte>(rawBuffers[jointsBufferView.Buffer], jointsBufferView.ByteOffset + jointsAccessor.ByteOffset, jointsAccessor.Count * 16));
                }

                for (int v = 0; v < vertices.Length; v++)
                {
                    vertices[v].Position = positionSpan[v];

                    if (normalsSpan != null)
                    {
                        vertices[v].Normal = normalsSpan[v];
                    }

                    if (texCoordSpan != null)
                    {
                        vertices[v].TexCoord = texCoordSpan[v];
                    }

                    if (tangentSpan != null)
                    {
                        vertices[v].Tangent = new Vector3(tangentSpan[v].X, tangentSpan[v].Y, tangentSpan[v].Z);
                        if (normalsSpan != null)
                        {
                            vertices[v].BiTangent = Vector3.Cross(normalsSpan[v], vertices[v].Tangent) * tangentSpan[v].W;
                        }
                    }
                }

                SkeletalMesh skeletalMesh = new SkeletalMesh();

                if (prim.Indices == null)
                {
                    skeletalMesh = SkeletalMesh.FromVertices(vertices);
                }
                else
                {
                    int      primIndex     = prim.Indices.Value;
                    Accessor indexAccessor = GetAccessorByIndex(primIndex, model);
                    if (indexAccessor != null)
                    {
                        if (indexAccessor.Type != Accessor.TypeEnum.SCALAR)
                        {
                            throw new InvalidDataException("Index accessor must have type SCALAR");
                        }

                        uint[] indices = new uint[indexAccessor.Count];

                        BufferView indexBufferView = model.BufferViews[indexAccessor.BufferView.Value];
                        switch (indexAccessor.ComponentType)
                        {
                        case Accessor.ComponentTypeEnum.UNSIGNED_BYTE:
                        {
                            ReadOnlySpan <byte> indexSpan =
                                new ReadOnlySpan <byte>(rawBuffers[indexBufferView.Buffer], indexBufferView.ByteOffset + indexAccessor.ByteOffset, indexAccessor.Count);

                            for (int idx = 0; idx < indices.Length; idx++)
                            {
                                indices[idx] = (uint)indexSpan[idx];
                            }
                        }
                        break;

                        case Accessor.ComponentTypeEnum.UNSIGNED_SHORT:
                        {
                            ReadOnlySpan <ushort> indexSpan = MemoryMarshal.Cast <byte, ushort>(
                                new ReadOnlySpan <byte>(rawBuffers[indexBufferView.Buffer], indexBufferView.ByteOffset + indexAccessor.ByteOffset, indexAccessor.Count * 2));

                            for (int idx = 0; idx < indices.Length; idx++)
                            {
                                indices[idx] = (uint)indexSpan[idx];
                            }
                        }
                        break;

                        case Accessor.ComponentTypeEnum.UNSIGNED_INT:
                        {
                            ReadOnlySpan <uint> indexSpan = MemoryMarshal.Cast <byte, uint>(
                                new ReadOnlySpan <byte>(rawBuffers[indexBufferView.Buffer], indexBufferView.ByteOffset + indexAccessor.ByteOffset, indexAccessor.Count * 4));

                            indices = indexSpan.ToArray();
                        }
                        break;

                        default:
                            throw new NotImplementedException("ComponentType " + indexAccessor.ComponentType + " not implemented.");
                        }

                        skeletalMesh = SkeletalMesh.FromVertices(vertices, indices);
                    }
                }

                skeletalMesh.Name = mesh.Name + "_" + p + ".skeletalMesh";
                packageToSaveIn.StoreAsset(skeletalMesh);
                meshes.Add(skeletalMesh);

                Materials.Material material = CreateMaterialForMesh(prim, model);
                skeletalMesh.Material = material;
            }

            return(meshes);
        }
Exemplo n.º 4
0
        private void LoadMesh(SkinnedMesh mesh, ColladaMesh colladaMesh, ColladaMesh.PrimitiveList primitives, Matrix objectMatrix)
        {
            //EDIT: the vertices now include the objectmatrix, so this is always identity
            objectMatrix      = colladaMesh.objectMatrix;
            mesh.Shader.World = Matrix.Identity;

            mesh.Primitives = new Primitives();

            mesh.Primitives.PrimitiveCount = primitives.PrimitiveCount;
            mesh.Primitives.VertexCount    = primitives.PrimitiveCount * 3;

            if (primitives.PrimitiveCount * 3 >= int.MaxValue)
            {
                throw new Exception("Too many vertices");
            }
            int numVertices = primitives.PrimitiveCount * 3;



            // TODO check if all inputs are available

            /*if ( meshPart.ContainsInput( ColladaMesh.Input.InputSemantics.Position ) ) positions = new Vector3[ numVertices ];
             * if ( meshPart.ContainsInput( ColladaMesh.Input.InputSemantics.Normal ) ) normals = new Vector3[ numVertices ];
             * if ( meshPart.ContainsInput( ColladaMesh.Input.InputSemantics.Texcoord, 1 ) ) texCoords = new Vector3[ numVertices ];
             * if ( meshPart.ContainsInput( ColladaMesh.Input.InputSemantics.TexTangent, 1 ) ) tangents = new Vector3[ numVertices ];*/


            SkinnedTangentVertex[] vertices = new SkinnedTangentVertex[numVertices];

            for (int i = 0; i < numVertices; i++)
            {
                SkinnedTangentVertex v = new SkinnedTangentVertex();
                v.pos    = primitives.GetVector3(ColladaMesh.Input.InputSemantics.Position, i); //GetPosition( i );
                v.normal = primitives.GetVector3(ColladaMesh.Input.InputSemantics.Normal, i);


                // Texture Coordinates
                Vector3 texcoord = Vector3.Zero;
                if (primitives.ContainsInput(ColladaMesh.Input.InputSemantics.Texcoord, 1))
                {
                    texcoord   = primitives.GetVector3(ColladaMesh.Input.InputSemantics.Texcoord, 1, i);
                    texcoord.Y = 1.0f - texcoord.Y; // V coordinate is inverted in max
                }
                v.uv = new Vector2(texcoord.X, texcoord.Y);

                // Tangent
                Vector3 tangent = Vector3.Zero;
                if (primitives.ContainsInput(ColladaMesh.Input.InputSemantics.TexTangent, 1))
                {
                    v.tangent = primitives.GetVector3(ColladaMesh.Input.InputSemantics.TexTangent, 1, i);
                }


                // Bone weights and joint indices

                int positionIndex = primitives.GetSourceIndex(
                    primitives.GetInput(ColladaMesh.Input.InputSemantics.Position), i);

                // WARNING: THIS IS EXTREMELY IMPORTANT AND COSTED MORE THAN 6 HOURS DEBUGGING
                //    Indexes are PREMULTIPLIED BY 3!!!
                v.blendIndices = colladaMesh.vertexSkinJoints[positionIndex];
                //v.blendIndices = Vector3.One;
                v.blendIndices = v.blendIndices * 3;
                v.blendWeights = colladaMesh.vertexSkinWeights[positionIndex];

                v.pos    = Vector3.Transform(v.pos, objectMatrix);
                v.normal = Vector3.Transform(v.normal, objectMatrix);

                vertices[i] = v;
            }

            //vertices[ 0 ].pos = Vector3.Transform( Vector3.Zero, Matrix.Invert( objectMatrix ) );
            //vertices[ 0 ].pos = Vector3.Zero;

            mesh.Primitives.InitializeFromVertices(skinnedModel.Game, vertices, SkinnedTangentVertex.SizeInBytes);

            //CalculateBoundingBox();
            //CalculateBoundingSphere();


            //TODO: Material, maybe not really a good way of doing this
            //LoadDataFromColladaMaterial( meshPart.Material );
        }
        } // GenerateVertexAndIndexBuffers()

        /// <summary>
        /// Render the animated model (will call UpdateAnimation internally,
        /// but if you do that yourself before calling this method, it gets
        /// optimized out). Rendering always uses the skinnedNormalMapping shader
        /// with the DiffuseSpecular20 technique.
        /// </summary>
        /// <param name="renderMatrix">Render matrix</param>
        public void Render(Matrix renderMatrix, ShaderEffect effect)
        {
            if (vertexBuffer == null || indexBuffer == null)
            {
                return;
            }

            // Make sure we use the correct vertex declaration for our shader.
            //game.GraphicsDevice.VertexDeclaration = TangentVertex.VertexDeclaration;

            // Set the world matrix for this object (often Identity).
            // The renderMatrix is directly applied to the matrices we use
            // as bone matrices for the shader (has to be done this way because
            // the bone matrices are transmitted transposed and we could lose
            // important render matrix translation data if we do not apply it there).
            //BaseGame.WorldMatrix = objectMatrix;
            // And set all bone matrices (Goblin has 40, but we support up to 80).
            //ShaderEffect.skinnedNormalMapping.SetBoneMatrices(
            //    GetBoneMatrices( renderMatrix ) );

            // Rendering is pretty straight forward (if you know how anyway).
            //ShaderEffect.skinnedNormalMapping.Render(
            //    material, "DiffuseSpecular20",
            //    RenderVertices );

            Matrix worldMatrix =

                //Matrix.CreateFromYawPitchRoll( -MathHelper.PiOver2, -MathHelper.PiOver2, 0 ) *
                //Matrix.CreateFromYawPitchRoll( 0, -MathHelper.PiOver2, 0 )
                renderMatrix;

            //* Matrix.CreateFromYawPitchRoll( -MathHelper.PiOver2, -MathHelper.PiOver2, 0 );

            effect.SetParametersCollada(material);

            //effect.ViewProjMatrix = model.Game.Camera.ViewProjection;


            //Microsoft.Xna.Framework.Graphics.SamplerState s = new SamplerState();
            //object o = effect.Effect.Parameters[ "DiffuseTextureSampler " ];


            if (verticesSkinned.Count > 0)
            {
                /*worldMatrix = Matrix.Identity;
                 * worldMatrix = model.bones[ 0 ].invBoneSkinMatrix * model.bones[ 0 ].initialMatrix;*/
                /*worldMatrix = objectMatrix;
                 * effect.WorldMatrix = worldMatrix;
                 * effect.WorldViewProjMatrix = worldMatrix * model.Game.Camera.ViewProjection;
                 * worldMatrix = Matrix.Identity;*/
                /*game.GraphicsDevice.VertexDeclaration = SkinnedTangentVertex.VertexDeclaration;
                 *
                 *
                 * //SetBoneMatrices( effect, model.GetBoneMatrices( worldMatrix ) );
                 *
                 *
                 *
                 * effect.RenderCollada( "SpecularPerPixelColored", RenderVerticesSkinned );*/


                effect.Effect.Parameters["viewProj"].SetValue(model.Game.Camera.ViewProjection);
                effect.Effect.Parameters["world"].SetValue(objectMatrix);
                //Matrix mat = Matrix.Identity;
                //mat.Translation = new Vector3( 1, 0, 0 );
                //effect.Effect.Parameters[ "world" ].SetValue( mat  );
                effect.Effect.Parameters["cameraPos"].SetValue(model.Game.Camera.ViewInverse.Translation);



                if (vertexDeclaration != null)
                {
                    vertexDeclaration = SkinnedTangentVertex.CreateVertexDeclaration(game);
                }
                game.GraphicsDevice.VertexDeclaration = vertexDeclaration;
                SetBoneMatrices(effect, model.GetBoneMatrices(renderMatrix));

                if (material.DiffuseTexture == null)
                {
                    effect.RenderCollada("DiffuseSpecularColored20", RenderVerticesSkinned);
                }
                else
                {
                    effect.RenderCollada("DiffuseSpecular20", RenderVerticesSkinned);
                }
            }
            else
            {
                worldMatrix                = objectMatrix * worldMatrix;
                effect.WorldMatrix         = worldMatrix;
                effect.WorldViewProjMatrix = worldMatrix * model.Game.Camera.ViewProjection;
                if (vertexDeclaration != null)
                {
                    vertexDeclaration = TangentVertexExtensions.CreateVertexDeclaration(game);
                }
                game.GraphicsDevice.VertexDeclaration = vertexDeclaration;

                if (material.DiffuseTexture == null)
                {
                    effect.RenderCollada("SpecularPerPixelColored", RenderVertices);
                }
                else if (material.NormalTexture == null || game.Keyboard.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.Y))
                {
                    effect.RenderCollada("SpecularPerPixel", RenderVertices);
                }
                else
                {
                    if (game.Keyboard.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.N))
                    {
                        effect.RenderCollada("SpecularPerPixel", RenderVertices);
                    }
                    else
                    {
                        //Normal Mapping
                        effect.RenderCollada("SpecularPerPixelNormalMapping", RenderVertices);
                    }
                }
            }
        } // Render(renderMatrix)
        } // GenerateVertexAndIndexBuffers()

        /// <summary>
        /// Optimize vertex buffer. Note: The vertices list array will be changed
        /// and shorted quite a lot here. We are also going to create the indices
        /// for the index buffer here (we don't have them yet, they are just
        /// sequential from the loading process above).
        ///
        /// Note: This method is highly optimized for speed, it performs
        /// hundred of times faster than OptimizeVertexBufferSlow, see below!
        /// </summary>
        /// <returns>ushort array for the optimized indices</returns>
        private ushort[] OptimizeVertexBufferSkinned()
        {
            List <SkinnedTangentVertex> newVertices =
                new List <SkinnedTangentVertex>();
            List <ushort> newIndices = new List <ushort>();

            // Helper to only search already added newVertices and for checking the
            // old position indices by transforming them into newVertices indices.
            List <int> newVerticesPositions = new List <int>();

            // Go over all vertices (indices are currently 1:1 with the vertices)
            for (int num = 0; num < verticesSkinned.Count; num++)
            {
                // Get current vertex
                SkinnedTangentVertex currentVertex = verticesSkinned[num];
                bool reusedExistingVertex          = false;

                // Find out which position index was used, then we can compare
                // all other vertices that share this position. They will not
                // all be equal, but some of them can be merged.
                int sharedPos = reuseVertexPositions[num];
                foreach (int otherVertexIndex in reverseReuseVertexPositions[sharedPos])
                {
                    // Only check the indices that have already been added!
                    if (otherVertexIndex != num &&
                        // Make sure we already are that far in our new index list
                        otherVertexIndex < newIndices.Count &&
                        // And make sure this index has been added to newVertices yet!
                        newIndices[otherVertexIndex] < newVertices.Count &&
                        // Then finally compare vertices (this call is slow, but thanks to
                        // all the other optimizations we don't have to call it that often)
                        SkinnedTangentVertex.NearlyEquals(
                            currentVertex, newVertices[newIndices[otherVertexIndex]]))
                    {
                        // Reuse the existing vertex, don't add it again, just
                        // add another index for it!
                        newIndices.Add((ushort)newIndices[otherVertexIndex]);
                        reusedExistingVertex = true;
                        break;
                    } // if (TangentVertex.NearlyEquals)
                }     // foreach (otherVertexIndex)

                if (reusedExistingVertex == false)
                {
                    // Add the currentVertex and set it as the current index
                    newIndices.Add((ushort)newVertices.Count);
                    newVertices.Add(currentVertex);
                } // if (reusedExistingVertex)
            }     // for (num)

            // Finally flip order of all triangles to allow us rendering
            // with CullCounterClockwiseFace (default for XNA) because all the data
            // is in CullClockwiseFace format right now!
            for (int num = 0; num < newIndices.Count / 3; num++)
            {
                ushort swap = newIndices[num * 3 + 1];
                newIndices[num * 3 + 1] = newIndices[num * 3 + 2];
                newIndices[num * 3 + 2] = swap;
            } // for

            // Reassign the vertices, we might have deleted some duplicates!
            verticesSkinned = newVertices;

            // And return index list for the caller
            return(newIndices.ToArray());
        } // OptimizeVertexBuffer()