< 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:160
Uncovered lines:5
Coverable lines:165
Total lines:414
Line coverage:96.9% (160 of 165)
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.1420092.96%
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 Unity.Collections;
 5using UnityEngine;
 6using UnityEngine.Rendering;
 7
 8namespace DCL
 9{
 10    public static class AvatarMeshCombinerUtils
 11    {
 12        internal const string AVATAR_MAP_PROPERTY_NAME = "_AvatarMap";
 13
 114        internal static readonly int[] AVATAR_MAP_ID_PROPERTIES =
 15        {
 16            Shader.PropertyToID(AVATAR_MAP_PROPERTY_NAME + "1"),
 17            Shader.PropertyToID(AVATAR_MAP_PROPERTY_NAME + "2"),
 18            Shader.PropertyToID(AVATAR_MAP_PROPERTY_NAME + "3"),
 19            Shader.PropertyToID(AVATAR_MAP_PROPERTY_NAME + "4"),
 20            Shader.PropertyToID(AVATAR_MAP_PROPERTY_NAME + "5"),
 21            Shader.PropertyToID(AVATAR_MAP_PROPERTY_NAME + "6"),
 22            Shader.PropertyToID(AVATAR_MAP_PROPERTY_NAME + "7"),
 23            Shader.PropertyToID(AVATAR_MAP_PROPERTY_NAME + "8"),
 24            Shader.PropertyToID(AVATAR_MAP_PROPERTY_NAME + "9"),
 25            Shader.PropertyToID(AVATAR_MAP_PROPERTY_NAME + "10"),
 26            Shader.PropertyToID(AVATAR_MAP_PROPERTY_NAME + "11"),
 27            Shader.PropertyToID(AVATAR_MAP_PROPERTY_NAME + "12")
 28        };
 29
 130        private static bool VERBOSE = false;
 131        private static ILogger logger = new Logger(Debug.unityLogger.logHandler) { filterLogType = VERBOSE ? LogType.Log
 32
 33        /// <summary>
 34        /// This method iterates over all the renderers contained in the given CombineLayer list, and
 35        /// outputs an array of all the bones per vertexes
 36        ///
 37        /// This is needed because Mesh.CombineMeshes don't calculate boneWeights correctly.
 38        /// When using Mesh.CombineMeshes, the boneWeights returned correspond to indexes of skeleton copies,
 39        /// not the same skeleton.
 40        /// </summary>
 41        /// <param name="layers">A CombineLayer list. You can generate this array using CombineLayerUtils.Slice().</para
 42        /// <returns>A list of Bones per vertex that share the same skeleton.</returns>
 43        public static NativeArray<byte> CombineBonesPerVertex(List<CombineLayer> layers)
 44        {
 2045            int layersCount = layers.Count;
 2046            int totalVertexes = 0;
 47
 2048            List<NativeArray<byte>> bonesPerVertexList = new List<NativeArray<byte>>();
 49
 10650            for (int layerIndex = 0; layerIndex < layersCount; layerIndex++)
 51            {
 3352                CombineLayer layer = layers[layerIndex];
 3353                var layerRenderers = layer.renderers;
 3354                int layerRenderersCount = layerRenderers.Count;
 55
 25456                for (int i = 0; i < layerRenderersCount; i++)
 57                {
 9458                    var bonesPerVertex = layerRenderers[i].sharedMesh.GetBonesPerVertex();
 59
 9460                    bonesPerVertexList.Add(bonesPerVertex);
 9461                    totalVertexes += bonesPerVertex.Length;
 62                }
 63            }
 64
 2065            NativeArray<byte> finalBpV = new NativeArray<byte>(totalVertexes, Allocator.Temp, NativeArrayOptions.Uniniti
 66
 2067            int indexOffset = 0;
 2068            int bonesPerVertexListCount = bonesPerVertexList.Count;
 69
 22870            for (int i = 0; i < bonesPerVertexListCount; i++)
 71            {
 9472                var narray = bonesPerVertexList[i];
 9473                int narrayLength = narray.Length;
 9474                NativeArray<byte>.Copy(narray, 0, finalBpV, indexOffset, narrayLength);
 9475                indexOffset += narrayLength;
 9476                narray.Dispose();
 77            }
 78
 2079            return finalBpV;
 80        }
 81
 82        /// <summary>
 83        /// This method iterates over all the renderers contained in the given CombineLayer list, and
 84        /// outputs an array of all the bone weights
 85        ///
 86        /// This is needed because Mesh.CombineMeshes don't calculate boneWeights correctly.
 87        /// When using Mesh.CombineMeshes, the boneWeights returned correspond to indexes of skeleton copies,
 88        /// not the same skeleton.
 89        /// </summary>
 90        /// <param name="layers">A CombineLayer list. You can generate this array using CombineLayerUtils.Slice().</para
 91        /// <returns>A list of bone weights that share the same skeleton.</returns>
 92        public static NativeArray<BoneWeight1> CombineBonesWeights(List<CombineLayer> layers)
 93        {
 2094            int layersCount = layers.Count;
 2095            int totalBones = 0;
 96
 2097            List<NativeArray<BoneWeight1>> boneWeightArrays = new List<NativeArray<BoneWeight1>>(10);
 98
 10699            for (int layerIndex = 0; layerIndex < layersCount; layerIndex++)
 100            {
 33101                CombineLayer layer = layers[layerIndex];
 33102                var layerRenderers = layer.renderers;
 33103                int layerRenderersCount = layerRenderers.Count;
 104
 254105                for (int i = 0; i < layerRenderersCount; i++)
 106                {
 94107                    var boneWeights = layerRenderers[i].sharedMesh.GetAllBoneWeights();
 94108                    boneWeightArrays.Add(boneWeights);
 94109                    totalBones += boneWeights.Length;
 110                }
 111            }
 112
 20113            NativeArray<BoneWeight1> finalBones = new NativeArray<BoneWeight1>(totalBones, Allocator.Temp, NativeArrayOp
 114
 20115            int indexOffset = 0;
 20116            var finalBonesCount = boneWeightArrays.Count;
 117
 228118            for (int i = 0; i < finalBonesCount; i++)
 119            {
 94120                var narray = boneWeightArrays[i];
 94121                int narrayLength = narray.Length;
 94122                NativeArray<BoneWeight1>.Copy(narray, 0, finalBones, indexOffset, narrayLength);
 94123                indexOffset += narrayLength;
 94124                narray.Dispose();
 125            }
 126
 20127            return finalBones;
 128        }
 129
 130
 131        /// <summary>
 132        /// FlattenMaterials take a CombineLayer list and returns a FlattenedMaterialsData object.
 133        ///
 134        /// This object can be used to construct a combined mesh that has uniform data encoded in uv attributes.
 135        /// This type of encoding can be used to greatly reduce draw calls for seemingly unrelated objects.
 136        ///
 137        /// The returned object also contains a single material per CombineLayer.
 138        /// </summary>
 139        /// <param name="layers">A CombineLayer list. You can generate this array using CombineLayerUtils.Slice().</para
 140        /// <param name="materialAsset">Material asset that will be cloned to generate the returned materials.</param>
 141        /// <returns>A FlattenedMaterialsData object. This object can be used to construct a combined mesh that has unif
 142        public static FlattenedMaterialsData FlattenMaterials(List<CombineLayer> layers, Material materialAsset)
 143        {
 21144            int layersCount = layers.Count;
 145
 21146            int finalVertexCount = 0;
 21147            int currentVertexCount = 0;
 148
 112149            for (int layerIndex = 0; layerIndex < layersCount; layerIndex++)
 150            {
 35151                CombineLayer layer = layers[layerIndex];
 35152                var layerRenderers = layer.renderers;
 35153                int layerRenderersCount = layerRenderers.Count;
 154
 258155                for (int i = 0; i < layerRenderersCount; i++)
 156                {
 94157                    var renderer = layerRenderers[i];
 94158                    finalVertexCount += renderer.sharedMesh.vertexCount;
 159                }
 160            }
 161
 21162            var result = new FlattenedMaterialsData(finalVertexCount);
 163
 112164            for (int layerIndex = 0; layerIndex < layersCount; layerIndex++)
 165            {
 35166                CombineLayer layer = layers[layerIndex];
 35167                var layerRenderers = layer.renderers;
 168
 35169                Material newMaterial = new Material(materialAsset);
 170
 35171                CullMode cullMode = layer.cullMode;
 35172                bool isOpaque = layer.isOpaque;
 173
 35174                if ( isOpaque )
 21175                    MaterialUtils.SetOpaque(newMaterial);
 176                else
 14177                    MaterialUtils.SetTransparent(newMaterial);
 178
 35179                newMaterial.SetInt(ShaderUtils.Cull, (int)cullMode);
 180
 35181                result.materials.Add( newMaterial );
 182
 35183                int layerRenderersCount = layerRenderers.Count;
 184
 258185                for (int i = 0; i < layerRenderersCount; i++)
 186                {
 187
 94188                    var renderer = layerRenderers[i];
 189
 190                    // Bone Weights
 94191                    var sharedMesh = renderer.sharedMesh;
 94192                    int vertexCount = sharedMesh.vertexCount;
 193
 194                    // Texture IDs
 94195                    Material mat = renderer.sharedMaterial;
 196
 94197                    Texture2D baseMap = (Texture2D)mat.GetTexture(ShaderUtils.BaseMap);
 94198                    Texture2D emissionMap = (Texture2D)mat.GetTexture(ShaderUtils.EmissionMap);
 94199                    float cutoff = mat.GetFloat(ShaderUtils.Cutoff);
 200
 94201                    bool baseMapIdIsValid = baseMap != null && layer.textureToId.ContainsKey(baseMap);
 94202                    bool emissionMapIdIsValid = emissionMap != null && layer.textureToId.ContainsKey(emissionMap);
 203
 94204                    int baseMapId = baseMapIdIsValid ? layer.textureToId[baseMap] : -1;
 94205                    int emissionMapId = emissionMapIdIsValid ? layer.textureToId[emissionMap] : -1;
 206
 94207                    if ( baseMapId != -1 )
 208                    {
 88209                        if ( baseMapId < AVATAR_MAP_ID_PROPERTIES.Length )
 210                        {
 88211                            int targetMap = AVATAR_MAP_ID_PROPERTIES[baseMapId];
 88212                            newMaterial.SetTexture(targetMap, baseMap);
 88213                        }
 214                        else
 215                        {
 0216                            if (VERBOSE)
 0217                                logger.Log(LogType.Error, "FlattenMaterials", $"Base Map ID out of bounds! {baseMapId}")
 218                        }
 219                    }
 220
 94221                    if ( emissionMapId != -1 )
 222                    {
 7223                        if ( baseMapId < AVATAR_MAP_ID_PROPERTIES.Length )
 224                        {
 7225                            int targetMap = AVATAR_MAP_ID_PROPERTIES[emissionMapId];
 7226                            newMaterial.SetTexture(targetMap, emissionMap);
 7227                        }
 228                        else
 229                        {
 0230                            if (VERBOSE)
 0231                                logger.Log(LogType.Error, "FlattenMaterials", $"Emission Map ID out of bounds! {emission
 232                        }
 233                    }
 234
 94235                    Vector4 baseColor = mat.GetVector(ShaderUtils.BaseColor);
 94236                    Vector4 emissionColor = mat.GetVector(ShaderUtils.EmissionColor);
 94237                    Vector3 texturePointerData = new Vector3(baseMapId, emissionMapId, cutoff);
 238
 63742239                    for ( int ai = 0; ai < vertexCount; ai++ )
 240                    {
 31777241                        result.texturePointers[currentVertexCount] = texturePointerData;
 31777242                        result.colors[currentVertexCount] = baseColor;
 31777243                        result.emissionColors[currentVertexCount] = emissionColor;
 31777244                        currentVertexCount++;
 245                    }
 246
 94247                    if (VERBOSE)
 0248                        logger.Log($"Layer {i} - vertexCount: {vertexCount} - texturePointers: ({baseMapId}, {emissionMa
 249
 250                }
 251
 35252                SRPBatchingHelper.OptimizeMaterial(newMaterial);
 253
 254            }
 255
 21256            return result;
 257        }
 258
 259        /// <summary>
 260        /// ComputeSubMeshes iterates over the given CombineLayer list, and returns a list that can be used to map a
 261        /// sub-mesh for each CombineLayer object. A CombineLayer object can group more than a single mesh.
 262        ///
 263        /// Note that this had to be done because Mesh.CombineMeshes lack the option of controlling the sub-mesh
 264        /// output. Currently the only options are to combine everything in a single sub-mesh, or generate a single sub-
 265        /// per combined mesh. The CombineLayer approach may need to combine specific meshes to a single sub-mesh
 266        /// (because they all can have the same material, if they share the same render state -- i.e. transparency or cu
 267        /// </summary>
 268        /// <param name="layers">A CombineLayer list. You can generate this array using CombineLayerUtils.Slice().</para
 269        /// <returns>A SubMeshDescriptor list that can be used later to set the sub-meshes of the final combined mesh
 270        /// in a way that each sub-mesh corresponds with its own layer.</returns>
 271        public static List<SubMeshDescriptor> ComputeSubMeshes(List<CombineLayer> layers)
 272        {
 20273            List<SubMeshDescriptor> result = new List<SubMeshDescriptor>();
 20274            int layersCount = layers.Count;
 20275            int subMeshIndexOffset = 0;
 276
 106277            for (int layerIndex = 0; layerIndex < layersCount; layerIndex++)
 278            {
 33279                CombineLayer layer = layers[layerIndex];
 33280                var layerRenderers = layer.renderers;
 33281                int layerRenderersCount = layerRenderers.Count;
 282
 33283                int subMeshVertexCount = 0;
 33284                int subMeshIndexCount = 0;
 285
 254286                for (int i = 0; i < layerRenderersCount; i++)
 287                {
 94288                    var renderer = layerRenderers[i];
 289
 94290                    int vertexCount = renderer.sharedMesh.vertexCount;
 94291                    int indexCount = (int)renderer.sharedMesh.GetIndexCount(0);
 292
 94293                    subMeshVertexCount += vertexCount;
 94294                    subMeshIndexCount += indexCount;
 295                }
 296
 33297                var subMesh = new SubMeshDescriptor(subMeshIndexOffset, subMeshIndexCount);
 33298                subMesh.vertexCount = subMeshVertexCount;
 33299                result.Add(subMesh);
 300
 33301                subMeshIndexOffset += subMeshIndexCount;
 302            }
 303
 20304            return result;
 305        }
 306
 307        /// <summary>
 308        /// ComputeCombineInstancesData returns a CombineInstance list that can be used to combine all the meshes
 309        /// specified by the given CombineLayer list. This is done via Mesh.CombineMeshes() Unity method.
 310        /// </summary>
 311        /// <param name="layers">A CombineLayer list. You can generate this array using CombineLayerUtils.Slice()</param
 312        /// <returns>CombineInstance list usable by Mesh.CombineMeshes()</returns>
 313        public static List<CombineInstance> ComputeCombineInstancesData(List<CombineLayer> layers)
 314        {
 19315            var result = new List<CombineInstance>();
 19316            int layersCount = layers.Count;
 317
 100318            for (int layerIndex = 0; layerIndex < layersCount; layerIndex++)
 319            {
 31320                CombineLayer layer = layers[layerIndex];
 31321                var layerRenderers = layer.renderers;
 31322                int layerRenderersCount = layerRenderers.Count;
 323
 242324                for (int i = 0; i < layerRenderersCount; i++)
 325                {
 90326                    var renderer = layerRenderers[i];
 327
 90328                    Transform meshTransform = renderer.transform;
 90329                    Transform prevParent = meshTransform.parent;
 90330                    meshTransform.SetParent(null, true);
 331
 90332                    result.Add( new CombineInstance()
 333                    {
 334                        subMeshIndex = 0, // this means the source sub-mesh, not destination
 335                        mesh = renderer.sharedMesh,
 336                        transform = meshTransform.localToWorldMatrix
 337                    });
 338
 90339                    meshTransform.SetParent( prevParent );
 340                }
 341            }
 342
 19343            return result;
 344        }
 345
 346        public static Mesh CombineMeshesWithLayers( List<CombineInstance> combineInstancesData, List<CombineLayer> layer
 347        {
 19348            Mesh result = new Mesh();
 349            // Is important to use the layerRenderers to combine (i.e. no the original renderers)
 350            // Layer renderers are in a specific order that must be abided, or the combining will be broken.
 19351            List<SkinnedMeshRenderer> layerRenderers = new List<SkinnedMeshRenderer>();
 352
 100353            for (int i = 0; i < layers.Count; i++)
 354            {
 31355                layerRenderers.AddRange( layers[i].renderers );
 356            }
 357
 19358            using (var bakedInstances = new BakedCombineInstances())
 359            {
 19360                bakedInstances.Bake(combineInstancesData, layerRenderers);
 19361                result.CombineMeshes(combineInstancesData.ToArray(), true, true);
 19362            }
 363
 19364            return result;
 365        }
 366
 367        /// <summary>
 368        /// ResetBones will reset the given SkinnedMeshRenderer bones to the original bindposes position.
 369        ///
 370        /// This is done without taking into account the rootBone. We need to do it this way because the meshes must be
 371        /// combined posing to match the raw bindposes matrix.
 372        ///
 373        /// If the combined mesh don't match the bindposes matrices, the resulting skinning will not work.
 374        ///
 375        /// For this reason, this method doesn't resemble the original method that unity uses to reset the skeleton foun
 376        /// https://github.com/Unity-Technologies/UnityCsReference/blob/61f92bd79ae862c4465d35270f9d1d57befd1761/Editor/
 377        /// </summary>
 378        internal static void ResetBones(Matrix4x4[] bindPoses, Transform[] bones)
 379        {
 2520380            for ( int i = 0 ; i < bones.Length; i++ )
 381            {
 1240382                Transform bone = bones[i];
 1240383                Matrix4x4 bindPose = bindPoses[i].inverse;
 1240384                bone.position = bindPose.MultiplyPoint3x4(Vector3.zero);
 1240385                bone.rotation = bindPose.rotation;
 386
 1240387                Vector3 bindPoseScale = bindPose.lossyScale;
 1240388                Vector3 boneScale = bone.lossyScale;
 389
 1240390                bone.localScale = new Vector3(bindPoseScale.x / boneScale.x,
 391                    bindPoseScale.y / boneScale.y,
 392                    bindPoseScale.z / boneScale.z);
 393            }
 394
 395#if UNITY_EDITOR
 20396            DrawDebugSkeleton(bones);
 397#endif
 20398        }
 399
 400        internal static void DrawDebugSkeleton(Transform[] bones)
 401        {
 2520402            for ( int i = 0 ; i < bones.Length; i++ )
 403            {
 1240404                Transform bone = bones[i];
 1240405                Debug.DrawLine(bone.position, bone.position + bone.forward, Color.cyan, 60);
 406
 4920407                foreach ( Transform child in bone )
 408                {
 1220409                    Debug.DrawLine(bone.position, child.position, Color.green, 60);
 410                }
 411            }
 20412        }
 413    }
 414}