< 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:253
Coverable lines:338
Total lines:712
Line coverage:25.1% (85 of 338)
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%
SetAvatarFade(...)0%20400%
SetImpostorFade(...)0%2100%
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<bool> OnFailEvent;
 42
 43        internal BodyShapeController bodyShapeController;
 106744        internal Dictionary<WearableItem, WearableController> wearableControllers = new Dictionary<WearableItem, Wearabl
 45        internal FacialFeatureController eyesController;
 46        internal FacialFeatureController eyebrowsController;
 47        internal FacialFeatureController mouthController;
 48        internal AvatarAnimatorLegacy animator;
 49        internal StickersController stickersController;
 50
 106751        private long lastStickerTimestamp = -1;
 52
 53        public bool isLoading;
 054        public bool isReady => bodyShapeController != null && bodyShapeController.isReady && wearableControllers != null
 55
 56        private Coroutine loadCoroutine;
 106757        private List<string> wearablesInUse = new List<string>();
 58        private AssetPromise_Texture bodySnapshotTexturePromise;
 59        private bool isDestroyed = false;
 60
 61        private void Awake()
 62        {
 19063            animator = GetComponent<AvatarAnimatorLegacy>();
 19064            stickersController = GetComponent<StickersController>();
 19065            avatarMeshCombiner = new AvatarMeshCombinerHelper();
 66
 19067            if (lodRenderer != null)
 168                SetImpostorVisibility(false);
 19069        }
 70
 71        public void ApplyModel(AvatarModel model, Action onSuccess, Action onFail)
 72        {
 12573            if ( this.model != null )
 74            {
 7675                if (model != null && this.model.Equals(model))
 76                {
 4677                    onSuccess?.Invoke();
 3378                    return;
 79                }
 80
 3081                bool wearablesChanged = !this.model.HaveSameWearablesAndColors(model);
 3082                bool expressionsChanged = !this.model.HaveSameExpressions(model);
 83
 3084                if (!wearablesChanged && expressionsChanged)
 85                {
 086                    this.model.expressionTriggerId = model.expressionTriggerId;
 087                    this.model.expressionTriggerTimestamp = model.expressionTriggerTimestamp;
 088                    this.model.stickerTriggerId = model.stickerTriggerId;
 089                    this.model.stickerTriggerTimestamp = model.stickerTriggerTimestamp;
 090                    UpdateExpression();
 091                    onSuccess?.Invoke();
 092                    return;
 93                }
 94            }
 95
 7996            this.model = new AvatarModel();
 7997            this.model.CopyFrom(model);
 7998            if (bodySnapshotTexturePromise != null)
 099                AssetPromiseKeeper_Texture.i.Forget(bodySnapshotTexturePromise);
 100
 101            // TODO(Brian): Find a better approach than overloading callbacks like this. This code is not readable.
 102            void onSuccessWrapper()
 103            {
 0104                onSuccess?.Invoke();
 0105                this.OnSuccessEvent -= onSuccessWrapper;
 0106            }
 107
 79108            this.OnSuccessEvent += onSuccessWrapper;
 109
 110            void onFailWrapper(bool isFatalError)
 111            {
 6112                onFail?.Invoke();
 6113                this.OnFailEvent -= onFailWrapper;
 6114            }
 115
 79116            this.OnFailEvent += onFailWrapper;
 117
 79118            isLoading = false;
 119
 79120            if (model == null)
 121            {
 0122                CleanupAvatar();
 0123                this.OnSuccessEvent?.Invoke();
 0124                return;
 125            }
 126
 79127            StopLoadingCoroutines();
 79128            isLoading = true;
 79129            loadCoroutine = CoroutineStarter.Start(LoadAvatar());
 79130        }
 131
 132        public void InitializeImpostor()
 133        {
 0134            UserProfile userProfile = null;
 0135            if (!string.IsNullOrEmpty(model?.id))
 0136                userProfile = UserProfileController.GetProfileByUserId(model.id);
 137
 0138            if (userProfile != null)
 139            {
 0140                bodySnapshotTexturePromise = new AssetPromise_Texture(userProfile.bodySnapshotURL);
 0141                bodySnapshotTexturePromise.OnSuccessEvent += asset => AvatarRendererHelpers.SetImpostorTexture(asset.tex
 0142                bodySnapshotTexturePromise.OnFailEvent += asset => AvatarRendererHelpers.RandomizeAndApplyGenericImposto
 0143                AssetPromiseKeeper_Texture.i.Keep(bodySnapshotTexturePromise);
 0144            }
 145            else
 146            {
 0147                AvatarRendererHelpers.RandomizeAndApplyGenericImpostor(lodMeshFilter.mesh);
 148            }
 0149        }
 150
 151        void StopLoadingCoroutines()
 152        {
 271153            if (loadCoroutine != null)
 78154                CoroutineStarter.Stop(loadCoroutine);
 155
 271156            loadCoroutine = null;
 271157        }
 158
 159        public void CleanupAvatar()
 160        {
 192161            StopLoadingCoroutines();
 192162            if (!isDestroyed)
 163            {
 3164                SetGOVisibility(true);
 3165                if (lodRenderer != null)
 3166                    SetImpostorVisibility(false);
 167            }
 168
 192169            avatarMeshCombiner.Dispose();
 192170            gpuSkinning = null;
 192171            eyebrowsController?.CleanUp();
 192172            eyebrowsController = null;
 173
 192174            eyesController?.CleanUp();
 192175            eyesController = null;
 176
 192177            mouthController?.CleanUp();
 192178            mouthController = null;
 179
 192180            bodyShapeController?.CleanUp();
 192181            bodyShapeController = null;
 182
 192183            using (var iterator = wearableControllers.GetEnumerator())
 184            {
 192185                while (iterator.MoveNext())
 186                {
 0187                    iterator.Current.Value.CleanUp();
 188                }
 192189            }
 190
 192191            wearableControllers.Clear();
 192192            model = null;
 192193            isLoading = false;
 192194            OnFailEvent = null;
 192195            OnSuccessEvent = null;
 196
 192197            CleanMergedAvatar();
 198
 192199            if (bodySnapshotTexturePromise != null)
 0200                AssetPromiseKeeper_Texture.i.Forget(bodySnapshotTexturePromise);
 201
 192202            CatalogController.RemoveWearablesInUse(wearablesInUse);
 192203            wearablesInUse.Clear();
 192204            OnVisualCue?.Invoke(IAvatarRenderer.VisualCue.CleanedUp);
 148205        }
 206
 207        void CleanUpUnusedItems()
 208        {
 0209            if (model.wearables == null)
 0210                return;
 211
 0212            var ids = wearableControllers.Keys.ToArray();
 213
 0214            for (var i = 0; i < ids.Length; i++)
 215            {
 0216                var currentId = ids[i];
 0217                var wearable = wearableControllers[currentId];
 218
 0219                if (!model.wearables.Contains(wearable.id) || !wearable.IsLoadedForBodyShape(model.bodyShape))
 220                {
 0221                    wearable.CleanUp();
 0222                    wearableControllers.Remove(currentId);
 223                }
 224            }
 0225        }
 226
 227        // TODO(Brian): Pure functions should be extracted from this big LoadAvatar() method and unit-test separately.
 228        //              The current approach has tech debt that's getting very expensive and is costing many hours of de
 229        //              Avatar Loading should be a self contained operation that doesn't depend on pool management and A
 230        //              lifecycle like it does now.
 231        private IEnumerator LoadAvatar()
 232        {
 233            // TODO(Brian): This is an ugly hack, all the loading should be performed
 234            //              without being afraid of the gameObject active state.
 158235            yield return new WaitUntil(() => gameObject.activeSelf);
 236
 6237            bool loadSoftFailed = false;
 238
 6239            WearableItem resolvedBody = null;
 240
 241            // TODO(Brian): Evaluate using UniTask<T> here instead of Helpers.Promise.
 6242            Helpers.Promise<WearableItem> avatarBodyPromise = null;
 6243            if (!string.IsNullOrEmpty(model.bodyShape))
 244            {
 0245                avatarBodyPromise = CatalogController.RequestWearable(model.bodyShape);
 0246            }
 247            else
 248            {
 6249                OnFailEvent?.Invoke(true);
 6250                yield break;
 251            }
 252
 0253            List<WearableItem> resolvedWearables = new List<WearableItem>();
 254
 255            // TODO(Brian): Evaluate using UniTask<T> here instead of Helpers.Promise.
 0256            List<Helpers.Promise<WearableItem>> avatarWearablePromises = new List<Helpers.Promise<WearableItem>>();
 0257            if (model.wearables != null)
 258            {
 0259                for (int i = 0; i < model.wearables.Count; i++)
 260                {
 0261                    avatarWearablePromises.Add(CatalogController.RequestWearable(model.wearables[i]));
 262                }
 263            }
 264
 265            // In this point, all the requests related to the avatar's wearables have been collected and sent to the Cat
 266            // From here we wait for the response of the requested wearables and process them.
 0267            if (avatarBodyPromise != null)
 268            {
 0269                yield return avatarBodyPromise;
 270
 0271                if (!string.IsNullOrEmpty(avatarBodyPromise.error))
 272                {
 0273                    Debug.LogError(avatarBodyPromise.error);
 0274                    loadSoftFailed = true;
 0275                }
 276                else
 277                {
 0278                    resolvedBody = avatarBodyPromise.value;
 0279                    wearablesInUse.Add(avatarBodyPromise.value.id);
 280                }
 281            }
 282
 0283            if (resolvedBody == null)
 284            {
 0285                isLoading = false;
 0286                OnFailEvent?.Invoke(true);
 0287                yield break;
 288            }
 289
 290            // TODO(Brian): Evaluate using UniTask<T> here instead of Helpers.Promise.
 0291            List<Helpers.Promise<WearableItem>> replacementPromises = new List<Helpers.Promise<WearableItem>>();
 292
 0293            foreach (var avatarWearablePromise in avatarWearablePromises)
 294            {
 0295                yield return avatarWearablePromise;
 296
 0297                if (!string.IsNullOrEmpty(avatarWearablePromise.error))
 298                {
 0299                    Debug.LogError(avatarWearablePromise.error);
 0300                    loadSoftFailed = true;
 0301                }
 302                else
 303                {
 0304                    WearableItem wearableItem = avatarWearablePromise.value;
 0305                    wearablesInUse.Add(wearableItem.id);
 306
 0307                    if (wearableItem.GetRepresentation(model.bodyShape) != null)
 308                    {
 0309                        resolvedWearables.Add(wearableItem);
 0310                    }
 311                    else
 312                    {
 0313                        model.wearables.Remove(wearableItem.id);
 0314                        string defaultReplacement = DefaultWearables.GetDefaultWearable(model.bodyShape, wearableItem.da
 0315                        if (!string.IsNullOrEmpty(defaultReplacement))
 316                        {
 0317                            model.wearables.Add(defaultReplacement);
 0318                            replacementPromises.Add(CatalogController.RequestWearable(defaultReplacement));
 319                        }
 320                    }
 321                }
 0322            }
 323
 0324            foreach (var wearablePromise in replacementPromises)
 325            {
 0326                yield return wearablePromise;
 327
 0328                if (!string.IsNullOrEmpty(wearablePromise.error))
 329                {
 0330                    Debug.LogError(wearablePromise.error);
 0331                    loadSoftFailed = true;
 0332                }
 333                else
 334                {
 0335                    WearableItem wearableItem = wearablePromise.value;
 0336                    wearablesInUse.Add(wearableItem.id);
 0337                    resolvedWearables.Add(wearableItem);
 338                }
 0339            }
 340
 0341            bool bodyIsDirty = false;
 0342            if (bodyShapeController != null && bodyShapeController.id != model?.bodyShape)
 343            {
 0344                bodyShapeController.CleanUp();
 0345                bodyShapeController = null;
 0346                bodyIsDirty = true;
 347            }
 348
 0349            if (bodyShapeController == null)
 350            {
 0351                HideAll();
 0352                bodyShapeController = new BodyShapeController(resolvedBody);
 0353                eyesController = FacialFeatureController.CreateDefaultFacialFeature(bodyShapeController.bodyShapeId, Cat
 0354                eyebrowsController = FacialFeatureController.CreateDefaultFacialFeature(bodyShapeController.bodyShapeId,
 0355                mouthController = FacialFeatureController.CreateDefaultFacialFeature(bodyShapeController.bodyShapeId, Ca
 0356            }
 357            else
 358            {
 359                //If bodyShape is downloading will call OnWearableLoadingSuccess (and therefore SetupDefaultMaterial) on
 0360                if (bodyShapeController.isReady)
 0361                    bodyShapeController.SetupHairAndSkinColors(model.skinColor, model.hairColor);
 362            }
 363
 364            //TODO(Brian): This logic should be performed in a testeable pure function instead of this inline approach.
 365            //             Moreover, this function should work against data, not wearableController instances.
 0366            bool wearablesIsDirty = false;
 0367            HashSet<string> unusedCategories = new HashSet<string>(Categories.ALL);
 0368            int wearableCount = resolvedWearables.Count;
 0369            for (int index = 0; index < wearableCount; index++)
 370            {
 0371                WearableItem wearable = resolvedWearables[index];
 0372                if (wearable == null)
 373                    continue;
 374
 0375                unusedCategories.Remove(wearable.data.category);
 0376                if (wearableControllers.ContainsKey(wearable))
 377                {
 0378                    if (wearableControllers[wearable].IsLoadedForBodyShape(bodyShapeController.bodyShapeId))
 0379                        UpdateWearableController(wearable);
 380                    else
 0381                        wearableControllers[wearable].CleanUp();
 0382                }
 383                else
 384                {
 0385                    AddWearableController(wearable);
 0386                    if (wearable.data.category != Categories.EYES && wearable.data.category != Categories.MOUTH && weara
 0387                        wearablesIsDirty = true;
 388                }
 389            }
 390
 0391            foreach (var category in unusedCategories)
 392            {
 393                switch (category)
 394                {
 395                    case Categories.EYES:
 0396                        eyesController = FacialFeatureController.CreateDefaultFacialFeature(bodyShapeController.bodyShap
 0397                        break;
 398                    case Categories.MOUTH:
 0399                        mouthController = FacialFeatureController.CreateDefaultFacialFeature(bodyShapeController.bodySha
 0400                        break;
 401                    case Categories.EYEBROWS:
 0402                        eyebrowsController = FacialFeatureController.CreateDefaultFacialFeature(bodyShapeController.body
 403                        break;
 404                }
 405            }
 406
 0407            HashSet<string> hiddenList = WearableItem.CompoundHidesList(bodyShapeController.bodyShapeId, resolvedWearabl
 0408            if (!bodyShapeController.isReady)
 409            {
 0410                bodyShapeController.Load(bodyShapeController.bodyShapeId, transform, OnWearableLoadingSuccess, OnBodySha
 411            }
 412
 0413            foreach (WearableController wearable in wearableControllers.Values)
 414            {
 0415                if (bodyIsDirty)
 0416                    wearable.boneRetargetingDirty = true;
 417
 0418                wearable.Load(bodyShapeController.bodyShapeId, transform, OnWearableLoadingSuccess, x => OnWearableLoadi
 0419                yield return null;
 420            }
 421
 422            // TODO(Brian): Evaluate using UniTask<T> instead of this way.
 0423            yield return new WaitUntil(() => bodyShapeController.isReady && wearableControllers.Values.All(x => x.isRead
 424
 0425            eyesController?.Load(bodyShapeController, model.eyeColor);
 0426            eyebrowsController?.Load(bodyShapeController, model.hairColor);
 0427            mouthController?.Load(bodyShapeController, model.skinColor);
 428
 429            //TODO(Brian): Evaluate using UniTask<T> instead of this way.
 0430            yield return new WaitUntil(() =>
 0431                (eyebrowsController == null || (eyebrowsController != null && eyebrowsController.isReady)) &&
 432                (eyesController == null || (eyesController != null && eyesController.isReady)) &&
 433                (mouthController == null || (mouthController != null && mouthController.isReady)));
 434
 0435            if (bodyIsDirty || wearablesIsDirty)
 436            {
 0437                OnVisualCue?.Invoke(IAvatarRenderer.VisualCue.Loaded);
 438            }
 439
 440            // TODO(Brian): unusedCategories and hiddenList management is a double negative PITA.
 441            //              The load process should define how the avatar should look like before
 442            //              loading it and put this information in a positive list
 443            //              (i.e. not negative, because leads to double negative checks).
 0444            bodyShapeController.SetActiveParts(unusedCategories.Contains(Categories.LOWER_BODY), unusedCategories.Contai
 0445            bodyShapeController.UpdateVisibility(hiddenList);
 0446            foreach (WearableController wearableController in wearableControllers.Values)
 447            {
 0448                wearableController.UpdateVisibility(hiddenList);
 449            }
 450
 0451            CleanUpUnusedItems();
 452
 0453            isLoading = false;
 454
 0455            SetWearableBones();
 456
 457            // TODO(Brian): Expression and sticker update shouldn't be part of avatar loading code!!!! Refactor me pleas
 0458            UpdateExpression();
 459
 0460            var allRenderers = wearableControllers.SelectMany( x => x.Value.GetRenderers() ).ToList();
 0461            allRenderers.AddRange( bodyShapeController.GetRenderers() );
 0462            bool mergeSuccess = MergeAvatar(allRenderers);
 463
 0464            if (mergeSuccess)
 465            {
 0466                gpuSkinning = new SimpleGPUSkinning(avatarMeshCombiner.renderer);
 467
 468                // Sample the animation manually and force an update in the GPUSkinning to avoid giant avatars
 0469                animator.SetIdleFrame();
 0470                animator.animation.Sample();
 0471                gpuSkinning.Update(true);
 0472            }
 473            else
 0474                loadSoftFailed = true;
 475
 476            // TODO(Brian): The loadSoftFailed flow is too convoluted--you never know which objects are nulled or empty
 477            //              before reaching this branching statement. The failure should be caught with a throw or other
 478            //              proper language feature.
 0479            if (loadSoftFailed)
 480            {
 0481                OnFailEvent?.Invoke(false);
 0482            }
 483            else
 484            {
 0485                OnSuccessEvent?.Invoke();
 486            }
 0487        }
 488
 489        void OnWearableLoadingSuccess(WearableController wearableController)
 490        {
 0491            if (wearableController == null || model == null)
 492            {
 0493                Debug.LogWarning($"WearableSuccess was called wrongly: IsWearableControllerNull=>{wearableController == 
 0494                OnWearableLoadingFail(wearableController, 0);
 0495                return;
 496            }
 497
 0498            wearableController.SetupHairAndSkinColors(model.skinColor, model.hairColor);
 0499        }
 500
 501        void OnBodyShapeLoadingFail(WearableController wearableController)
 502        {
 0503            Debug.LogError($"Avatar: {model?.name}  -  Failed loading bodyshape: {wearableController?.id}");
 0504            CleanupAvatar();
 0505            OnFailEvent?.Invoke(true);
 0506        }
 507
 508        void OnWearableLoadingFail(WearableController wearableController, int retriesCount = MAX_RETRIES)
 509        {
 0510            if (retriesCount <= 0)
 511            {
 0512                Debug.LogError($"Avatar: {model?.name}  -  Failed loading wearable: {wearableController?.id}");
 0513                CleanupAvatar();
 0514                OnFailEvent?.Invoke(false);
 0515                return;
 516            }
 517
 0518            wearableController.Load(bodyShapeController.id, transform, OnWearableLoadingSuccess, x => OnWearableLoadingF
 0519        }
 520
 521        private void SetWearableBones()
 522        {
 523            // NOTE(Brian): Set bones/rootBone of all wearables to be the same of the baseBody,
 524            //              so all of them are animated together.
 0525            using (var enumerator = wearableControllers.GetEnumerator())
 526            {
 0527                while (enumerator.MoveNext())
 528                {
 0529                    enumerator.Current.Value.SetAnimatorBones(bodyShapeController.bones, bodyShapeController.rootBone);
 530                }
 0531            }
 0532        }
 533
 534        private void UpdateExpression()
 535        {
 0536            SetExpression(model.expressionTriggerId, model.expressionTriggerTimestamp);
 537
 0538            if (lastStickerTimestamp != model.stickerTriggerTimestamp && model.stickerTriggerId != null)
 539            {
 0540                lastStickerTimestamp = model.stickerTriggerTimestamp;
 541
 0542                if ( stickersController != null )
 0543                    stickersController.PlayEmote(model.stickerTriggerId);
 544            }
 0545        }
 546
 547        public void SetExpression(string id, long timestamp)
 548        {
 2549            model.expressionTriggerId = id;
 2550            model.expressionTriggerTimestamp = timestamp;
 2551            animator.SetExpressionValues(id, timestamp);
 2552        }
 553
 554        private void AddWearableController(WearableItem wearable)
 555        {
 0556            if (wearable == null)
 0557                return;
 0558            switch (wearable.data.category)
 559            {
 560                case Categories.EYES:
 0561                    eyesController = new FacialFeatureController(wearable, eyeMaterial);
 0562                    break;
 563                case Categories.EYEBROWS:
 0564                    eyebrowsController = new FacialFeatureController(wearable, eyebrowMaterial);
 0565                    break;
 566                case Categories.MOUTH:
 0567                    mouthController = new FacialFeatureController(wearable, mouthMaterial);
 0568                    break;
 569                case Categories.BODY_SHAPE:
 570                    break;
 571
 572                default:
 0573                    var wearableController = new WearableController(wearable);
 0574                    wearableControllers.Add(wearable, wearableController);
 575                    break;
 576            }
 0577        }
 578
 579        private void UpdateWearableController(WearableItem wearable)
 580        {
 0581            var wearableController = wearableControllers[wearable];
 0582            switch (wearableController.category)
 583            {
 584                case Categories.EYES:
 585                case Categories.EYEBROWS:
 586                case Categories.MOUTH:
 587                case Categories.BODY_SHAPE:
 588                    break;
 589                default:
 590                    //If wearable is downloading will call OnWearableLoadingSuccess(and therefore SetupDefaultMaterial) 
 0591                    if (wearableController.isReady)
 0592                        wearableController.SetupHairAndSkinColors(model.skinColor, model.hairColor);
 593                    break;
 594            }
 0595        }
 596
 597        //TODO: Remove/replace once the class is easily mockable.
 598        protected void CopyFrom(AvatarRenderer original)
 599        {
 0600            this.wearableControllers = original.wearableControllers;
 0601            this.mouthController = original.mouthController;
 0602            this.bodyShapeController = original.bodyShapeController;
 0603            this.eyebrowsController = original.eyebrowsController;
 0604            this.eyesController = original.eyesController;
 0605        }
 606
 607        public void SetGOVisibility(bool newVisibility)
 608        {
 609            //NOTE(Brian): Avatar being loaded needs the renderer.enabled as false until the loading finishes.
 610            //             So we can' manipulate the values because it'd show an incomplete avatar. Its easier to just d
 59611            if (gameObject.activeSelf != newVisibility)
 16612                gameObject.SetActive(newVisibility);
 59613        }
 614
 615        public void SetRendererEnabled(bool newVisibility)
 616        {
 0617            if (mainMeshRenderer == null)
 0618                return;
 619
 0620            mainMeshRenderer.enabled = newVisibility;
 0621        }
 622
 8623        public void SetImpostorVisibility(bool impostorVisibility) { lodRenderer.gameObject.SetActive(impostorVisibility
 0624        public void SetImpostorForward(Vector3 newForward) { lodRenderer.transform.forward = newForward; }
 625
 626        public void SetAvatarFade(float avatarFade)
 627        {
 0628            if (bodyShapeController == null || !bodyShapeController.isReady)
 0629                return;
 630
 0631            Material[] mats = mainMeshRenderer.sharedMaterials;
 0632            for (int j = 0; j < mats.Length; j++)
 633            {
 0634                mats[j].SetFloat(ShaderUtils.DitherFade, avatarFade);
 635            }
 0636        }
 637
 638        public void SetImpostorFade(float impostorFade)
 639        {
 640            //TODO implement dither in Unlit shader
 0641            Color current = lodRenderer.material.GetColor(BASE_COLOR_PROPERTY);
 0642            current.a = impostorFade;
 0643            lodRenderer.material.SetColor(BASE_COLOR_PROPERTY, current);
 0644        }
 645
 646        private void HideAll()
 647        {
 648            // TODO: Cache this somewhere (maybe when the LoadAvatar finishes) instead of fetching this on every call
 0649            Renderer[] renderers = gameObject.GetComponentsInChildren<Renderer>();
 650
 0651            for (int i = 0; i < renderers.Length; i++)
 652            {
 0653                renderers[i].enabled = false;
 654            }
 0655        }
 656
 657        public void SetFacialFeaturesVisible(bool visible)
 658        {
 0659            if (bodyShapeController == null || !bodyShapeController.isReady)
 0660                return;
 661
 0662            if (isLoading)
 0663                return;
 664
 0665            bodyShapeController.SetFacialFeaturesVisible(visible, true);
 0666        }
 667
 668        public void SetSSAOEnabled(bool ssaoEnabled)
 669        {
 0670            if ( isLoading )
 0671                return;
 672
 0673            Material[] mats = mainMeshRenderer.sharedMaterials;
 674
 0675            for (int j = 0; j < mats.Length; j++)
 676            {
 0677                if (ssaoEnabled)
 0678                    mats[j].DisableKeyword("_SSAO_OFF");
 679                else
 0680                    mats[j].EnableKeyword("_SSAO_OFF");
 681            }
 0682        }
 683
 684        private bool MergeAvatar(IEnumerable<SkinnedMeshRenderer> allRenderers)
 685        {
 0686            var renderersToCombine = allRenderers.Where((r) => !r.transform.parent.gameObject.name.Contains("Mask")).ToL
 0687            bool success = avatarMeshCombiner.Combine(bodyShapeController.upperBodyRenderer, renderersToCombine.ToArray(
 688
 0689            if ( success )
 690            {
 0691                avatarMeshCombiner.container.transform.SetParent( transform, true );
 0692                avatarMeshCombiner.container.transform.localPosition = Vector3.zero;
 693            }
 694
 0695            return success;
 696        }
 697
 384698        void CleanMergedAvatar() { avatarMeshCombiner.Dispose(); }
 699
 700        private void LateUpdate()
 701        {
 18299702            if (gpuSkinning != null && mainMeshRenderer.enabled)
 0703                gpuSkinning.Update();
 18299704        }
 705
 706        protected virtual void OnDestroy()
 707        {
 189708            isDestroyed = true;
 189709            CleanupAvatar();
 189710        }
 711    }
 712}