< Summary

Class:DCL.AvatarRenderer
Assembly:AvatarShape
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Avatar/AvatarRenderer.cs
Covered lines:85
Uncovered lines:255
Coverable lines:340
Total lines:718
Line coverage:25% (85 of 340)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
AvatarRenderer()0%110100%
AvatarRenderer()0%110100%
Awake()0%220100%
ApplyModel(...)0%15.4610062.07%
InitializeImpostor()0%30500%
StopLoadingCoroutines()0%220100%
CleanupAvatar()0%10.0310093.55%
CleanUpUnusedItems()0%30500%
LoadAvatar()0%2651.845504.96%
OnWearableLoadingSuccess(...)0%12300%
OnBodyShapeLoadingFail(...)0%42600%
OnWearableLoadingFail(...)0%56700%
SetWearableBones()0%6200%
UpdateExpression()0%20400%
SetExpression(...)0%110100%
AddWearableController(...)0%56700%
UpdateWearableController(...)0%56700%
CopyFrom(...)0%2100%
SetGOVisibility(...)0%220100%
SetRendererEnabled(...)0%6200%
SetImpostorVisibility(...)0%110100%
SetImpostorForward(...)0%2100%
SetImpostorColor(...)0%2100%
SetAvatarFade(...)0%20400%
SetImpostorFade(...)0%6200%
HideAll()0%6200%
SetFacialFeaturesVisible(...)0%20400%
SetSSAOEnabled(...)0%20400%
MergeAvatar(...)0%12300%
CleanMergedAvatar()0%110100%
LateUpdate()0%3.333066.67%
OnDestroy()0%110100%

File(s)

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

