// --------------------------------------------------------------------------------------------------------------------
//
// Copyright (c) VRMADA, All rights reserved.
//
// --------------------------------------------------------------------------------------------------------------------
using System.Collections;
using UltimateXR.Avatar;
using UltimateXR.Core;
using UltimateXR.Core.Components;
using UltimateXR.Core.Settings;
using UltimateXR.Extensions.Unity.Render;
using UltimateXR.Locomotion;
using UnityEngine;
namespace UltimateXR.Rendering.LOD
{
///
/// Fixes LOD levels in VR, which by default do not work correctly. See
/// https://forum.unity.com/threads/lodgroup-in-vr.455394/.
/// In addition, when using non-smooth locomotion, it can switch LOD levels only when the avatar moved.
/// This avoids LOD switching caused by head movement, where the camera is. When using a smooth locomotion
/// system it will use regular LOD switching.
/// Using LOD switching on teleports only can be disabled using the "Only Fix LOD Bias" parameter.
///
[RequireComponent(typeof(LODGroup))]
public class UxrLODGroup : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private bool _onlyFixLodBias;
#endregion
#region Public Types & Data
///
/// Gets the Unity LODGroup component.
///
public LODGroup UnityLODGroup => GetCachedComponent();
#endregion
#region Unity
///
/// Subscribes to the global event called whenever any component is enabled.
///
protected override void Awake()
{
base.Awake();
if (!_onlyFixLodBias)
{
UxrAvatar.LocalAvatarChanged += UxrAvatar_LocalAvatarChanged;
}
}
///
/// Unsubscribes from the global event called whenever any component is enabled.
///
protected override void OnDestroy()
{
base.OnDestroy();
if (!_onlyFixLodBias)
{
UxrAvatar.LocalAvatarChanged -= UxrAvatar_LocalAvatarChanged;
}
}
///
/// Subscribes to the event called whenever an avatar was moved.
/// Also starts the LOD Bias fix coroutine and sets up the correct LOD level if there is a local avatar.
///
protected override void OnEnable()
{
base.OnEnable();
if (!_onlyFixLodBias)
{
UxrAvatar.GlobalAvatarMoved += UxrAvatar_GlobalAvatarMoved;
UpdateMode(UxrAvatar.LocalAvatar);
}
StartCoroutine(FixLodBiasCoroutine());
}
///
/// Unsubscribes from the event called whenever an avatar was moved.
///
protected override void OnDisable()
{
base.OnDisable();
if (!_onlyFixLodBias)
{
UxrAvatar.GlobalAvatarMoved -= UxrAvatar_GlobalAvatarMoved;
UnityLODGroup.ForceLOD(-1);
}
}
#endregion
#region Coroutines
///
/// Fixes the LOD bias so that the LOD switching in VR behaves like in the editor.
/// From: https://forum.unity.com/threads/lodgroup-in-vr.455394/
///
/// Coroutine enumerator
private IEnumerator FixLodBiasCoroutine()
{
// Wait until the camera gets the correct VR FOV
yield return null;
yield return null;
yield return null;
while (UxrAvatar.LocalAvatarCamera == null)
{
if (UxrAvatar.LocalAvatar != null && UxrAvatar.LocalStandardAvatarController == null)
{
// Replay or other.
yield break;
}
yield return null;
}
// Fix?
if (!s_lodGroupFixed)
{
float oldLodBias = QualitySettings.lodBias;
float editorCameraRadians = Mathf.PI / 3.0f;
QualitySettings.lodBias *= Mathf.Tan(UxrAvatar.LocalAvatarCamera.fieldOfView * Mathf.Deg2Rad / 2) / Mathf.Tan(editorCameraRadians / 2);
s_lodGroupFixed = true;
if (UxrGlobalSettings.Instance.LogLevelRendering >= UxrLogLevel.Relevant)
{
Debug.Log($"{UxrConstants.RenderingModule}: Fixing LOD Bias for VR cameras. Old value = {oldLodBias}, new value = {QualitySettings.lodBias}.");
}
}
}
#endregion
#region Event Handling Methods
///
/// Called whenever the local avatar changed. We use it to store whether the current local avatar
/// uses smooth locomotion.
///
/// Event sender
/// Event parameters
private void UxrAvatar_LocalAvatarChanged(object sender, UxrAvatarEventArgs e)
{
UpdateMode(e.Avatar);
}
///
/// Called whenever an avatar moved. In non-smooth locomotion mode it will be used to switch LOD levels manually.
///
/// Event sender
/// Event parameters
private void UxrAvatar_GlobalAvatarMoved(object sender, UxrAvatarMoveEventArgs e)
{
UxrAvatar avatar = sender as UxrAvatar;
if (avatar == UxrAvatar.LocalAvatar && !_isSmoothLocomotionEnabled)
{
EnableLevelRenderers(avatar.CameraComponent);
}
}
#endregion
#region Private Methods
///
/// Manually enables the LOD levels for a given camera.
///
///
private void EnableLevelRenderers(Camera cam)
{
UnityLODGroup.ForceLOD(UnityLODGroup.GetVisibleLevel(cam));
}
///
/// Updates the continuous/discrete operation mode of the component.
///
/// Avatar to update the current mode for
private void UpdateMode(UxrAvatar avatar)
{
_isSmoothLocomotionEnabled = avatar != null && avatar.AvatarController != null && avatar.AvatarController.UsesSmoothLocomotion;
bool autoLod = _isSmoothLocomotionEnabled || avatar == null || avatar.CameraComponent == null;
if (!autoLod)
{
if (avatar != null && avatar.CameraComponent != null)
{
EnableLevelRenderers(avatar.CameraComponent);
}
}
else
{
UnityLODGroup.EnableAllLevelRenderers();
UnityLODGroup.ForceLOD(-1);
}
}
#endregion
#region Private Types & Data
private static bool s_lodGroupFixed;
private bool _isSmoothLocomotionEnabled = true;
#endregion
}
}