Files
Kizza00232Jera 17532b524f [unity] avatars
2025-06-11 14:45:45 +02:00

657 lines
22 KiB
C#

/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#nullable enable
#if USING_XR_MANAGEMENT && (USING_XR_SDK_OCULUS || USING_XR_SDK_OPENXR) && !OVRPLUGIN_UNSUPPORTED_PLATFORM
#define USING_XR_SDK
#endif
// Before enabling, see note below in OnSceneLoaded:
// #define USE_TETRAHEDRALIZATION
using System;
using System.Collections.Generic;
using System.Linq;
using Oculus.Avatar2;
using OVR.OpenVR;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
/**
* Main logic for the LightingExample scene
*/
public class LightingExampleManager : MonoBehaviour, IUIControllerInterface
{
[System.Serializable]
public class SdkManagerWithDeprecationStatus
{
private enum DeprecationStatus
{
Recommended,
Deprecated,
ReferenceOnly,
}
private string[] DeprecationStatusString =
{
"Recommended",
"Deprecated",
"ReferenceOnly",
};
[SerializeField]
public OvrAvatarManager? manager;
[SerializeField]
private DeprecationStatus status;
public string GetDeprecationStatus()
{
return DeprecationStatusString[(int)status];
}
}
[Serializable]
private struct LightingConfig
{
public string Name;
// sorry but this doesn't work because the scene is not Serializable:
// public Scene LightingScene;
// Instead we have to manage the scene name:
public string SceneName;
[ColorUsage(false, true)] public Color AmbientColor;
public Material SkyboxMaterial;
[NonSerialized] public Button Button;
public LightingConfig(string name, string sceneName, Color ambientColor, Button button, Material skyboxMaterial)
{
Name = name;
SceneName = sceneName;
AmbientColor = ambientColor;
SkyboxMaterial = skyboxMaterial;
Button = button;
}
}
[SerializeField] private LightingConfig[]? _lightingConfigs;
[SerializeField] private SdkManagerWithDeprecationStatus[]? _sdkManagers;
// these do not support URP.
[SerializeField] private SdkManagerWithDeprecationStatus[]? _sdkManagersSupportingBuiltInPipelineOnly;
[SerializeField] private GameObject? _avatarsShowcasePrefab;
[SerializeField] private List<string> _preloadZipFiles = new List<string>();
[Header("UI")][SerializeField] private Canvas? _canvas;
[SerializeField] private Transform? _lightingNameGroup;
[SerializeField] private Button? _lightingButton;
[SerializeField] private Transform? _shaderNameGroup;
[SerializeField] private Button? _shaderButton;
[SerializeField] private Text? _infoText;
[Header("LOD Override")]
[Tooltip("Enable overriding Avatar LOD levels using controller/keyboard input.")]
[SerializeField] private bool _enableLODOverride = true;
[Tooltip("Enable using keyboard buttons for increasing/decreasing LOD levels. " +
"Default buttons:\nG to increase LOD levels\nF to decrease LOD levels.")]
[SerializeField]
private bool _keyboardDebugLODLevels;
[Tooltip("Display LOD Levels next to each Avatar (see AvatarLODManager::displayLODLabels).")]
[SerializeField]
private bool _displayLODLabels = false;
private static int _currentLighting;
private static int _currentShader;
[Tooltip("Enable shader selection using controller/keyboard input.")]
[SerializeField] private bool _enableShaderSelection;
// private GameObject _instantiatedLightRig;
[SerializeField]
private String? _currentSceneName;
private bool _isPaused;
private const string logScope = "LightingExampleManager";
#if USING_XR_SDK
private readonly UIInputControllerButton _prevLightConfigButton = new UIInputControllerButton
{
button = OVRInput.Button.One,
controller = OVRInput.Controller.LTouch,
description = "Change to previous Light config.",
scope = logScope
};
private readonly UIInputControllerButton _nextLightConfigButton = new UIInputControllerButton
{
button = OVRInput.Button.Two,
controller = OVRInput.Controller.LTouch,
description = "Change to next Light config.",
scope = logScope
};
private readonly UIInputControllerButton _prevShaderButton = new UIInputControllerButton
{
button = OVRInput.Button.One,
controller = OVRInput.Controller.RTouch,
description = "Change to previous Shader config.",
scope = logScope
};
private readonly UIInputControllerButton _nextShaderButton = new UIInputControllerButton
{
button = OVRInput.Button.Two,
controller = OVRInput.Controller.RTouch,
description = "Change to next Shader config.",
scope = logScope
};
#endif
private void Start()
{
#if USING_XR_SDK
// Show in world space in VR
if (_canvas != null)
{
_canvas.renderMode = RenderMode.WorldSpace;
_canvas.transform.position = new Vector3(0, 1, -1);
((RectTransform)_canvas.transform).sizeDelta = new Vector2(900, 400);
// Adjust WorldSpace canvas size, since changing renderMode to WorldSpace
// places an oversized canvas in the world
_canvas.transform.localScale = Vector3.one * 0.005f;
}
else
{
OvrAvatarLog.LogError("No Canvas found", logScope);
}
#endif
if (!OvrAvatarShaderDeprecationManager.IsURPEnabled() &&
_sdkManagers != null &&
_sdkManagersSupportingBuiltInPipelineOnly != null)
{
int originalLength = _sdkManagers?.Length ?? 0;
Array.Resize(ref _sdkManagers, originalLength + _sdkManagersSupportingBuiltInPipelineOnly.Length);
Array.Copy(_sdkManagersSupportingBuiltInPipelineOnly, 0, _sdkManagers, originalLength, _sdkManagersSupportingBuiltInPipelineOnly.Length);
}
if (_sdkManagers != null)
{
foreach (var sdkManager in _sdkManagers)
{
if (sdkManager.manager != null)
{
sdkManager.manager.PreloadZipFiles =
sdkManager.manager.PreloadZipFiles.Union(_preloadZipFiles).ToList();
}
}
}
SetupUI();
if (_sdkManagers != null)
{
if (_sdkManagers[_currentShader].manager != null)
{
Instantiate(_sdkManagers[_currentShader].manager);
}
else
{
OvrAvatarLog.LogError($"No SDK Manager found for shader: {_sdkManagers[_currentShader]}", logScope);
}
}
else
{
OvrAvatarLog.LogError("No SDK Managers found", logScope);
}
if (_lightingConfigs != null)
{
var sceneNames = _lightingConfigs.Select(config => config.SceneName).ToArray();
var lightingScenesAvailable = AreAllLightingScenesAvailable(sceneNames);
if (lightingScenesAvailable)
{
SetLightingConfig(_currentLighting);
}
else
{
var errorMsg =
"Unable to load environments. Please use MetaAvatarsSDK => Lighting Example => Add Environments, or manually include the following scenes in your build under File => Build Settings: " +
string.Join(", ", sceneNames) + ".";
OvrAvatarLog.LogError(errorMsg);
if (_infoText != null)
{
_infoText.text = errorMsg;
_infoText.color = Color.red;
}
else
{
OvrAvatarLog.LogError("No Info Text found. Error message is: " + errorMsg, logScope);
}
}
}
else
{
OvrAvatarLog.LogError("No Lighting Configs found", logScope);
}
if (_avatarsShowcasePrefab == null)
{
OvrAvatarLog.LogError("No Avatars showcase prefab found", logScope);
return;
}
GameObject showcase = Instantiate(_avatarsShowcasePrefab);
showcase.transform.SetPositionAndRotation(transform.localPosition, transform.localRotation);
if (_enableLODOverride)
{
AddLODOverrideToChildren(showcase);
}
#if USE_TETRAHEDRALIZATION
LightProbes.needsRetetrahedralization += conductTetrahedralization;
#endif
if (UIManager.Instance == null || !UIManager.Instance.Initialized)
{
OvrAvatarLog.LogWarning("LightingExampleManager::Start : Could not retrieve UIManager instance.");
return;
}
UIManager.Instance.AddOnLoadSceneEvent(HandleOnSceneWillLoad);
UIManager.Instance.AddOnPauseEvent(HandleOnSceneWillPause);
UIManager.Instance.AddOnResumeEvent(HandleOnSceneWillResume);
}
private void HandleOnSceneWillLoad()
{
ResetLightsAndShaders();
}
private void HandleOnSceneWillPause()
{
_isPaused = true;
}
private void HandleOnSceneWillResume()
{
_isPaused = false;
}
private void OnDestroy()
{
if (UIManager.Instance != null)
{
if (UIManager.Instance.Initialized)
{
UIManager.Instance.RemoveOnResumeEvent(HandleOnSceneWillResume);
UIManager.Instance.RemoveOnPauseEvent(HandleOnSceneWillPause);
UIManager.Instance.RemoveOnLoadSceneEvent(HandleOnSceneWillLoad);
}
}
#if USE_TETRAHEDRALIZATION
LightProbes.needsRetetrahedralization -= conductTetrahedralization;
#endif
}
private void AddLODOverrideToChildren(GameObject showcaseGameObject)
{
if (showcaseGameObject == null)
{
return;
}
if (_displayLODLabels)
{
AvatarLODManager.Instance.debug.displayLODLabels = true;
}
OvrAvatarEntity[] entities = showcaseGameObject.GetComponentsInChildren<OvrAvatarEntity>(true);
if (entities == null || entities.Length == 0)
{
return;
}
foreach (var entity in entities)
{
AddLODOverrideToEntity(entity);
}
}
private void AddLODOverrideToEntity(OvrAvatarEntity entity)
{
if (entity.GetComponent<AvatarLODOverride>())
{
return;
}
entity.gameObject.AddComponent<AvatarLODOverride>();
#if UNITY_EDITOR
if (_keyboardDebugLODLevels)
{
entity.GetComponent<AvatarLODOverride>().EnableKeyboardDebug();
}
#endif
}
private void Update()
{
#if USING_XR_SDK
if (_isPaused)
{
return;
}
// LightingExampleManager already handles UI pause,
// so it doesn't need to explicitly check for !UIManager.IsPaused
if (OVRInput.GetActiveController() != OVRInput.Controller.Hands)
{
if (_lightingConfigs != null)
{
if (_sdkManagers != null)
{
if (OVRInput.GetUp(_prevLightConfigButton.button, _prevLightConfigButton.controller))
{
SetLightingConfig(WrapArrayIndex(_currentLighting, -1, _lightingConfigs.Length));
}
else if (OVRInput.GetUp(_nextLightConfigButton.button, _nextLightConfigButton.controller))
{
SetLightingConfig(WrapArrayIndex(_currentLighting, 1, _lightingConfigs.Length));
}
else if (_enableShaderSelection && OVRInput.GetUp(_prevShaderButton.button, _prevShaderButton.controller))
{
SetShader(WrapArrayIndex(_currentShader, -1, _sdkManagers.Length));
}
else if (_enableShaderSelection && OVRInput.GetUp(_nextShaderButton.button, _nextShaderButton.controller))
{
SetShader(WrapArrayIndex(_currentShader, 1, _sdkManagers.Length));
}
}
else
{
OvrAvatarLog.LogError("No SDK Managers found", logScope);
}
}
else
{
OvrAvatarLog.LogError("No lighting configs found", logScope);
}
}
#endif
}
private void SetupUI()
{
if (_lightingConfigs != null && _lightingButton != null && _lightingNameGroup != null)
{
for (int i = 0; i < _lightingConfigs.Length; ++i)
{
Button button = i == 0 ? _lightingButton : Instantiate(_lightingButton, _lightingNameGroup);
int lightingIndex = i;
button.onClick.AddListener(() => SetLightingConfig(lightingIndex));
_lightingConfigs[i].Button = button;
button.GetComponentInChildren<Text>().text = _lightingConfigs[i].Name;
}
}
else
{
OvrAvatarLog.LogError("No lighting configs, button, or name group found", logScope);
}
if (!_enableShaderSelection && _sdkManagers != null && _shaderButton != null && _shaderNameGroup != null)
{
Button button = _shaderButton;
button.onClick.AddListener(() => SetShader(_currentShader));
Text buttonText = button.GetComponentInChildren<Text>();
if (_sdkManagers[_currentShader].manager != null)
{
buttonText.text = (_sdkManagers[_currentShader].manager?.name ?? "Unknown");
}
else
{
OvrAvatarLog.LogError($"{_sdkManagers[_currentShader]} has a null manager", logScope);
}
button.interactable = false;
buttonText.fontStyle = FontStyle.Bold;
}
else if (_sdkManagers != null && _shaderButton != null && _shaderNameGroup != null)
{
for (int i = 0; i < _sdkManagers.Length; ++i)
{
Button button = i == 0 ? _shaderButton : Instantiate(_shaderButton, _shaderNameGroup);
int shaderIndex = i;
button.onClick.AddListener(() => SetShader(shaderIndex));
Text buttonText = button.GetComponentInChildren<Text>();
var deprecationStatus = _sdkManagers[i].GetDeprecationStatus();
if (_sdkManagers[i].manager != null)
{
var managerName = _sdkManagers[i].manager?.name ?? "Unknown";
managerName = managerName.Replace("(deprecated)", string.Empty, StringComparison.OrdinalIgnoreCase);
buttonText.text = (managerName) + $" ({deprecationStatus})";
}
else
{
OvrAvatarLog.LogError($"{_sdkManagers[i]} has a null manager", logScope);
}
button.interactable = i != _currentShader;
buttonText.fontStyle = i == _currentShader ? FontStyle.Bold : FontStyle.Normal;
}
}
else
{
OvrAvatarLog.LogError("No SDK Managers, shader button, or shader name group found", logScope);
}
}
private bool IsSceneLoading()
{
return !OvrAvatarManager.hasInstance || OvrAvatarManager.Instance.IsLoadingAvatar || OvrAvatarManager.Instance.IsLoadingResources;
}
private static bool AreAllLightingScenesAvailable(string[]? sceneNamesToCheck)
{
if (sceneNamesToCheck == null)
{
OvrAvatarLog.LogError("Scene Names are null - No lighting configs found", logScope);
return false;
}
string[] allSceneNamesInBuild = new string[SceneManager.sceneCountInBuildSettings];
for (int i = 0; i < SceneManager.sceneCountInBuildSettings; i++)
{
string scenePath = SceneUtility.GetScenePathByBuildIndex(i);
string sceneName = System.IO.Path.GetFileNameWithoutExtension(scenePath);
allSceneNamesInBuild[i] = sceneName;
}
// Check if all scenes in sceneNamesToCheck exist in allSceneNamesInBuild
return sceneNamesToCheck.All(name => allSceneNamesInBuild.Contains(name));
}
private void SetLightingConfig(int index)
{
if (_isPaused)
{
return;
}
if (IsSceneLoading())
{
// Disable lighting config changes during scene switches or when avatars are loading
return;
}
_currentLighting = index;
if (!String.IsNullOrEmpty(_currentSceneName))
{
SceneManager.UnloadSceneAsync(_currentSceneName);
}
if (_lightingConfigs != null)
{
_currentSceneName = _lightingConfigs[index].SceneName;
SceneManager.LoadScene(_currentSceneName, LoadSceneMode.Additive);
if (UIManager.Instance == null)
{
OvrAvatarLog.LogError("No UIManager instance found", logScope);
return;
}
UIManager.Instance.AddOnSceneLoadedEvent(OnSceneLoaded);
}
else
{
OvrAvatarLog.LogError("No lighting configs found", logScope);
}
}
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
if (_lightingConfigs == null)
{
OvrAvatarLog.LogError("No lighting configs found", logScope);
return;
}
RenderSettings.ambientLight = _lightingConfigs[_currentLighting].AmbientColor;
RenderSettings.skybox = _lightingConfigs[_currentLighting].SkyboxMaterial;
for (int i = 0; i < _lightingConfigs.Length; ++i)
{
_lightingConfigs[i].Button.interactable = i != _currentLighting;
_lightingConfigs[i].Button.GetComponentInChildren<Text>().fontStyle =
i == _currentLighting ? FontStyle.Bold : FontStyle.Normal;
}
if (UIManager.Instance == null)
{
OvrAvatarLog.LogError("No UIManager instance found", logScope);
return;
}
#if !USE_TETRAHEDRALIZATION
// NOTE: after the load, we need to trigger the updating of the light probes to correct ambient lighting.
// Official documentation for this is here:
// https://docs.unity3d.com/Manual/light-probes-and-scene-loading.html
// https://docs.unity3d.com/ScriptReference/LightProbes-needsRetetrahedralization.html
// HOWEVER, calling this function after an additive scene load proves uneffective.
// For now, it seems that this can only be corrected by a singular scene load, as triggered by SetShader:
SetShader(_currentShader); // this reload of the shader is needed to restore ambient light to the avatars
#endif
UIManager.Instance.RemoveOnSceneLoadedEvent(OnSceneLoaded);
}
#if USE_TETRAHEDRALIZATION
public void conductTetrahedralization()
{
LightProbes.Tetrahedralize();
}
#endif
private void SetShader(int index)
{
if (_isPaused)
{
return;
}
if (IsSceneLoading())
{
// Disable shader changes during scene switches or when avatars are loading
return;
}
_currentShader = index;
// Destroy OvrAvatarManager so we can recreate it with the new shader config
var managerGameObject = OvrAvatarManager.Instance.gameObject;
OvrAvatarManager.ResetInstance();
Destroy(managerGameObject);
// Reload the scene
SceneManager.LoadScene(SceneManager.GetActiveScene().name, LoadSceneMode.Single);
}
private void ResetLightsAndShaders()
{
_currentShader = 0;
_currentLighting = 0;
// Destroy OvrAvatarManager so we can recreate it with the new shader config
var managerGameObject = OvrAvatarManager.Instance.gameObject;
Destroy(managerGameObject);
if (_sdkManagers != null)
{
var sdkManager = _sdkManagers[_currentShader].manager;
if (sdkManager != null)
{
Instantiate(sdkManager);
}
else
{
OvrAvatarLog.LogError($"No SDK Manager found for shader: {_sdkManagers[_currentShader]}", logScope);
}
}
else
{
OvrAvatarLog.LogError("No SDK Managers found", logScope);
}
if (_lightingConfigs != null)
{
RenderSettings.ambientLight = _lightingConfigs[_currentLighting].AmbientColor;
}
else
{
OvrAvatarLog.LogError("No lighting configs found", logScope);
}
}
private static int WrapArrayIndex(int current, int offset, int length)
{
return (current + offset + length) % length;
}
#if USING_XR_SDK
public List<UIInputControllerButton> GetControlSchema()
{
var buttons = new List<UIInputControllerButton>
{
_prevLightConfigButton,
_prevShaderButton,
_nextLightConfigButton,
_nextShaderButton,
};
return buttons;
}
#endif
}