using System; using System.IO; using System.Collections.Generic; using System.Runtime.InteropServices; using UnityEngine; namespace UnityExtension { public static class MeshExt { //------------------------------------------------------------------------------------------------------------ public static void RecalculateTangents(this Mesh lMesh) { //speed up math by copying the mesh arrays int[] triangles = lMesh.triangles; Vector3[] vertices = lMesh.vertices; Vector2[] uv = lMesh.uv; Vector3[] normals = lMesh.normals; //variable definitions int triangleCount = triangles.Length; int vertexCount = vertices.Length; Vector3[] tan1 = new Vector3[vertexCount]; Vector3[] tan2 = new Vector3[vertexCount]; Vector4[] tangents = new Vector4[vertexCount]; for (long a = 0; a < triangleCount; a += 3) { long i1 = triangles[a + 0]; long i2 = triangles[a + 1]; long i3 = triangles[a + 2]; Vector3 v1 = vertices[i1]; Vector3 v2 = vertices[i2]; Vector3 v3 = vertices[i3]; Vector2 w1 = uv[i1]; Vector2 w2 = uv[i2]; Vector2 w3 = uv[i3]; float x1 = v2.x - v1.x; float x2 = v3.x - v1.x; float y1 = v2.y - v1.y; float y2 = v3.y - v1.y; float z1 = v2.z - v1.z; float z2 = v3.z - v1.z; float s1 = w2.x - w1.x; float s2 = w3.x - w1.x; float t1 = w2.y - w1.y; float t2 = w3.y - w1.y; float r = 1.0f / (s1 * t2 - s2 * t1); Vector3 sdir = new Vector3((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r); Vector3 tdir = new Vector3((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r); tan1[i1] += sdir; tan1[i2] += sdir; tan1[i3] += sdir; tan2[i1] += tdir; tan2[i2] += tdir; tan2[i3] += tdir; } for (long a = 0; a < vertexCount; ++a) { Vector3 n = normals[a]; Vector3 t = tan1[a]; //Vector3 tmp = (t - n * Vector3.Dot(n, t)).normalized; //tangents[a] = new Vector4(tmp.x, tmp.y, tmp.z); Vector3.OrthoNormalize(ref n, ref t); tangents[a].x = t.x; tangents[a].y = t.y; tangents[a].z = t.z; tangents[a].w = (Vector3.Dot(Vector3.Cross(n, t), tan2[a]) < 0.0f) ? -1.0f : 1.0f; } lMesh.tangents = tangents; } //------------------------------------------------------------------------------------------------------------ public static void LoadOBJ(this Mesh lMesh, OBJData lData) { List lVertices = new List(); List lNormals = new List(); List lUVs = new List(); List[] lIndices = new List[lData.m_Groups.Count]; Dictionary lVertexIndexRemap = new Dictionary(); bool lHasNormals = lData.m_Normals.Count > 0; bool lHasUVs = lData.m_UVs.Count > 0; lMesh.subMeshCount = lData.m_Groups.Count; for (int lGCount = 0; lGCount < lData.m_Groups.Count; ++lGCount) { OBJGroup lGroup = lData.m_Groups[lGCount]; lIndices[lGCount] = new List(); for (int lFCount = 0; lFCount < lGroup.Faces.Count; ++lFCount) { OBJFace lFace = lGroup.Faces[lFCount]; // Unity3d doesn't support non-triangle faces // so we do simple fan triangulation for (int lVCount = 1; lVCount < lFace.Count - 1; ++lVCount) { foreach (int i in new int[]{0, lVCount, lVCount + 1}) { OBJFaceVertex lFaceVertex = lFace[i]; int lVertexIndex = -1; if (!lVertexIndexRemap.TryGetValue(lFaceVertex, out lVertexIndex)) { lVertexIndexRemap[lFaceVertex] = lVertices.Count; lVertexIndex = lVertices.Count; lVertices.Add(lData.m_Vertices[lFaceVertex.m_VertexIndex]); if (lHasUVs) { lUVs.Add(lData.m_UVs[lFaceVertex.m_UVIndex]); } if (lHasNormals) { lNormals.Add(lData.m_Normals[lFaceVertex.m_NormalIndex]); } } lIndices[lGCount].Add(lVertexIndex); } } } } lMesh.triangles = new int[]{ }; lMesh.vertices = lVertices.ToArray(); lMesh.uv = lUVs.ToArray(); lMesh.normals = lNormals.ToArray(); if (!lHasNormals) { lMesh.RecalculateNormals(); } lMesh.RecalculateTangents(); for (int lGCount = 0; lGCount < lData.m_Groups.Count; ++lGCount) { lMesh.SetTriangles(lIndices[lGCount].ToArray(), lGCount); } } //------------------------------------------------------------------------------------------------------------ public static OBJData EncodeOBJ(this Mesh lMesh) { OBJData lData = new OBJData { m_Vertices = new List(lMesh.vertices), m_UVs = new List(lMesh.uv), m_Normals = new List(lMesh.normals), m_UV2s = new List(lMesh.uv2), m_Colors = new List(lMesh.colors) }; int[] lIndices = null; OBJGroup lGroup = null; OBJFace lFace = null; OBJFaceVertex lFaceVertex = null; for (int lMCount = 0; lMCount < lMesh.subMeshCount; ++lMCount) { lIndices = lMesh.GetTriangles(lMCount); lGroup = new OBJGroup(lMesh.name + "_" + lMCount.ToString()); for (int lCount = 0; lCount < lIndices.Length; lCount += 3) { lFace = new OBJFace(); lFaceVertex = new OBJFaceVertex(); lFaceVertex.m_VertexIndex = lData.m_Vertices.Count > 0 ? lIndices[lCount] : -1; lFaceVertex.m_UVIndex = lData.m_UVs.Count > 0 ? lIndices[lCount] : -1; lFaceVertex.m_NormalIndex = lData.m_Normals.Count > 0 ? lIndices[lCount] : -1; lFaceVertex.m_UV2Index = lData.m_UV2s.Count > 0 ? lIndices[lCount] : -1; lFaceVertex.m_ColorIndex = lData.m_Colors.Count > 0 ? lIndices[lCount] : -1; lFace.AddVertex(lFaceVertex); lFaceVertex = new OBJFaceVertex(); lFaceVertex.m_VertexIndex = lData.m_Vertices.Count > 0 ? lIndices[lCount + 1] : -1; lFaceVertex.m_UVIndex = lData.m_UVs.Count > 0 ? lIndices[lCount + 1] : -1; lFaceVertex.m_NormalIndex = lData.m_Normals.Count > 0 ? lIndices[lCount + 1] : -1; lFaceVertex.m_UV2Index = lData.m_UV2s.Count > 0 ? lIndices[lCount + 1] : -1; lFaceVertex.m_ColorIndex = lData.m_Colors.Count > 0 ? lIndices[lCount + 1] : -1; lFace.AddVertex(lFaceVertex); lFaceVertex = new OBJFaceVertex(); lFaceVertex.m_VertexIndex = lData.m_Vertices.Count > 0 ? lIndices[lCount + 2] : -1; lFaceVertex.m_UVIndex = lData.m_UVs.Count > 0 ? lIndices[lCount + 2] : -1; lFaceVertex.m_NormalIndex = lData.m_Normals.Count > 0 ? lIndices[lCount + 2] : -1; lFaceVertex.m_UV2Index = lData.m_UV2s.Count > 0 ? lIndices[lCount + 2] : -1; lFaceVertex.m_ColorIndex = lData.m_Colors.Count > 0 ? lIndices[lCount + 2] : -1; lFace.AddVertex(lFaceVertex); lGroup.AddFace(lFace); } lData.m_Groups.Add(lGroup); } return lData; } //------------------------------------------------------------------------------------------------------------ internal const int MESH_BINARY_HEADER_SIZE = 20; internal const short MESH_BINARY_SIGNATURE = 0xF5; internal const short MESH_BINARY_VERSION = 1; //------------------------------------------------------------------------------------------------------------ public static bool LoadBinary(this Mesh lMesh, byte[] lData) { int lSizeOfVector2 = Marshal.SizeOf(typeof(Vector2)); int lSizeOfVector3 = Marshal.SizeOf(typeof(Vector3)); int lSizeOfVector4 = Marshal.SizeOf(typeof(Vector4)); int lSizeOfMatrix4x4 = Marshal.SizeOf(typeof(Matrix4x4)); int lSizeOfBoneWeight = Marshal.SizeOf(typeof(BoneWeight)); int lSizeOfColor = Marshal.SizeOf(typeof(Color)); int lDataOffset = MESH_BINARY_HEADER_SIZE; int lDeltaOffset = 0; if (lData == null || lData.Length < MESH_BINARY_HEADER_SIZE) { return false; } // Header short lSignature = BitConverter.ToInt16(lData, 0); short lVersion = BitConverter.ToInt16(lData, 2); if (lSignature != MESH_BINARY_SIGNATURE || lVersion != MESH_BINARY_VERSION) { return false; } lMesh.Clear(); int lVertexCount = BitConverter.ToInt32(lData, 4); int lIndexCount = BitConverter.ToInt32(lData, 8); int lSubMeshCount = BitConverter.ToInt32(lData, 12); byte lFlags = lData[16]; bool lUVFlag = (lFlags & 1) > 0; bool lUV1Flag = (lFlags & 2) > 0; bool lUV2Flag = (lFlags & 4) > 0; bool lNormalFlag = (lFlags & 8) > 0; bool lTangentFlag = (lFlags & 16) > 0; bool lColorFlag = (lFlags & 32) > 0; bool lBindPoseFlag = (lFlags & 64) > 0; bool lBoneWeightFlag = (lFlags & 128) > 0; // Vertices Vector3[] lVertices = new Vector3[lVertexCount]; lDeltaOffset = lVertices.Length * lSizeOfVector3; GCHandle lHandle = GCHandle.Alloc(lVertices, GCHandleType.Pinned); Marshal.Copy(lData, lDataOffset, lHandle.AddrOfPinnedObject(), lDeltaOffset); lHandle.Free(); lDataOffset += lDeltaOffset; lMesh.vertices = lVertices; lVertices = null; // UV Channel 0 Vector2[] lUVs = null; if (lUVFlag == true) { lUVs = new Vector2[lVertexCount]; lDeltaOffset = lUVs.Length * lSizeOfVector2; lHandle = GCHandle.Alloc(lUVs, GCHandleType.Pinned); Marshal.Copy(lData, lDataOffset, lHandle.AddrOfPinnedObject(), lDeltaOffset); lHandle.Free(); lDataOffset += lDeltaOffset; lMesh.uv = lUVs; Debug.Log("UV Count : " + lUVs.Length); lUVs = null; } // UV Channel 1 if (lUV1Flag == true) { lUVs = new Vector2[lVertexCount]; lDeltaOffset = lUVs.Length * lSizeOfVector2; lHandle = GCHandle.Alloc(lUVs, GCHandleType.Pinned); Marshal.Copy(lData, lDataOffset, lHandle.AddrOfPinnedObject(), lDeltaOffset); lHandle.Free(); lDataOffset += lDeltaOffset; lMesh.uv2 = lUVs; Debug.Log("UV1 Count : " + lUVs.Length); lUVs = null; } // UV Channel 2 if (lUV2Flag == true) { lUVs = new Vector2[lVertexCount]; lDeltaOffset = lUVs.Length * lSizeOfVector2; lHandle = GCHandle.Alloc(lUVs, GCHandleType.Pinned); Marshal.Copy(lData, lDataOffset, lHandle.AddrOfPinnedObject(), lDeltaOffset); lHandle.Free(); lDataOffset += lDeltaOffset; lMesh.uv2 = lUVs; Debug.Log("UV2 Count : " + lUVs.Length); lUVs = null; } // Normals if (lNormalFlag == true) { Vector3[] lNormals = new Vector3[lVertexCount]; lDeltaOffset = lNormals.Length * lSizeOfVector3; lHandle = GCHandle.Alloc(lNormals, GCHandleType.Pinned); Marshal.Copy(lData, lDataOffset, lHandle.AddrOfPinnedObject(), lDeltaOffset); lHandle.Free(); lDataOffset += lDeltaOffset; lMesh.normals = lNormals; Debug.Log("Normal Count : " + lNormals.Length); lNormals = null; } // Tangents if (lTangentFlag == true) { Vector4[] lTangents = new Vector4[lVertexCount]; lDeltaOffset = lTangents.Length * lSizeOfVector4; lHandle = GCHandle.Alloc(lTangents, GCHandleType.Pinned); Marshal.Copy(lData, lDataOffset, lHandle.AddrOfPinnedObject(), lDeltaOffset); lHandle.Free(); lDataOffset += lDeltaOffset; lMesh.tangents = lTangents; Debug.Log("Tangents Count : " + lTangents.Length); lTangents = null; } // Colors if (lColorFlag == true) { Color[] lColors = new Color[lVertexCount]; lDeltaOffset = lColors.Length * lSizeOfColor; lHandle = GCHandle.Alloc(lColors, GCHandleType.Pinned); Marshal.Copy(lData, lDataOffset, lHandle.AddrOfPinnedObject(), lDeltaOffset); lHandle.Free(); lDataOffset += lDeltaOffset; lMesh.colors = lColors; lColors = null; } // Bind Poses if (lBindPoseFlag == true) { Matrix4x4[] lBindPoses = new Matrix4x4[lVertexCount]; lDeltaOffset = lBindPoses.Length * lSizeOfMatrix4x4; lHandle = GCHandle.Alloc(lBindPoses, GCHandleType.Pinned); Marshal.Copy(lData, lDataOffset, lHandle.AddrOfPinnedObject(), lDeltaOffset); lHandle.Free(); lDataOffset += lDeltaOffset; lMesh.bindposes = lBindPoses; lBindPoses = null; } // Bone Weights if (lBoneWeightFlag == true) { BoneWeight[] lBoneWeight = new BoneWeight[lVertexCount]; lDeltaOffset = lBoneWeight.Length * lSizeOfBoneWeight; lHandle = GCHandle.Alloc(lBoneWeight, GCHandleType.Pinned); Marshal.Copy(lData, lDataOffset, lHandle.AddrOfPinnedObject(), lDeltaOffset); lHandle.Free(); lDataOffset += lDeltaOffset; lMesh.boneWeights = lBoneWeight; lBoneWeight = null; } // Indices int[] lIndices = new int[lIndexCount]; lDeltaOffset = lIndices.Length * sizeof(int); Buffer.BlockCopy(lData, lDataOffset, lIndices, 0, lDeltaOffset); lDataOffset += lDeltaOffset; lMesh.triangles = lIndices; lIndices = null; // SubMesh Indices for (int lSubMeshIndex = 0; lSubMeshIndex < lSubMeshCount; ++lSubMeshIndex) { int lSubMeshIndexCount = BitConverter.ToInt32(lData, lDataOffset); lDataOffset += sizeof(int); lIndices = new int[lSubMeshIndexCount]; lDeltaOffset = lIndices.Length * sizeof(int); Buffer.BlockCopy(lData, lDataOffset, lIndices, 0, lDeltaOffset); lDataOffset += lDeltaOffset; if (lIndices.Length > 0 && lIndices.Length % 3 == 0) { lMesh.SetTriangles(lIndices, lSubMeshIndex); } lIndices = null; } return true; } //------------------------------------------------------------------------------------------------------------ public static byte[] EncodeBinary(this Mesh lMesh) { // Currently no support for BlendShape int lSizeOfVector2 = Marshal.SizeOf(typeof(Vector2)); int lSizeOfVector3 = Marshal.SizeOf(typeof(Vector3)); int lSizeOfVector4 = Marshal.SizeOf(typeof(Vector4)); int lSizeOfMatrix4x4 = Marshal.SizeOf(typeof(Matrix4x4)); int lSizeOfBoneWeight = Marshal.SizeOf(typeof(BoneWeight)); int lSizeOfColor = Marshal.SizeOf(typeof(Color)); int lDataSize = MESH_BINARY_HEADER_SIZE; int lDeltaSize = 0; bool lUVFlag = false, lUV1Flag = false, lUV2Flag = false, lNormalFlag = false, lTangentFlag = false, lColorFlag = false, lBindPoseFlag = false, lBoneWeightFlag = false; byte[] lData = new byte[lDataSize]; // Vertices Vector3[] lVertices = lMesh.vertices; Int32Converter lVertexCount = lVertices.Length; lDeltaSize = lVertices.Length * lSizeOfVector3; Array.Resize(ref lData, lDataSize + lDeltaSize); GCHandle lHandle = GCHandle.Alloc(lVertices, GCHandleType.Pinned); Marshal.Copy(lHandle.AddrOfPinnedObject(), lData, lDataSize, lDeltaSize); lHandle.Free(); lDataSize += lDeltaSize; lVertices = null; // UV Channel 0 Vector2[] lUVs = lMesh.uv; if (lUVs.Length > 0) { lUVFlag = true; lDeltaSize = lUVs.Length * lSizeOfVector2; Array.Resize(ref lData, lDataSize + lDeltaSize); lHandle = GCHandle.Alloc(lUVs, GCHandleType.Pinned); Marshal.Copy(lHandle.AddrOfPinnedObject(), lData, lDataSize, lDeltaSize); lHandle.Free(); lDataSize += lDeltaSize; } lUVs = null; // UV Channel 1 lUVs = lMesh.uv2; if (lUVs.Length > 0) { lUV1Flag = true; lDeltaSize = lUVs.Length * lSizeOfVector2; Array.Resize(ref lData, lDataSize + lDeltaSize); lHandle = GCHandle.Alloc(lUVs, GCHandleType.Pinned); Marshal.Copy(lHandle.AddrOfPinnedObject(), lData, lDataSize, lDeltaSize); lHandle.Free(); lDataSize += lDeltaSize; } lUVs = null; // UV Channel 2 lUVs = lMesh.uv2; if (lUVs.Length > 0) { lUV2Flag = true; lDeltaSize = lUVs.Length * lSizeOfVector2; Array.Resize(ref lData, lDataSize + lDeltaSize); lHandle = GCHandle.Alloc(lUVs, GCHandleType.Pinned); Marshal.Copy(lHandle.AddrOfPinnedObject(), lData, lDataSize, lDeltaSize); lHandle.Free(); lDataSize += lDeltaSize; } lUVs = null; // Normals Vector3[] lNormals = lMesh.normals; if (lNormals.Length > 0) { lNormalFlag = true; lDeltaSize = lNormals.Length * lSizeOfVector3; Array.Resize(ref lData, lDataSize + lDeltaSize); lHandle = GCHandle.Alloc(lNormals, GCHandleType.Pinned); Marshal.Copy(lHandle.AddrOfPinnedObject(), lData, lDataSize, lDeltaSize); lHandle.Free(); lDataSize += lDeltaSize; } lNormals = null; // Tangents Vector4[] lTangents = lMesh.tangents; if (lTangents.Length > 0) { lTangentFlag = true; lDeltaSize = lTangents.Length * lSizeOfVector4; Array.Resize(ref lData, lDataSize + lDeltaSize); lHandle = GCHandle.Alloc(lTangents, GCHandleType.Pinned); Marshal.Copy(lHandle.AddrOfPinnedObject(), lData, lDataSize, lDeltaSize); lHandle.Free(); lDataSize += lDeltaSize; } lTangents = null; // Colors Color[] lColors = lMesh.colors; if (lColors.Length > 0) { lColorFlag = true; lDeltaSize = lColors.Length * lSizeOfColor; Array.Resize(ref lData, lDataSize + lDeltaSize); lHandle = GCHandle.Alloc(lColors, GCHandleType.Pinned); Marshal.Copy(lHandle.AddrOfPinnedObject(), lData, lDataSize, lDeltaSize); lHandle.Free(); lDataSize += lDeltaSize; } lColors = null; // BindPoses Matrix4x4[] lBindPoses = lMesh.bindposes; if (lBindPoses.Length > 0) { lBindPoseFlag = true; lDeltaSize = lBindPoses.Length * lSizeOfMatrix4x4; Array.Resize(ref lData, lDataSize + lDeltaSize); lHandle = GCHandle.Alloc(lBindPoses, GCHandleType.Pinned); Marshal.Copy(lHandle.AddrOfPinnedObject(), lData, lDataSize, lDeltaSize); lHandle.Free(); lDataSize += lDeltaSize; } lBindPoses = null; // BoneWeight BoneWeight[] lBoneWeight = lMesh.boneWeights; if (lBoneWeight.Length > 0) { lBoneWeightFlag = true; lDeltaSize = lBoneWeight.Length * lSizeOfBoneWeight; Array.Resize(ref lData, lDataSize + lDeltaSize); lHandle = GCHandle.Alloc(lBoneWeight, GCHandleType.Pinned); Marshal.Copy(lHandle.AddrOfPinnedObject(), lData, lDataSize, lDeltaSize); lHandle.Free(); lDataSize += lDeltaSize; } lBoneWeight = null; // Indices int[] lIndices = lMesh.triangles; Int32Converter lIndexCount = lIndices.Length; lDeltaSize = lIndices.Length * sizeof(int); Array.Resize(ref lData, lDataSize + lDeltaSize); Buffer.BlockCopy(lIndices, 0, lData, lDataSize, lDeltaSize); lDataSize += lDeltaSize; lIndices = null; // SubMesh Indices Int32Converter lSubMeshCount = lMesh.subMeshCount; for (int lSubMeshIndex = 0; lSubMeshIndex < lSubMeshCount; ++lSubMeshIndex) { lIndices = lMesh.GetTriangles(lSubMeshIndex); Int32Converter lSubMeshIndexCount = lIndices.Length; lDeltaSize = sizeof(int) + (lIndices.Length * sizeof(int)); Array.Resize(ref lData, lDataSize + lDeltaSize); lData[lDataSize + 0] = lSubMeshIndexCount.Byte1; lData[lDataSize + 1] = lSubMeshIndexCount.Byte2; lData[lDataSize + 2] = lSubMeshIndexCount.Byte3; lData[lDataSize + 3] = lSubMeshIndexCount.Byte4; Buffer.BlockCopy(lIndices, 0, lData, lDataSize, lDeltaSize - sizeof(int)); lDataSize += lDeltaSize; } // Header lData[0] = (byte)(MESH_BINARY_SIGNATURE & 0xFF); lData[1] = (byte)((MESH_BINARY_SIGNATURE >> 8) & 0xFF); lData[2] = (byte)(MESH_BINARY_VERSION & 0xFF); lData[3] = (byte)((MESH_BINARY_VERSION >> 8) & 0xFF); lData[4] = lVertexCount.Byte1; lData[5] = lVertexCount.Byte2; lData[6] = lVertexCount.Byte3; lData[7] = lVertexCount.Byte4; lData[8] = lIndexCount.Byte1; lData[9] = lIndexCount.Byte2; lData[10] = lIndexCount.Byte3; lData[11] = lIndexCount.Byte4; lData[12] = lSubMeshCount.Byte1; lData[13] = lSubMeshCount.Byte2; lData[14] = lSubMeshCount.Byte3; lData[15] = lSubMeshCount.Byte4; lData[16] = (byte)( (lUVFlag ? 1 : 0) | (lUV1Flag ? 2 : 0) | (lUV2Flag ? 4 : 0) | (lNormalFlag ? 8 : 0) | (lTangentFlag ? 16 : 0) | (lColorFlag ? 32 : 0) | (lBindPoseFlag ? 64 : 0) | (lBoneWeightFlag ? 128 : 0)); return lData; } } }