Files
2025-11-30 08:35:03 +02:00

242 lines
8.7 KiB
C#

// SPDX-FileCopyrightText: 2023 Unity Technologies and the glTFast authors
// SPDX-License-Identifier: Apache-2.0
#if DRACO_IS_RECENT
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
using Unity.Collections;
using Draco;
using GLTFast.Logging;
using GLTFast.Schema;
using UnityEngine.Assertions;
using UnityEngine.Rendering;
using Mesh = UnityEngine.Mesh;
namespace GLTFast {
class DracoMeshGenerator : MeshGeneratorBase {
Bounds? m_Bounds;
readonly bool m_NeedsNormals;
readonly bool m_NeedsTangents;
public DracoMeshGenerator(
IReadOnlyList<MeshPrimitiveBase> primitives,
SubMeshAssignment[] subMeshAssignments,
string[] morphTargetNames,
string meshName,
GltfImportBase gltfImport
)
: base(meshName)
{
// TODO: Add support for decoding multiple primitives into one mesh with sub-meshes.
Assert.IsTrue(
primitives.Count == 1,
"Draco-compressed, multi primitives/sub-mesh meshes are not supported."
);
Assert.IsNull(
subMeshAssignments,
"Draco-compressed, multi primitives/sub-mesh meshes are not supported."
);
var morphTargets = primitives[0].targets;
var hasMorphTargets = morphTargets != null && morphTargets.Length > 0;
var vertexCount = 0;
var vertexIntervals = hasMorphTargets
? new int[primitives.Count + 1]
: null;
for (var index = 0; index < primitives.Count; index++)
{
var primitive = primitives[index];
Assert.IsTrue(primitive.IsDracoCompressed);
var posAccessor = ((IGltfBuffers)gltfImport).GetAccessor(primitive.attributes.POSITION);
if (hasMorphTargets)
{
vertexIntervals[index] = vertexCount;
}
vertexCount += posAccessor.count;
var bounds = posAccessor.TryGetBounds();
if (bounds.HasValue)
{
m_Bounds = bounds.Value;
}
else
{
gltfImport.Logger?.Error(LogCode.MeshBoundsMissing, primitive.attributes.POSITION.ToString());
}
if (primitive.material < 0)
{
m_NeedsNormals = true;
}
else
{
var material = gltfImport.GetSourceMaterial(primitive.material);
m_NeedsNormals |= material.RequiresNormals;
m_NeedsTangents |= material.RequiresTangents;
}
}
if (hasMorphTargets)
{
InitializeMorphTargets(
primitives,
morphTargetNames,
vertexIntervals,
vertexCount,
morphTargets,
gltfImport
);
}
m_CreationTask = Decode(primitives, gltfImport);
}
void InitializeMorphTargets(
IReadOnlyList<MeshPrimitiveBase> primitives,
string[] morphTargetNames,
int[] vertexIntervals,
int vertexCount,
MorphTarget[] morphTargets,
GltfImportBase gltfImport
)
{
vertexIntervals[vertexIntervals.Length-1] = vertexCount;
m_MorphTargetsGenerator = new MorphTargetsGenerator(
vertexCount,
1,
morphTargets.Length,
morphTargetNames,
morphTargets[0].NORMAL >= 0,
morphTargets[0].TANGENT >= 0,
gltfImport
);
for (var subMesh = 0; subMesh < primitives.Count; subMesh++)
{
var primitive = primitives[subMesh];
for (var morphTargetIndex = 0; morphTargetIndex < primitive.targets.Length; morphTargetIndex++)
{
var target = primitive.targets[morphTargetIndex];
m_MorphTargetsGenerator.AddMorphTarget( 0, morphTargetIndex, target);
}
}
}
async Task<Mesh> Decode(IReadOnlyList<MeshPrimitiveBase> primitives, IGltfBuffers buffers)
{
Mesh mesh = null;
foreach (var primitive in primitives)
{
var dracoExt = primitive.Extensions.KHR_draco_mesh_compression;
var buffer = buffers.GetBufferView(dracoExt.bufferView, out _);
mesh = await StartDecode(buffer.AsNativeArrayReadOnly(), dracoExt.attributes);
}
if (mesh is null) {
return null;
}
if (m_Bounds.HasValue) {
mesh.bounds = m_Bounds.Value;
// Setting the sub-meshes' bounds to the overall bounds
// Calculating the actual sub-mesh bounds (by iterating the verts referenced
// by the sub-mesh indices) would be slow. Also, hardly any glTFs re-use
// the same vertex buffer across primitives of a node (which is the
// only way a mesh can have sub-meshes)
for (var i = 0; i < mesh.subMeshCount; i++) {
var subMeshDescriptor = mesh.GetSubMesh(i);
subMeshDescriptor.bounds = m_Bounds.Value;
mesh.SetSubMesh(
i,
subMeshDescriptor,
MeshUpdateFlags.DontValidateIndices
| MeshUpdateFlags.DontResetBoneBounds
| MeshUpdateFlags.DontNotifyMeshUsers
| MeshUpdateFlags.DontRecalculateBounds
);
}
} else {
mesh.RecalculateBounds();
}
if (m_MorphTargetsGenerator != null) {
await m_MorphTargetsGenerator.ApplyOnMeshAndDispose(mesh);
}
mesh.name = m_MeshName;
#if GLTFAST_KEEP_MESH_DATA
mesh.UploadMeshData(false);
#endif
return mesh;
}
async Task<Mesh> StartDecode(NativeArray<byte>.ReadOnly data, Attributes dracoAttributes)
{
var flags = DecodeSettings.ConvertSpace;
if (m_NeedsTangents)
{
flags |= DecodeSettings.RequireNormalsAndTangents;
}
else if (m_NeedsNormals)
{
flags |= DecodeSettings.RequireNormals;
}
if (m_MorphTargetsGenerator != null)
{
flags |= DecodeSettings.ForceUnityVertexLayout;
}
return await DracoDecoder.DecodeMesh(data, flags, GenerateAttributeIdMap(dracoAttributes));
}
static Dictionary<VertexAttribute, int> GenerateAttributeIdMap(Attributes attributes)
{
var result = new Dictionary<VertexAttribute, int>();
if (attributes.POSITION >= 0)
result[VertexAttribute.Position] = attributes.POSITION;
if (attributes.NORMAL >= 0)
result[VertexAttribute.Normal] = attributes.NORMAL;
if (attributes.TANGENT >= 0)
result[VertexAttribute.Tangent] = attributes.TANGENT;
if (attributes.COLOR_0 >= 0)
result[VertexAttribute.Color] = attributes.COLOR_0;
if (attributes.TEXCOORD_0 >= 0)
result[VertexAttribute.TexCoord0] = attributes.TEXCOORD_0;
if (attributes.TEXCOORD_1 >= 0)
result[VertexAttribute.TexCoord1] = attributes.TEXCOORD_1;
if (attributes.TEXCOORD_2 >= 0)
result[VertexAttribute.TexCoord2] = attributes.TEXCOORD_2;
if (attributes.TEXCOORD_3 >= 0)
result[VertexAttribute.TexCoord3] = attributes.TEXCOORD_3;
if (attributes.TEXCOORD_4 >= 0)
result[VertexAttribute.TexCoord4] = attributes.TEXCOORD_4;
if (attributes.TEXCOORD_5 >= 0)
result[VertexAttribute.TexCoord5] = attributes.TEXCOORD_5;
if (attributes.TEXCOORD_6 >= 0)
result[VertexAttribute.TexCoord6] = attributes.TEXCOORD_6;
if (attributes.TEXCOORD_7 >= 0)
result[VertexAttribute.TexCoord7] = attributes.TEXCOORD_7;
if (attributes.WEIGHTS_0 >= 0)
result[VertexAttribute.BlendWeight] = attributes.WEIGHTS_0;
if (attributes.JOINTS_0 >= 0)
result[VertexAttribute.BlendIndices] = attributes.JOINTS_0;
return result;
}
}
}
#endif // DRACO_IS_RECENT