< 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:133
Uncovered lines:3
Coverable lines:136
Total lines:352
Line coverage:97.7% (133 of 136)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
AvatarMeshCombinerUtils()0%330100%
ComputeBoneWeights(...)0%330100%
FlattenMaterials(...)0%18.0218095.83%
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 System.Linq;
 4using DCL.Helpers;
 5using UnityEngine;
 6using UnityEngine.Rendering;
 7using Object = UnityEngine.Object;
 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
 35        /// <summary>
 36        /// This method iterates over all the renderers contained in the given CombineLayer list, and
 37        /// outputs an array of all the BoneWeights of the renderers in order.
 38        ///
 39        /// This is needed because Mesh.CombineMeshes don't calculate boneWeights correctly.
 40        /// When using Mesh.CombineMeshes, the boneWeights returned correspond to indexes of skeleton copies,
 41        /// not the same skeleton.
 42        /// </summary>
 43        /// <param name="layers">A CombineLayer list. You can generate this array using CombineLayerUtils.Slice().</para
 44        /// <returns>A list of BoneWeights that share the same skeleton.</returns>
 45        public static List<BoneWeight> ComputeBoneWeights( List<CombineLayer> layers )
 46        {
 1747            List<BoneWeight> result = new List<BoneWeight>();
 1748            int layersCount = layers.Count;
 49
 9050            for (int layerIndex = 0; layerIndex < layersCount; layerIndex++)
 51            {
 2852                CombineLayer layer = layers[layerIndex];
 2853                var layerRenderers = layer.renderers;
 54
 2855                int layerRenderersCount = layerRenderers.Count;
 56
 21257                for (int i = 0; i < layerRenderersCount; i++)
 58                {
 7859                    var renderer = layerRenderers[i];
 60
 61                    // Bone Weights
 7862                    var sharedMesh = renderer.sharedMesh;
 7863                    var meshBoneWeights = sharedMesh.boneWeights;
 7864                    result.AddRange(meshBoneWeights);
 65                }
 66            }
 67
 1768            return result;
 69        }
 70
 71        /// <summary>
 72        /// FlattenMaterials take a CombineLayer list and returns a FlattenedMaterialsData object.
 73        ///
 74        /// This object can be used to construct a combined mesh that has uniform data encoded in uv attributes.
 75        /// This type of encoding can be used to greatly reduce draw calls for seemingly unrelated objects.
 76        ///
 77        /// The returned object also contains a single material per CombineLayer.
 78        /// </summary>
 79        /// <param name="layers">A CombineLayer list. You can generate this array using CombineLayerUtils.Slice().</para
 80        /// <param name="materialAsset">Material asset that will be cloned to generate the returned materials.</param>
 81        /// <returns>A FlattenedMaterialsData object. This object can be used to construct a combined mesh that has unif
 82        public static FlattenedMaterialsData FlattenMaterials(List<CombineLayer> layers, Material materialAsset)
 83        {
 1884            var result = new FlattenedMaterialsData();
 1885            int layersCount = layers.Count;
 86
 1887            int finalVertexCount = 0;
 1888            int currentVertexCount = 0;
 89
 9690            for (int layerIndex = 0; layerIndex < layersCount; layerIndex++)
 91            {
 3092                CombineLayer layer = layers[layerIndex];
 3093                var layerRenderers = layer.renderers;
 3094                int layerRenderersCount = layerRenderers.Count;
 95
 21696                for (int i = 0; i < layerRenderersCount; i++)
 97                {
 7898                    var renderer = layerRenderers[i];
 7899                    finalVertexCount += renderer.sharedMesh.vertexCount;
 100                }
 101            }
 102
 18103            result.colors = new Vector4[finalVertexCount];
 18104            result.emissionColors = new Vector4[finalVertexCount];
 18105            result.texturePointers = new Vector3[finalVertexCount];
 106
 96107            for (int layerIndex = 0; layerIndex < layersCount; layerIndex++)
 108            {
 30109                CombineLayer layer = layers[layerIndex];
 30110                var layerRenderers = layer.renderers;
 111
 30112                Material newMaterial = new Material(materialAsset);
 113
 30114                CullMode cullMode = layer.cullMode;
 30115                bool isOpaque = layer.isOpaque;
 116
 30117                if ( isOpaque )
 18118                    MaterialUtils.SetOpaque(newMaterial);
 119                else
 12120                    MaterialUtils.SetTransparent(newMaterial);
 121
 30122                newMaterial.SetInt(ShaderUtils.Cull, (int)cullMode);
 123
 30124                result.materials.Add( newMaterial );
 125
 30126                int layerRenderersCount = layerRenderers.Count;
 127
 216128                for (int i = 0; i < layerRenderersCount; i++)
 129                {
 78130                    var renderer = layerRenderers[i];
 131
 132                    // Bone Weights
 78133                    var sharedMesh = renderer.sharedMesh;
 78134                    int vertexCount = sharedMesh.vertexCount;
 135
 136                    // Texture IDs
 78137                    Material mat = renderer.sharedMaterial;
 138
 78139                    Texture2D baseMap = (Texture2D)mat.GetTexture(ShaderUtils.BaseMap);
 78140                    Texture2D emissionMap = (Texture2D)mat.GetTexture(ShaderUtils.EmissionMap);
 78141                    float cutoff = mat.GetFloat(ShaderUtils.Cutoff);
 142
 78143                    bool baseMapIdIsValid = baseMap != null && layer.textureToId.ContainsKey(baseMap);
 78144                    bool emissionMapIdIsValid = emissionMap != null && layer.textureToId.ContainsKey(emissionMap);
 145
 78146                    int baseMapId = baseMapIdIsValid ? layer.textureToId[baseMap] : -1;
 78147                    int emissionMapId = emissionMapIdIsValid ? layer.textureToId[emissionMap] : -1;
 148
 78149                    if ( baseMapId != -1 )
 150                    {
 74151                        if ( baseMapId < AVATAR_MAP_ID_PROPERTIES.Length )
 152                        {
 74153                            int targetMap = AVATAR_MAP_ID_PROPERTIES[baseMapId];
 74154                            newMaterial.SetTexture(targetMap, baseMap);
 74155                        }
 156                        else
 157                        {
 0158                            logger.Log(LogType.Error, "FlattenMaterials", $"Base Map ID out of bounds! {baseMapId}");
 159                        }
 160                    }
 161
 78162                    if ( emissionMapId != -1 )
 163                    {
 7164                        if ( baseMapId < AVATAR_MAP_ID_PROPERTIES.Length )
 165                        {
 7166                            int targetMap = AVATAR_MAP_ID_PROPERTIES[emissionMapId];
 7167                            newMaterial.SetTexture(targetMap, emissionMap);
 7168                        }
 169                        else
 170                        {
 0171                            logger.Log(LogType.Error, "FlattenMaterials", $"Emission Map ID out of bounds! {emissionMapI
 172                        }
 173                    }
 174
 78175                    Vector4 baseColor = mat.GetVector(ShaderUtils.BaseColor);
 78176                    Vector4 emissionColor = mat.GetVector(ShaderUtils.EmissionColor);
 78177                    Vector3 texturePointerData = new Vector3(baseMapId, emissionMapId, cutoff);
 178
 48650179                    for ( int ai = 0; ai < vertexCount; ai++ )
 180                    {
 24247181                        result.texturePointers[currentVertexCount] = texturePointerData;
 24247182                        result.colors[currentVertexCount] = baseColor;
 24247183                        result.emissionColors[currentVertexCount] = emissionColor;
 24247184                        currentVertexCount++;
 185                    }
 186
 78187                    if (VERBOSE)
 0188                        logger.Log($"Layer {i} - vertexCount: {vertexCount} - texturePointers: ({baseMapId}, {emissionMa
 189                }
 190
 30191                SRPBatchingHelper.OptimizeMaterial(newMaterial);
 192            }
 193
 18194            return result;
 195        }
 196
 197        /// <summary>
 198        /// ComputeSubMeshes iterates over the given CombineLayer list, and returns a list that can be used to map a
 199        /// sub-mesh for each CombineLayer object. A CombineLayer object can group more than a single mesh.
 200        ///
 201        /// Note that this had to be done because Mesh.CombineMeshes lack the option of controlling the sub-mesh
 202        /// output. Currently the only options are to combine everything in a single sub-mesh, or generate a single sub-
 203        /// per combined mesh. The CombineLayer approach may need to combine specific meshes to a single sub-mesh
 204        /// (because they all can have the same material, if they share the same render state -- i.e. transparency or cu
 205        /// </summary>
 206        /// <param name="layers">A CombineLayer list. You can generate this array using CombineLayerUtils.Slice().</para
 207        /// <returns>A SubMeshDescriptor list that can be used later to set the sub-meshes of the final combined mesh
 208        /// in a way that each sub-mesh corresponds with its own layer.</returns>
 209        public static List<SubMeshDescriptor> ComputeSubMeshes(List<CombineLayer> layers)
 210        {
 17211            List<SubMeshDescriptor> result = new List<SubMeshDescriptor>();
 17212            int layersCount = layers.Count;
 17213            int subMeshIndexOffset = 0;
 214
 90215            for (int layerIndex = 0; layerIndex < layersCount; layerIndex++)
 216            {
 28217                CombineLayer layer = layers[layerIndex];
 28218                var layerRenderers = layer.renderers;
 28219                int layerRenderersCount = layerRenderers.Count;
 220
 28221                int subMeshVertexCount = 0;
 28222                int subMeshIndexCount = 0;
 223
 212224                for (int i = 0; i < layerRenderersCount; i++)
 225                {
 78226                    var renderer = layerRenderers[i];
 227
 78228                    int vertexCount = renderer.sharedMesh.vertexCount;
 78229                    int indexCount = (int)renderer.sharedMesh.GetIndexCount(0);
 230
 78231                    subMeshVertexCount += vertexCount;
 78232                    subMeshIndexCount += indexCount;
 233                }
 234
 28235                var subMesh = new SubMeshDescriptor(subMeshIndexOffset, subMeshIndexCount);
 28236                subMesh.vertexCount = subMeshVertexCount;
 28237                result.Add(subMesh);
 238
 28239                subMeshIndexOffset += subMeshIndexCount;
 240            }
 241
 17242            return result;
 243        }
 244
 245        /// <summary>
 246        /// ComputeCombineInstancesData returns a CombineInstance list that can be used to combine all the meshes
 247        /// specified by the given CombineLayer list. This is done via Mesh.CombineMeshes() Unity method.
 248        /// </summary>
 249        /// <param name="layers">A CombineLayer list. You can generate this array using CombineLayerUtils.Slice()</param
 250        /// <returns>CombineInstance list usable by Mesh.CombineMeshes()</returns>
 251        public static List<CombineInstance> ComputeCombineInstancesData(List<CombineLayer> layers)
 252        {
 16253            var result = new List<CombineInstance>();
 16254            int layersCount = layers.Count;
 255
 84256            for (int layerIndex = 0; layerIndex < layersCount; layerIndex++)
 257            {
 26258                CombineLayer layer = layers[layerIndex];
 26259                var layerRenderers = layer.renderers;
 26260                int layerRenderersCount = layerRenderers.Count;
 261
 200262                for (int i = 0; i < layerRenderersCount; i++)
 263                {
 74264                    var renderer = layerRenderers[i];
 265
 74266                    Transform meshTransform = renderer.transform;
 74267                    Transform prevParent = meshTransform.parent;
 74268                    meshTransform.SetParent(null, true);
 269
 74270                    result.Add( new CombineInstance()
 271                    {
 272                        subMeshIndex = 0, // this means the source sub-mesh, not destination
 273                        mesh = renderer.sharedMesh,
 274                        transform = meshTransform.localToWorldMatrix
 275                    });
 276
 74277                    meshTransform.SetParent( prevParent );
 278                }
 279            }
 280
 16281            return result;
 282        }
 283
 284        public static Mesh CombineMeshesWithLayers( List<CombineInstance> combineInstancesData, List<CombineLayer> layer
 285        {
 16286            Mesh result = new Mesh();
 287            // Is important to use the layerRenderers to combine (i.e. no the original renderers)
 288            // Layer renderers are in a specific order that must be abided, or the combining will be broken.
 16289            List<SkinnedMeshRenderer> layerRenderers = new List<SkinnedMeshRenderer>();
 290
 84291            for (int i = 0; i < layers.Count; i++)
 292            {
 26293                layerRenderers.AddRange( layers[i].renderers );
 294            }
 295
 16296            using (var bakedInstances = new BakedCombineInstances())
 297            {
 16298                bakedInstances.Bake(combineInstancesData, layerRenderers);
 16299                result.CombineMeshes(combineInstancesData.ToArray(), true, true);
 16300            }
 301
 16302            return result;
 303        }
 304
 305        /// <summary>
 306        /// ResetBones will reset the given SkinnedMeshRenderer bones to the original bindposes position.
 307        ///
 308        /// This is done without taking into account the rootBone. We need to do it this way because the meshes must be
 309        /// combined posing to match the raw bindposes matrix.
 310        ///
 311        /// If the combined mesh don't match the bindposes matrices, the resulting skinning will not work.
 312        ///
 313        /// For this reason, this method doesn't resemble the original method that unity uses to reset the skeleton foun
 314        /// https://github.com/Unity-Technologies/UnityCsReference/blob/61f92bd79ae862c4465d35270f9d1d57befd1761/Editor/
 315        /// </summary>
 316        internal static void ResetBones(Matrix4x4[] bindPoses, Transform[] bones)
 317        {
 2142318            for ( int i = 0 ; i < bones.Length; i++ )
 319            {
 1054320                Transform bone = bones[i];
 1054321                Matrix4x4 bindPose = bindPoses[i].inverse;
 1054322                bone.position = bindPose.MultiplyPoint3x4(Vector3.zero);
 1054323                bone.rotation = bindPose.rotation;
 324
 1054325                Vector3 bindPoseScale = bindPose.lossyScale;
 1054326                Vector3 boneScale = bone.lossyScale;
 327
 1054328                bone.localScale = new Vector3(bindPoseScale.x / boneScale.x,
 329                    bindPoseScale.y / boneScale.y,
 330                    bindPoseScale.z / boneScale.z);
 331            }
 332
 333#if UNITY_EDITOR
 17334            DrawDebugSkeleton(bones);
 335#endif
 17336        }
 337
 338        internal static void DrawDebugSkeleton(Transform[] bones)
 339        {
 2142340            for ( int i = 0 ; i < bones.Length; i++ )
 341            {
 1054342                Transform bone = bones[i];
 1054343                Debug.DrawLine(bone.position, bone.position + bone.forward, Color.cyan, 60);
 344
 4182345                foreach ( Transform child in bone )
 346                {
 1037347                    Debug.DrawLine(bone.position, child.position, Color.green, 60);
 348                }
 349            }
 17350        }
 351    }
 352}