< 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:158
Uncovered lines:5
Coverable lines:163
Total lines:415
Line coverage:96.9% (158 of 163)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
AvatarMeshCombinerUtils()0%330100%
CombineBonesPerVertex(...)0%440100%
CombineBonesWeights(...)0%440100%
FlattenMaterials(...)0%20.1520092.75%
ComputeSubMeshes(...)0%330100%
ComputeCombineInstancesData(...)0%330100%
CombineMeshesWithLayers(...)0%330100%
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 Unity.Collections;
 6using UnityEngine;
 7using UnityEngine.Rendering;
 8
 9namespace DCL
 10{
 11    public static class AvatarMeshCombinerUtils
 12    {
 13        internal const string AVATAR_MAP_PROPERTY_NAME = "_AvatarMap";
 14
 115        internal static readonly int[] AVATAR_MAP_ID_PROPERTIES =
 16        {
 17            Shader.PropertyToID(AVATAR_MAP_PROPERTY_NAME + "1"),
 18            Shader.PropertyToID(AVATAR_MAP_PROPERTY_NAME + "2"),
 19            Shader.PropertyToID(AVATAR_MAP_PROPERTY_NAME + "3"),
 20            Shader.PropertyToID(AVATAR_MAP_PROPERTY_NAME + "4"),
 21            Shader.PropertyToID(AVATAR_MAP_PROPERTY_NAME + "5"),
 22            Shader.PropertyToID(AVATAR_MAP_PROPERTY_NAME + "6"),
 23            Shader.PropertyToID(AVATAR_MAP_PROPERTY_NAME + "7"),
 24            Shader.PropertyToID(AVATAR_MAP_PROPERTY_NAME + "8"),
 25            Shader.PropertyToID(AVATAR_MAP_PROPERTY_NAME + "9"),
 26            Shader.PropertyToID(AVATAR_MAP_PROPERTY_NAME + "10"),
 27            Shader.PropertyToID(AVATAR_MAP_PROPERTY_NAME + "11"),
 28            Shader.PropertyToID(AVATAR_MAP_PROPERTY_NAME + "12")
 29        };
 30
 131        private static bool VERBOSE = false;
 132        private static ILogger logger = new Logger(Debug.unityLogger.logHandler) { filterLogType = VERBOSE ? LogType.Log
 33
 34        /// <summary>
 35        /// This method iterates over all the renderers contained in the given CombineLayer list, and
 36        /// outputs an array of all the bones per vertexes
 37        ///
 38        /// This is needed because Mesh.CombineMeshes don't calculate boneWeights correctly.
 39        /// When using Mesh.CombineMeshes, the boneWeights returned correspond to indexes of skeleton copies,
 40        /// not the same skeleton.
 41        /// </summary>
 42        /// <param name="layers">A CombineLayer list. You can generate this array using CombineLayerUtils.Slice().</para
 43        /// <returns>A list of Bones per vertex that share the same skeleton.</returns>
 44        public static NativeArray<byte> CombineBonesPerVertex(List<CombineLayer> layers)
 45        {
 2046            int layersCount = layers.Count;
 2047            int totalVertexes = 0;
 48
 2049            List<NativeArray<byte>> bonesPerVertexList = new List<NativeArray<byte>>();
 50
 10651            for (int layerIndex = 0; layerIndex < layersCount; layerIndex++)
 52            {
 3353                CombineLayer layer = layers[layerIndex];
 3354                var layerRenderers = layer.renderers;
 3355                int layerRenderersCount = layerRenderers.Count;
 56
 25457                for (int i = 0; i < layerRenderersCount; i++)
 58                {
 9459                    var bonesPerVertex = layerRenderers[i].sharedMesh.GetBonesPerVertex();
 60
 9461                    bonesPerVertexList.Add(bonesPerVertex);
 9462                    totalVertexes += bonesPerVertex.Length;
 63                }
 64            }
 65
 2066            NativeArray<byte> finalBpV = new NativeArray<byte>(totalVertexes, Allocator.Temp, NativeArrayOptions.Uniniti
 67
 2068            int indexOffset = 0;
 2069            int bonesPerVertexListCount = bonesPerVertexList.Count;
 70
 22871            for (int i = 0; i < bonesPerVertexListCount; i++)
 72            {
 9473                var narray = bonesPerVertexList[i];
 9474                int narrayLength = narray.Length;
 9475                NativeArray<byte>.Copy(narray, 0, finalBpV, indexOffset, narrayLength);
 9476                indexOffset += narrayLength;
 9477                narray.Dispose();
 78            }
 79
 2080            return finalBpV;
 81        }
 82
 83        /// <summary>
 84        /// This method iterates over all the renderers contained in the given CombineLayer list, and
 85        /// outputs an array of all the bone weights
 86        ///
 87        /// This is needed because Mesh.CombineMeshes don't calculate boneWeights correctly.
 88        /// When using Mesh.CombineMeshes, the boneWeights returned correspond to indexes of skeleton copies,
 89        /// not the same skeleton.
 90        /// </summary>
 91        /// <param name="layers">A CombineLayer list. You can generate this array using CombineLayerUtils.Slice().</para
 92        /// <returns>A list of bone weights that share the same skeleton.</returns>
 93        public static NativeArray<BoneWeight1> CombineBonesWeights(List<CombineLayer> layers)
 94        {
 2095            int layersCount = layers.Count;
 2096            int totalBones = 0;
 97
 2098            List<NativeArray<BoneWeight1>> boneWeightArrays = new List<NativeArray<BoneWeight1>>(10);
 99
 106100            for (int layerIndex = 0; layerIndex < layersCount; layerIndex++)
 101            {
 33102                CombineLayer layer = layers[layerIndex];
 33103                var layerRenderers = layer.renderers;
 33104                int layerRenderersCount = layerRenderers.Count;
 105
 254106                for (int i = 0; i < layerRenderersCount; i++)
 107                {
 94108                    var boneWeights = layerRenderers[i].sharedMesh.GetAllBoneWeights();
 94109                    boneWeightArrays.Add(boneWeights);
 94110                    totalBones += boneWeights.Length;
 111                }
 112            }
 113
 20114            NativeArray<BoneWeight1> finalBones = new NativeArray<BoneWeight1>(totalBones, Allocator.Temp, NativeArrayOp
 115
 20116            int indexOffset = 0;
 20117            var finalBonesCount = boneWeightArrays.Count;
 118
 228119            for (int i = 0; i < finalBonesCount; i++)
 120            {
 94121                var narray = boneWeightArrays[i];
 94122                int narrayLength = narray.Length;
 94123                NativeArray<BoneWeight1>.Copy(narray, 0, finalBones, indexOffset, narrayLength);
 94124                indexOffset += narrayLength;
 94125                narray.Dispose();
 126            }
 127
 20128            return finalBones;
 129        }
 130
 131
 132        /// <summary>
 133        /// FlattenMaterials take a CombineLayer list and returns a FlattenedMaterialsData object.
 134        ///
 135        /// This object can be used to construct a combined mesh that has uniform data encoded in uv attributes.
 136        /// This type of encoding can be used to greatly reduce draw calls for seemingly unrelated objects.
 137        ///
 138        /// The returned object also contains a single material per CombineLayer.
 139        /// </summary>
 140        /// <param name="layers">A CombineLayer list. You can generate this array using CombineLayerUtils.Slice().</para
 141        /// <param name="materialAsset">Material asset that will be cloned to generate the returned materials.</param>
 142        /// <returns>A FlattenedMaterialsData object. This object can be used to construct a combined mesh that has unif
 143        public static FlattenedMaterialsData FlattenMaterials(List<CombineLayer> layers, Material materialAsset)
 144        {
 21145            int layersCount = layers.Count;
 146
 21147            int finalVertexCount = 0;
 21148            int currentVertexCount = 0;
 149
 112150            for (int layerIndex = 0; layerIndex < layersCount; layerIndex++)
 151            {
 35152                CombineLayer layer = layers[layerIndex];
 35153                var layerRenderers = layer.renderers;
 35154                int layerRenderersCount = layerRenderers.Count;
 155
 258156                for (int i = 0; i < layerRenderersCount; i++)
 157                {
 94158                    var renderer = layerRenderers[i];
 94159                    finalVertexCount += renderer.sharedMesh.vertexCount;
 160                }
 161            }
 162
 21163            var result = new FlattenedMaterialsData(finalVertexCount);
 164
 112165            for (int layerIndex = 0; layerIndex < layersCount; layerIndex++)
 166            {
 35167                CombineLayer layer = layers[layerIndex];
 35168                var layerRenderers = layer.renderers;
 169
 35170                Material newMaterial = new Material(materialAsset);
 171
 35172                CullMode cullMode = layer.cullMode;
 35173                bool isOpaque = layer.isOpaque;
 174
 35175                if ( isOpaque )
 21176                    MaterialUtils.SetOpaque(newMaterial);
 177                else
 14178                    MaterialUtils.SetTransparent(newMaterial);
 179
 35180                newMaterial.SetInt(ShaderUtils.Cull, (int)cullMode);
 181
 35182                result.materials.Add( newMaterial );
 183
 35184                int layerRenderersCount = layerRenderers.Count;
 185
 258186                for (int i = 0; i < layerRenderersCount; i++)
 187                {
 188
 94189                    var renderer = layerRenderers[i];
 190
 191                    // Bone Weights
 94192                    var sharedMesh = renderer.sharedMesh;
 94193                    int vertexCount = sharedMesh.vertexCount;
 194
 195                    // Texture IDs
 94196                    Material mat = renderer.sharedMaterial;
 197
 94198                    Texture2D baseMap = (Texture2D)mat.GetTexture(ShaderUtils.BaseMap);
 94199                    Texture2D emissionMap = (Texture2D)mat.GetTexture(ShaderUtils.EmissionMap);
 94200                    float cutoff = mat.GetFloat(ShaderUtils.Cutoff);
 201
 94202                    bool baseMapIdIsValid = baseMap != null && layer.textureToId.ContainsKey(baseMap);
 94203                    bool emissionMapIdIsValid = emissionMap != null && layer.textureToId.ContainsKey(emissionMap);
 204
 94205                    int baseMapId = baseMapIdIsValid ? layer.textureToId[baseMap] : -1;
 94206                    int emissionMapId = emissionMapIdIsValid ? layer.textureToId[emissionMap] : -1;
 207
 94208                    if ( baseMapId != -1 )
 209                    {
 88210                        if ( baseMapId < AVATAR_MAP_ID_PROPERTIES.Length )
 211                        {
 88212                            int targetMap = AVATAR_MAP_ID_PROPERTIES[baseMapId];
 88213                            newMaterial.SetTexture(targetMap, baseMap);
 214                        }
 215                        else
 216                        {
 0217                            if (VERBOSE)
 0218                                logger.Log(LogType.Error, "FlattenMaterials", $"Base Map ID out of bounds! {baseMapId}")
 219                        }
 220                    }
 221
 94222                    if ( emissionMapId != -1 )
 223                    {
 7224                        if ( baseMapId < AVATAR_MAP_ID_PROPERTIES.Length )
 225                        {
 7226                            int targetMap = AVATAR_MAP_ID_PROPERTIES[emissionMapId];
 7227                            newMaterial.SetTexture(targetMap, emissionMap);
 228                        }
 229                        else
 230                        {
 0231                            if (VERBOSE)
 0232                                logger.Log(LogType.Error, "FlattenMaterials", $"Emission Map ID out of bounds! {emission
 233                        }
 234                    }
 235
 94236                    Vector4 baseColor = mat.GetVector(ShaderUtils.BaseColor);
 94237                    Vector4 emissionColor = mat.GetVector(ShaderUtils.EmissionColor);
 94238                    Vector3 texturePointerData = new Vector3(baseMapId, emissionMapId, cutoff);
 239
 63742240                    for ( int ai = 0; ai < vertexCount; ai++ )
 241                    {
 31777242                        result.texturePointers[currentVertexCount] = texturePointerData;
 31777243                        result.colors[currentVertexCount] = baseColor;
 31777244                        result.emissionColors[currentVertexCount] = emissionColor;
 31777245                        currentVertexCount++;
 246                    }
 247
 94248                    if (VERBOSE)
 0249                        logger.Log($"Layer {i} - vertexCount: {vertexCount} - texturePointers: ({baseMapId}, {emissionMa
 250
 251                }
 252
 35253                SRPBatchingHelper.OptimizeMaterial(newMaterial);
 254
 255            }
 256
 21257            return result;
 258        }
 259
 260        /// <summary>
 261        /// ComputeSubMeshes iterates over the given CombineLayer list, and returns a list that can be used to map a
 262        /// sub-mesh for each CombineLayer object. A CombineLayer object can group more than a single mesh.
 263        ///
 264        /// Note that this had to be done because Mesh.CombineMeshes lack the option of controlling the sub-mesh
 265        /// output. Currently the only options are to combine everything in a single sub-mesh, or generate a single sub-
 266        /// per combined mesh. The CombineLayer approach may need to combine specific meshes to a single sub-mesh
 267        /// (because they all can have the same material, if they share the same render state -- i.e. transparency or cu
 268        /// </summary>
 269        /// <param name="layers">A CombineLayer list. You can generate this array using CombineLayerUtils.Slice().</para
 270        /// <returns>A SubMeshDescriptor list that can be used later to set the sub-meshes of the final combined mesh
 271        /// in a way that each sub-mesh corresponds with its own layer.</returns>
 272        public static List<SubMeshDescriptor> ComputeSubMeshes(List<CombineLayer> layers)
 273        {
 20274            List<SubMeshDescriptor> result = new List<SubMeshDescriptor>();
 20275            int layersCount = layers.Count;
 20276            int subMeshIndexOffset = 0;
 277
 106278            for (int layerIndex = 0; layerIndex < layersCount; layerIndex++)
 279            {
 33280                CombineLayer layer = layers[layerIndex];
 33281                var layerRenderers = layer.renderers;
 33282                int layerRenderersCount = layerRenderers.Count;
 283
 33284                int subMeshVertexCount = 0;
 33285                int subMeshIndexCount = 0;
 286
 254287                for (int i = 0; i < layerRenderersCount; i++)
 288                {
 94289                    var renderer = layerRenderers[i];
 290
 94291                    int vertexCount = renderer.sharedMesh.vertexCount;
 94292                    int indexCount = (int)renderer.sharedMesh.GetIndexCount(0);
 293
 94294                    subMeshVertexCount += vertexCount;
 94295                    subMeshIndexCount += indexCount;
 296                }
 297
 33298                var subMesh = new SubMeshDescriptor(subMeshIndexOffset, subMeshIndexCount);
 33299                subMesh.vertexCount = subMeshVertexCount;
 33300                result.Add(subMesh);
 301
 33302                subMeshIndexOffset += subMeshIndexCount;
 303            }
 304
 20305            return result;
 306        }
 307
 308        /// <summary>
 309        /// ComputeCombineInstancesData returns a CombineInstance list that can be used to combine all the meshes
 310        /// specified by the given CombineLayer list. This is done via Mesh.CombineMeshes() Unity method.
 311        /// </summary>
 312        /// <param name="layers">A CombineLayer list. You can generate this array using CombineLayerUtils.Slice()</param
 313        /// <returns>CombineInstance list usable by Mesh.CombineMeshes()</returns>
 314        public static List<CombineInstance> ComputeCombineInstancesData(List<CombineLayer> layers)
 315        {
 19316            var result = new List<CombineInstance>();
 19317            int layersCount = layers.Count;
 318
 100319            for (int layerIndex = 0; layerIndex < layersCount; layerIndex++)
 320            {
 31321                CombineLayer layer = layers[layerIndex];
 31322                var layerRenderers = layer.renderers;
 31323                int layerRenderersCount = layerRenderers.Count;
 324
 242325                for (int i = 0; i < layerRenderersCount; i++)
 326                {
 90327                    var renderer = layerRenderers[i];
 328
 90329                    Transform meshTransform = renderer.transform;
 90330                    Transform prevParent = meshTransform.parent;
 90331                    meshTransform.SetParent(null, true);
 332
 90333                    result.Add( new CombineInstance()
 334                    {
 335                        subMeshIndex = 0, // this means the source sub-mesh, not destination
 336                        mesh = renderer.sharedMesh,
 337                        transform = meshTransform.localToWorldMatrix
 338                    });
 339
 90340                    meshTransform.SetParent( prevParent );
 341                }
 342            }
 343
 19344            return result;
 345        }
 346
 347        public static Mesh CombineMeshesWithLayers( List<CombineInstance> combineInstancesData, List<CombineLayer> layer
 348        {
 19349            Mesh result = new Mesh();
 350            // Is important to use the layerRenderers to combine (i.e. no the original renderers)
 351            // Layer renderers are in a specific order that must be abided, or the combining will be broken.
 19352            List<SkinnedMeshRenderer> layerRenderers = new List<SkinnedMeshRenderer>();
 353
 100354            for (int i = 0; i < layers.Count; i++)
 355            {
 31356                layerRenderers.AddRange( layers[i].renderers );
 357            }
 358
 19359            using (var bakedInstances = new BakedCombineInstances())
 360            {
 19361                bakedInstances.Bake(combineInstancesData, layerRenderers);
 19362                result.CombineMeshes(combineInstancesData.ToArray(), true, true);
 19363            }
 364
 19365            return result;
 366        }
 367
 368        /// <summary>
 369        /// ResetBones will reset the given SkinnedMeshRenderer bones to the original bindposes position.
 370        ///
 371        /// This is done without taking into account the rootBone. We need to do it this way because the meshes must be
 372        /// combined posing to match the raw bindposes matrix.
 373        ///
 374        /// If the combined mesh don't match the bindposes matrices, the resulting skinning will not work.
 375        ///
 376        /// For this reason, this method doesn't resemble the original method that unity uses to reset the skeleton foun
 377        /// https://github.com/Unity-Technologies/UnityCsReference/blob/61f92bd79ae862c4465d35270f9d1d57befd1761/Editor/
 378        /// </summary>
 379        internal static void ResetBones(Matrix4x4[] bindPoses, Transform[] bones)
 380        {
 2520381            for ( int i = 0 ; i < bones.Length; i++ )
 382            {
 1240383                Transform bone = bones[i];
 1240384                Matrix4x4 bindPose = bindPoses[i].inverse;
 1240385                bone.position = bindPose.MultiplyPoint3x4(Vector3.zero);
 1240386                bone.rotation = bindPose.rotation;
 387
 1240388                Vector3 bindPoseScale = bindPose.lossyScale;
 1240389                Vector3 boneScale = bone.lossyScale;
 390
 1240391                bone.localScale = new Vector3(bindPoseScale.x / boneScale.x,
 392                    bindPoseScale.y / boneScale.y,
 393                    bindPoseScale.z / boneScale.z);
 394            }
 395
 396#if UNITY_EDITOR
 20397            DrawDebugSkeleton(bones);
 398#endif
 20399        }
 400
 401        internal static void DrawDebugSkeleton(Transform[] bones)
 402        {
 2520403            for ( int i = 0 ; i < bones.Length; i++ )
 404            {
 1240405                Transform bone = bones[i];
 1240406                Debug.DrawLine(bone.position, bone.position + bone.forward, Color.cyan, 60);
 407
 4920408                foreach ( Transform child in bone )
 409                {
 1220410                    Debug.DrawLine(bone.position, child.position, Color.green, 60);
 411                }
 412            }
 20413        }
 414    }
 415}