< Summary

Class:DCL.AvatarMeshCombinerUtils
Assembly:AvatarMeshCombiner
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Avatar/AvatarMeshCombiner/AvatarMeshCombinerUtils.cs
Covered lines:146
Uncovered lines:5
Coverable lines:151
Total lines:381
Line coverage:96.6% (146 of 151)
Covered branches:0
Total branches:0
Covered methods:8
Total methods:8
Method coverage:100% (8 of 8)

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
AvatarMeshCombinerUtils()0%330100%
CombineBones(...)0%550100%
FlattenMaterials(...)0%14.1414091.07%
ComputeSubMeshes(...)0%330100%
ComputeCombineInstancesData(...)0%330100%
CombineMeshesWithLayers(...)0%220100%
ResetBones(...)0%220100%
DrawDebugSkeleton(...)0%440100%

File(s)

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

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using DCL.Helpers;
 4using DCL.Shaders;
 5using MainScripts.DCL.Helpers.Utils;
 6using Unity.Collections;
 7using UnityEngine;
 8using UnityEngine.Pool;
 9using UnityEngine.Rendering;
 10
 11namespace DCL
 12{
 13    public static class AvatarMeshCombinerUtils
 14    {
 15        internal const string AVATAR_MAP_PROPERTY_NAME = "_AvatarMap";
 16
 117        internal static readonly int[] AVATAR_MAP_ID_PROPERTIES =
 18        {
 19            Shader.PropertyToID(AVATAR_MAP_PROPERTY_NAME + "1"),
 20            Shader.PropertyToID(AVATAR_MAP_PROPERTY_NAME + "2"),
 21            Shader.PropertyToID(AVATAR_MAP_PROPERTY_NAME + "3"),
 22            Shader.PropertyToID(AVATAR_MAP_PROPERTY_NAME + "4"),
 23            Shader.PropertyToID(AVATAR_MAP_PROPERTY_NAME + "5"),
 24            Shader.PropertyToID(AVATAR_MAP_PROPERTY_NAME + "6"),
 25            Shader.PropertyToID(AVATAR_MAP_PROPERTY_NAME + "7"),
 26            Shader.PropertyToID(AVATAR_MAP_PROPERTY_NAME + "8"),
 27            Shader.PropertyToID(AVATAR_MAP_PROPERTY_NAME + "9"),
 28            Shader.PropertyToID(AVATAR_MAP_PROPERTY_NAME + "10"),
 29            Shader.PropertyToID(AVATAR_MAP_PROPERTY_NAME + "11"),
 30            Shader.PropertyToID(AVATAR_MAP_PROPERTY_NAME + "12")
 31        };
 32
 133        private static bool VERBOSE = false;
 134        private static ILogger logger = new Logger(Debug.unityLogger.logHandler) { filterLogType = VERBOSE ? LogType.Log
 35
 36
 37        /// <summary>
 38        /// This method iterates over all the renderers contained in the given CombineLayer list, and
 39        /// outputs an array of all the bones per vertexes and an array of all the bone weights
 40        ///
 41        /// This is needed because Mesh.CombineMeshes don't calculate boneWeights correctly.
 42        /// When using Mesh.CombineMeshes, the boneWeights returned correspond to indexes of skeleton copies,
 43        /// not the same skeleton.
 44        /// </summary>
 45        /// <param name="layers">A CombineLayer list. You can generate this array using CombineLayerUtils.Slice().</para
 46        /// <returns>
 47        /// A list of Bones per vertex that share the same skeleton.
 48        /// A list of bone weights that share the same skeleton.
 49        /// </returns>
 50        public static (NativeArray<byte> bonesPerVertex, NativeArray<BoneWeight1> boneWeights) CombineBones(CombineLayer
 51        {
 1452            int layersCount = layers.Count;
 1453            int totalVertexes = 0;
 1454            int totalBones = 0;
 55
 1456            List<NativeArray<byte>> bonesPerVertexList = ListPool<NativeArray<byte>>.Get();
 1457            List<NativeArray<BoneWeight1>> boneWeightArrays = ListPool<NativeArray<BoneWeight1>>.Get();
 58
 8059            for (int layerIndex = 0; layerIndex < layersCount; layerIndex++)
 60            {
 2661                CombineLayer layer = layers[layerIndex];
 2662                var layerRenderers = layer.Renderers;
 2663                int layerRenderersCount = layerRenderers.Count;
 64
 21665                for (int i = 0; i < layerRenderersCount; i++)
 66                {
 8267                    var bonesPerVertex = layerRenderers[i].sharedMesh.GetBonesPerVertex();
 8268                    bonesPerVertexList.Add(bonesPerVertex);
 69
 8270                    var boneWeights = layerRenderers[i].sharedMesh.GetAllBoneWeights();
 8271                    boneWeightArrays.Add(boneWeights);
 72
 8273                    totalVertexes += bonesPerVertex.Length;
 8274                    totalBones += boneWeights.Length;
 75                }
 76            }
 77
 1478            NativeArray<byte> finalBpV = new NativeArray<byte>(totalVertexes, Allocator.Temp, NativeArrayOptions.Uniniti
 1479            NativeArray<BoneWeight1> finalBones = new NativeArray<BoneWeight1>(totalBones, Allocator.Temp, NativeArrayOp
 80
 1481            int indexOffset = 0;
 1482            int bonesPerVertexListCount = bonesPerVertexList.Count;
 83
 19284            for (int i = 0; i < bonesPerVertexListCount; i++)
 85            {
 8286                var narray = bonesPerVertexList[i];
 8287                int narrayLength = narray.Length;
 8288                NativeArray<byte>.Copy(narray, 0, finalBpV, indexOffset, narrayLength);
 8289                indexOffset += narrayLength;
 8290                narray.Dispose();
 91            }
 92
 1493            indexOffset = 0;
 1494            var finalBonesCount = boneWeightArrays.Count;
 95
 19296            for (int i = 0; i < finalBonesCount; i++)
 97            {
 8298                var narray = boneWeightArrays[i];
 8299                int narrayLength = narray.Length;
 82100                NativeArray<BoneWeight1>.Copy(narray, 0, finalBones, indexOffset, narrayLength);
 82101                indexOffset += narrayLength;
 82102                narray.Dispose();
 103            }
 104
 14105            ListPool<NativeArray<byte>>.Release(bonesPerVertexList);
 14106            ListPool<NativeArray<BoneWeight1>>.Release(boneWeightArrays);
 107
 14108            return (finalBpV, finalBones);
 109        }
 110
 111
 112        /// <summary>
 113        /// FlattenMaterials take a CombineLayer list and returns a FlattenedMaterialsData object.
 114        ///
 115        /// This object can be used to construct a combined mesh that has uniform data encoded in uv attributes.
 116        /// This type of encoding can be used to greatly reduce draw calls for seemingly unrelated objects.
 117        ///
 118        /// The returned object also contains a single material per CombineLayer.
 119        /// </summary>
 120        /// <param name="layers">A CombineLayer list. You can generate this array using CombineLayerUtils.Slice().</para
 121        /// <param name="materialAsset">Material asset that will be cloned to generate the returned materials.</param>
 122        /// <returns>A FlattenedMaterialsData object. This object can be used to construct a combined mesh that has unif
 123        public static FlattenedMaterialsData FlattenMaterials(CombineLayersList layers, Material materialAsset)
 124        {
 15125            int layersCount = layers.Count;
 126
 15127            int finalVertexCount = layers.TotalVerticesCount;
 15128            var result = new FlattenedMaterialsData(finalVertexCount, layersCount);
 129
 15130            int currentVertexCount = 0;
 131
 86132            for (int layerIndex = 0; layerIndex < layersCount; layerIndex++)
 133            {
 28134                CombineLayer layer = layers[layerIndex];
 28135                var layerRenderers = layer.Renderers;
 136
 28137                Material newMaterial = new Material(materialAsset);
 138
 28139                CullMode cullMode = layer.cullMode;
 28140                bool isOpaque = layer.isOpaque;
 141
 28142                if (isOpaque)
 15143                    MaterialUtils.SetOpaque(newMaterial);
 144                else
 13145                    MaterialUtils.SetTransparent(newMaterial);
 146
 28147                newMaterial.SetInt(ShaderUtils.Cull, (int)cullMode);
 148
 28149                result.materials[layerIndex] = newMaterial;
 150
 28151                int layerRenderersCount = layerRenderers.Count;
 152
 220153                for (int i = 0; i < layerRenderersCount; i++)
 154                {
 82155                    var renderer = layerRenderers[i];
 156
 157                    // Bone Weights
 82158                    var sharedMesh = renderer.sharedMesh;
 82159                    int vertexCount = sharedMesh.vertexCount;
 160
 161                    // Texture IDs
 82162                    Material mat = renderer.sharedMaterial;
 163
 82164                    Texture2D baseMap = (Texture2D)mat.GetTexture(ShaderUtils.BaseMap);
 82165                    Texture2D emissionMap = (Texture2D)mat.GetTexture(ShaderUtils.EmissionMap);
 82166                    float cutoff = mat.GetFloat(ShaderUtils.Cutoff);
 167
 82168                    int baseMapId = -1;
 82169                    if (baseMap != null && layer.textureToId.TryGetValue(baseMap, out baseMapId))
 170                    {
 82171                        if (baseMapId < AVATAR_MAP_ID_PROPERTIES.Length)
 172                        {
 82173                            int targetMap = AVATAR_MAP_ID_PROPERTIES[baseMapId];
 82174                            newMaterial.SetTexture(targetMap, baseMap);
 175                        }
 176                        else
 177                        {
 0178                            if (VERBOSE)
 0179                                logger.Log(LogType.Error, "FlattenMaterials", $"Base Map ID out of bounds! {baseMapId}")
 180                        }
 181                    }
 182
 82183                    int emissionMapId = -1;
 82184                    if (emissionMap != null && layer.textureToId.TryGetValue(emissionMap, out emissionMapId))
 185                    {
 4186                        if (emissionMapId < AVATAR_MAP_ID_PROPERTIES.Length)
 187                        {
 4188                            int targetMap = AVATAR_MAP_ID_PROPERTIES[emissionMapId];
 4189                            newMaterial.SetTexture(targetMap, emissionMap);
 190                        }
 191                        else
 192                        {
 0193                            if (VERBOSE)
 0194                                logger.Log(LogType.Error, "FlattenMaterials", $"Emission Map ID out of bounds! {emission
 195                        }
 196                    }
 197
 82198                    Vector4 baseColor = mat.GetVector(ShaderUtils.BaseColor);
 82199                    Vector4 emissionColor = mat.GetVector(ShaderUtils.EmissionColor);
 82200                    Vector3 texturePointerData = new Vector3(baseMapId, emissionMapId, cutoff);
 201
 41764202                    for (int ai = 0; ai < vertexCount; ai++)
 203                    {
 20800204                        result.texturePointers[currentVertexCount] = texturePointerData;
 20800205                        result.colors[currentVertexCount] = baseColor;
 20800206                        result.emissionColors[currentVertexCount] = emissionColor;
 20800207                        currentVertexCount++;
 208                    }
 209
 82210                    if (VERBOSE)
 0211                        logger.Log($"Layer {i} - vertexCount: {vertexCount} - texturePointers: ({baseMapId}, {emissionMa
 212                }
 213
 28214                SRPBatchingHelper.OptimizeMaterial(newMaterial);
 215            }
 216
 15217            return result;
 218        }
 219
 220        /// <summary>
 221        /// ComputeSubMeshes iterates over the given CombineLayer list, and returns a list that can be used to map a
 222        /// sub-mesh for each CombineLayer object. A CombineLayer object can group more than a single mesh.
 223        ///
 224        /// Note that this had to be done because Mesh.CombineMeshes lack the option of controlling the sub-mesh
 225        /// output. Currently the only options are to combine everything in a single sub-mesh, or generate a single sub-
 226        /// per combined mesh. The CombineLayer approach may need to combine specific meshes to a single sub-mesh
 227        /// (because they all can have the same material, if they share the same render state -- i.e. transparency or cu
 228        /// </summary>
 229        /// <param name="layers">A CombineLayer list. You can generate this array using CombineLayerUtils.Slice().</para
 230        /// <returns>A SubMeshDescriptor list that can be used later to set the sub-meshes of the final combined mesh
 231        /// in a way that each sub-mesh corresponds with its own layer.</returns>
 232        public static NativeArray<SubMeshDescriptor> ComputeSubMeshes(CombineLayersList layers)
 233        {
 14234            int layersCount = layers.Count;
 14235            int subMeshIndexOffset = 0;
 236
 14237            var result = new NativeArray<SubMeshDescriptor>(layersCount, Allocator.Temp, NativeArrayOptions.Uninitialize
 238
 80239            for (int layerIndex = 0; layerIndex < layersCount; layerIndex++)
 240            {
 26241                CombineLayer layer = layers[layerIndex];
 26242                var layerRenderers = layer.Renderers;
 26243                int layerRenderersCount = layerRenderers.Count;
 244
 26245                int subMeshVertexCount = 0;
 26246                int subMeshIndexCount = 0;
 247
 216248                for (int i = 0; i < layerRenderersCount; i++)
 249                {
 82250                    var renderer = layerRenderers[i];
 251
 82252                    int vertexCount = renderer.sharedMesh.vertexCount;
 82253                    int indexCount = (int)renderer.sharedMesh.GetIndexCount(0);
 254
 82255                    subMeshVertexCount += vertexCount;
 82256                    subMeshIndexCount += indexCount;
 257                }
 258
 26259                var subMesh = new SubMeshDescriptor(subMeshIndexOffset, subMeshIndexCount)
 260                    {
 261                        vertexCount = subMeshVertexCount,
 262                    };
 263
 26264                result[layerIndex] = subMesh;
 265
 26266                subMeshIndexOffset += subMeshIndexCount;
 267            }
 268
 14269            return result;
 270        }
 271
 272        /// <summary>
 273        /// ComputeCombineInstancesData returns a CombineInstance list that can be used to combine all the meshes
 274        /// specified by the given CombineLayer list. This is done via Mesh.CombineMeshes() Unity method.
 275        /// </summary>
 276        /// <param name="layers">A CombineLayer list. You can generate this array using CombineLayerUtils.Slice()</param
 277        /// <returns>CombineInstance list usable by Mesh.CombineMeshes()</returns>
 278        public static CombineInstance[] ComputeCombineInstancesData(CombineLayersList layers)
 279        {
 13280            int layersCount = layers.Count;
 13281            var combineInstancesCount = layers.TotalRenderersCount;
 282
 13283            var result = new CombineInstance[combineInstancesCount];
 284
 13285            var combineInstanceIndex = 0;
 286
 74287            for (int layerIndex = 0; layerIndex < layersCount; layerIndex++)
 288            {
 24289                CombineLayer layer = layers[layerIndex];
 24290                var layerRenderers = layer.Renderers;
 24291                int layerRenderersCount = layerRenderers.Count;
 292
 204293                for (int i = 0; i < layerRenderersCount; i++)
 294                {
 78295                    var renderer = layerRenderers[i];
 296
 78297                    Transform meshTransform = renderer.transform;
 78298                    Transform prevParent = meshTransform.parent;
 78299                    meshTransform.SetParent(null, true);
 300
 78301                    result[combineInstanceIndex] = new CombineInstance
 302                    {
 303                        subMeshIndex = 0, // this means the source sub-mesh, not destination
 304                        mesh = renderer.sharedMesh,
 305                        transform = meshTransform.localToWorldMatrix
 306                    };
 307
 78308                    combineInstanceIndex++;
 78309                    meshTransform.SetParent(prevParent);
 310                }
 311            }
 312
 13313            return result;
 314        }
 315
 316        public static Mesh CombineMeshesWithLayers(CombineInstance[] combineInstancesData, in CombineLayersList layers)
 317        {
 13318            Mesh result = new Mesh();
 319
 320            // Is important to use the layerRenderers to combine (i.e. no the original renderers)
 321            // Layer renderers are in a specific order that must be abided, or the combining will be broken.
 13322            using var allRenderersRental = PoolUtils.RentList<SkinnedMeshRenderer>();
 13323            var layerRenderers = allRenderersRental.GetList();
 324
 74325            for (var i = 0; i < layers.Layers.Count; i++)
 326            {
 24327                CombineLayer t = layers.Layers[i];
 24328                layerRenderers.AddRange(t.Renderers);
 329            }
 330
 13331            using var bakedInstances = BakedCombineInstances.Bake(combineInstancesData, layerRenderers);
 13332            result.CombineMeshes(combineInstancesData, true, true);
 333
 13334            return result;
 13335        }
 336
 337        /// <summary>
 338        /// ResetBones will reset the given SkinnedMeshRenderer bones to the original bindposes position.
 339        ///
 340        /// This is done without taking into account the rootBone. We need to do it this way because the meshes must be
 341        /// combined posing to match the raw bindposes matrix.
 342        ///
 343        /// If the combined mesh don't match the bindposes matrices, the resulting skinning will not work.
 344        ///
 345        /// For this reason, this method doesn't resemble the original method that unity uses to reset the skeleton foun
 346        /// https://github.com/Unity-Technologies/UnityCsReference/blob/61f92bd79ae862c4465d35270f9d1d57befd1761/Editor/
 347        /// </summary>
 348        internal static void ResetBones(Matrix4x4[] bindPoses, IReadOnlyList<Transform> bones)
 349        {
 1764350            for (int i = 0; i < bones.Count; i++)
 351            {
 868352                Transform bone = bones[i];
 868353                Matrix4x4 bindPose = bindPoses[i].inverse;
 868354                bone.position = bindPose.MultiplyPoint3x4(Vector3.zero);
 868355                bone.rotation = bindPose.rotation;
 356
 868357                Vector3 bindPoseScale = bindPose.lossyScale;
 868358                Vector3 boneScale = bone.lossyScale;
 359
 868360                bone.localScale = new Vector3(bindPoseScale.x / boneScale.x,
 361                    bindPoseScale.y / boneScale.y,
 362                    bindPoseScale.z / boneScale.z);
 363            }
 364
 365#if UNITY_EDITOR
 14366            DrawDebugSkeleton(bones);
 367#endif
 14368        }
 369
 370        internal static void DrawDebugSkeleton(IReadOnlyList<Transform> bones)
 371        {
 1764372            for (int i = 0; i < bones.Count; i++)
 373            {
 868374                Transform bone = bones[i];
 868375                Debug.DrawLine(bone.position, bone.position + bone.forward, Color.cyan, 60);
 376
 4298377                foreach (Transform child in bone) { Debug.DrawLine(bone.position, child.position, Color.green, 60); }
 378            }
 14379        }
 380    }
 381}