Files
2024-12-06 15:44:37 +08:00

1669 lines
65 KiB
C#

// Copyright HTC Corporation All Rights Reserved.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using Debug = UnityEngine.Debug;
using VIVE.OpenXR.FirstPersonObserver;
using VIVE.OpenXR.SecondaryViewConfiguration;
using VIVE.OpenXR.Toolkits.Spectator.Helper;
namespace VIVE.OpenXR.Toolkits.Spectator
{
/// <summary>
/// Name: SpectatorCameraManager.cs
/// Role: Manager (Singleton)
/// Responsibility: Manage the spectator camera in content layer
/// </summary>
public partial class SpectatorCameraManager : MonoBehaviour, ISpectatorCameraSetting
{
private static SpectatorCameraManager _instance;
/// <summary>
/// SpectatorCameraManager static instance (Singleton)
/// </summary>
public static SpectatorCameraManager Instance => _instance;
public static SpectatorCameraBased SpectatorCameraBased => SpectatorCameraBased.Instance;
/// <summary>
/// To denote whether the SpectatorCameraManager is init successfully or not
/// </summary>
private bool InitSuccess { get; set; }
[SerializeField]
private SpectatorCameraHelper.CameraSourceRef cameraSourceRef = SpectatorCameraHelper.CAMERA_SOURCE_REF_DEFAULT;
/// <summary>
/// The current spectator camera tracking source
/// </summary>
public SpectatorCameraHelper.CameraSourceRef CameraSourceRef
{
get => cameraSourceRef;
set
{
if (cameraSourceRef == value)
{
return;
}
cameraSourceRef = value;
if (Application.isPlaying)
{
CameraStateChangingProcessing();
SpectatorCameraBased.SetViewFromHmd(value == SpectatorCameraHelper.CameraSourceRef.Hmd);
}
}
}
[SerializeField] private LayerMask layerMask = SpectatorCameraHelper.LayerMaskDefault;
public LayerMask LayerMask
{
get => layerMask;
set
{
if (layerMask == value)
{
return;
}
layerMask = value;
// This var in here is only for hmd source. Tracker has own layer mask var.
if (Application.isPlaying && IsCameraSourceAsHmd())
{
SpectatorCameraBased.SpectatorCamera.cullingMask = layerMask;
}
}
}
[field: SerializeField]
public bool IsSmoothCameraMovement { get; set; } = SpectatorCameraHelper.IS_SMOOTH_CAMERA_MOVEMENT_DEFAULT;
[field: SerializeField]
public int SmoothCameraMovementSpeed { get; set; } = SpectatorCameraHelper.SMOOTH_CAMERA_MOVEMENT_SPEED_DEFAULT;
[SerializeField] private bool isFrustumShowed = SpectatorCameraHelper.IS_FRUSTUM_SHOWED_DEFAULT;
public bool IsFrustumShowed
{
get => isFrustumShowed;
set
{
if (isFrustumShowed == value)
{
return;
}
isFrustumShowed = value;
if (Application.isPlaying && IsCameraSourceAsHmd())
{
SetupFrustum();
}
}
}
[SerializeField] private float verticalFov = SpectatorCameraHelper.VERTICAL_FOV_DEFAULT;
public float VerticalFov
{
get => verticalFov;
set
{
if (Math.Abs(verticalFov - value) < SpectatorCameraHelper.COMPARE_FLOAT_MEDIUM_THRESHOLD)
{
return;
}
verticalFov = Mathf.Clamp(value,
SpectatorCameraHelper.VERTICAL_FOV_MIN,
SpectatorCameraHelper.VERTICAL_FOV_MAX);
if (Application.isPlaying && IsCameraSourceAsHmd())
{
SpectatorCameraBased.SpectatorCamera.fieldOfView = verticalFov;
SetupFrustum();
}
}
}
#region Panorama properties
[field: SerializeField]
public SpectatorCameraHelper.SpectatorCameraPanoramaResolution PanoramaResolution { get; set; } =
SpectatorCameraHelper.PANORAMA_RESOLUTION_DEFAULT;
[field: SerializeField]
public TextureProcessHelper.PictureOutputFormat PanoramaOutputFormat { get; set; } =
SpectatorCameraHelper.PANORAMA_OUTPUT_FORMAT_DEFAULT;
[field: SerializeField]
public TextureProcessHelper.PanoramaType PanoramaOutputType { get; set; } =
SpectatorCameraHelper.PANORAMA_TYPE_DEFAULT;
#endregion
[SerializeField] private SpectatorCameraHelper.FrustumLineCount frustumLineCount =
SpectatorCameraHelper.FRUSTUM_LINE_COUNT_DEFAULT;
public SpectatorCameraHelper.FrustumLineCount FrustumLineCount
{
get => frustumLineCount;
set
{
if (frustumLineCount == value)
{
return;
}
frustumLineCount = value;
if (Application.isPlaying && IsCameraSourceAsHmd())
{
SetupFrustumLine();
}
}
}
[SerializeField] private SpectatorCameraHelper.FrustumCenterLineCount frustumCenterLineCount =
SpectatorCameraHelper.FRUSTUM_CENTER_LINE_COUNT_DEFAULT;
public SpectatorCameraHelper.FrustumCenterLineCount FrustumCenterLineCount
{
get => frustumCenterLineCount;
set
{
if (frustumCenterLineCount == value)
{
return;
}
frustumCenterLineCount = value;
if (Application.isPlaying && IsCameraSourceAsHmd())
{
SetupFrustumCenterLine();
}
}
}
[SerializeField] private float frustumLineWidth = SpectatorCameraHelper.FRUSTUM_LINE_WIDTH_DEFAULT;
public float FrustumLineWidth
{
get => frustumLineWidth;
set
{
if (Math.Abs(frustumLineWidth - value) < SpectatorCameraHelper.COMPARE_FLOAT_SUPER_SMALL_THRESHOLD)
{
return;
}
frustumLineWidth = Mathf.Clamp(
value,
SpectatorCameraHelper.FRUSTUM_LINE_WIDTH_MIN,
SpectatorCameraHelper.FRUSTUM_LINE_WIDTH_MAX);
if (Application.isPlaying && IsCameraSourceAsHmd())
{
SetupFrustumLine();
}
}
}
[SerializeField] private float frustumCenterLineWidth = SpectatorCameraHelper.FRUSTUM_CENTER_LINE_WIDTH_DEFAULT;
public float FrustumCenterLineWidth
{
get => frustumCenterLineWidth;
set
{
if (Math.Abs(frustumCenterLineWidth - value) <
SpectatorCameraHelper.COMPARE_FLOAT_SUPER_SMALL_THRESHOLD)
{
return;
}
frustumCenterLineWidth = Mathf.Clamp(value, SpectatorCameraHelper.FRUSTUM_CENTER_LINE_WIDTH_MIN,
SpectatorCameraHelper.FRUSTUM_CENTER_LINE_WIDTH_MAX);
if (Application.isPlaying && IsCameraSourceAsHmd())
{
SetupFrustumCenterLine();
}
}
}
[SerializeField] private Color frustumLineColor = SpectatorCameraHelper.LineColorDefault;
public Color FrustumLineColor
{
get => frustumLineColor;
set
{
if (frustumLineColor == value)
{
return;
}
frustumLineColor = value;
if (Application.isPlaying && IsCameraSourceAsHmd())
{
SetupFrustumLine();
}
}
}
[SerializeField] private Color frustumCenterLineColor = SpectatorCameraHelper.LineColorDefault;
public Color FrustumCenterLineColor
{
get => frustumCenterLineColor;
set
{
if (frustumCenterLineColor == value)
{
return;
}
frustumCenterLineColor = value;
if (Application.isPlaying && IsCameraSourceAsHmd())
{
SetupFrustumCenterLine();
}
}
}
#region Varibles of the camera prefeb, camera gameobject, camera handler and main (rig) camera
/// <summary>
/// The camera prefab. It will be created as a GameObject and denote a spectator camera at runtime.
/// </summary>
[field: SerializeField] private GameObject SpectatorCameraPrefab { get; set; }
/// <summary>
/// The GameObject is created from SpectatorCameraPrefab.
/// </summary>
public GameObject SpectatorCamera { get; private set; }
#endregion
#region Varibles of last value of camera FOV and main camera position and rotation
/// <summary>
/// The previous value of the position of the main (hmd) camera (P.S. only use in CameraSourceRef.Hmd mode)
/// </summary>
private Vector3 CameraLastPosition { get; set; }
/// <summary>
/// The previous value of the rotation of the main (hmd) camera (P.S. only use in CameraSourceRef.Hmd mode)
/// </summary>
private Quaternion CameraLastRotation { get; set; }
#endregion
#region Variables of visualization FOV
/// <summary>
/// The frustum line root
/// </summary>
private GameObject FrustumLineRoot { get; set; }
/// <summary>
/// The frustum center line root
/// </summary>
private GameObject FrustumCenterLineRoot { get; set; }
/// <summary>
/// The list contain frustum line (LineRenderer)
/// </summary>
private List<LineRenderer> frustumLineList;
/// <summary>
/// The list contain frustum center line (LineRenderer)
/// </summary>
private List<LineRenderer> frustumCenterLineList;
#endregion
#region Varibles of tracker list includes all trackers in the scene, the current tracker candidate, and its index in the tracker list
private List<SpectatorCameraTracker> spectatorCameraTrackerList;
/// <summary>
/// The tracker list that contains all tracker in the scene
/// </summary>
public IReadOnlyList<SpectatorCameraTracker> SpectatorCameraTrackerList => spectatorCameraTrackerList;
[SerializeField] private SpectatorCameraTracker followSpectatorCameraTracker;
/// <summary>
/// The tracker candidate that the camera will follow in "Tracker mode".
/// </summary>
public SpectatorCameraTracker FollowSpectatorCameraTracker
{
get => followSpectatorCameraTracker;
set
{
if (followSpectatorCameraTracker == value)
{
return;
}
followSpectatorCameraTracker = value;
if (Application.isPlaying)
{
AddSpectatorCameraTracker(value);
if (IsCameraSourceAsTracker())
{
CameraStateChangingProcessing();
}
}
}
}
/// <summary>
/// The index of the current candidate of the tracker in the tracker list.
/// </summary>
private int FollowSpectatorCameraTrackerIndex
{
get
{
if (spectatorCameraTrackerList != null && FollowSpectatorCameraTracker != null)
{
return spectatorCameraTrackerList.IndexOf(FollowSpectatorCameraTracker);
}
return -1;
}
}
#endregion
#region Public Functions of camera setting I/O
public void ResetSetting()
{
CameraSourceRef = SpectatorCameraHelper.CameraSourceRef.Hmd;
LayerMask = SpectatorCameraHelper.LayerMaskDefault;
IsSmoothCameraMovement = SpectatorCameraHelper.IS_SMOOTH_CAMERA_MOVEMENT_DEFAULT;
SmoothCameraMovementSpeed = SpectatorCameraHelper.SMOOTH_CAMERA_MOVEMENT_SPEED_DEFAULT;
IsFrustumShowed = SpectatorCameraHelper.IS_FRUSTUM_SHOWED_DEFAULT;
VerticalFov = SpectatorCameraHelper.VERTICAL_FOV_DEFAULT;
PanoramaResolution = SpectatorCameraHelper.PANORAMA_RESOLUTION_DEFAULT;
PanoramaOutputFormat = SpectatorCameraHelper.PANORAMA_OUTPUT_FORMAT_DEFAULT;
PanoramaOutputType = SpectatorCameraHelper.PANORAMA_TYPE_DEFAULT;
FrustumLineCount = SpectatorCameraHelper.FRUSTUM_LINE_COUNT_DEFAULT;
FrustumCenterLineCount = SpectatorCameraHelper.FRUSTUM_CENTER_LINE_COUNT_DEFAULT;
FrustumLineWidth = SpectatorCameraHelper.FRUSTUM_LINE_WIDTH_DEFAULT;
FrustumCenterLineWidth = SpectatorCameraHelper.FRUSTUM_CENTER_LINE_WIDTH_DEFAULT;
FrustumLineColor = SpectatorCameraHelper.LineColorDefault;
FrustumCenterLineColor = SpectatorCameraHelper.LineColorDefault;
SpectatorCameraViewMaterial = null;
}
public void ExportSetting2JsonFile(in SpectatorCameraHelper.AttributeFileLocation attributeFileLocation)
{
#if !UNITY_EDITOR
if (attributeFileLocation is SpectatorCameraHelper.AttributeFileLocation.ResourceFolder)
{
Debug.LogError("It's not allowed to save setting to resource folder in runtime mode");
return;
}
#endif
var data = new SpectatorCameraHelper.SpectatorCameraAttribute(
CameraSourceRef,
Vector3.zero, // HMD does not need to save the position
Quaternion.identity, // HMD does not need to save the rotation
LayerMask,
IsSmoothCameraMovement,
SmoothCameraMovementSpeed,
IsFrustumShowed,
VerticalFov,
PanoramaResolution,
PanoramaOutputFormat,
PanoramaOutputType,
FrustumLineCount,
FrustumCenterLineCount,
FrustumLineWidth,
FrustumCenterLineWidth,
FrustumLineColor,
FrustumCenterLineColor);
#if UNITY_EDITOR
if (attributeFileLocation is SpectatorCameraHelper.AttributeFileLocation.ResourceFolder)
{
SpectatorCameraHelper.SaveAttributeData2ResourcesFolder(
SceneManager.GetActiveScene().name,
gameObject.name,
data);
}
else if (attributeFileLocation is SpectatorCameraHelper.AttributeFileLocation.PersistentFolder)
{
SpectatorCameraHelper.SaveAttributeData2PersistentFolder(
SceneManager.GetActiveScene().name,
gameObject.name,
data);
}
#else
SpectatorCameraHelper.SaveAttributeData2PersistentFolder(
SceneManager.GetActiveScene().name,
gameObject.name,
data);
#endif
}
public void LoadSettingFromJsonFile(in string jsonFilePath)
{
bool loadSuccess = SpectatorCameraHelper.LoadAttributeFileFromFolder(
jsonFilePath,
out SpectatorCameraHelper.SpectatorCameraAttribute data);
if (loadSuccess)
{
ApplyData(data);
}
else
{
Debug.Log($"Load setting from {jsonFilePath} file to {gameObject.name} failed.");
}
}
/// <summary>
/// Load the setting (JSON) file via input scene name, GameObject (hmd) name, and the file location (resource folder or persistent folder).
/// </summary>
/// <param name="sceneName">The scene name.</param>
/// <param name="hmdName">The GameObject name.</param>
/// <param name="attributeFileLocation"> The enum SpectatorCameraHelper.AttributeFileLocation.</param>
public void LoadSettingFromJsonFile(
in string sceneName,
in string hmdName,
in SpectatorCameraHelper.AttributeFileLocation attributeFileLocation)
{
if (string.IsNullOrEmpty(sceneName) || string.IsNullOrEmpty(hmdName))
{
Debug.LogError("Scene name or hmd name is null or empty");
return;
}
var loadSuccess = false;
SpectatorCameraHelper.SpectatorCameraAttribute data = new SpectatorCameraHelper.SpectatorCameraAttribute();
if (attributeFileLocation is SpectatorCameraHelper.AttributeFileLocation.ResourceFolder)
{
loadSuccess = SpectatorCameraHelper.LoadAttributeFileFromResourcesFolder(
sceneName,
hmdName,
out data);
}
else if (attributeFileLocation is SpectatorCameraHelper.AttributeFileLocation.PersistentFolder)
{
loadSuccess = SpectatorCameraHelper.LoadAttributeFileFromPersistentFolder(
sceneName,
hmdName,
out data);
}
if (loadSuccess)
{
ApplyData(data);
}
else
{
var fileDirectory = string.Empty;
if (attributeFileLocation is SpectatorCameraHelper.AttributeFileLocation.ResourceFolder)
{
fileDirectory = System.IO.Path.Combine(Application.dataPath, "Resources");
}
else if (attributeFileLocation is SpectatorCameraHelper.AttributeFileLocation.PersistentFolder)
{
fileDirectory = Application.persistentDataPath;
}
var fileName =
SpectatorCameraHelper.GetSpectatorCameraAttributeFileNamePattern(sceneName, hmdName);
Debug.Log(
$"Load setting from {fileDirectory}/{fileName} file to {hmdName} failed.");
}
}
public void ApplyData(in SpectatorCameraHelper.SpectatorCameraAttribute data)
{
CameraSourceRef = data.source;
LayerMask = data.layerMask;
IsSmoothCameraMovement = data.isSmoothCameraMovement;
SmoothCameraMovementSpeed = data.smoothCameraMovementSpeed;
IsFrustumShowed = data.isFrustumShowed;
VerticalFov = data.verticalFov;
PanoramaResolution = data.panoramaResolution;
PanoramaOutputFormat = data.panoramaOutputFormat;
PanoramaOutputType = data.panoramaOutputType;
FrustumLineCount = data.frustumLineCount;
FrustumCenterLineCount = data.frustumCenterLineCount;
FrustumLineWidth = data.frustumLineWidth;
FrustumCenterLineWidth = data.frustumCenterLineWidth;
FrustumLineColor = data.frustumLineColor;
FrustumCenterLineColor = data.frustumCenterLineColor;
}
#endregion
#region Public functions of add/remove tracker
/// <summary>
/// Add the spectator camera tracker to the tracker list in SpectatorCameraManager.
/// </summary>
/// <param name="tracker">The tracker you want to add.</param>
public void AddSpectatorCameraTracker(SpectatorCameraTracker tracker)
{
if (spectatorCameraTrackerList == null)
{
spectatorCameraTrackerList = new List<SpectatorCameraTracker>();
}
if (spectatorCameraTrackerList.Contains(tracker))
{
return;
}
spectatorCameraTrackerList.Add(tracker);
}
/// <summary>
/// Remove the spectator camera tracker from the tracker list in SpectatorCameraManager.
/// </summary>
/// <param name="tracker">The tracker you want to remove.</param>
public void RemoveSpectatorCameraTracker(SpectatorCameraTracker tracker)
{
if (spectatorCameraTrackerList == null)
{
return;
}
if (spectatorCameraTrackerList.Contains(tracker))
{
spectatorCameraTrackerList.Remove(tracker);
if (FollowSpectatorCameraTracker == tracker)
{
if (spectatorCameraTrackerList.Count > 0)
{
// If the tracker that is removed is the current tracker candidate and there are still
// some trackers in the list, change the current tracker candidate to the first tracker
// in the list
FollowSpectatorCameraTracker = spectatorCameraTrackerList[0];
}
else
{
// If the tracker that is removed is the current tracker candidate and there is no
// tracker in the list, change the camera source to HMD and set the current tracker
// candidate to null
CameraSourceRef = SpectatorCameraHelper.CameraSourceRef.Hmd;
FollowSpectatorCameraTracker = null;
}
}
}
}
#endregion
#region Public function of 360 photo capture
/// <summary>
/// Capture the 360 photo at the current spectator camera position and rotation.
/// There are two types of panorama type: <b>Monoscopic</b> and <b>Stereoscopic</b>
/// can be chosen.
/// </summary>
public void CaptureSpectatorCamera360Photo()
{
if (!Application.isPlaying)
{
Debug.LogWarning("Pls play the application for 360 photo capture.");
return;
}
if (!SpectatorCameraBased.IsAllowSpectatorCameraCapture360Image)
{
Debug.LogError(
"The spectator camera capture 360 image feature is not enabled, pls enable it on the" +
"OpenXR Setting page in Unity editor project setting first.");
return;
}
// If the spectator handle is not set, return
if (!IsSpectatorCameraHandlerExist())
{
Debug.LogError(
"Cannot init the function for capturing 360, pls make sure the spectator handler is init.");
return;
}
#region Create a new camera component for capture 360 photo
Transform refTransform;
LayerMask layerMaskValue;
SpectatorCameraHelper.SpectatorCameraPanoramaResolution panoramaResolutionValue;
TextureProcessHelper.PictureOutputFormat panoramaOutputFormatValue;
TextureProcessHelper.PanoramaType panoramaOutputTypeValue;
// Create a new GameObject which position is according to the CameraSourceRef.
// If CameraSourceRef = HMD, refer the transform from camera main (hmd),
// otherwise, refer the transform from tracker.
switch (CameraSourceRef)
{
case SpectatorCameraHelper.CameraSourceRef.Hmd:
{
if (IsMainCameraExist())
{
refTransform = SpectatorCameraBased.GetMainCamera().transform;
layerMaskValue = LayerMask;
panoramaResolutionValue = PanoramaResolution;
panoramaOutputFormatValue = PanoramaOutputFormat;
panoramaOutputTypeValue = PanoramaOutputType;
}
else
{
Debug.LogWarning("Cannot find the main camera in the scene to capture 360 photo.");
return;
}
}
break;
case SpectatorCameraHelper.CameraSourceRef.Tracker:
{
if (!IsFollowTrackerExist())
{
if (spectatorCameraTrackerList == null)
{
spectatorCameraTrackerList = new List<SpectatorCameraTracker>();
}
if (SpectatorCameraTrackerList.Count > 0)
{
// If there is no tracker assign to the FollowSpectatorCameraTracker and there are
// some trackers in the scene, change to use the first tracker in the list as default
FollowSpectatorCameraTracker = SpectatorCameraTrackerList[0];
}
else
{
// If there is no tracker in the scene, change to use the HMD as default
CameraSourceRef = SpectatorCameraHelper.CameraSourceRef.Hmd;
return;
}
}
refTransform = FollowSpectatorCameraTracker.transform;
layerMaskValue = FollowSpectatorCameraTracker.LayerMask;
panoramaResolutionValue = FollowSpectatorCameraTracker.PanoramaResolution;
panoramaOutputFormatValue = FollowSpectatorCameraTracker.PanoramaOutputFormat;
panoramaOutputTypeValue = FollowSpectatorCameraTracker.PanoramaOutputType;
}
break;
default:
throw new ArgumentOutOfRangeException();
}
// Rotation ignore but only Y-axis (yaw).
var spectatorCamera360 = new GameObject
{
transform =
{
position = refTransform.position
}
}.AddComponent<Camera>();
// Set the spectatorCamera360's stereo target eye according to the panorama type
spectatorCamera360.stereoTargetEye =
panoramaOutputTypeValue is TextureProcessHelper.PanoramaType.Stereoscopic
? StereoTargetEyeMask.Both
: StereoTargetEyeMask.None;
// Set the spectatorCamera360's culling mask
spectatorCamera360.cullingMask = layerMaskValue;
// Set the spectatorCamera360's eye distance
spectatorCamera360.stereoSeparation = SpectatorCameraHelper.STEREO_SEPARATION_DEFAULT;
#endregion
RenderTexture capture360ResultEquirect = TextureProcessHelper.Capture360RenderTexture(
spectatorCamera360,
(int)panoramaResolutionValue,
panoramaOutputTypeValue);
// Destroy the gameObject
Destroy(spectatorCamera360.gameObject);
if (capture360ResultEquirect == null)
{
Debug.LogWarning(
"Capture360RenderTexture return null, pls check the error log on the above for more details.");
return;
}
var filename = $"{DateTime.Now:yyyy-MM-dd-HH-mm-ss}";
try
{
TextureProcessHelper.SaveRenderTextureToDisk(
imageAlbumName: SpectatorCameraHelper.SAVE_PHOTO360_ALBUM_NAME
, imageNameWithoutFileExtension: filename
, imageOutputFormat: panoramaOutputFormatValue
, sourceRenderTexture: capture360ResultEquirect
, yawRotation: refTransform.rotation.eulerAngles.y
#if !UNITY_ANDROID || UNITY_EDITOR
, saveDirectory: Application.persistentDataPath
#endif
);
}
catch (Exception e)
{
Debug.LogError($"Error on output the panoramic photo: {e}.");
}
finally
{
// Release the Temporary RenderTexture
RenderTexture.ReleaseTemporary(capture360ResultEquirect);
}
}
#endregion
#region Public functions of safety check
/// <summary>
/// Check whether the current state of the spectator camera is following the hmd or not.
/// </summary>
/// <returns>True if the spectator camera is following the hmd. Otherwise, return false.</returns>
public bool IsCameraSourceAsHmd()
{
return CameraSourceRef is SpectatorCameraHelper.CameraSourceRef.Hmd;
}
/// <summary>
/// Check whether the current state of the spectator camera is following the tracker or not.
/// </summary>
/// <returns>True if the spectator camera is following the tracker. Otherwise, return false.</returns>
public bool IsCameraSourceAsTracker()
{
return CameraSourceRef is SpectatorCameraHelper.CameraSourceRef.Tracker;
}
/// <summary>
/// Check whether the current tracker tracked by the spectator camera equals the tracker you input.
/// </summary>
/// <param name="tracker">The tracker you want to check</param>
/// <returns>True if equals. Otherwise, return false.</returns>
public bool IsFollowTrackerEqualTo(SpectatorCameraTracker tracker)
{
return FollowSpectatorCameraTracker == tracker;
}
/// <summary>
/// Check whether the spectator camera exists.
/// </summary>
/// <returns>True if the spectator camera exists. Otherwise, return false.</returns>
public bool IsSpectatorCameraHandlerExist()
{
return SpectatorCameraBased.SpectatorCamera != null;
}
/// <summary>
/// Check whether the main camera exists in the currently loaded scene.
/// </summary>
/// <returns>True if the main camera exists. Otherwise, return false.</returns>
public bool IsMainCameraExist()
{
return SpectatorCameraBased.GetMainCamera() != null;
}
/// <summary>
/// Check whether the current tracker tracked by the spectator camera exists or not.
/// </summary>
/// <returns>True if the tracker exists. Otherwise, return false.</returns>
public bool IsFollowTrackerExist()
{
return FollowSpectatorCameraTracker != null;
}
#endregion
#region Functions of visualization camera view ray
/// <summary>
/// Setup the frustum and frustum center line
/// </summary>
public void SetupFrustum()
{
SetupFrustumLine();
SetupFrustumCenterLine();
}
/// <summary>
/// Setup the frustum line
/// </summary>
public void SetupFrustumLine()
{
bool isFrustumShowedValue;
SpectatorCameraHelper.FrustumLineCount frustumLineCountValue;
float frustumLineWidthValue;
Color frustumLineColorValue;
switch (CameraSourceRef)
{
case SpectatorCameraHelper.CameraSourceRef.Hmd:
{
if (IsMainCameraExist())
{
isFrustumShowedValue = IsFrustumShowed;
frustumLineCountValue = FrustumLineCount;
frustumLineWidthValue = FrustumLineWidth;
frustumLineColorValue = FrustumLineColor;
}
else
{
Debug.LogWarning("Main camera does not exist in the scene");
return;
}
}
break;
case SpectatorCameraHelper.CameraSourceRef.Tracker:
{
if (!IsFollowTrackerExist())
{
if (spectatorCameraTrackerList == null)
{
spectatorCameraTrackerList = new List<SpectatorCameraTracker>();
}
if (SpectatorCameraTrackerList.Count > 0)
{
// If there is no tracker assign to the FollowSpectatorCameraTracker and there are
// some trackers in the scene, change to use the first tracker in the list as default
FollowSpectatorCameraTracker = SpectatorCameraTrackerList[0];
}
else
{
// If there is no tracker in the scene, change to use the HMD as default
CameraSourceRef = SpectatorCameraHelper.CameraSourceRef.Hmd;
return;
}
}
isFrustumShowedValue = FollowSpectatorCameraTracker.IsFrustumShowed;
frustumLineCountValue = FollowSpectatorCameraTracker.FrustumLineCount;
frustumLineWidthValue = FollowSpectatorCameraTracker.FrustumLineWidth;
frustumLineColorValue = FollowSpectatorCameraTracker.FrustumLineColor;
}
break;
default:
throw new ArgumentOutOfRangeException();
}
if (
#if UNITY_EDITOR
!(SpectatorCameraBased.IsDebugSpectatorCamera && SpectatorCameraBased.IsRecording) ||
#else
(!SpectatorCameraBased.IsRecording &&
/* Because OpenXR periodically changes the spectator enabled flag, we need
to consider checking the state with a time delay so that we can make sure
it is changing for a long while or just periodically. */
Math.Abs(SpectatorCameraBased.LastRecordingStateIsActiveTime - SpectatorCameraBased.LastRecordingStateIsDisableTime) >
SpectatorCameraBased.RECORDING_STATE_CHANGE_THRESHOLD_IN_SECOND
) ||
#endif
!isFrustumShowedValue ||
frustumLineCountValue is SpectatorCameraHelper.FrustumLineCount.None)
{
if (FrustumLineRoot != null)
{
FrustumLineRoot.SetActive(false);
}
return;
}
if (FrustumLineRoot == null)
{
FrustumLineRoot = new GameObject(SpectatorCameraHelper.FRUSTUM_LINE_ROOT_NAME_DEFAULT);
FrustumLineRoot.transform.SetParent(SpectatorCamera.transform, false);
}
FrustumLineRoot.SetActive(true);
if (frustumLineList != null && frustumLineList.Count > 0)
{
// Destroy all the line renderer and then re-init
// in order to make sure that using new variables
// e.g. line count, width and color
foreach (LineRenderer item in frustumLineList)
{
Destroy(item.gameObject);
}
frustumLineList.Clear();
}
SetupLineRenderer(
lineCount: (int)frustumLineCountValue,
lineWidth: frustumLineWidthValue,
lineNamePrefix: SpectatorCameraHelper.FRUSTUM_LINE_NAME_PREFIX_DEFAULT,
lineMaterial: new Material(Shader.Find(SpectatorCameraHelper.LINE_SHADER_NAME_DEFAULT))
{
color = frustumLineColorValue
},
lineParent: FrustumLineRoot.transform,
lineList: out frustumLineList);
}
/// <summary>
/// Setup the frustum center line
/// </summary>
public void SetupFrustumCenterLine()
{
bool isFrustumShowedValue;
SpectatorCameraHelper.FrustumCenterLineCount frustumCenterLineCountValue;
float frustumCenterLineWidthValue;
Color frustumCenterLineColorValue;
switch (CameraSourceRef)
{
case SpectatorCameraHelper.CameraSourceRef.Hmd:
{
if (IsMainCameraExist())
{
isFrustumShowedValue = IsFrustumShowed;
frustumCenterLineCountValue = FrustumCenterLineCount;
frustumCenterLineWidthValue = FrustumCenterLineWidth;
frustumCenterLineColorValue = FrustumCenterLineColor;
}
else
{
Debug.LogWarning("Main camera does not exist in the scene");
return;
}
}
break;
case SpectatorCameraHelper.CameraSourceRef.Tracker:
{
if (!IsFollowTrackerExist())
{
if (spectatorCameraTrackerList == null)
{
spectatorCameraTrackerList = new List<SpectatorCameraTracker>();
}
if (SpectatorCameraTrackerList.Count > 0)
{
// If there is no tracker assign to the FollowSpectatorCameraTracker and there are
// some trackers in the scene, change to use the first tracker in the list as default
FollowSpectatorCameraTracker = SpectatorCameraTrackerList[0];
}
else
{
// If there is no tracker in the scene, change to use the HMD as default
CameraSourceRef = SpectatorCameraHelper.CameraSourceRef.Hmd;
return;
}
}
isFrustumShowedValue = FollowSpectatorCameraTracker.IsFrustumShowed;
frustumCenterLineCountValue = FollowSpectatorCameraTracker.FrustumCenterLineCount;
frustumCenterLineWidthValue = FollowSpectatorCameraTracker.FrustumCenterLineWidth;
frustumCenterLineColorValue = FollowSpectatorCameraTracker.FrustumCenterLineColor;
}
break;
default:
throw new ArgumentOutOfRangeException();
}
if (
#if UNITY_EDITOR
!(SpectatorCameraBased.IsDebugSpectatorCamera && SpectatorCameraBased.IsRecording) ||
#else
(!SpectatorCameraBased.IsRecording &&
/* Because OpenXR periodically changes the spectator enabled flag, we need
to consider checking the state with a time delay so that we can make sure
it is changing for a long while or just periodically. */
Math.Abs(SpectatorCameraBased.LastRecordingStateIsActiveTime - SpectatorCameraBased.LastRecordingStateIsDisableTime) >
SpectatorCameraBased.RECORDING_STATE_CHANGE_THRESHOLD_IN_SECOND
) ||
#endif
!isFrustumShowedValue ||
frustumCenterLineCountValue is SpectatorCameraHelper.FrustumCenterLineCount.None)
{
if (FrustumCenterLineRoot != null)
{
FrustumCenterLineRoot.SetActive(false);
}
return;
}
if (FrustumCenterLineRoot == null)
{
FrustumCenterLineRoot = new GameObject(SpectatorCameraHelper.FRUSTUM_CENTER_LINE_ROOT_NAME_DEFAULT);
FrustumCenterLineRoot.transform.SetParent(SpectatorCamera.transform, false);
}
FrustumCenterLineRoot.SetActive(true);
if (frustumCenterLineList != null && frustumCenterLineList.Count > 0)
{
// Destroy all the line renderer and then re-init
// in order to make sure that using new variables
// e.g. line count, width and color
foreach (LineRenderer item in frustumCenterLineList)
{
Destroy(item.gameObject);
}
frustumCenterLineList.Clear();
}
SetupLineRenderer(
lineCount: (int)frustumCenterLineCountValue,
lineWidth: frustumCenterLineWidthValue,
lineNamePrefix: SpectatorCameraHelper.FRUSTUM_CENTER_LINE_NAME_PREFIX_DEFAULT,
lineMaterial: new Material(Shader.Find(SpectatorCameraHelper.LINE_SHADER_NAME_DEFAULT))
{
color = frustumCenterLineColorValue
},
lineParent: FrustumCenterLineRoot.transform,
lineList: out frustumCenterLineList);
}
/// <summary>
/// Setup the line renderer in order to render the frustum and frustum center line
/// </summary>
/// <param name="lineCount">The total number of the LineRenderer</param>
/// <param name="lineWidth">The width of the line</param>
/// <param name="lineNamePrefix">The GameObject name prefix. It attaches the LineRenderer component</param>
/// <param name="lineMaterial">The material of the line</param>
/// <param name="lineParent">The parent GameObject of all GameObjects that include the LineRenderer component</param>
/// <param name="lineList">The return value, which is the list of the Line Renderer and all of it is already initiated with the input parameter</param>
private void SetupLineRenderer(
in int lineCount,
in float lineWidth,
in string lineNamePrefix,
in Material lineMaterial,
in Transform lineParent,
out List<LineRenderer> lineList)
{
lineList = new List<LineRenderer>(lineCount);
for (int i = 0; i < lineCount; i++)
{
var obj = new GameObject($"{lineNamePrefix}{i}");
obj.transform.SetParent(lineParent, false);
// Set to "UI" layer
obj.layer = LayerMask.NameToLayer("UI");
var lr = obj.AddComponent<LineRenderer>();
lr.useWorldSpace = false;
lr.sharedMaterial = lineMaterial;
lr.startWidth = lineWidth;
lr.endWidth = lineWidth;
lr.alignment = LineAlignment.View;
lineList.Add(lr);
}
CalculateLineRendererPosition(lineList);
}
/// <summary>
/// Calculate the line renderer position
/// </summary>
/// <param name="lineList">The list that saves all LineRenderer</param>
private void CalculateLineRendererPosition(List<LineRenderer> lineList)
{
if (!IsSpectatorCameraHandlerExist())
{
return;
}
var frustumCornersVector = new Vector3[SpectatorCameraHelper.FRUSTUM_OUT_CORNERS_COUNT];
Camera spectatorHandlerInternalCamera = SpectatorCameraBased.SpectatorCamera;
spectatorHandlerInternalCamera.CalculateFrustumCorners(
new Rect(0, 0, 1, 1),
spectatorHandlerInternalCamera.farClipPlane,
Camera.MonoOrStereoscopicEye.Mono,
frustumCornersVector);
int setLineStep = lineList.Count / SpectatorCameraHelper.FRUSTUM_OUT_CORNERS_COUNT;
for (var currentCorner = 0;
currentCorner < SpectatorCameraHelper.FRUSTUM_OUT_CORNERS_COUNT;
currentCorner++)
{
for (var currentLineStep = 0; currentLineStep < setLineStep; currentLineStep++)
{
Vector3 currentVector = Vector3.Lerp(
frustumCornersVector[currentCorner],
currentCorner < SpectatorCameraHelper.FRUSTUM_OUT_CORNERS_COUNT - 1
// If currentCorner is not the last one, draw the line between current corner and next corner.
? frustumCornersVector[currentCorner + 1]
// Otherwise, draw the line between the last corner and the first corner.
: frustumCornersVector[0],
currentLineStep / (float)setLineStep);
SetLineRendererPosition(
lineList[currentCorner * setLineStep + currentLineStep],
currentVector,
SpectatorCameraHelper.FRUSTUM_LINE_BEGIN_DEFAULT);
}
}
}
/// <summary>
/// Set the line renderer position
/// </summary>
/// <param name="lineRenderer">The LineRenderer that will set to</param>
/// <param name="endPoint">The end position of the line</param>
/// <param name="startOffset">The offset of the start position of the line</param>
private static void SetLineRendererPosition(LineRenderer lineRenderer, Vector3 endPoint, float startOffset = 0)
{
lineRenderer.SetPosition(0, startOffset < SpectatorCameraHelper.COMPARE_FLOAT_SMALL_THRESHOLD
? Vector3.zero
: (endPoint).normalized * startOffset);
lineRenderer.SetPosition(1, endPoint);
}
#endregion
/// <summary>
/// The function is called when the camera source is changed.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">Occur when enum CameraSourceRef is undefined.</exception>
private void CameraStateChangingProcessing()
{
// Update layer mask
// Update vertical fov
// Update last position and rotation (avoid camera pose interpolation between two different camera source)
// Update is frustum showed
LayerMask layerMaskValue;
float verticalFovValue;
Transform changeCameraTransform;
switch (CameraSourceRef)
{
case SpectatorCameraHelper.CameraSourceRef.Hmd:
{
if (IsMainCameraExist())
{
layerMaskValue = LayerMask;
verticalFovValue = VerticalFov;
changeCameraTransform = SpectatorCameraBased.GetMainCamera().transform;
}
else
{
Debug.LogWarning("Main camera does not exist in the scene");
return;
}
}
break;
case SpectatorCameraHelper.CameraSourceRef.Tracker:
{
if (!IsFollowTrackerExist())
{
if (spectatorCameraTrackerList == null)
{
spectatorCameraTrackerList = new List<SpectatorCameraTracker>();
}
if (SpectatorCameraTrackerList.Count > 0)
{
// If there is no tracker assign to the FollowSpectatorCameraTracker and there are
// some trackers in the scene, change to use the first tracker in the list as default
FollowSpectatorCameraTracker = SpectatorCameraTrackerList[0];
}
else
{
// If there is no tracker in the scene, change to use the HMD as default
CameraSourceRef = SpectatorCameraHelper.CameraSourceRef.Hmd;
return;
}
}
layerMaskValue = FollowSpectatorCameraTracker.LayerMask;
verticalFovValue = FollowSpectatorCameraTracker.VerticalFov;
changeCameraTransform = FollowSpectatorCameraTracker.transform;
}
break;
default:
throw new ArgumentOutOfRangeException();
}
CameraLastPosition = changeCameraTransform.position;
CameraLastRotation = changeCameraTransform.rotation;
SpectatorCameraBased.SpectatorCamera.cullingMask = layerMaskValue;
SpectatorCameraBased.SpectatorCamera.fieldOfView = verticalFovValue;
SetupFrustum();
}
/// <summary>
/// Sometimes, if the user sets material on both SpectatorCameraBased or SpectatorCameraManager script,
/// always use the top of control setting (aka SpectatorCameraManager) as a final value.
/// This function will be called when the recording is started.
/// </summary>
private void SetSpectatorCameraViewMaterial()
{
if (SpectatorCameraViewMaterial ||
SpectatorCameraBased.SpectatorCameraViewMaterial != SpectatorCameraViewMaterial)
{
SpectatorCameraBased.SpectatorCameraViewMaterial = SpectatorCameraViewMaterial;
}
}
#region Unity lifecycle event functions
private IEnumerator Start()
{
InitSuccess = false;
#if !UNITY_EDITOR && UNITY_ANDROID
// To check, "XR_MSFT_first_person_observer" is enough because it
// requires "XR_MSFT_secondary_view_configuration" to be enabled also.
if (!ViveFirstPersonObserver.IsExtensionEnabled())
{
Debug.LogWarning(
$"The OpenXR extension, {ViveSecondaryViewConfiguration.OPEN_XR_EXTENSION_STRING} " +
$"or {ViveFirstPersonObserver.OPEN_XR_EXTENSION_STRING}, is disabled. " +
"Please enable the extension before building the app.");
Debug.Log("Destroy the SpectatorCameraManager");
Destroy(this);
yield break;
}
#endif
if (_instance != null && _instance != this)
{
Debug.Log("Destroy the SpectatorCameraManager because it already exist one instance.");
Destroy(this);
yield break;
}
_instance = this;
// To prevent this from being destroyed on load, check whether this gameObject has a parent;
// if so, set it to no game parent.
if (transform.parent != null)
{
transform.SetParent(null);
}
DontDestroyOnLoad(_instance.gameObject);
if (!SpectatorCameraBased)
{
_ = new GameObject("Spectator Camera Base", typeof(SpectatorCameraBased))
{
transform =
{
position = Vector3.zero,
rotation = Quaternion.identity
}
};
// Wait one frame for creating SpectatorCameraBased.
yield return null;
}
SpectatorCameraBased.SetViewFromHmd(CameraSourceRef == SpectatorCameraHelper.CameraSourceRef.Hmd);
if (SpectatorCameraViewMaterial)
{
SpectatorCameraBased.SpectatorCameraViewMaterial = SpectatorCameraViewMaterial;
}
// Create camera
CreateCameraPrefab();
// Init camera parameter
InitCameraPose();
InitCameraFov();
InitCameraLayerMask();
// Register event
SpectatorCameraBased.OnSpectatorStart += SetupFrustum;
SpectatorCameraBased.OnSpectatorStart += SetSpectatorCameraViewMaterial;
SpectatorCameraBased.OnSpectatorStop += SetupFrustum;
SceneManager.sceneLoaded += OnSceneLoaded;
// For opening the app the first time, manually call SetupFrustum to
// avoid missing show frustum if the user is already recording
SetupFrustum();
InitSuccess = true;
yield break;
// Create the spectator camera (prefab)
void CreateCameraPrefab()
{
if (SpectatorCameraPrefab)
{
SpectatorCamera = Instantiate(SpectatorCameraPrefab);
}
else
{
SpectatorCamera = GameObject.CreatePrimitive(PrimitiveType.Sphere);
SpectatorCamera.transform.localScale =
SpectatorCameraHelper.SpectatorCameraSpherePrefabScaleDefault;
}
SpectatorCameraBased.SpectatorCameraGameObject.transform.SetParent(SpectatorCamera.transform, false);
DontDestroyOnLoad(SpectatorCamera);
}
// Init the camera pose
void InitCameraPose()
{
Vector3 position;
Quaternion rotation;
switch (CameraSourceRef)
{
case SpectatorCameraHelper.CameraSourceRef.Hmd:
{
if (IsMainCameraExist())
{
Transform mainCameraTransform = SpectatorCameraBased.GetMainCamera().transform;
position = mainCameraTransform.position;
rotation = mainCameraTransform.rotation;
}
else
{
Debug.LogWarning("Main camera does not exist in the scene");
return;
}
}
break;
case SpectatorCameraHelper.CameraSourceRef.Tracker:
{
if (!IsFollowTrackerExist())
{
if (spectatorCameraTrackerList == null)
{
spectatorCameraTrackerList = new List<SpectatorCameraTracker>();
}
if (SpectatorCameraTrackerList.Count > 0)
{
// If there is no tracker assign to the FollowSpectatorCameraTracker and there are
// some trackers in the scene, change to use the first tracker in the list as default
FollowSpectatorCameraTracker = SpectatorCameraTrackerList[0];
}
else
{
// If there is no tracker in the scene, change to use the HMD as default
CameraSourceRef = SpectatorCameraHelper.CameraSourceRef.Hmd;
return;
}
}
Transform followTrackerTransform = FollowSpectatorCameraTracker.transform;
position = followTrackerTransform.position;
rotation = followTrackerTransform.rotation;
}
break;
default:
throw new ArgumentOutOfRangeException();
}
CameraLastPosition = position;
CameraLastRotation = rotation;
// Init camera prefab position
SpectatorCamera.transform.position = position;
// Init camera prefab rotation
SpectatorCamera.transform.rotation = rotation;
}
// Init the camera FOV
void InitCameraFov()
{
float fov;
switch (CameraSourceRef)
{
case SpectatorCameraHelper.CameraSourceRef.Hmd:
{
if (IsMainCameraExist())
{
fov = VerticalFov;
}
else
{
Debug.LogWarning("Main camera does not exist in the scene");
return;
}
}
break;
case SpectatorCameraHelper.CameraSourceRef.Tracker:
{
if (!IsFollowTrackerExist())
{
if (spectatorCameraTrackerList == null)
{
spectatorCameraTrackerList = new List<SpectatorCameraTracker>();
}
if (SpectatorCameraTrackerList.Count > 0)
{
// If there is no tracker assign to the FollowSpectatorCameraTracker and there are
// some trackers in the scene, change to use the first tracker in the list as default
FollowSpectatorCameraTracker = SpectatorCameraTrackerList[0];
}
else
{
// If there is no tracker in the scene, change to use the HMD as default
CameraSourceRef = SpectatorCameraHelper.CameraSourceRef.Hmd;
return;
}
}
fov = FollowSpectatorCameraTracker.VerticalFov;
}
break;
default:
throw new ArgumentOutOfRangeException();
}
SpectatorCameraBased.SpectatorCamera.fieldOfView = fov;
}
// Init the camera layer mask
void InitCameraLayerMask()
{
LayerMask layerMaskValue;
switch (CameraSourceRef)
{
case SpectatorCameraHelper.CameraSourceRef.Hmd:
{
if (IsMainCameraExist())
{
layerMaskValue = SpectatorCameraBased.GetMainCamera().cullingMask;
}
else
{
Debug.LogWarning("Main camera does not exist in the scene");
return;
}
}
break;
case SpectatorCameraHelper.CameraSourceRef.Tracker:
{
if (!IsFollowTrackerExist())
{
if (spectatorCameraTrackerList == null)
{
spectatorCameraTrackerList = new List<SpectatorCameraTracker>();
}
if (SpectatorCameraTrackerList.Count > 0)
{
// If there is no tracker assign to the FollowSpectatorCameraTracker and there are
// some trackers in the scene, change to use the first tracker in the list as default
FollowSpectatorCameraTracker = SpectatorCameraTrackerList[0];
}
else
{
// If there is no tracker in the scene, change to use the HMD as default
CameraSourceRef = SpectatorCameraHelper.CameraSourceRef.Hmd;
return;
}
}
layerMaskValue = FollowSpectatorCameraTracker.LayerMask;
}
break;
default:
throw new ArgumentOutOfRangeException();
}
SpectatorCameraBased.SpectatorCamera.cullingMask = layerMaskValue;
}
}
private void LateUpdate()
{
if (!InitSuccess)
{
return;
}
Vector3 position;
Quaternion rotation;
bool isSmoothCameraMovementValue;
float smoothCameraMovementSpeedValue;
switch (CameraSourceRef)
{
case SpectatorCameraHelper.CameraSourceRef.Hmd:
{
if (IsMainCameraExist())
{
Transform mainCameraTransform = SpectatorCameraBased.GetMainCamera().transform;
position = mainCameraTransform.position;
rotation = mainCameraTransform.rotation;
isSmoothCameraMovementValue = IsSmoothCameraMovement;
smoothCameraMovementSpeedValue = SmoothCameraMovementSpeed;
}
else
{
Debug.LogWarning("Main camera does not exist in the scene");
return;
}
}
break;
case SpectatorCameraHelper.CameraSourceRef.Tracker:
{
if (!IsFollowTrackerExist())
{
if (spectatorCameraTrackerList == null)
{
spectatorCameraTrackerList = new List<SpectatorCameraTracker>();
}
if (SpectatorCameraTrackerList.Count > 0)
{
// If there is no tracker assign to the FollowSpectatorCameraTracker and there are
// some trackers in the scene, change to use the first tracker in the list as default
FollowSpectatorCameraTracker = SpectatorCameraTrackerList[0];
}
else
{
// If there is no tracker in the scene, change to use the HMD as default
CameraSourceRef = SpectatorCameraHelper.CameraSourceRef.Hmd;
return;
}
}
isSmoothCameraMovementValue = FollowSpectatorCameraTracker.IsSmoothCameraMovement;
smoothCameraMovementSpeedValue = FollowSpectatorCameraTracker.SmoothCameraMovementSpeed;
Transform trackerTransform = FollowSpectatorCameraTracker.transform;
position = trackerTransform.position;
rotation = trackerTransform.rotation;
}
break;
default:
throw new ArgumentOutOfRangeException();
}
if (isSmoothCameraMovementValue)
{
position = Vector3.Lerp(CameraLastPosition, position, Time.deltaTime * smoothCameraMovementSpeedValue);
// To avoid problem:
// https://discussions.unity.com/t/error-compareapproximately-ascalar-0-0f-with-quaternion-lerp/161461/2
float t = Mathf.Clamp(Time.deltaTime * smoothCameraMovementSpeedValue, 0f, 1 - Mathf.Epsilon);
rotation = Quaternion.Lerp(CameraLastRotation, rotation, t);
}
CameraLastPosition = position;
CameraLastRotation = rotation;
// Set camera prefab position and rotation
SpectatorCamera.transform.position = position;
SpectatorCamera.transform.rotation = rotation;
}
private void OnDestroy()
{
if (!InitSuccess)
{
return;
}
Debug.Log("OnDestroy");
if (SpectatorCameraBased)
{
SpectatorCameraBased.SpectatorCameraGameObject.transform.SetParent(null);
SpectatorCameraBased.OnSpectatorStart -= SetupFrustum;
SpectatorCameraBased.OnSpectatorStart -= SetSpectatorCameraViewMaterial;
SpectatorCameraBased.OnSpectatorStop -= SetupFrustum;
}
SceneManager.sceneLoaded -= OnSceneLoaded;
Destroy(SpectatorCamera);
_instance = null;
}
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
if (!InitSuccess)
{
return;
}
Debug.Log($"OnSceneLoaded: {scene.name}");
// Reset camera reference source
CameraSourceRef = SpectatorCameraHelper.CameraSourceRef.Hmd;
// Reset Tracker and tracker list
FollowSpectatorCameraTracker = null;
spectatorCameraTrackerList?.Clear();
}
#endregion
}
}