< Summary

Class:DCL.AvatarMeshCombiner
Assembly:AvatarMeshCombiner
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Avatar/AvatarMeshCombiner/AvatarMeshCombiner.cs
Covered lines:41
Uncovered lines:2
Coverable lines:43
Total lines:182
Line coverage:95.3% (41 of 43)
Covered branches:0
Total branches:0
Covered methods:2
Total methods:2
Method coverage:100% (2 of 2)

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
BoneTransform(...)0%110100%
CombineSkinnedMeshes(...)0%9.019095.35%

File(s)

/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Avatar/AvatarMeshCombiner/AvatarMeshCombiner.cs

#LineLine coverage
 1using System.Collections.Generic;
 2using Unity.Collections;
 3using UnityEngine;
 4using UnityEngine.Pool;
 5
 6namespace DCL
 7{
 8    /// <summary>
 9    /// This class is used to determine the original materials uniform properties
 10    /// will be passed on to UV values. texturePointers and emissionColors are bound to UV channels.
 11    /// Colors are bound to the color channel.
 12    /// </summary>
 13    public struct FlattenedMaterialsData
 14    {
 15        public Material[] materials;
 16        public NativeArray<Vector3> texturePointers;
 17        public NativeArray<Vector4> colors;
 18        public NativeArray<Vector4> emissionColors;
 19
 20        public FlattenedMaterialsData(int vertexCount, int materialsCount)
 21        {
 22            texturePointers = new NativeArray<Vector3>(vertexCount, Allocator.Temp, NativeArrayOptions.UninitializedMemo
 23            colors = new NativeArray<Vector4>(vertexCount, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
 24            emissionColors = new NativeArray<Vector4>(vertexCount, Allocator.Temp, NativeArrayOptions.UninitializedMemor
 25            materials = new Material[materialsCount];
 26        }
 27
 28        public void Dispose()
 29        {
 30            texturePointers.Dispose();
 31            colors.Dispose();
 32            emissionColors.Dispose();
 33        }
 34    }
 35
 36    /// <summary>
 37    /// This utility class can combine avatar meshes into a single optimal mesh.
 38    /// </summary>
 39    public static class AvatarMeshCombiner
 40    {
 41        public struct Output
 42        {
 43            public bool isValid;
 44            public Mesh mesh;
 45            public Material[] materials;
 46        }
 47
 48        private readonly struct BoneTransform
 49        {
 50            public readonly Vector3 Pos;
 51            public readonly Quaternion Rot;
 52            public readonly Vector3 Scale;
 53
 54            public BoneTransform(Vector3 pos, Quaternion rot, Vector3 scale)
 55            {
 74456                Pos = pos;
 74457                Rot = rot;
 74458                Scale = scale;
 74459            }
 60        }
 61
 62        // These must match the channels defined in the material shader.
 63        // Channels must be defined with the TEXCOORD semantic.
 64        private const int TEXTURE_POINTERS_UV_CHANNEL_INDEX = 2;
 65        private const int EMISSION_COLORS_UV_CHANNEL_INDEX = 3;
 66
 67        /// <summary>
 68        /// CombineSkinnedMeshes combines a list of skinned mesh renderers that share the same bone structure into a
 69        /// single mesh with the fewest numbers of sub-meshes as possible. It will return a list of materials that match
 70        /// the number of sub-meshes to be used with a Renderer component.
 71        /// <br/>
 72        /// The sub-meshes are divided according to the following constraints:
 73        /// <ul>
 74        /// <li>Culling mode</li>
 75        /// <li>Transparency or opacity of each renderer to be combined</li>
 76        /// <li>Texture count of the accumulated renderers, only including emission and albedo textures.</li>
 77        /// </ul>
 78        /// </summary>
 79        /// <param name="bindPoses">Bindposes that will be used by all the renderers.</param>
 80        /// <param name="bones">Bones that will be used by all the renderers.</param>
 81        /// <param name="renderers">Renderers to be combined.</param>
 82        /// <param name="materialAsset">Material asset to be used in the resulting Output object. This Material will be 
 83        /// <returns>An Output object with the mesh and materials. Output.isValid will return true if the combining is s
 84        public static Output CombineSkinnedMeshes(Matrix4x4[] bindPoses, IReadOnlyList<Transform> bones, IReadOnlyList<S
 85        {
 1386            Output result = new Output();
 87
 1388            var bonesTransforms = ListPool<BoneTransform>.Get();
 89
 1390            if (keepPose)
 91            {
 151292                foreach (var bone in bones)
 74493                    bonesTransforms.Add(new BoneTransform(bone.position, bone.rotation, bone.localScale));
 94            }
 95
 96            //
 97            // Reset bones to put character in T pose. Renderers are going to be baked later.
 98            // This is a workaround, it had to be done because renderers original matrices don't match the T pose.
 99            // We need wearables in T pose to properly combine the avatar mesh.
 100            //
 13101            AvatarMeshCombinerUtils.ResetBones(bindPoses, bones);
 102
 103            // Get combined layers. Layers are groups of renderers that have a id -> tex mapping.
 104            //
 105            // This id is going to get written to uv channels so the material can use up to 12 textures
 106            // in a single draw call.
 107            //
 108            // Layers are divided accounting for the 12 textures limit and transparency/opaque limit.
 109
 13110            using var layers = CombineLayersList.Rent();
 111
 13112            if (!CombineLayerUtils.TrySlice(renderers, layers))
 113            {
 0114                result.isValid = false;
 0115                return result;
 116            }
 117
 118            // Here, the final combined mesh is generated. This mesh still doesn't have the UV encoded
 119            // samplers and some needed attributes. Those will be added below.
 13120            var combineInstancesData = AvatarMeshCombinerUtils.ComputeCombineInstancesData(layers);
 13121            Mesh finalMesh = AvatarMeshCombinerUtils.CombineMeshesWithLayers(combineInstancesData, layers);
 122
 123            // Note about bindPoses and boneWeights reassignment:
 124            //
 125            // This has to be done because CombineMeshes doesn't identify different meshes
 126            // with boneWeights that correspond to the same bones. Also, bindposes are
 127            // stacked and repeated for each mesh when they shouldn't.
 128            //
 129            // This is OK when combining multiple SkinnedMeshRenderers that animate independently,
 130            // but not in our use case.
 131
 13132            finalMesh.bindposes = bindPoses;
 133
 13134            var (bonesPerVertex, boneWeights) = AvatarMeshCombinerUtils.CombineBones(layers);
 13135            finalMesh.SetBoneWeights(bonesPerVertex, boneWeights);
 136
 13137            bonesPerVertex.Dispose();
 13138            boneWeights.Dispose();
 139
 13140            var flattenedMaterialsData = AvatarMeshCombinerUtils.FlattenMaterials(layers, materialAsset);
 13141            finalMesh.SetUVs(EMISSION_COLORS_UV_CHANNEL_INDEX, flattenedMaterialsData.emissionColors);
 13142            finalMesh.SetUVs(TEXTURE_POINTERS_UV_CHANNEL_INDEX, flattenedMaterialsData.texturePointers);
 13143            finalMesh.SetColors(flattenedMaterialsData.colors);
 144
 13145            flattenedMaterialsData.Dispose();
 146
 147            // Each layer corresponds with a subMesh. This is to take advantage of the sharedMaterials array.
 148            //
 149            // When a renderer has many sub-meshes, each materials array element correspond to the sub-mesh of
 150            // the same index. Each layer needs to be renderer with its own material, so it becomes very useful.
 151            //
 13152            var subMeshDescriptors = AvatarMeshCombinerUtils.ComputeSubMeshes(layers);
 153
 13154            if (subMeshDescriptors.Length > 1)
 155            {
 11156                finalMesh.subMeshCount = subMeshDescriptors.Length;
 11157                finalMesh.SetSubMeshes(subMeshDescriptors);
 158            }
 159
 13160            finalMesh.Optimize();
 161
 13162            result.mesh = finalMesh;
 13163            result.materials = flattenedMaterialsData.materials;
 13164            result.isValid = true;
 165
 13166            if (keepPose)
 167            {
 1512168                for (int i = 0; i < bones.Count; i++)
 169                {
 744170                    var boneTransform = bonesTransforms[i];
 744171                    bones[i].position = boneTransform.Pos;
 744172                    bones[i].rotation = boneTransform.Rot;
 744173                    bones[i].localScale = boneTransform.Scale;
 174                }
 175
 12176                ListPool<BoneTransform>.Release(bonesTransforms);
 177            }
 178
 13179            return result;
 13180        }
 181    }
 182}