/// <summary> /// Exports the specified model as an obj file with a texture /// </summary> /// <param name="models">The models to combine and export</param> public void ExportModelsAsObj(List <VModel> models) { var objMeshes = new List <ObjMesh>(); var animations = GetAllAnimations(models[0]); var animation = animations.First(a => a.Name.Contains("idle")); foreach (var model in models) { var objMesh = new ObjMesh(); // Load mesh var meshes = LoadModelMeshes(model); var mesh = meshes[0].Mesh; LoadVMeshIntoMesh(objMesh, mesh); // Apply animation var animMatrices = animation.GetAnimationMatrices(0, model.GetSkeleton(0)); if (objMesh.BlendWeights.Count == 0 && objMesh.BlendIndices.Count != 0) { objMesh.BlendWeights = objMesh.BlendIndices.Select(idxs => new Vector4((float)0.25, (float)0.25, (float)0.25, (float)0.25)).ToList(); } for (int i = 0; i < objMesh.VertexCount; i++) { var position = objMesh.Positions[i]; var resultVectors = new List <Vector3>(); float[] weights = new float[] { objMesh.BlendWeights[i].X, objMesh.BlendWeights[i].Y, objMesh.BlendWeights[i].Z, objMesh.BlendWeights[i].W, }; for (int m = 0; m < 4; m++) { var matrixIndex = (int)objMesh.BlendIndices[i][m]; resultVectors.Add(Vector3.Multiply(Vector3.Transform(position, animMatrices[matrixIndex]), weights[m])); } objMesh.Positions[i] = resultVectors.Aggregate((v1, v2) => Vector3.Add(v1, v2)); } // Add to list objMeshes.Add(objMesh); } // Merge all objMeshes into one // - merge textures and fix tex coords // - create one final merged texture // - append all vertex points, and fix indexes // - also make sure original v texcoords start in 0-1(wrapping dont work well with our new method) // var finalMesh = new ObjMesh(); // Take the initial material as the base of the merged materials finalMesh.Material = objMeshes.FirstOrDefault().Material.Clone(); var textureImages = objMeshes.Select(o => o.Material.TextureImage).ToList(); finalMesh.Material.TextureImage = ObjMaterial.StackImages(objMeshes.Select(o => o.Material.TextureImage).ToList()); finalMesh.Material.NormalsImage = ObjMaterial.StackImages(objMeshes.Select(o => o.Material.NormalsImage).ToList()); finalMesh.Material.SpecularImage = ObjMaterial.StackImages(objMeshes.Select(o => o.Material.SpecularImage).ToList()); float vCoord = 0; foreach (var objMesh in objMeshes) { var startIndex = finalMesh.VertexCount; finalMesh.Positions.AddRange(objMesh.Positions); finalMesh.Normals.AddRange(objMesh.Normals); float textureHeightModifier = (float)objMesh.Material.TextureImage.Height / finalMesh.Material.TextureImage.Height; finalMesh.TextureCoords.AddRange(objMesh.TextureCoords.Select(uv => { var v = uv.Y; // Lock in to 0-1 while (v > 1) { v -= 1; } while (v < 0) { v += 1; } // Adjust for merged image v = (v * textureHeightModifier) + vCoord; return(new Vector2(uv.X, v)); })); vCoord += textureHeightModifier; objMesh.Faces.ForEach(f => f.ForEach(v => v.IncreaseIndexing(startIndex))); finalMesh.Faces.AddRange(objMesh.Faces); } // Transform to be smaller and upright //finalMesh.Positions = finalMesh.Positions.Select(v => v = Vector3.Transform(v, TRANSFORMSOURCETOSTANDARD)).ToList(); finalMesh.WriteToFiles("out"); }
/// <summary> /// Loads the VMesh into our Objmesh /// </summary> /// <param name="objMesh">The objmesh to load into</param> /// <param name="mesh">The VMesh to load</param> private void LoadVMeshIntoMesh(ObjMesh objMesh, VMesh mesh) { var data = mesh.GetData(); var vbib = mesh.VBIB; foreach (var sceneObject in data.GetArray("m_sceneObjects")) { foreach (var drawCall in sceneObject.GetArray("m_drawCalls")) { var startingVertexCount = objMesh.Positions.Count; // Set this so we can offset the indicies for triangles correctly var vertexBufferInfo = drawCall.GetArray("m_vertexBuffers")[0]; // In what situation can we have more than 1 vertex buffer per draw call? var vertexBufferIndex = (int)vertexBufferInfo.GetIntegerProperty("m_hBuffer"); var vertexBuffer = vbib.VertexBuffers[vertexBufferIndex]; var indexBufferInfo = drawCall.GetSubCollection("m_indexBuffer"); var indexBufferIndex = (int)indexBufferInfo.GetIntegerProperty("m_hBuffer"); var indexBuffer = vbib.IndexBuffers[indexBufferIndex]; // Set vertex attributes foreach (var attribute in vertexBuffer.Attributes) { var buffer = ReadAttributeBuffer(vertexBuffer, attribute); var numComponents = buffer.Length / vertexBuffer.Count; if (attribute.Name == "BLENDINDICES") { var byteBuffer = buffer.Select(f => (byte)f).ToArray(); var rawBufferData = new byte[buffer.Length]; System.Buffer.BlockCopy(byteBuffer, 0, rawBufferData, 0, rawBufferData.Length); var blendIndices = rawBufferData.ChunkBy(4); objMesh.BlendIndices.AddRange(blendIndices); continue; } if (attribute.Name == "BLENDWEIGHT") { var vectors = ToVector4Array(buffer); objMesh.BlendWeights.AddRange(vectors); } if (attribute.Name == "POSITION") { var vectors = ToVector3Array(buffer); objMesh.Positions.AddRange(vectors); } if (attribute.Name == "NORMAL") { if (VMesh.IsCompressedNormalTangent(drawCall)) { var vectors = ToVector4Array(buffer); var(normals, tangents) = DecompressNormalTangents(vectors); objMesh.Normals.AddRange(normals); } else { var vectors = ToVector3Array(buffer); objMesh.Normals.AddRange(vectors); } continue; } if (attribute.Name == "TEXCOORD") { if (numComponents != 2) { // ignore textcoords that arent 2 continue; } var vectors = ToVector2Array(buffer); objMesh.TextureCoords.AddRange(vectors); } } // Set index buffer var startIndex = (int)drawCall.GetIntegerProperty("m_nStartIndex"); var indexCount = (int)drawCall.GetIntegerProperty("m_nIndexCount"); var indices = ReadIndices(indexBuffer, startIndex, indexCount); var newFaces = indices.ChunkBy(3).Select(idxList => idxList.Select(idx => new ObjFaceVertex(idx + startingVertexCount)).ToList()).ToList(); objMesh.Faces.AddRange(newFaces); var materialPath = drawCall.GetProperty <string>("m_material"); var materialResource = VpkLoader.LoadFile(materialPath + "_c"); var renderMaterial = (VMaterial)materialResource.DataBlock; var matName = Path.GetFileNameWithoutExtension(materialPath); if (objMesh.Material == null) { objMesh.Material = ObjMaterial.FromVMaterial(renderMaterial, Path.GetFileNameWithoutExtension(materialPath), VpkLoader); } else if (matName != objMesh.Material.Name) { // throw new Exception("2 different mats in same object"); } } } }