< 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:138
Uncovered lines:3
Coverable lines:141
Total lines:362
Line coverage:97.8% (138 of 141)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
AvatarMeshCombinerUtils()0%330100%
ComputeBoneWeights(...)0%440100%
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 BoneWeight[] ComputeBoneWeights( List<CombineLayer> layers )
 46        {
 1847            int layersCount = layers.Count;
 48
 1849            int resultSize = 0;
 50
 1851            List<BoneWeight[]> boneWeightArrays = new List<BoneWeight[]>(10);
 52
 9453            for (int layerIndex = 0; layerIndex < layersCount; layerIndex++)
 54            {
 2955                CombineLayer layer = layers[layerIndex];
 2956                var layerRenderers = layer.renderers;
 57
 2958                int layerRenderersCount = layerRenderers.Count;
 59
 21860                for (int i = 0; i < layerRenderersCount; i++)
 61                {
 8062                    var boneWeights = layerRenderers[i].sharedMesh.boneWeights;
 8063                    boneWeightArrays.Add(boneWeights);
 8064                    resultSize += boneWeights.Length;
 65                }
 66            }
 67
 1868            BoneWeight[] result = new BoneWeight[resultSize];
 69
 1870            int copyOffset = 0;
 19671            for ( int i = 0; i < boneWeightArrays.Count; i++ )
 72            {
 8073                Array.Copy(boneWeightArrays[i], 0, result, copyOffset, boneWeightArrays[i].Length);
 8074                copyOffset += boneWeightArrays[i].Length;
 75            }
 76
 1877            return result;
 78        }
 79
 80
 81        /// <summary>
 82        /// FlattenMaterials take a CombineLayer list and returns a FlattenedMaterialsData object.
 83        ///
 84        /// This object can be used to construct a combined mesh that has uniform data encoded in uv attributes.
 85        /// This type of encoding can be used to greatly reduce draw calls for seemingly unrelated objects.
 86        ///
 87        /// The returned object also contains a single material per CombineLayer.
 88        /// </summary>
 89        /// <param name="layers">A CombineLayer list. You can generate this array using CombineLayerUtils.Slice().</para
 90        /// <param name="materialAsset">Material asset that will be cloned to generate the returned materials.</param>
 91        /// <returns>A FlattenedMaterialsData object. This object can be used to construct a combined mesh that has unif
 92        public static FlattenedMaterialsData FlattenMaterials(List<CombineLayer> layers, Material materialAsset)
 93        {
 1994            var result = new FlattenedMaterialsData();
 1995            int layersCount = layers.Count;
 96
 1997            int finalVertexCount = 0;
 1998            int currentVertexCount = 0;
 99
 100100            for (int layerIndex = 0; layerIndex < layersCount; layerIndex++)
 101            {
 31102                CombineLayer layer = layers[layerIndex];
 31103                var layerRenderers = layer.renderers;
 31104                int layerRenderersCount = layerRenderers.Count;
 105
 222106                for (int i = 0; i < layerRenderersCount; i++)
 107                {
 80108                    var renderer = layerRenderers[i];
 80109                    finalVertexCount += renderer.sharedMesh.vertexCount;
 110                }
 111            }
 112
 19113            result.colors = new Vector4[finalVertexCount];
 19114            result.emissionColors = new Vector4[finalVertexCount];
 19115            result.texturePointers = new Vector3[finalVertexCount];
 116
 100117            for (int layerIndex = 0; layerIndex < layersCount; layerIndex++)
 118            {
 31119                CombineLayer layer = layers[layerIndex];
 31120                var layerRenderers = layer.renderers;
 121
 31122                Material newMaterial = new Material(materialAsset);
 123
 31124                CullMode cullMode = layer.cullMode;
 31125                bool isOpaque = layer.isOpaque;
 126
 31127                if ( isOpaque )
 19128                    MaterialUtils.SetOpaque(newMaterial);
 129                else
 12130                    MaterialUtils.SetTransparent(newMaterial);
 131
 31132                newMaterial.SetInt(ShaderUtils.Cull, (int)cullMode);
 133
 31134                result.materials.Add( newMaterial );
 135
 31136                int layerRenderersCount = layerRenderers.Count;
 137
 222138                for (int i = 0; i < layerRenderersCount; i++)
 139                {
 80140                    var renderer = layerRenderers[i];
 141
 142                    // Bone Weights
 80143                    var sharedMesh = renderer.sharedMesh;
 80144                    int vertexCount = sharedMesh.vertexCount;
 145
 146                    // Texture IDs
 80147                    Material mat = renderer.sharedMaterial;
 148
 80149                    Texture2D baseMap = (Texture2D)mat.GetTexture(ShaderUtils.BaseMap);
 80150                    Texture2D emissionMap = (Texture2D)mat.GetTexture(ShaderUtils.EmissionMap);
 80151                    float cutoff = mat.GetFloat(ShaderUtils.Cutoff);
 152
 80153                    bool baseMapIdIsValid = baseMap != null && layer.textureToId.ContainsKey(baseMap);
 80154                    bool emissionMapIdIsValid = emissionMap != null && layer.textureToId.ContainsKey(emissionMap);
 155
 80156                    int baseMapId = baseMapIdIsValid ? layer.textureToId[baseMap] : -1;
 80157                    int emissionMapId = emissionMapIdIsValid ? layer.textureToId[emissionMap] : -1;
 158
 80159                    if ( baseMapId != -1 )
 160                    {
 74161                        if ( baseMapId < AVATAR_MAP_ID_PROPERTIES.Length )
 162                        {
 74163                            int targetMap = AVATAR_MAP_ID_PROPERTIES[baseMapId];
 74164                            newMaterial.SetTexture(targetMap, baseMap);
 74165                        }
 166                        else
 167                        {
 0168                            logger.Log(LogType.Error, "FlattenMaterials", $"Base Map ID out of bounds! {baseMapId}");
 169                        }
 170                    }
 171
 80172                    if ( emissionMapId != -1 )
 173                    {
 7174                        if ( baseMapId < AVATAR_MAP_ID_PROPERTIES.Length )
 175                        {
 7176                            int targetMap = AVATAR_MAP_ID_PROPERTIES[emissionMapId];
 7177                            newMaterial.SetTexture(targetMap, emissionMap);
 7178                        }
 179                        else
 180                        {
 0181                            logger.Log(LogType.Error, "FlattenMaterials", $"Emission Map ID out of bounds! {emissionMapI
 182                        }
 183                    }
 184
 80185                    Vector4 baseColor = mat.GetVector(ShaderUtils.BaseColor);
 80186                    Vector4 emissionColor = mat.GetVector(ShaderUtils.EmissionColor);
 80187                    Vector3 texturePointerData = new Vector3(baseMapId, emissionMapId, cutoff);
 188
 56758189                    for ( int ai = 0; ai < vertexCount; ai++ )
 190                    {
 28299191                        result.texturePointers[currentVertexCount] = texturePointerData;
 28299192                        result.colors[currentVertexCount] = baseColor;
 28299193                        result.emissionColors[currentVertexCount] = emissionColor;
 28299194                        currentVertexCount++;
 195                    }
 196
 80197                    if (VERBOSE)
 0198                        logger.Log($"Layer {i} - vertexCount: {vertexCount} - texturePointers: ({baseMapId}, {emissionMa
 199                }
 200
 31201                SRPBatchingHelper.OptimizeMaterial(newMaterial);
 202            }
 203
 19204            return result;
 205        }
 206
 207        /// <summary>
 208        /// ComputeSubMeshes iterates over the given CombineLayer list, and returns a list that can be used to map a
 209        /// sub-mesh for each CombineLayer object. A CombineLayer object can group more than a single mesh.
 210        ///
 211        /// Note that this had to be done because Mesh.CombineMeshes lack the option of controlling the sub-mesh
 212        /// output. Currently the only options are to combine everything in a single sub-mesh, or generate a single sub-
 213        /// per combined mesh. The CombineLayer approach may need to combine specific meshes to a single sub-mesh
 214        /// (because they all can have the same material, if they share the same render state -- i.e. transparency or cu
 215        /// </summary>
 216        /// <param name="layers">A CombineLayer list. You can generate this array using CombineLayerUtils.Slice().</para
 217        /// <returns>A SubMeshDescriptor list that can be used later to set the sub-meshes of the final combined mesh
 218        /// in a way that each sub-mesh corresponds with its own layer.</returns>
 219        public static List<SubMeshDescriptor> ComputeSubMeshes(List<CombineLayer> layers)
 220        {
 18221            List<SubMeshDescriptor> result = new List<SubMeshDescriptor>();
 18222            int layersCount = layers.Count;
 18223            int subMeshIndexOffset = 0;
 224
 94225            for (int layerIndex = 0; layerIndex < layersCount; layerIndex++)
 226            {
 29227                CombineLayer layer = layers[layerIndex];
 29228                var layerRenderers = layer.renderers;
 29229                int layerRenderersCount = layerRenderers.Count;
 230
 29231                int subMeshVertexCount = 0;
 29232                int subMeshIndexCount = 0;
 233
 218234                for (int i = 0; i < layerRenderersCount; i++)
 235                {
 80236                    var renderer = layerRenderers[i];
 237
 80238                    int vertexCount = renderer.sharedMesh.vertexCount;
 80239                    int indexCount = (int)renderer.sharedMesh.GetIndexCount(0);
 240
 80241                    subMeshVertexCount += vertexCount;
 80242                    subMeshIndexCount += indexCount;
 243                }
 244
 29245                var subMesh = new SubMeshDescriptor(subMeshIndexOffset, subMeshIndexCount);
 29246                subMesh.vertexCount = subMeshVertexCount;
 29247                result.Add(subMesh);
 248
 29249                subMeshIndexOffset += subMeshIndexCount;
 250            }
 251
 18252            return result;
 253        }
 254
 255        /// <summary>
 256        /// ComputeCombineInstancesData returns a CombineInstance list that can be used to combine all the meshes
 257        /// specified by the given CombineLayer list. This is done via Mesh.CombineMeshes() Unity method.
 258        /// </summary>
 259        /// <param name="layers">A CombineLayer list. You can generate this array using CombineLayerUtils.Slice()</param
 260        /// <returns>CombineInstance list usable by Mesh.CombineMeshes()</returns>
 261        public static List<CombineInstance> ComputeCombineInstancesData(List<CombineLayer> layers)
 262        {
 17263            var result = new List<CombineInstance>();
 17264            int layersCount = layers.Count;
 265
 88266            for (int layerIndex = 0; layerIndex < layersCount; layerIndex++)
 267            {
 27268                CombineLayer layer = layers[layerIndex];
 27269                var layerRenderers = layer.renderers;
 27270                int layerRenderersCount = layerRenderers.Count;
 271
 206272                for (int i = 0; i < layerRenderersCount; i++)
 273                {
 76274                    var renderer = layerRenderers[i];
 275
 76276                    Transform meshTransform = renderer.transform;
 76277                    Transform prevParent = meshTransform.parent;
 76278                    meshTransform.SetParent(null, true);
 279
 76280                    result.Add( new CombineInstance()
 281                    {
 282                        subMeshIndex = 0, // this means the source sub-mesh, not destination
 283                        mesh = renderer.sharedMesh,
 284                        transform = meshTransform.localToWorldMatrix
 285                    });
 286
 76287                    meshTransform.SetParent( prevParent );
 288                }
 289            }
 290
 17291            return result;
 292        }
 293
 294        public static Mesh CombineMeshesWithLayers( List<CombineInstance> combineInstancesData, List<CombineLayer> layer
 295        {
 17296            Mesh result = new Mesh();
 297            // Is important to use the layerRenderers to combine (i.e. no the original renderers)
 298            // Layer renderers are in a specific order that must be abided, or the combining will be broken.
 17299            List<SkinnedMeshRenderer> layerRenderers = new List<SkinnedMeshRenderer>();
 300
 88301            for (int i = 0; i < layers.Count; i++)
 302            {
 27303                layerRenderers.AddRange( layers[i].renderers );
 304            }
 305
 17306            using (var bakedInstances = new BakedCombineInstances())
 307            {
 17308                bakedInstances.Bake(combineInstancesData, layerRenderers);
 17309                result.CombineMeshes(combineInstancesData.ToArray(), true, true);
 17310            }
 311
 17312            return result;
 313        }
 314
 315        /// <summary>
 316        /// ResetBones will reset the given SkinnedMeshRenderer bones to the original bindposes position.
 317        ///
 318        /// This is done without taking into account the rootBone. We need to do it this way because the meshes must be
 319        /// combined posing to match the raw bindposes matrix.
 320        ///
 321        /// If the combined mesh don't match the bindposes matrices, the resulting skinning will not work.
 322        ///
 323        /// For this reason, this method doesn't resemble the original method that unity uses to reset the skeleton foun
 324        /// https://github.com/Unity-Technologies/UnityCsReference/blob/61f92bd79ae862c4465d35270f9d1d57befd1761/Editor/
 325        /// </summary>
 326        internal static void ResetBones(Matrix4x4[] bindPoses, Transform[] bones)
 327        {
 2268328            for ( int i = 0 ; i < bones.Length; i++ )
 329            {
 1116330                Transform bone = bones[i];
 1116331                Matrix4x4 bindPose = bindPoses[i].inverse;
 1116332                bone.position = bindPose.MultiplyPoint3x4(Vector3.zero);
 1116333                bone.rotation = bindPose.rotation;
 334
 1116335                Vector3 bindPoseScale = bindPose.lossyScale;
 1116336                Vector3 boneScale = bone.lossyScale;
 337
 1116338                bone.localScale = new Vector3(bindPoseScale.x / boneScale.x,
 339                    bindPoseScale.y / boneScale.y,
 340                    bindPoseScale.z / boneScale.z);
 341            }
 342
 343#if UNITY_EDITOR
 18344            DrawDebugSkeleton(bones);
 345#endif
 18346        }
 347
 348        internal static void DrawDebugSkeleton(Transform[] bones)
 349        {
 2268350            for ( int i = 0 ; i < bones.Length; i++ )
 351            {
 1116352                Transform bone = bones[i];
 1116353                Debug.DrawLine(bone.position, bone.position + bone.forward, Color.cyan, 60);
 354
 4428355                foreach ( Transform child in bone )
 356                {
 1098357                    Debug.DrawLine(bone.position, child.position, Color.green, 60);
 358                }
 359            }
 18360        }
 361    }
 362}