// Copyright 2017 The Draco Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // using System; using System.Collections; using System.Collections.Generic; using System.Runtime.InteropServices; using Unity.Collections.LowLevel.Unsafe; using UnityEngine; public unsafe class DracoMeshLoader { // These values must be exactly the same as the values in draco_types.h. // Attribute data type. enum DataType { DT_INVALID = 0, DT_INT8, DT_UINT8, DT_INT16, DT_UINT16, DT_INT32, DT_UINT32, DT_INT64, DT_UINT64, DT_FLOAT32, DT_FLOAT64, DT_BOOL }; // These values must be exactly the same as the values in // geometry_attribute.h. // Attribute type. enum AttributeType { INVALID = -1, POSITION = 0, NORMAL, COLOR, TEX_COORD, // A special id used to mark attributes that are not assigned to any known // predefined use case. Such attributes are often used for a shader specific // data. GENERIC }; // The order must be consistent with C++ interface. [StructLayout (LayoutKind.Sequential)] public struct DracoData { public int dataType; public IntPtr data; } [StructLayout (LayoutKind.Sequential)] public struct DracoAttribute { public int attributeType; public int dataType; public int numComponents; public int uniqueId; } [StructLayout (LayoutKind.Sequential)] public struct DracoMesh { public int numFaces; public int numVertices; public int numAttributes; } // Release data associated with DracoMesh. [DllImport ("dracodec_unity")] private static extern void ReleaseDracoMesh( DracoMesh**mesh); // Release data associated with DracoAttribute. [DllImport ("dracodec_unity")] private static extern void ReleaseDracoAttribute(DracoAttribute**attr); // Release attribute data. [DllImport ("dracodec_unity")] private static extern void ReleaseDracoData( DracoData**data); // Decodes compressed Draco::Mesh in buffer to mesh. On input, mesh // must be null. The returned mesh must released with ReleaseDracoMesh. [DllImport ("dracodec_unity")] private static extern int DecodeDracoMesh( byte[] buffer, int length, DracoMesh**mesh); // Returns the DracoAttribute at index in mesh. On input, attribute must be // null. The returned attr must be released with ReleaseDracoAttribute. [DllImport ("dracodec_unity")] private static extern bool GetAttribute( DracoMesh* mesh, int index, DracoAttribute**attr); // Returns the DracoAttribute of type at index in mesh. On input, attribute // must be null. E.g. If the mesh has two texture coordinates then // GetAttributeByType(mesh, AttributeType.TEX_COORD, 1, &attr); will return // the second TEX_COORD attribute. The returned attr must be released with // ReleaseDracoAttribute. [DllImport ("dracodec_unity")] private static extern bool GetAttributeByType( DracoMesh* mesh, AttributeType type, int index, DracoAttribute**attr); // Returns the DracoAttribute with unique_id in mesh. On input, attribute // must be null.The returned attr must be released with // ReleaseDracoAttribute. [DllImport ("dracodec_unity")] private static extern bool GetAttributeByUniqueId(DracoMesh* mesh, int unique_id, DracoAttribute**attr); // Returns an array of indices as well as the type of data in data_type. On // input, indices must be null. The returned indices must be released with // ReleaseDracoData. [DllImport ("dracodec_unity")] private static extern bool GetMeshIndices( DracoMesh* mesh, DracoData**indices); // Returns an array of attribute data as well as the type of data in // data_type. On input, data must be null. The returned data must be // released with ReleaseDracoData. [DllImport ("dracodec_unity")] private static extern bool GetAttributeData( DracoMesh* mesh, DracoAttribute* attr, DracoData**data); public int LoadMeshFromAsset(string assetName, ref List meshes) { TextAsset asset = Resources.Load(assetName, typeof(TextAsset)) as TextAsset; if (asset == null) { Debug.Log ("Didn't load file!"); return -1; } byte[] encodedData = asset.bytes; Debug.Log(encodedData.Length.ToString()); if (encodedData.Length == 0) { Debug.Log ("Didn't load encoded data!"); return -1; } return ConvertDracoMeshToUnity(encodedData, ref meshes); } // Decodes a Draco mesh, creates a Unity mesh from the decoded data and // adds the Unity mesh to meshes. encodedData is the compressed Draco mesh. public unsafe int ConvertDracoMeshToUnity(byte[] encodedData, ref List meshes) { float startTime = Time.realtimeSinceStartup; DracoMesh *mesh = null; if (DecodeDracoMesh(encodedData, encodedData.Length, &mesh) <= 0) { Debug.Log("Failed: Decoding error."); return -1; } float decodeTimeMilli = (Time.realtimeSinceStartup - startTime) * 1000.0f; Debug.Log("decodeTimeMilli: " + decodeTimeMilli.ToString()); Debug.Log("Num indices: " + mesh->numFaces.ToString()); Debug.Log("Num vertices: " + mesh->numVertices.ToString()); Debug.Log("Num attributes: " + mesh->numAttributes.ToString()); Mesh unityMesh = CreateUnityMesh(mesh); UnityMeshToCamera(ref unityMesh); meshes.Add(unityMesh); int numFaces = mesh->numFaces; ReleaseDracoMesh(&mesh); return numFaces; } // Creates a Unity mesh from the decoded Draco mesh. public unsafe Mesh CreateUnityMesh(DracoMesh *dracoMesh) { float startTime = Time.realtimeSinceStartup; int numFaces = dracoMesh->numFaces; int[] newTriangles = new int[dracoMesh->numFaces * 3]; Vector3[] newVertices = new Vector3[dracoMesh->numVertices]; Vector2[] newUVs = null; Vector3[] newNormals = null; Color[] newColors = null; byte[] newGenerics = null; // Copy face indices. DracoData *indicesData; GetMeshIndices(dracoMesh, &indicesData); int elementSize = DataTypeSize((DracoMeshLoader.DataType)indicesData->dataType); int *indices = (int*)(indicesData->data); var indicesPtr = UnsafeUtility.AddressOf(ref newTriangles[0]); UnsafeUtility.MemCpy(indicesPtr, indices, newTriangles.Length * elementSize); ReleaseDracoData(&indicesData); // Copy positions. DracoAttribute *attr = null; GetAttributeByType(dracoMesh, AttributeType.POSITION, 0, &attr); DracoData* posData = null; GetAttributeData(dracoMesh, attr, &posData); elementSize = DataTypeSize((DracoMeshLoader.DataType)posData->dataType) * attr->numComponents; var newVerticesPtr = UnsafeUtility.AddressOf(ref newVertices[0]); UnsafeUtility.MemCpy(newVerticesPtr, (void*)posData->data, dracoMesh->numVertices * elementSize); ReleaseDracoData(&posData); ReleaseDracoAttribute(&attr); // Copy normals. if (GetAttributeByType(dracoMesh, AttributeType.NORMAL, 0, &attr)) { DracoData* normData = null; if (GetAttributeData(dracoMesh, attr, &normData)) { elementSize = DataTypeSize((DracoMeshLoader.DataType)normData->dataType) * attr->numComponents; newNormals = new Vector3[dracoMesh->numVertices]; var newNormalsPtr = UnsafeUtility.AddressOf(ref newNormals[0]); UnsafeUtility.MemCpy(newNormalsPtr, (void*)normData->data, dracoMesh->numVertices * elementSize); Debug.Log("Decoded mesh normals."); ReleaseDracoData(&normData); ReleaseDracoAttribute(&attr); } } // Copy texture coordinates. if (GetAttributeByType(dracoMesh, AttributeType.TEX_COORD, 0, &attr)) { DracoData* texData = null; if (GetAttributeData(dracoMesh, attr, &texData)) { elementSize = DataTypeSize((DracoMeshLoader.DataType)texData->dataType) * attr->numComponents; newUVs = new Vector2[dracoMesh->numVertices]; var newUVsPtr = UnsafeUtility.AddressOf(ref newUVs[0]); UnsafeUtility.MemCpy(newUVsPtr, (void*)texData->data, dracoMesh->numVertices * elementSize); Debug.Log("Decoded mesh texcoords."); ReleaseDracoData(&texData); ReleaseDracoAttribute(&attr); } } // Copy colors. if (GetAttributeByType(dracoMesh, AttributeType.COLOR, 0, &attr)) { DracoData* colorData = null; if (GetAttributeData(dracoMesh, attr, &colorData)) { elementSize = DataTypeSize((DracoMeshLoader.DataType)colorData->dataType) * attr->numComponents; newColors = new Color[dracoMesh->numVertices]; var newColorsPtr = UnsafeUtility.AddressOf(ref newColors[0]); UnsafeUtility.MemCpy(newColorsPtr, (void*)colorData->data, dracoMesh->numVertices * elementSize); Debug.Log("Decoded mesh colors."); ReleaseDracoData(&colorData); ReleaseDracoAttribute(&attr); } } // Copy generic data. This script does not do anyhting with the generic // data. if (GetAttributeByType(dracoMesh, AttributeType.GENERIC, 0, &attr)) { DracoData* genericData = null; if (GetAttributeData(dracoMesh, attr, &genericData)) { elementSize = DataTypeSize((DracoMeshLoader.DataType)genericData->dataType) * attr->numComponents; newGenerics = new byte[dracoMesh->numVertices * elementSize]; var newGenericPtr = UnsafeUtility.AddressOf(ref newGenerics[0]); UnsafeUtility.MemCpy(newGenericPtr, (void*)genericData->data, dracoMesh->numVertices * elementSize); Debug.Log("Decoded mesh generic data."); ReleaseDracoData(&genericData); ReleaseDracoAttribute(&attr); } } float copyDecodedDataTimeMilli = (Time.realtimeSinceStartup - startTime) * 1000.0f; Debug.Log("copyDecodedDataTimeMilli: " + copyDecodedDataTimeMilli.ToString()); startTime = Time.realtimeSinceStartup; Mesh mesh = new Mesh(); #if UNITY_2017_3_OR_NEWER mesh.indexFormat = (newVertices.Length > System.UInt16.MaxValue) ? UnityEngine.Rendering.IndexFormat.UInt32 : UnityEngine.Rendering.IndexFormat.UInt16; #else if (newVertices.Length > System.UInt16.MaxValue) { throw new System.Exception("Draco meshes with more than 65535 vertices are only supported from Unity 2017.3 onwards."); } #endif mesh.vertices = newVertices; mesh.SetTriangles(newTriangles, 0, true); if (newUVs != null) { mesh.uv = newUVs; } if (newNormals != null) { mesh.normals = newNormals; } else { mesh.RecalculateNormals(); Debug.Log("Mesh doesn't have normals, recomputed."); } if (newColors != null) { mesh.colors = newColors; } float convertTimeMilli = (Time.realtimeSinceStartup - startTime) * 1000.0f; Debug.Log("convertTimeMilli: " + convertTimeMilli.ToString()); return mesh; } // Scale and translate the decoded mesh so it will be visible to // a new camera's default settings. public unsafe void UnityMeshToCamera(ref Mesh mesh) { float startTime = Time.realtimeSinceStartup; mesh.RecalculateBounds(); float scale = 0.5f / mesh.bounds.extents.x; if (0.5f / mesh.bounds.extents.y < scale) { scale = 0.5f / mesh.bounds.extents.y; } if (0.5f / mesh.bounds.extents.z < scale) { scale = 0.5f / mesh.bounds.extents.z; } Vector3[] vertices = mesh.vertices; int i = 0; while (i < vertices.Length) { vertices[i] *= scale; i++; } mesh.vertices = vertices; mesh.RecalculateBounds(); Vector3 translate = mesh.bounds.center; translate.x = 0 - mesh.bounds.center.x; translate.y = 0 - mesh.bounds.center.y; translate.z = 2 - mesh.bounds.center.z; i = 0; while (i < vertices.Length) { vertices[i] += translate; i++; } mesh.vertices = vertices; float transformTimeMilli = (Time.realtimeSinceStartup - startTime) * 1000.0f; Debug.Log("transformTimeMilli: " + transformTimeMilli.ToString()); } private int DataTypeSize(DataType dt) { switch (dt) { case DataType.DT_INT8: case DataType.DT_UINT8: return 1; case DataType.DT_INT16: case DataType.DT_UINT16: return 2; case DataType.DT_INT32: case DataType.DT_UINT32: return 4; case DataType.DT_INT64: case DataType.DT_UINT64: return 8; case DataType.DT_FLOAT32: return 4; case DataType.DT_FLOAT64: return 8; case DataType.DT_BOOL: return 1; default: return -1; } } }