< 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:118
Uncovered lines:2
Coverable lines:120
Total lines:328
Line coverage:98.3% (118 of 120)
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%14.0114096%
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.Collections.Generic;
 2using System.Linq;
 3using DCL.Helpers;
 4using UnityEngine;
 5using UnityEngine.Rendering;
 6using Object = UnityEngine.Object;
 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
 34        /// <summary>
 35        /// This method iterates over all the renderers contained in the given CombineLayer list, and
 36        /// outputs an array of all the BoneWeights of the renderers in order.
 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 BoneWeights that share the same skeleton.</returns>
 44        public static List<BoneWeight> ComputeBoneWeights( List<CombineLayer> layers )
 45        {
 1546            List<BoneWeight> result = new List<BoneWeight>();
 1547            int layersCount = layers.Count;
 48
 8249            for (int layerIndex = 0; layerIndex < layersCount; layerIndex++)
 50            {
 2651                CombineLayer layer = layers[layerIndex];
 2652                var layerRenderers = layer.renderers;
 53
 2654                int layerRenderersCount = layerRenderers.Count;
 55
 20056                for (int i = 0; i < layerRenderersCount; i++)
 57                {
 7458                    var renderer = layerRenderers[i];
 59
 60                    // Bone Weights
 7461                    var sharedMesh = renderer.sharedMesh;
 7462                    var meshBoneWeights = sharedMesh.boneWeights;
 7463                    result.AddRange(meshBoneWeights);
 64                }
 65            }
 66
 1567            return result;
 68        }
 69
 70        /// <summary>
 71        /// FlattenMaterials take a CombineLayer list and returns a FlattenedMaterialsData object.
 72        ///
 73        /// This object can be used to construct a combined mesh that has uniform data encoded in uv attributes.
 74        /// This type of encoding can be used to greatly reduce draw calls for seemingly unrelated objects.
 75        ///
 76        /// The returned object also contains a single material per CombineLayer.
 77        /// </summary>
 78        /// <param name="layers">A CombineLayer list. You can generate this array using CombineLayerUtils.Slice().</para
 79        /// <param name="materialAsset">Material asset that will be cloned to generate the returned materials.</param>
 80        /// <returns>A FlattenedMaterialsData object. This object can be used to construct a combined mesh that has unif
 81        public static FlattenedMaterialsData FlattenMaterials(List<CombineLayer> layers, Material materialAsset)
 82        {
 1683            var result = new FlattenedMaterialsData();
 1684            int layersCount = layers.Count;
 85
 8886            for (int layerIndex = 0; layerIndex < layersCount; layerIndex++)
 87            {
 2888                CombineLayer layer = layers[layerIndex];
 2889                var layerRenderers = layer.renderers;
 90
 2891                Material newMaterial = new Material(materialAsset);
 92
 2893                CullMode cullMode = layer.cullMode;
 2894                bool isOpaque = layer.isOpaque;
 95
 2896                if ( isOpaque )
 1697                    MaterialUtils.SetOpaque(newMaterial);
 98                else
 1299                    MaterialUtils.SetTransparent(newMaterial);
 100
 28101                newMaterial.SetInt(ShaderUtils.Cull, (int)cullMode);
 102
 28103                result.materials.Add( newMaterial );
 104
 28105                int layerRenderersCount = layerRenderers.Count;
 106
 204107                for (int i = 0; i < layerRenderersCount; i++)
 108                {
 74109                    var renderer = layerRenderers[i];
 110
 111                    // Bone Weights
 74112                    var sharedMesh = renderer.sharedMesh;
 74113                    int vertexCount = sharedMesh.vertexCount;
 114
 115                    // Texture IDs
 74116                    Material mat = renderer.sharedMaterial;
 117
 74118                    Texture2D baseMap = (Texture2D)mat.GetTexture(ShaderUtils.BaseMap);
 74119                    Texture2D emissionMap = (Texture2D)mat.GetTexture(ShaderUtils.EmissionMap);
 74120                    float cutoff = mat.GetFloat(ShaderUtils.Cutoff);
 121
 74122                    bool baseMapIdIsValid = baseMap != null && layer.textureToId.ContainsKey(baseMap);
 74123                    bool emissionMapIdIsValid = emissionMap != null && layer.textureToId.ContainsKey(emissionMap);
 124
 74125                    int baseMapId = baseMapIdIsValid ? layer.textureToId[baseMap] : -1;
 74126                    int emissionMapId = emissionMapIdIsValid ? layer.textureToId[emissionMap] : -1;
 127
 74128                    result.texturePointers.AddRange(Enumerable.Repeat(new Vector3(baseMapId, emissionMapId, cutoff), ver
 129
 74130                    if ( baseMapId != -1 )
 131                    {
 70132                        if ( baseMapId < AVATAR_MAP_ID_PROPERTIES.Length )
 133                        {
 70134                            int targetMap = AVATAR_MAP_ID_PROPERTIES[baseMapId];
 70135                            newMaterial.SetTexture(targetMap, baseMap);
 70136                        }
 137                        else
 138                        {
 0139                            logger.Log(LogType.Error, "FlattenMaterials", $"Base Map ID out of bounds! {baseMapId}");
 140                        }
 141                    }
 142
 74143                    if ( emissionMapId != -1 )
 144                    {
 7145                        if ( baseMapId < AVATAR_MAP_ID_PROPERTIES.Length )
 146                        {
 7147                            int targetMap = AVATAR_MAP_ID_PROPERTIES[emissionMapId];
 7148                            newMaterial.SetTexture(targetMap, emissionMap);
 7149                        }
 150                        else
 151                        {
 0152                            logger.Log(LogType.Error, "FlattenMaterials", $"Emission Map ID out of bounds! {emissionMapI
 153                        }
 154                    }
 155
 156                    // Base Colors
 74157                    Vector4 baseColor = mat.GetVector(ShaderUtils.BaseColor);
 74158                    result.colors.AddRange(Enumerable.Repeat(baseColor, vertexCount));
 159
 160                    // Emission Colors
 74161                    Vector4 emissionColor = mat.GetVector(ShaderUtils.EmissionColor);
 74162                    result.emissionColors.AddRange(Enumerable.Repeat(emissionColor, vertexCount));
 163
 74164                    logger.Log($"Layer {i} - vertexCount: {vertexCount} - texturePointers: ({baseMapId}, {emissionMapId}
 165                }
 166
 28167                SRPBatchingHelper.OptimizeMaterial(newMaterial);
 168            }
 169
 16170            return result;
 171        }
 172
 173        /// <summary>
 174        /// ComputeSubMeshes iterates over the given CombineLayer list, and returns a list that can be used to map a
 175        /// sub-mesh for each CombineLayer object. A CombineLayer object can group more than a single mesh.
 176        ///
 177        /// Note that this had to be done because Mesh.CombineMeshes lack the option of controlling the sub-mesh
 178        /// output. Currently the only options are to combine everything in a single sub-mesh, or generate a single sub-
 179        /// per combined mesh. The CombineLayer approach may need to combine specific meshes to a single sub-mesh
 180        /// (because they all can have the same material, if they share the same render state -- i.e. transparency or cu
 181        /// </summary>
 182        /// <param name="layers">A CombineLayer list. You can generate this array using CombineLayerUtils.Slice().</para
 183        /// <returns>A SubMeshDescriptor list that can be used later to set the sub-meshes of the final combined mesh
 184        /// in a way that each sub-mesh corresponds with its own layer.</returns>
 185        public static List<SubMeshDescriptor> ComputeSubMeshes(List<CombineLayer> layers)
 186        {
 15187            List<SubMeshDescriptor> result = new List<SubMeshDescriptor>();
 15188            int layersCount = layers.Count;
 15189            int subMeshIndexOffset = 0;
 190
 82191            for (int layerIndex = 0; layerIndex < layersCount; layerIndex++)
 192            {
 26193                CombineLayer layer = layers[layerIndex];
 26194                var layerRenderers = layer.renderers;
 26195                int layerRenderersCount = layerRenderers.Count;
 196
 26197                int subMeshVertexCount = 0;
 26198                int subMeshIndexCount = 0;
 199
 200200                for (int i = 0; i < layerRenderersCount; i++)
 201                {
 74202                    var renderer = layerRenderers[i];
 203
 74204                    int vertexCount = renderer.sharedMesh.vertexCount;
 74205                    int indexCount = (int)renderer.sharedMesh.GetIndexCount(0);
 206
 74207                    subMeshVertexCount += vertexCount;
 74208                    subMeshIndexCount += indexCount;
 209                }
 210
 26211                var subMesh = new SubMeshDescriptor(subMeshIndexOffset, subMeshIndexCount);
 26212                subMesh.vertexCount = subMeshVertexCount;
 26213                result.Add(subMesh);
 214
 26215                subMeshIndexOffset += subMeshIndexCount;
 216            }
 217
 15218            return result;
 219        }
 220
 221        /// <summary>
 222        /// ComputeCombineInstancesData returns a CombineInstance list that can be used to combine all the meshes
 223        /// specified by the given CombineLayer list. This is done via Mesh.CombineMeshes() Unity method.
 224        /// </summary>
 225        /// <param name="layers">A CombineLayer list. You can generate this array using CombineLayerUtils.Slice()</param
 226        /// <returns>CombineInstance list usable by Mesh.CombineMeshes()</returns>
 227        public static List<CombineInstance> ComputeCombineInstancesData(List<CombineLayer> layers)
 228        {
 14229            var result = new List<CombineInstance>();
 14230            int layersCount = layers.Count;
 231
 76232            for (int layerIndex = 0; layerIndex < layersCount; layerIndex++)
 233            {
 24234                CombineLayer layer = layers[layerIndex];
 24235                var layerRenderers = layer.renderers;
 24236                int layerRenderersCount = layerRenderers.Count;
 237
 188238                for (int i = 0; i < layerRenderersCount; i++)
 239                {
 70240                    var renderer = layerRenderers[i];
 241
 70242                    Transform meshTransform = renderer.transform;
 70243                    Transform prevParent = meshTransform.parent;
 70244                    meshTransform.SetParent(null, true);
 245
 70246                    result.Add( new CombineInstance()
 247                    {
 248                        subMeshIndex = 0, // this means the source sub-mesh, not destination
 249                        mesh = renderer.sharedMesh,
 250                        transform = meshTransform.localToWorldMatrix
 251                    });
 252
 70253                    meshTransform.SetParent( prevParent );
 254                }
 255            }
 256
 14257            return result;
 258        }
 259
 260        public static Mesh CombineMeshesWithLayers( List<CombineInstance> combineInstancesData, List<CombineLayer> layer
 261        {
 14262            Mesh result = new Mesh();
 263            // Is important to use the layerRenderers to combine (i.e. no the original renderers)
 264            // Layer renderers are in a specific order that must be abided, or the combining will be broken.
 14265            List<SkinnedMeshRenderer> layerRenderers = new List<SkinnedMeshRenderer>();
 266
 76267            for (int i = 0; i < layers.Count; i++)
 268            {
 24269                layerRenderers.AddRange( layers[i].renderers );
 270            }
 271
 14272            using (var bakedInstances = new BakedCombineInstances())
 273            {
 14274                bakedInstances.Bake(combineInstancesData, layerRenderers);
 14275                result.CombineMeshes(combineInstancesData.ToArray(), true, true);
 14276            }
 277
 14278            return result;
 279        }
 280
 281        /// <summary>
 282        /// ResetBones will reset the given SkinnedMeshRenderer bones to the original bindposes position.
 283        ///
 284        /// This is done without taking into account the rootBone. We need to do it this way because the meshes must be
 285        /// combined posing to match the raw bindposes matrix.
 286        ///
 287        /// If the combined mesh don't match the bindposes matrices, the resulting skinning will not work.
 288        ///
 289        /// For this reason, this method doesn't resemble the original method that unity uses to reset the skeleton foun
 290        /// https://github.com/Unity-Technologies/UnityCsReference/blob/61f92bd79ae862c4465d35270f9d1d57befd1761/Editor/
 291        /// </summary>
 292        internal static void ResetBones(Matrix4x4[] bindPoses, Transform[] bones)
 293        {
 1890294            for ( int i = 0 ; i < bones.Length; i++ )
 295            {
 930296                Transform bone = bones[i];
 930297                Matrix4x4 bindPose = bindPoses[i].inverse;
 930298                bone.position = bindPose.MultiplyPoint3x4(Vector3.zero);
 930299                bone.rotation = bindPose.rotation;
 300
 930301                Vector3 bindPoseScale = bindPose.lossyScale;
 930302                Vector3 boneScale = bone.lossyScale;
 303
 930304                bone.localScale = new Vector3(bindPoseScale.x / boneScale.x,
 305                    bindPoseScale.y / boneScale.y,
 306                    bindPoseScale.z / boneScale.z);
 307            }
 308
 309#if UNITY_EDITOR
 15310            DrawDebugSkeleton(bones);
 311#endif
 15312        }
 313
 314        internal static void DrawDebugSkeleton(Transform[] bones)
 315        {
 1890316            for ( int i = 0 ; i < bones.Length; i++ )
 317            {
 930318                Transform bone = bones[i];
 930319                Debug.DrawLine(bone.position, bone.position + bone.forward, Color.cyan, 60);
 320
 3690321                foreach ( Transform child in bone )
 322                {
 915323                    Debug.DrawLine(bone.position, child.position, Color.green, 60);
 324                }
 325            }
 15326        }
 327    }
 328}