#LineLine coverage
 1using System;
 2using System.Collections;
 3using System.Collections.Generic;
 4using System.Linq;
 5using DCL.Helpers;
 6using UnityEditor;
 7using UnityEngine;
 8using static WearableLiterals;
 9
 10namespace DCL
 11{
 12    public class AvatarRenderer : MonoBehaviour, IAvatarRenderer
 13    {
 114        private static readonly int BASE_COLOR_PROPERTY = Shader.PropertyToID("_BaseColor");
 15        private const int MAX_RETRIES = 5;
 16
 17        public Material defaultMaterial;
 18        public Material eyeMaterial;
 19        public Material eyebrowMaterial;
 20        public Material mouthMaterial;
 21
 22        public MeshRenderer lodRenderer;
 23        public MeshFilter lodMeshFilter;
 24
 25        private AvatarModel model;
 26        private AvatarMeshCombinerHelper avatarMeshCombiner;
 27        private SimpleGPUSkinning gpuSkinning = null;
 28
 29        private Renderer mainMeshRenderer
 30        {
 31            get
 32            {
 033                if (gpuSkinning != null)
 034                    return gpuSkinning.renderer;
 035                return avatarMeshCombiner.renderer;
 36            }
 37        }
 38
 39        public event Action<IAvatarRenderer.VisualCue> OnVisualCue;
 40        public event Action OnSuccessEvent;
 41        public event Action<float> OnImpostorAlphaValueUpdate;
 42        public event Action<bool> OnFailEvent;
 43
 44        internal BodyShapeController bodyShapeController;
 107045        internal Dictionary<WearableItem, WearableController> wearableControllers = new Dictionary<WearableItem, Wearabl
 46        internal FacialFeatureController eyesController;
 47        internal FacialFeatureController eyebrowsController;
 48        internal FacialFeatureController mouthController;
 49        internal AvatarAnimatorLegacy animator;
 50        internal StickersController stickersController;
 51
 107052        private long lastStickerTimestamp = -1;
 53
 54        public bool isLoading;
 055        public bool isReady => bodyShapeController != null && bodyShapeController.isReady && wearableControllers != null
 56
 57        private Coroutine loadCoroutine;
 107058        private List<string> wearablesInUse = new List<string>();
 59        private AssetPromise_Texture bodySnapshotTexturePromise;
 60        private bool isDestroyed = false;
 61
 62        private void Awake()
 63        {
 19064            animator = GetComponent<AvatarAnimatorLegacy>();
 19065            stickersController = GetComponent<StickersController>();
 19066            avatarMeshCombiner = new AvatarMeshCombinerHelper();
 67
 19068            if (lodRenderer != null)
 169                SetImpostorVisibility(false);
 19070        }
 71
 72        public void ApplyModel(AvatarModel model, Action onSuccess, Action onFail)
 73        {
 12574            if ( this.model != null )
 75            {
 7676                if (model != null && this.model.Equals(model))
 77                {
 4678                    onSuccess?.Invoke();
 3379                    return;
 80                }
 81
 3082                bool wearablesChanged = !this.model.HaveSameWearablesAndColors(model);
 3083                bool expressionsChanged = !this.model.HaveSameExpressions(model);
 84
 3085                if (!wearablesChanged && expressionsChanged)
 86                {
 087                    this.model.expressionTriggerId = model.expressionTriggerId;
 088                    this.model.expressionTriggerTimestamp = model.expressionTriggerTimestamp;
 089                    this.model.stickerTriggerId = model.stickerTriggerId;
 090                    this.model.stickerTriggerTimestamp = model.stickerTriggerTimestamp;
 091                    UpdateExpression();
 092                    onSuccess?.Invoke();
 093                    return;
 94                }
 95            }
 96
 7997            this.model = new AvatarModel();
 7998            this.model.CopyFrom(model);
 7999            if (bodySnapshotTexturePromise != null)
 0100                AssetPromiseKeeper_Texture.i.Forget(bodySnapshotTexturePromise);
 101
 102            // TODO(Brian): Find a better approach than overloading callbacks like this. This code is not readable.
 103            void onSuccessWrapper()
 104            {
 0105                onSuccess?.Invoke();
 0106                this.OnSuccessEvent -= onSuccessWrapper;
 0107            }
 108
 79109            this.OnSuccessEvent += onSuccessWrapper;
 110
 111            void onFailWrapper(bool isFatalError)
 112            {
 6113                onFail?.Invoke();
 6114                this.OnFailEvent -= onFailWrapper;
 6115            }
 116
 79117            this.OnFailEvent += onFailWrapper;
 118
 79119            isLoading = false;
 120
 79121            if (model == null)
 122            {
 0123                CleanupAvatar();
 0124                this.OnSuccessEvent?.Invoke();
 0125                return;
 126            }
 127
 79128            StopLoadingCoroutines();
 79129            isLoading = true;
 79130            loadCoroutine = CoroutineStarter.Start(LoadAvatar());
 79131        }
 132
 133        public void InitializeImpostor()
 134        {
 0135            UserProfile userProfile = null;
 0136            if (!string.IsNullOrEmpty(model?.id))
 0137                userProfile = UserProfileController.GetProfileByUserId(model.id);
 138
 0139            if (userProfile != null)
 140            {
 0141                bodySnapshotTexturePromise = new AssetPromise_Texture(userProfile.bodySnapshotURL);
 0142                bodySnapshotTexturePromise.OnSuccessEvent += asset => AvatarRendererHelpers.SetImpostorTexture(asset.tex
 0143                bodySnapshotTexturePromise.OnFailEvent += asset => AvatarRendererHelpers.RandomizeAndApplyGenericImposto
 0144                AssetPromiseKeeper_Texture.i.Keep(bodySnapshotTexturePromise);
 0145            }
 146            else
 147            {
 0148                AvatarRendererHelpers.RandomizeAndApplyGenericImpostor(lodMeshFilter.mesh);
 149            }
 0150        }
 151
 152        void StopLoadingCoroutines()
 153        {
 271154            if (loadCoroutine != null)
 78155                CoroutineStarter.Stop(loadCoroutine);
 156
 271157            loadCoroutine = null;
 271158        }
 159
 160        public void CleanupAvatar()
 161        {
 192162            StopLoadingCoroutines();
 192163            if (!isDestroyed)
 164            {
 3165                SetGOVisibility(true);
 3166                if (lodRenderer != null)
 3167                    SetImpostorVisibility(false);
 168            }
 169
 192170            avatarMeshCombiner.Dispose();
 192171            gpuSkinning = null;
 192172            eyebrowsController?.CleanUp();
 192173            eyebrowsController = null;
 174
 192175            eyesController?.CleanUp();
 192176            eyesController = null;
 177
 192178            mouthController?.CleanUp();
 192179            mouthController = null;
 180
 192181            bodyShapeController?.CleanUp();
 192182            bodyShapeController = null;
 183
 192184            using (var iterator = wearableControllers.GetEnumerator())
 185            {
 192186                while (iterator.MoveNext())
 187                {
 0188                    iterator.Current.Value.CleanUp();
 189                }
 192190            }
 191
 192192            wearableControllers.Clear();
 192193            model = null;
 192194            isLoading = false;
 192195            OnFailEvent = null;
 192196            OnSuccessEvent = null;
 197
 192198            CleanMergedAvatar();
 199
 192200            if (bodySnapshotTexturePromise != null)
 0201                AssetPromiseKeeper_Texture.i.Forget(bodySnapshotTexturePromise);
 202
 192203            CatalogController.RemoveWearablesInUse(wearablesInUse);
 192204            wearablesInUse.Clear();
 192205            OnVisualCue?.Invoke(IAvatarRenderer.VisualCue.CleanedUp);
 148206        }
 207
 208        void CleanUpUnusedItems()
 209        {
 0210            if (model.wearables == null)
 0211                return;
 212
 0213            var ids = wearableControllers.Keys.ToArray();
 214
 0215            for (var i = 0; i < ids.Length; i++)
 216            {
 0217                var currentId = ids[i];
 0218                var wearable = wearableControllers[currentId];
 219
 0220                if (!model.wearables.Contains(wearable.id) || !wearable.IsLoadedForBodyShape(model.bodyShape))
 221                {
 0222                    wearable.CleanUp();
 0223                    wearableControllers.Remove(currentId);
 224                }
 225            }
 0226        }
 227
 228        // TODO(Brian): Pure functions should be extracted from this big LoadAvatar() method and unit-test separately.
 229        //              The current approach has tech debt that's getting very expensive and is costing many hours of de
 230        //              Avatar Loading should be a self contained operation that doesn't depend on pool management and A
 231        //              lifecycle like it does now.
 232        private IEnumerator LoadAvatar()
 233        {
 234            // TODO(Brian): This is an ugly hack, all the loading should be performed
 235            //              without being afraid of the gameObject active state.
 158236            yield return new WaitUntil(() => gameObject.activeSelf);
 237
 6238            bool loadSoftFailed = false;
 239
 6240            WearableItem resolvedBody = null;
 241
 242            // TODO(Brian): Evaluate using UniTask<T> here instead of Helpers.Promise.
 6243            Helpers.Promise<WearableItem> avatarBodyPromise = null;
 6244            if (!string.IsNullOrEmpty(model.bodyShape))
 245            {
 0246                avatarBodyPromise = CatalogController.RequestWearable(model.bodyShape);
 0247            }
 248            else
 249            {
 6250                OnFailEvent?.Invoke(true);
 6251                yield break;
 252            }
 253
 0254            List<WearableItem> resolvedWearables = new List<WearableItem>();
 255
 256            // TODO(Brian): Evaluate using UniTask<T> here instead of Helpers.Promise.
 0257            List<Helpers.Promise<WearableItem>> avatarWearablePromises = new List<Helpers.Promise<WearableItem>>();
 0258            if (model.wearables != null)
 259            {
 0260                for (int i = 0; i < model.wearables.Count; i++)
 261                {
 0262                    avatarWearablePromises.Add(CatalogController.RequestWearable(model.wearables[i]));
 263                }
 264            }
 265
 266            // In this point, all the requests related to the avatar's wearables have been collected and sent to the Cat
 267            // From here we wait for the response of the requested wearables and process them.
 0268            if (avatarBodyPromise != null)
 269            {
 0270                yield return avatarBodyPromise;
 271
 0272                if (!string.IsNullOrEmpty(avatarBodyPromise.error))
 273                {
 0274                    Debug.LogError(avatarBodyPromise.error);
 0275                    loadSoftFailed = true;
 0276                }
 277                else
 278                {
 0279                    resolvedBody = avatarBodyPromise.value;
 0280                    wearablesInUse.Add(avatarBodyPromise.value.id);
 281                }
 282            }
 283
 0284            if (resolvedBody == null)
 285            {
 0286                isLoading = false;
 0287                OnFailEvent?.Invoke(true);
 0288                yield break;
 289            }
 290
 291            // TODO(Brian): Evaluate using UniTask<T> here instead of Helpers.Promise.
 0292            List<Helpers.Promise<WearableItem>> replacementPromises = new List<Helpers.Promise<WearableItem>>();
 293
 0294            foreach (var avatarWearablePromise in avatarWearablePromises)
 295            {
 0296                yield return avatarWearablePromise;
 297
 0298                if (!string.IsNullOrEmpty(avatarWearablePromise.error))
 299                {
 0300                    Debug.LogError(avatarWearablePromise.error);
 0301                    loadSoftFailed = true;
 0302                }
 303                else
 304                {
 0305                    WearableItem wearableItem = avatarWearablePromise.value;
 0306                    wearablesInUse.Add(wearableItem.id);
 307
 0308                    if (wearableItem.GetRepresentation(model.bodyShape) != null)
 309                    {
 0310                        resolvedWearables.Add(wearableItem);
 0311                    }
 312                    else
 313                    {
 0314                        model.wearables.Remove(wearableItem.id);
 0315                        string defaultReplacement = DefaultWearables.GetDefaultWearable(model.bodyShape, wearableItem.da
 0316                        if (!string.IsNullOrEmpty(defaultReplacement))
 317                        {
 0318                            model.wearables.Add(defaultReplacement);
 0319                            replacementPromises.Add(CatalogController.RequestWearable(defaultReplacement));
 320                        }
 321                    }
 322                }
 0323            }
 324
 0325            foreach (var wearablePromise in replacementPromises)
 326            {
 0327                yield return wearablePromise;
 328
 0329                if (!string.IsNullOrEmpty(wearablePromise.error))
 330                {
 0331                    Debug.LogError(wearablePromise.error);
 0332                    loadSoftFailed = true;
 0333                }
 334                else
 335                {
 0336                    WearableItem wearableItem = wearablePromise.value;
 0337                    wearablesInUse.Add(wearableItem.id);
 0338                    resolvedWearables.Add(wearableItem);
 339                }
 0340            }
 341
 0342            bool bodyIsDirty = false;
 0343            if (bodyShapeController != null && bodyShapeController.id != model?.bodyShape)
 344            {
 0345                bodyShapeController.CleanUp();
 0346                bodyShapeController = null;
 0347                bodyIsDirty = true;
 348            }
 349
 0350            if (bodyShapeController == null)
 351            {
 0352                HideAll();
 0353                bodyShapeController = new BodyShapeController(resolvedBody);
 0354                eyesController = FacialFeatureController.CreateDefaultFacialFeature(bodyShapeController.bodyShapeId, Cat
 0355                eyebrowsController = FacialFeatureController.CreateDefaultFacialFeature(bodyShapeController.bodyShapeId,
 0356                mouthController = FacialFeatureController.CreateDefaultFacialFeature(bodyShapeController.bodyShapeId, Ca
 0357            }
 358            else
 359            {
 360                //If bodyShape is downloading will call OnWearableLoadingSuccess (and therefore SetupDefaultMaterial) on
 0361                if (bodyShapeController.isReady)
 0362                    bodyShapeController.SetupHairAndSkinColors(model.skinColor, model.hairColor);
 363            }
 364
 365            //TODO(Brian): This logic should be performed in a testeable pure function instead of this inline approach.
 366            //             Moreover, this function should work against data, not wearableController instances.
 0367            bool wearablesIsDirty = false;
 0368            HashSet<string> unusedCategories = new HashSet<string>(Categories.ALL);
 0369            int wearableCount = resolvedWearables.Count;
 0370            for (int index = 0; index < wearableCount; index++)
 371            {
 0372                WearableItem wearable = resolvedWearables[index];
 0373                if (wearable == null)
 374                    continue;
 375
 0376                unusedCategories.Remove(wearable.data.category);
 0377                if (wearableControllers.ContainsKey(wearable))
 378                {
 0379                    if (wearableControllers[wearable].IsLoadedForBodyShape(bodyShapeController.bodyShapeId))
 0380                        UpdateWearableController(wearable);
 381                    else
 0382                        wearableControllers[wearable].CleanUp();
 0383                }
 384                else
 385                {
 0386                    AddWearableController(wearable);
 0387                    if (wearable.data.category != Categories.EYES && wearable.data.category != Categories.MOUTH && weara
 0388                        wearablesIsDirty = true;
 389                }
 390            }
 391
 0392            foreach (var category in unusedCategories)
 393            {
 394                switch (category)
 395                {
 396                    case Categories.EYES:
 0397                        eyesController = FacialFeatureController.CreateDefaultFacialFeature(bodyShapeController.bodyShap
 0398                        break;
 399                    case Categories.MOUTH:
 0400                        mouthController = FacialFeatureController.CreateDefaultFacialFeature(bodyShapeController.bodySha
 0401                        break;
 402                    case Categories.EYEBROWS:
 0403                        eyebrowsController = FacialFeatureController.CreateDefaultFacialFeature(bodyShapeController.body
 404                        break;
 405                }
 406            }
 407
 0408            HashSet<string> hiddenList = WearableItem.CompoundHidesList(bodyShapeController.bodyShapeId, resolvedWearabl
 0409            if (!bodyShapeController.isReady)
 410            {
 0411                bodyShapeController.Load(bodyShapeController.bodyShapeId, transform, OnWearableLoadingSuccess, OnBodySha
 412            }
 413
 0414            foreach (WearableController wearable in wearableControllers.Values)
 415            {
 0416                if (bodyIsDirty)
 0417                    wearable.boneRetargetingDirty = true;
 418
 0419                wearable.Load(bodyShapeController.bodyShapeId, transform, OnWearableLoadingSuccess, x => OnWearableLoadi
 0420                yield return null;
 421            }
 422
 423            // TODO(Brian): Evaluate using UniTask<T> instead of this way.
 0424            yield return new WaitUntil(() => bodyShapeController.isReady && wearableControllers.Values.All(x => x.isRead
 425
 0426            eyesController?.Load(bodyShapeController, model.eyeColor);
 0427            eyebrowsController?.Load(bodyShapeController, model.hairColor);
 0428            mouthController?.Load(bodyShapeController, model.skinColor);
 429
 430            //TODO(Brian): Evaluate using UniTask<T> instead of this way.
 0431            yield return new WaitUntil(() =>
 0432                (eyebrowsController == null || (eyebrowsController != null && eyebrowsController.isReady)) &&
 433                (eyesController == null || (eyesController != null && eyesController.isReady)) &&
 434                (mouthController == null || (mouthController != null && mouthController.isReady)));
 435
 0436            if (bodyIsDirty || wearablesIsDirty)
 437            {
 0438                OnVisualCue?.Invoke(IAvatarRenderer.VisualCue.Loaded);
 439            }
 440
 441            // TODO(Brian): unusedCategories and hiddenList management is a double negative PITA.
 442            //              The load process should define how the avatar should look like before
 443            //              loading it and put this information in a positive list
 444            //              (i.e. not negative, because leads to double negative checks).
 0445            bodyShapeController.SetActiveParts(unusedCategories.Contains(Categories.LOWER_BODY), unusedCategories.Contai
 0446            bodyShapeController.UpdateVisibility(hiddenList);
 0447            foreach (WearableController wearableController in wearableControllers.Values)
 448            {
 0449                wearableController.UpdateVisibility(hiddenList);
 450            }
 451
 0452            CleanUpUnusedItems();
 453
 0454            isLoading = false;
 455
 0456            SetWearableBones();
 457
 458            // TODO(Brian): Expression and sticker update shouldn't be part of avatar loading code!!!! Refactor me pleas
 0459            UpdateExpression();
 460
 0461            var allRenderers = wearableControllers.SelectMany( x => x.Value.GetRenderers() ).ToList();
 0462            allRenderers.AddRange( bodyShapeController.GetRenderers() );
 0463            bool mergeSuccess = MergeAvatar(allRenderers);
 464
 0465            if (mergeSuccess)
 466            {
 0467                gpuSkinning = new SimpleGPUSkinning(avatarMeshCombiner.renderer);
 468
 469                // Sample the animation manually and force an update in the GPUSkinning to avoid giant avatars
 0470                animator.SetIdleFrame();
 0471                animator.animation.Sample();
 0472                gpuSkinning.Update(true);
 0473            }
 474            else
 0475                loadSoftFailed = true;
 476
 477            // TODO(Brian): The loadSoftFailed flow is too convoluted--you never know which objects are nulled or empty
 478            //              before reaching this branching statement. The failure should be caught with a throw or other
 479            //              proper language feature.
 0480            if (loadSoftFailed)
 481            {
 0482                OnFailEvent?.Invoke(false);
 0483            }
 484            else
 485            {
 0486                OnSuccessEvent?.Invoke();
 487            }
 0488        }
 489
 490        void OnWearableLoadingSuccess(WearableController wearableController)
 491        {
 0492            if (wearableController == null || model == null)
 493            {
 0494                Debug.LogWarning($"WearableSuccess was called wrongly: IsWearableControllerNull=>{wearableController == 
 0495                OnWearableLoadingFail(wearableController, 0);
 0496                return;
 497            }
 498
 0499            wearableController.SetupHairAndSkinColors(model.skinColor, model.hairColor);
 0500        }
 501
 502        void OnBodyShapeLoadingFail(WearableController wearableController)
 503        {
 0504            Debug.LogError($"Avatar: {model?.name}  -  Failed loading bodyshape: {wearableController?.id}");
 0505            CleanupAvatar();
 0506            OnFailEvent?.Invoke(true);
 0507        }
 508
 509        void OnWearableLoadingFail(WearableController wearableController, int retriesCount = MAX_RETRIES)
 510        {
 0511            if (retriesCount <= 0)
 512            {
 0513                Debug.LogError($"Avatar: {model?.name}  -  Failed loading wearable: {wearableController?.id}");
 0514                CleanupAvatar();
 0515                OnFailEvent?.Invoke(false);
 0516                return;
 517            }
 518
 0519            wearableController.Load(bodyShapeController.id, transform, OnWearableLoadingSuccess, x => OnWearableLoadingF
 0520        }
 521
 522        private void SetWearableBones()
 523        {
 524            // NOTE(Brian): Set bones/rootBone of all wearables to be the same of the baseBody,
 525            //              so all of them are animated together.
 0526            using (var enumerator = wearableControllers.GetEnumerator())
 527            {
 0528                while (enumerator.MoveNext())
 529                {
 0530                    enumerator.Current.Value.SetAnimatorBones(bodyShapeController.bones, bodyShapeController.rootBone);
 531                }
 0532            }
 0533        }
 534
 535        private void UpdateExpression()
 536        {
 0537            SetExpression(model.expressionTriggerId, model.expressionTriggerTimestamp);
 538
 0539            if (lastStickerTimestamp != model.stickerTriggerTimestamp && model.stickerTriggerId != null)
 540            {
 0541                lastStickerTimestamp = model.stickerTriggerTimestamp;
 542
 0543                if ( stickersController != null )
 0544                    stickersController.PlayEmote(model.stickerTriggerId);
 545            }
 0546        }
 547
 548        public void SetExpression(string id, long timestamp)
 549        {
 2550            model.expressionTriggerId = id;
 2551            model.expressionTriggerTimestamp = timestamp;
 2552            animator.SetExpressionValues(id, timestamp);
 2553        }
 554
 555        private void AddWearableController(WearableItem wearable)
 556        {
 0557            if (wearable == null)
 0558                return;
 0559            switch (wearable.data.category)
 560            {
 561                case Categories.EYES:
 0562                    eyesController = new FacialFeatureController(wearable, eyeMaterial);
 0563                    break;
 564                case Categories.EYEBROWS:
 0565                    eyebrowsController = new FacialFeatureController(wearable, eyebrowMaterial);
 0566                    break;
 567                case Categories.MOUTH:
 0568                    mouthController = new FacialFeatureController(wearable, mouthMaterial);
 0569                    break;
 570                case Categories.BODY_SHAPE:
 571                    break;
 572
 573                default:
 0574                    var wearableController = new WearableController(wearable);
 0575                    wearableControllers.Add(wearable, wearableController);
 576                    break;
 577            }
 0578        }
 579
 580        private void UpdateWearableController(WearableItem wearable)
 581        {
 0582            var wearableController = wearableControllers[wearable];
 0583            switch (wearableController.category)
 584            {
 585                case Categories.EYES:
 586                case Categories.EYEBROWS:
 587                case Categories.MOUTH:
 588                case Categories.BODY_SHAPE:
 589                    break;
 590                default:
 591                    //If wearable is downloading will call OnWearableLoadingSuccess(and therefore SetupDefaultMaterial) 
 0592                    if (wearableController.isReady)
 0593                        wearableController.SetupHairAndSkinColors(model.skinColor, model.hairColor);
 594                    break;
 595            }
 0596        }
 597
 598        //TODO: Remove/replace once the class is easily mockable.
 599        protected void CopyFrom(AvatarRenderer original)
 600        {
 0601            this.wearableControllers = original.wearableControllers;
 0602            this.mouthController = original.mouthController;
 0603            this.bodyShapeController = original.bodyShapeController;
 0604            this.eyebrowsController = original.eyebrowsController;
 0605            this.eyesController = original.eyesController;
 0606        }
 607
 608        public void SetGOVisibility(bool newVisibility)
 609        {
 610            //NOTE(Brian): Avatar being loaded needs the renderer.enabled as false until the loading finishes.
 611            //             So we can' manipulate the values because it'd show an incomplete avatar. Its easier to just d
 59612            if (gameObject.activeSelf != newVisibility)
 16613                gameObject.SetActive(newVisibility);
 59614        }
 615
 616        public void SetRendererEnabled(bool newVisibility)
 617        {
 0618            if (mainMeshRenderer == null)
 0619                return;
 620
 0621            mainMeshRenderer.enabled = newVisibility;
 0622        }
 623
 8624        public void SetImpostorVisibility(bool impostorVisibility) { lodRenderer.gameObject.SetActive(impostorVisibility
 625
 0626        public void SetImpostorForward(Vector3 newForward) { lodRenderer.transform.forward = newForward; }
 627
 0628        public void SetImpostorColor(Color newColor) { AvatarRendererHelpers.SetImpostorTintColor(lodRenderer.material, 
 629
 630        public void SetAvatarFade(float avatarFade)
 631        {
 0632            if (bodyShapeController == null || !bodyShapeController.isReady)
 0633                return;
 634
 0635            Material[] mats = mainMeshRenderer.sharedMaterials;
 0636            for (int j = 0; j < mats.Length; j++)
 637            {
 0638                mats[j].SetFloat(ShaderUtils.DitherFade, avatarFade);
 639            }
 0640        }
 641
 642        public void SetImpostorFade(float impostorFade)
 643        {
 644            //TODO implement dither in Unlit shader
 0645            Color current = lodRenderer.material.GetColor(BASE_COLOR_PROPERTY);
 0646            current.a = impostorFade;
 0647            lodRenderer.material.SetColor(BASE_COLOR_PROPERTY, current);
 648
 0649            OnImpostorAlphaValueUpdate?.Invoke(impostorFade);
 0650        }
 651
 652        private void HideAll()
 653        {
 654            // TODO: Cache this somewhere (maybe when the LoadAvatar finishes) instead of fetching this on every call
 0655            Renderer[] renderers = gameObject.GetComponentsInChildren<Renderer>();
 656
 0657            for (int i = 0; i < renderers.Length; i++)
 658            {
 0659                renderers[i].enabled = false;
 660            }
 0661        }
 662
 663        public void SetFacialFeaturesVisible(bool visible)
 664        {
 0665            if (bodyShapeController == null || !bodyShapeController.isReady)
 0666                return;
 667
 0668            if (isLoading)
 0669                return;
 670
 0671            bodyShapeController.SetFacialFeaturesVisible(visible, true);
 0672        }
 673
 674        public void SetSSAOEnabled(bool ssaoEnabled)
 675        {
 0676            if ( isLoading )
 0677                return;
 678
 0679            Material[] mats = mainMeshRenderer.sharedMaterials;
 680
 0681            for (int j = 0; j < mats.Length; j++)
 682            {
 0683                if (ssaoEnabled)
 0684                    mats[j].DisableKeyword("_SSAO_OFF");
 685                else
 0686                    mats[j].EnableKeyword("_SSAO_OFF");
 687            }
 0688        }
 689
 690        private bool MergeAvatar(IEnumerable<SkinnedMeshRenderer> allRenderers)
 691        {
 0692            var renderersToCombine = allRenderers.Where((r) => !r.transform.parent.gameObject.name.Contains("Mask")).ToL
 0693            bool success = avatarMeshCombiner.Combine(bodyShapeController.upperBodyRenderer, renderersToCombine.ToArray(
 694
 0695            if ( success )
 696            {
 0697                avatarMeshCombiner.container.transform.SetParent( transform, true );
 0698                avatarMeshCombiner.container.transform.localPosition = Vector3.zero;
 699            }
 700
 0701            return success;
 702        }
 703
 384704        void CleanMergedAvatar() { avatarMeshCombiner.Dispose(); }
 705
 706        private void LateUpdate()
 707        {
 18368708            if (gpuSkinning != null && mainMeshRenderer.enabled)
 0709                gpuSkinning.Update();
 18368710        }
 711
 712        protected virtual void OnDestroy()
 713        {
 189714            isDestroyed = true;
 189715            CleanupAvatar();
 189716        }
 717    }
 718}