// SPDX-FileCopyrightText: 2023 Unity Technologies and the Draco for Unity authors // SPDX-License-Identifier: Apache-2.0 #if UNITY_STANDALONE || UNITY_WEBGL || UNITY_IOS || UNITY_TVOS || UNITY_VISIONOS || UNITY_ANDROID || UNITY_WSA || UNITY_LUMIN #define DRACO_PLATFORM_SUPPORTED #else #define DRACO_PLATFORM_NOT_SUPPORTED #endif using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Threading.Tasks; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using Unity.Jobs; using UnityEngine; using UnityEngine.Rendering; [assembly: InternalsVisibleTo("Draco.Editor")] namespace Draco { /// /// Provides Draco mesh decoding. /// public static class DracoDecoder { /// /// These ensure best performance when using DecodeMesh variants that use /// as parameter. Pass them to the subsequent /// /// method. They're used internally for DecodeMesh variants returning a directly. /// public const MeshUpdateFlags defaultMeshUpdateFlags = MeshUpdateFlags.DontRecalculateBounds | MeshUpdateFlags.DontValidateIndices | MeshUpdateFlags.DontNotifyMeshUsers | MeshUpdateFlags.DontResetBoneBounds; /// /// Decodes a Draco mesh. /// /// MeshData used to create the mesh /// Compressed Draco data /// A DecodeResult public static async Task DecodeMesh( Mesh.MeshData meshData, NativeSlice encodedData ) { return await DecodeMesh(meshData, encodedData, DecodeSettings.Default, null); } /// /// Decode setting flags public static async Task DecodeMesh( Mesh.MeshData meshData, NativeSlice encodedData, DecodeSettings decodeSettings ) { return await DecodeMesh(meshData, encodedData, decodeSettings, null); } /// /// Attribute type to index map public static async Task DecodeMesh( Mesh.MeshData meshData, NativeSlice encodedData, DecodeSettings decodeSettings, Dictionary attributeIdMap ) { CertifySupportedPlatform( #if UNITY_EDITOR false #endif ); var encodedDataPtr = GetUnsafeReadOnlyIntPtr(encodedData); var result = await DecodeMesh( meshData, encodedDataPtr, encodedData.Length, decodeSettings, attributeIdMap ); return result; } /// public static async Task DecodeMesh( Mesh.MeshData meshData, byte[] encodedData ) { return await DecodeMesh(meshData, encodedData, DecodeSettings.Default, null); } /// /// Decode setting flags public static async Task DecodeMesh( Mesh.MeshData meshData, byte[] encodedData, DecodeSettings decodeSettings ) { return await DecodeMesh(meshData, encodedData, decodeSettings, null); } /// /// Attribute type to index map public static async Task DecodeMesh( Mesh.MeshData meshData, byte[] encodedData, DecodeSettings decodeSettings, Dictionary attributeIdMap ) { CertifySupportedPlatform( #if UNITY_EDITOR false #endif ); var encodedDataPtr = PinGCArrayAndGetDataAddress(encodedData, out var gcHandle); var result = await DecodeMesh( meshData, encodedDataPtr, encodedData.Length, decodeSettings, attributeIdMap ); UnsafeUtility.ReleaseGCObject(gcHandle); return result; } /// /// Decodes a Draco mesh. /// /// /// Consider using /// for increased performance. /// /// Compressed Draco data /// Unity Mesh or null in case of errors public static async Task DecodeMesh( NativeSlice encodedData ) { return await DecodeMesh(encodedData, DecodeSettings.Default, null); } /// /// Decode setting flags public static async Task DecodeMesh( NativeSlice encodedData, DecodeSettings decodeSettings ) { return await DecodeMesh(encodedData, decodeSettings, null); } /// /// Attribute type to index map public static async Task DecodeMesh( NativeSlice encodedData, DecodeSettings decodeSettings, Dictionary attributeIdMap ) { CertifySupportedPlatform( #if UNITY_EDITOR false #endif ); var encodedDataPtr = GetUnsafeReadOnlyIntPtr(encodedData); var meshDataArray = Mesh.AllocateWritableMeshData(1); var mesh = meshDataArray[0]; var result = await DecodeMesh( mesh, encodedDataPtr, encodedData.Length, decodeSettings, attributeIdMap ); if (!result.success) { meshDataArray.Dispose(); return null; } var unityMesh = new Mesh(); Mesh.ApplyAndDisposeWritableMeshData(meshDataArray, unityMesh, defaultMeshUpdateFlags); if (result.boneWeightData != null) { result.boneWeightData.ApplyOnMesh(unityMesh); result.boneWeightData.Dispose(); } if (unityMesh.GetTopology(0) == MeshTopology.Triangles) { if (result.calculateNormals) { unityMesh.RecalculateNormals(); } if ((decodeSettings & DecodeSettings.RequireTangents) != 0) { unityMesh.RecalculateTangents(); } } return unityMesh; } /// public static async Task DecodeMesh( byte[] encodedData ) { return await DecodeMesh(encodedData, DecodeSettings.Default, null); } /// public static async Task DecodeMesh( byte[] encodedData, DecodeSettings decodeSettings ) { return await DecodeMesh(encodedData, decodeSettings, null); } /// public static async Task DecodeMesh( byte[] encodedData, DecodeSettings decodeSettings, Dictionary attributeIdMap ) { CertifySupportedPlatform( #if UNITY_EDITOR false #endif ); return await DecodeMeshInternal( encodedData, decodeSettings, attributeIdMap ); } /// /// Creates an attribute type to index map from indices for bone weights and joints. /// /// Bone weights attribute index. /// Bone joints attribute index. /// public static Dictionary CreateAttributeIdMap( int weightsAttributeId, int jointsAttributeId ) { Dictionary result = null; if (weightsAttributeId >= 0) { result = new Dictionary { [VertexAttribute.BlendWeight] = weightsAttributeId }; } if (jointsAttributeId >= 0) { result ??= new Dictionary(); result[VertexAttribute.BlendIndices] = jointsAttributeId; } return result; } internal static async Task DecodeMeshInternal( byte[] encodedData, DecodeSettings decodeSettings, Dictionary attributeIdMap #if UNITY_EDITOR ,bool sync = false #endif ) { var encodedDataPtr = PinGCArrayAndGetDataAddress(encodedData, out var gcHandle); var meshDataArray = Mesh.AllocateWritableMeshData(1); var mesh = meshDataArray[0]; var result = await DecodeMesh( mesh, encodedDataPtr, encodedData.Length, decodeSettings, attributeIdMap #if UNITY_EDITOR ,sync #endif ); UnsafeUtility.ReleaseGCObject(gcHandle); if (!result.success) { meshDataArray.Dispose(); return null; } var unityMesh = new Mesh(); Mesh.ApplyAndDisposeWritableMeshData(meshDataArray, unityMesh, defaultMeshUpdateFlags); unityMesh.bounds = result.bounds; if (result.calculateNormals) { unityMesh.RecalculateNormals(); } if ((decodeSettings & DecodeSettings.RequireTangents) != 0) { unityMesh.RecalculateTangents(); } return unityMesh; } static async Task DecodeMesh( Mesh.MeshData meshData, IntPtr encodedData, int size, DecodeSettings decodeSettings, Dictionary attributeIdMap #if UNITY_EDITOR ,bool sync = false #endif ) { var dracoNative = new DracoNative(meshData, decodeSettings); #if UNITY_EDITOR if (sync) { dracoNative.InitSync(encodedData, size); } else #endif { await WaitForJobHandle(dracoNative.Init(encodedData, size)); } if (dracoNative.ErrorOccured()) { dracoNative.DisposeDracoMesh(); return new DecodeResult(); } dracoNative.CreateMesh( out var calculateNormals, attributeIdMap ); #if UNITY_EDITOR if (sync) { dracoNative.DecodeVertexDataSync(); } else #endif { await WaitForJobHandle(dracoNative.DecodeVertexData()); } var error = dracoNative.ErrorOccured(); dracoNative.DisposeDracoMesh(); if (error) { return new DecodeResult(); } var bounds = dracoNative.CreateBounds(); var success = dracoNative.PopulateMeshData(bounds); BoneWeightData boneWeightData = null; if (success && dracoNative.hasBoneWeightData) { boneWeightData = new BoneWeightData(dracoNative.bonesPerVertex, dracoNative.boneWeights); dracoNative.DisposeBoneWeightData(); } return new DecodeResult( success, bounds, calculateNormals, boneWeightData ); } static async Task WaitForJobHandle(JobHandle jobHandle) { while (!jobHandle.IsCompleted) { await Task.Yield(); } jobHandle.Complete(); } static unsafe IntPtr GetUnsafeReadOnlyIntPtr(NativeSlice encodedData) { return (IntPtr)encodedData.GetUnsafeReadOnlyPtr(); } static unsafe IntPtr PinGCArrayAndGetDataAddress(byte[] encodedData, out ulong gcHandle) { return (IntPtr)UnsafeUtility.PinGCArrayAndGetDataAddress(encodedData, out gcHandle); } #if !UNITY_EDITOR && DRACO_PLATFORM_SUPPORTED [System.Diagnostics.Conditional("FALSE")] #endif internal static void CertifySupportedPlatform( #if UNITY_EDITOR bool editorImport #endif ) { #if DRACO_PLATFORM_NOT_SUPPORTED #if UNITY_EDITOR #if !DRACO_IGNORE_PLATFORM_NOT_SUPPORTED if (!editorImport) { throw new NotSupportedException("Draco for Unity is not supported on the active build target. This will not work in a build, please switch to a supported platform in the build settings. You can bypass this exception in the Editor by setting the scripting define `DRACO_IGNORE_PLATFORM_NOT_SUPPORTED`."); } #endif // !DRACO_IGNORE_PLATFORM_NOT_SUPPORTED #else // In a build, always throw the exception. throw new NotSupportedException("Draco for Unity is not supported on this platform."); #endif #endif // DRACO_PLATFORM_NOT_SUPPORTED } } }