522 lines
18 KiB
C#
522 lines
18 KiB
C#
using UnityEngine;
|
||
using UnityEngine.Serialization;
|
||
using System.Collections;
|
||
using System;
|
||
|
||
#if UNITY_2017_2_OR_NEWER
|
||
using UnityEngine.XR;
|
||
#endif
|
||
|
||
namespace LIV.SDK.Unity
|
||
{
|
||
[System.Flags]
|
||
public enum INVALIDATION_FLAGS : uint
|
||
{
|
||
NONE = 0,
|
||
HMD_CAMERA = 1,
|
||
STAGE = 2,
|
||
MR_CAMERA_PREFAB = 4,
|
||
EXCLUDE_BEHAVIOURS = 8
|
||
}
|
||
|
||
/// <summary>
|
||
/// The LIV SDK provides a spectator view of your application.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// <para>It contextualizes what the user feels & experiences by capturing their body directly inside your world!</para>
|
||
/// <para>Thanks to our software, creators can film inside your app and have full control over the camera.</para>
|
||
/// <para>With the power of out-of-engine compositing, a creator can express themselves freely without limits;</para>
|
||
/// <para>as a real person or an avatar!</para>
|
||
/// </remarks>
|
||
/// <example>
|
||
/// <code>
|
||
/// public class StartFromScriptExample : MonoBehaviour
|
||
/// {
|
||
/// [SerializeField] Camera _hmdCamera;
|
||
/// [SerializeField] Transform _stage;
|
||
/// [SerializeField] Transform _stageTransform;
|
||
/// [SerializeField] Camera _mrCameraPrefab;
|
||
|
||
/// LIV.SDK.Unity.LIV _liv;
|
||
|
||
/// private void OnEnable()
|
||
/// {
|
||
/// _liv = gameObject.AddComponent<LIV.SDK.Unity.LIV>();
|
||
/// _liv.HMDCamera = _hmdCamera;
|
||
/// _liv.stage = _stage;
|
||
/// _liv.stageTransform = _stageTransform;
|
||
/// _liv.MRCameraPrefab = _mrCameraPrefab;
|
||
/// }
|
||
|
||
/// private void OnDisable()
|
||
/// {
|
||
/// if (_liv == null) return;
|
||
/// Destroy(_liv);
|
||
/// _liv = null;
|
||
/// }
|
||
/// }
|
||
/// </code>
|
||
/// </example>
|
||
[HelpURL("https://liv.tv/sdk-unity-docs")]
|
||
[AddComponentMenu("LIV/LIV")]
|
||
public class LIV : MonoBehaviour
|
||
{
|
||
/// <summary>
|
||
/// triggered when the LIV SDK is activated by the LIV App and enabled by the game.
|
||
/// </summary>
|
||
public System.Action onActivate = null;
|
||
/// <summary>
|
||
/// triggered before the Mixed Reality camera is about to render.
|
||
/// </summary>
|
||
public System.Action<SDKRender> onPreRender = null;
|
||
/// <summary>
|
||
/// triggered before the LIV SDK starts rendering background image.
|
||
/// </summary>
|
||
public System.Action<SDKRender> onPreRenderBackground = null;
|
||
/// <summary>
|
||
/// triggered after the LIV SDK starts rendering background image.
|
||
/// </summary>
|
||
public System.Action<SDKRender> onPostRenderBackground = null;
|
||
/// <summary>
|
||
/// triggered before the LIV SDK starts rendering the foreground image.
|
||
/// </summary>
|
||
public System.Action<SDKRender> onPreRenderForeground = null;
|
||
/// <summary>
|
||
/// triggered after the LIV SDK starts rendering the foreground image.
|
||
/// </summary>
|
||
public System.Action<SDKRender> onPostRenderForeground = null;
|
||
/// <summary>
|
||
/// triggered after the Mixed Reality camera has finished rendering.
|
||
/// </summary>
|
||
public System.Action<SDKRender> onPostRender = null;
|
||
/// <summary>
|
||
/// triggered when the LIV SDK is deactivated by the LIV App or disabled by the game.
|
||
/// </summary>
|
||
public System.Action onDeactivate = null;
|
||
|
||
[Tooltip("This is the topmost transform of your VR rig.")]
|
||
[FormerlySerializedAs("TrackedSpaceOrigin")]
|
||
[SerializeField] Transform _stage = null;
|
||
/// <summary>
|
||
/// This is the topmost transform of your VR rig.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// <para>When implementing VR locomotion(teleporting, joystick, etc),</para>
|
||
/// <para>this is the GameObject that you should move around your scene.</para>
|
||
/// <para>It represents the centre of the user’s playspace.</para>
|
||
/// </remarks>
|
||
public Transform stage {
|
||
get {
|
||
return _stage == null ? transform.parent : _stage;
|
||
}
|
||
set {
|
||
if (value == null)
|
||
{
|
||
Debug.LogWarning("LIV: Stage cannot be null!");
|
||
}
|
||
|
||
if (_stage != value)
|
||
{
|
||
_stageCandidate = value;
|
||
_invalidate = (INVALIDATION_FLAGS)SDKUtils.SetFlag((uint)_invalidate, (uint)INVALIDATION_FLAGS.STAGE, true);
|
||
}
|
||
}
|
||
}
|
||
|
||
[Obsolete("Use stage instead")]
|
||
public Transform trackedSpaceOrigin {
|
||
get {
|
||
return stage;
|
||
}
|
||
set {
|
||
stage = value;
|
||
}
|
||
}
|
||
|
||
public Matrix4x4 stageLocalToWorldMatrix {
|
||
get {
|
||
return (stage != null) ? stage.localToWorldMatrix : Matrix4x4.identity;
|
||
}
|
||
}
|
||
|
||
public Matrix4x4 stageWorldToLocalMatrix {
|
||
get {
|
||
return (stage != null) ? stage.worldToLocalMatrix : Matrix4x4.identity;
|
||
}
|
||
}
|
||
|
||
[Tooltip("This transform is an additional wrapper to the user’s playspace.")]
|
||
[FormerlySerializedAs("StageTransform")]
|
||
[SerializeField] Transform _stageTransform = null;
|
||
/// <summary>
|
||
/// This transform is an additional wrapper to the user’s playspace.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// <para>It allows for user-controlled transformations for special camera effects & transitions.</para>
|
||
/// <para>If a creator is using a static camera, this transformation can give the illusion of camera movement.</para>
|
||
/// </remarks>
|
||
public Transform stageTransform {
|
||
get {
|
||
return _stageTransform;
|
||
}
|
||
set {
|
||
_stageTransform = value;
|
||
}
|
||
}
|
||
|
||
[Tooltip("This is the camera responsible for rendering the user’s HMD.")]
|
||
[FormerlySerializedAs("HMDCamera")]
|
||
[SerializeField] Camera _HMDCamera = null;
|
||
/// <summary>
|
||
/// This is the camera responsible for rendering the user’s HMD.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// <para>The LIV SDK, by default clones this object to match your application’s rendering setup.</para>
|
||
/// <para>You can use your own camera prefab should you want to!</para>
|
||
/// </remarks>
|
||
public Camera HMDCamera {
|
||
get {
|
||
return _HMDCamera;
|
||
}
|
||
set {
|
||
if (value == null)
|
||
{
|
||
Debug.LogWarning("LIV: HMD Camera cannot be null!");
|
||
}
|
||
|
||
if (_HMDCamera != value)
|
||
{
|
||
_HMDCameraCandidate = value;
|
||
_invalidate = (INVALIDATION_FLAGS)SDKUtils.SetFlag((uint)_invalidate, (uint)INVALIDATION_FLAGS.HMD_CAMERA, true);
|
||
}
|
||
}
|
||
}
|
||
|
||
[Tooltip("Camera prefab for customized rendering.")]
|
||
[FormerlySerializedAs("MRCameraPrefab")]
|
||
[SerializeField] Camera _MRCameraPrefab = null;
|
||
/// <summary>
|
||
/// Camera prefab for customized rendering.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// <para>By default, LIV uses the HMD camera as a reference for the Mixed Reality camera.</para>
|
||
/// <para>It is cloned and set up as a Mixed Reality camera.This approach works for most apps.</para>
|
||
/// <para>However, some games can experience issues because of custom MonoBehaviours attached to this camera.</para>
|
||
/// <para>You can use a custom camera prefab for those cases.</para>
|
||
/// </remarks>
|
||
public Camera MRCameraPrefab {
|
||
get {
|
||
return _MRCameraPrefab;
|
||
}
|
||
set {
|
||
if (_MRCameraPrefab != value)
|
||
{
|
||
_MRCameraPrefabCandidate = value;
|
||
_invalidate = (INVALIDATION_FLAGS)SDKUtils.SetFlag((uint)_invalidate, (uint)INVALIDATION_FLAGS.MR_CAMERA_PREFAB, true);
|
||
}
|
||
}
|
||
}
|
||
|
||
[Tooltip("This option disables all standard Unity assets for the Mixed Reality rendering.")]
|
||
[FormerlySerializedAs("DisableStandardAssets")]
|
||
[SerializeField] bool _disableStandardAssets = false;
|
||
/// <summary>
|
||
/// This option disables all standard Unity assets for the Mixed Reality rendering.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// <para>Unity’s standard assets can interfere with the alpha channel that LIV needs to composite MR correctly.</para>
|
||
/// </remarks>
|
||
public bool disableStandardAssets {
|
||
get {
|
||
return _disableStandardAssets;
|
||
}
|
||
set {
|
||
_disableStandardAssets = value;
|
||
}
|
||
}
|
||
|
||
[Tooltip("The layer mask defines exactly which object layers should be rendered in MR.")]
|
||
[FormerlySerializedAs("SpectatorLayerMask")]
|
||
[SerializeField] LayerMask _spectatorLayerMask = ~0;
|
||
/// <summary>
|
||
/// The layer mask defines exactly which object layers should be rendered in MR.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// <para>You should use this to hide any in-game avatar that you’re using.</para>
|
||
/// <para>LIV is meant to include the user’s body for you!</para>
|
||
/// <para>Certain HMD-based effects should be disabled here too.</para>
|
||
/// <para>Also, this can be used to render special effects or additional UI only to the MR camera.</para>
|
||
/// <para>Useful for showing the player’s health, or current score!</para>
|
||
/// </remarks>
|
||
public LayerMask spectatorLayerMask {
|
||
get {
|
||
return _spectatorLayerMask;
|
||
}
|
||
set {
|
||
_spectatorLayerMask = value;
|
||
}
|
||
}
|
||
|
||
[Tooltip("This is for removing unwanted scripts from the cloned MR camera.")]
|
||
[FormerlySerializedAs("ExcludeBehaviours")]
|
||
[SerializeField]
|
||
string[] _excludeBehaviours = new string[] {
|
||
"AudioListener",
|
||
"Collider",
|
||
"SteamVR_Camera",
|
||
"SteamVR_Fade",
|
||
"SteamVR_ExternalCamera"
|
||
};
|
||
/// <summary>
|
||
/// This is for removing unwanted scripts from the cloned MR camera.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// <para>By default, we remove the AudioListener, Colliders and SteamVR scripts, as these are not necessary for rendering MR!</para>
|
||
/// <para>The excluded string must match the name of the MonoBehaviour.</para>
|
||
/// </remarks>
|
||
public string[] excludeBehaviours {
|
||
get {
|
||
return _excludeBehaviours;
|
||
}
|
||
set {
|
||
if (_excludeBehaviours != value)
|
||
{
|
||
_excludeBehavioursCandidate = value;
|
||
_invalidate = (INVALIDATION_FLAGS)SDKUtils.SetFlag((uint)_invalidate, (uint)INVALIDATION_FLAGS.EXCLUDE_BEHAVIOURS, true);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// Recovers corrupted alpha channel when using post-effects.
|
||
/// </summary>
|
||
///
|
||
[Tooltip("Recovers corrupted alpha channel when using post-effects.")]
|
||
[FormerlySerializedAs("FixPostEffectsAlpha")]
|
||
[SerializeField]
|
||
private bool _fixPostEffectsAlpha = false;
|
||
public bool fixPostEffectsAlpha {
|
||
get {
|
||
return _fixPostEffectsAlpha;
|
||
}
|
||
set {
|
||
_fixPostEffectsAlpha = value;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Is the curret LIV SDK setup valid.
|
||
/// </summary>
|
||
public bool isValid {
|
||
get {
|
||
if (_invalidate != INVALIDATION_FLAGS.NONE) return false;
|
||
|
||
if (_HMDCamera == null)
|
||
{
|
||
Debug.LogError("LIV: HMD Camera is a required parameter!");
|
||
return false;
|
||
}
|
||
|
||
if (_stage == null)
|
||
{
|
||
Debug.LogWarning("LIV: Tracked space origin should be assigned!");
|
||
}
|
||
|
||
if (_spectatorLayerMask == 0)
|
||
{
|
||
Debug.LogWarning("LIV: The spectator layer mask is set to not show anything. Is this correct?");
|
||
}
|
||
|
||
return true;
|
||
}
|
||
}
|
||
|
||
bool _isActive = false;
|
||
/// <summary>
|
||
/// Is the LIV SDK currently active.
|
||
/// </summary>
|
||
public bool isActive {
|
||
get {
|
||
return _isActive;
|
||
}
|
||
}
|
||
|
||
private bool _isReady {
|
||
get {
|
||
return isValid && _enabled && SDKBridge.IsActive;
|
||
}
|
||
}
|
||
|
||
private SDKRender _render = null;
|
||
|
||
/// <summary>
|
||
/// Script responsible for the MR rendering.
|
||
/// </summary>
|
||
public SDKRender render { get { return _render; } }
|
||
|
||
private bool _wasReady = false;
|
||
|
||
private INVALIDATION_FLAGS _invalidate = INVALIDATION_FLAGS.NONE;
|
||
private Transform _stageCandidate = null;
|
||
private Camera _HMDCameraCandidate = null;
|
||
private Camera _MRCameraPrefabCandidate = null;
|
||
private string[] _excludeBehavioursCandidate = null;
|
||
|
||
private bool _enabled = false;
|
||
private Coroutine _waitForEndOfFrameCoroutine;
|
||
|
||
void OnEnable()
|
||
{
|
||
_enabled = true;
|
||
UpdateSDKReady();
|
||
}
|
||
|
||
void Update()
|
||
{
|
||
UpdateSDKReady();
|
||
Invalidate();
|
||
}
|
||
|
||
void OnDisable()
|
||
{
|
||
_enabled = false;
|
||
UpdateSDKReady();
|
||
}
|
||
|
||
IEnumerator WaitForUnityEndOfFrame()
|
||
{
|
||
while (Application.isPlaying && enabled)
|
||
{
|
||
yield return new WaitForEndOfFrame();
|
||
if (isActive)
|
||
{
|
||
_render.Render();
|
||
}
|
||
}
|
||
}
|
||
|
||
void UpdateSDKReady()
|
||
{
|
||
bool ready = _isReady;
|
||
if (ready != _wasReady)
|
||
{
|
||
OnSDKReadyChanged(ready);
|
||
_wasReady = ready;
|
||
}
|
||
}
|
||
|
||
void OnSDKReadyChanged(bool value)
|
||
{
|
||
if (value)
|
||
{
|
||
OnSDKActivate();
|
||
}
|
||
else
|
||
{
|
||
OnSDKDeactivate();
|
||
}
|
||
}
|
||
|
||
void OnSDKActivate()
|
||
{
|
||
Debug.Log("LIV: Compositor connected, setting up Mixed Reality!");
|
||
SubmitSDKOutput();
|
||
CreateAssets();
|
||
StartRenderCoroutine();
|
||
_isActive = true;
|
||
if (onActivate != null) onActivate.Invoke();
|
||
}
|
||
|
||
void OnSDKDeactivate()
|
||
{
|
||
Debug.Log("LIV: Compositor disconnected, cleaning up Mixed Reality.");
|
||
if (onDeactivate != null) onDeactivate.Invoke();
|
||
StopRenderCoroutine();
|
||
DestroyAssets();
|
||
_isActive = false;
|
||
}
|
||
|
||
void CreateAssets()
|
||
{
|
||
DestroyAssets();
|
||
_render = new SDKRender(this);
|
||
}
|
||
|
||
void DestroyAssets()
|
||
{
|
||
if (_render != null)
|
||
{
|
||
_render.Dispose();
|
||
_render = null;
|
||
}
|
||
}
|
||
|
||
void StartRenderCoroutine()
|
||
{
|
||
StopRenderCoroutine();
|
||
_waitForEndOfFrameCoroutine = StartCoroutine(WaitForUnityEndOfFrame());
|
||
}
|
||
|
||
void StopRenderCoroutine()
|
||
{
|
||
if (_waitForEndOfFrameCoroutine != null)
|
||
{
|
||
StopCoroutine(_waitForEndOfFrameCoroutine);
|
||
_waitForEndOfFrameCoroutine = null;
|
||
}
|
||
}
|
||
|
||
void SubmitSDKOutput()
|
||
{
|
||
SDKApplicationOutput output = SDKApplicationOutput.empty;
|
||
output.supportedFeatures = FEATURES.BACKGROUND_RENDER |
|
||
FEATURES.FOREGROUND_RENDER |
|
||
FEATURES.OVERRIDE_POST_PROCESSING |
|
||
FEATURES.FIX_FOREGROUND_ALPHA;
|
||
|
||
output.sdkID = SDKConstants.SDK_ID;
|
||
output.sdkVersion = SDKConstants.SDK_VERSION;
|
||
output.engineName = SDKConstants.ENGINE_NAME;
|
||
output.engineVersion = Application.unityVersion;
|
||
output.applicationName = Application.productName;
|
||
output.applicationVersion = Application.version;
|
||
output.graphicsAPI = SystemInfo.graphicsDeviceType.ToString();
|
||
#if UNITY_2017_2_OR_NEWER
|
||
output.xrDeviceName = XRSettings.loadedDeviceName;
|
||
#endif
|
||
SDKBridge.SubmitApplicationOutput(output);
|
||
}
|
||
|
||
void Invalidate()
|
||
{
|
||
if (SDKUtils.ContainsFlag((uint)_invalidate, (uint)INVALIDATION_FLAGS.STAGE))
|
||
{
|
||
_stage = _stageCandidate;
|
||
_stageCandidate = null;
|
||
_invalidate = (INVALIDATION_FLAGS)SDKUtils.SetFlag((uint)_invalidate, (uint)INVALIDATION_FLAGS.STAGE, false);
|
||
}
|
||
|
||
if (SDKUtils.ContainsFlag((uint)_invalidate, (uint)INVALIDATION_FLAGS.HMD_CAMERA))
|
||
{
|
||
_HMDCamera = _HMDCameraCandidate;
|
||
_HMDCameraCandidate = null;
|
||
_invalidate = (INVALIDATION_FLAGS)SDKUtils.SetFlag((uint)_invalidate, (uint)INVALIDATION_FLAGS.HMD_CAMERA, false);
|
||
}
|
||
|
||
if (SDKUtils.ContainsFlag((uint)_invalidate, (uint)INVALIDATION_FLAGS.MR_CAMERA_PREFAB))
|
||
{
|
||
_MRCameraPrefab = _MRCameraPrefabCandidate;
|
||
_MRCameraPrefabCandidate = null;
|
||
_invalidate = (INVALIDATION_FLAGS)SDKUtils.SetFlag((uint)_invalidate, (uint)INVALIDATION_FLAGS.MR_CAMERA_PREFAB, false);
|
||
}
|
||
|
||
if (SDKUtils.ContainsFlag((uint)_invalidate, (uint)INVALIDATION_FLAGS.EXCLUDE_BEHAVIOURS))
|
||
{
|
||
_excludeBehaviours = _excludeBehavioursCandidate;
|
||
_excludeBehavioursCandidate = null;
|
||
_invalidate = (INVALIDATION_FLAGS)SDKUtils.SetFlag((uint)_invalidate, (uint)INVALIDATION_FLAGS.EXCLUDE_BEHAVIOURS, false);
|
||
}
|
||
}
|
||
}
|
||
} |