// 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
}
}
}