< Summary

Class:DCL.AvatarMeshCombiner
Assembly:AvatarMeshCombiner
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Avatar/AvatarMeshCombiner/AvatarMeshCombiner.cs
Covered lines:25
Uncovered lines:2
Coverable lines:27
Total lines:159
Line coverage:92.5% (25 of 27)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
CombineSkinnedMeshes(...)0%33092.59%

File(s)

/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Avatar/AvatarMeshCombiner/AvatarMeshCombiner.cs

#LineLine coverage
 1using System.Collections.Generic;
 2using System.Data.Common;
 3using System.Linq;
 4using Unity.Collections;
 5using UnityEditor;
 6using UnityEngine;
 7using UnityEngine.Rendering;
 8
 9namespace DCL
 10{
 11    /// <summary>
 12    /// This class is used by the AvatarMeshCombiner to combine meshes. Each layer represents a new generated sub-mesh.
 13    /// </summary>
 14    public class CombineLayer
 15    {
 16        public List<SkinnedMeshRenderer> renderers = new List<SkinnedMeshRenderer>();
 17        public Dictionary<Texture2D, int> textureToId = new Dictionary<Texture2D, int>();
 18        public CullMode cullMode;
 19        public bool isOpaque;
 20
 21        public override string ToString()
 22        {
 23            string rendererString = $"renderer count: {renderers?.Count ?? 0}";
 24            string textureIdString = "texture ids: {";
 25
 26            foreach ( var kvp in textureToId )
 27            {
 28                textureIdString += $" tx hash: {kvp.Key.GetHashCode()} id: {kvp.Value} ";
 29            }
 30
 31            textureIdString += "}";
 32
 33            return $"cullMode: {cullMode} - isOpaque: {isOpaque} - {rendererString} - {textureIdString}";
 34        }
 35    }
 36
 37    /// <summary>
 38    /// This class is used to determine the original materials uniform properties
 39    /// will be passed on to UV values. texturePointers and emissionColors are bound to UV channels.
 40    /// Colors are bound to the color channel.
 41    /// </summary>
 42    public class FlattenedMaterialsData
 43    {
 44        public List<Material> materials = new List<Material>();
 45        public Vector3[] texturePointers;
 46        public Vector4[] colors;
 47        public Vector4[] emissionColors;
 48    }
 49
 50    /// <summary>
 51    /// This utility class can combine avatar meshes into a single optimal mesh.
 52    /// </summary>
 53    public static class AvatarMeshCombiner
 54    {
 55        public struct Output
 56        {
 57            public bool isValid;
 58            public Mesh mesh;
 59            public Material[] materials;
 60        }
 61
 62        // These must match the channels defined in the material shader.
 63        // Channels must be defined with the TEXCOORD semantic.
 64        private const int TEXTURE_POINTERS_UV_CHANNEL_INDEX = 2;
 65        private const int EMISSION_COLORS_UV_CHANNEL_INDEX = 3;
 66
 67        /// <summary>
 68        /// CombineSkinnedMeshes combines a list of skinned mesh renderers that share the same bone structure into a
 69        /// single mesh with the fewest numbers of sub-meshes as possible. It will return a list of materials that match
 70        /// the number of sub-meshes to be used with a Renderer component.
 71        /// <br/>
 72        /// The sub-meshes are divided according to the following constraints:
 73        /// <ul>
 74        /// <li>Culling mode</li>
 75        /// <li>Transparency or opacity of each renderer to be combined</li>
 76        /// <li>Texture count of the accumulated renderers, only including emission and albedo textures.</li>
 77        /// </ul>
 78        /// </summary>
 79        /// <param name="bindPoses">Bindposes that will be used by all the renderers.</param>
 80        /// <param name="bones">Bones that will be used by all the renderers.</param>
 81        /// <param name="renderers">Renderers to be combined.</param>
 82        /// <param name="materialAsset">Material asset to be used in the resulting Output object. This Material will be 
 83        /// <returns>An Output object with the mesh and materials. Output.isValid will return true if the combining is s
 84        public static Output CombineSkinnedMeshes(Matrix4x4[] bindPoses, Transform[] bones, SkinnedMeshRenderer[] render
 85        {
 1686            Output result = new Output();
 87
 88            //
 89            // Reset bones to put character in T pose. Renderers are going to be baked later.
 90            // This is a workaround, it had to be done because renderers original matrices don't match the T pose.
 91            // We need wearables in T pose to properly combine the avatar mesh.
 92            //
 1693            AvatarMeshCombinerUtils.ResetBones(bindPoses, bones);
 94
 95            //
 96            // Get combined layers. Layers are groups of renderers that have a id -> tex mapping.
 97            //
 98            // This id is going to get written to uv channels so the material can use up to 12 textures
 99            // in a single draw call.
 100            //
 101            // Layers are divided accounting for the 12 textures limit and transparency/opaque limit.
 102            //
 16103            List<CombineLayer> layers = CombineLayerUtils.Slice( renderers );
 104
 16105            if ( layers == null )
 106            {
 0107                result.isValid = false;
 0108                return result;
 109            }
 110
 111            // Here, the final combined mesh is generated. This mesh still doesn't have the UV encoded
 112            // samplers and some needed attributes. Those will be added below.
 16113            var combineInstancesData = AvatarMeshCombinerUtils.ComputeCombineInstancesData( layers );
 16114            Mesh finalMesh = AvatarMeshCombinerUtils.CombineMeshesWithLayers(combineInstancesData, layers);
 115
 116            // Note about bindPoses and boneWeights reassignment:
 117            //
 118            // This has to be done because CombineMeshes doesn't identify different meshes
 119            // with boneWeights that correspond to the same bones. Also, bindposes are
 120            // stacked and repeated for each mesh when they shouldn't.
 121            //
 122            // This is OK when combining multiple SkinnedMeshRenderers that animate independently,
 123            // but not in our use case.
 124
 16125            finalMesh.bindposes = bindPoses;
 126
 16127            var boneWeights = AvatarMeshCombinerUtils.ComputeBoneWeights( layers );
 16128            finalMesh.boneWeights = boneWeights.ToArray();
 129
 16130            var flattenedMaterialsData = AvatarMeshCombinerUtils.FlattenMaterials( layers, materialAsset );
 16131            finalMesh.SetUVs(EMISSION_COLORS_UV_CHANNEL_INDEX, flattenedMaterialsData.emissionColors);
 16132            finalMesh.SetUVs(TEXTURE_POINTERS_UV_CHANNEL_INDEX, flattenedMaterialsData.texturePointers);
 133
 16134            var tempArray = new NativeArray<Vector4>(flattenedMaterialsData.colors.Length, Allocator.Temp);
 16135            tempArray.CopyFrom(flattenedMaterialsData.colors.ToArray());
 16136            finalMesh.SetColors(tempArray);
 16137            tempArray.Dispose();
 138            // Each layer corresponds with a subMesh. This is to take advantage of the sharedMaterials array.
 139            //
 140            // When a renderer has many sub-meshes, each materials array element correspond to the sub-mesh of
 141            // the same index. Each layer needs to be renderer with its own material, so it becomes very useful.
 142            //
 16143            var subMeshDescriptors = AvatarMeshCombinerUtils.ComputeSubMeshes( layers );
 16144            if ( subMeshDescriptors.Count > 1 )
 145            {
 10146                finalMesh.subMeshCount = subMeshDescriptors.Count;
 10147                finalMesh.SetSubMeshes(subMeshDescriptors);
 148            }
 149
 16150            finalMesh.Optimize();
 151
 16152            result.mesh = finalMesh;
 16153            result.materials = flattenedMaterialsData.materials.ToArray();
 16154            result.isValid = true;
 155
 16156            return result;
 157        }
 158    }
 159}