holopy3/Assets/Normal/Realtime/Avatars/RealtimeAvatar.cs
2020-12-10 15:25:12 +01:00

401 lines
No EOL
16 KiB
C#

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR;
using Normal.Realtime.Serialization;
using Normal.Utility;
namespace Normal.Realtime {
[ExecutionOrder(-95)] // Make sure our Update() runs before the default to so that the avatar positions are as up to date as possible when everyone else's Update() runs.
public class RealtimeAvatar : RealtimeComponent {
// Local Player
[Serializable]
public class LocalPlayer {
public Transform root;
public Transform head;
public Transform leftHand;
public Transform rightHand;
}
public LocalPlayer localPlayer { get { return _localPlayer; } set { SetLocalPlayer(value); } }
#pragma warning disable 0649 // Disable variable is never assigned to warning.
private LocalPlayer _localPlayer;
#pragma warning restore 0649
// Device Type
public enum DeviceType : uint {
Unknown = 0,
OpenVR = 1,
Oculus = 2,
}
public DeviceType deviceType { get { return DeviceTypeFromUInt(_model.deviceType); } set { _model.deviceType = DeviceTypeToUInt(value); } }
// Prefab
public Transform head { get { return _head; } }
public Transform leftHand { get { return _leftHand; } }
public Transform rightHand { get { return _rightHand; } }
#pragma warning disable 0649 // Disable variable is never assigned to warning.
[SerializeField] private Transform _head;
[SerializeField] private Transform _leftHand;
[SerializeField] private Transform _rightHand;
#pragma warning restore 0649
// Serialization
private RealtimeAvatarModel _model;
public RealtimeAvatarModel model { get { return _model; } set { SetModel(value); } }
private RealtimeAvatarManager _realtimeAvatarManager;
private static List<XRNodeState> _nodeStates = new List<XRNodeState>();
void Start() {
// Register with RealtimeAvatarManager
try {
_realtimeAvatarManager = realtime.GetComponent<RealtimeAvatarManager>();
_realtimeAvatarManager._RegisterAvatar(realtimeView.ownerID, this);
} catch (NullReferenceException) {
Debug.LogError("RealtimeAvatar failed to register with RealtimeAvatarManager component. Was this avatar prefab instantiated by RealtimeAvatarManager?");
}
}
void OnDestroy() {
// Unregister with RealtimeAvatarManager
if (_realtimeAvatarManager != null)
_realtimeAvatarManager._UnregisterAvatar(this);
// Unregister for events
localPlayer = null;
}
void FixedUpdate() {
UpdateAvatarTransformsForLocalPlayer();
}
void Update() {
UpdateAvatarTransformsForLocalPlayer();
}
void LateUpdate() {
UpdateAvatarTransformsForLocalPlayer();
}
void SetModel(RealtimeAvatarModel model) {
if (model == _model)
return;
if (_model != null) {
_model.activeStateChanged -= ActiveStateChanged;
}
_model = model;
if (_model != null) {
_model.activeStateChanged += ActiveStateChanged;
}
}
void SetLocalPlayer(LocalPlayer localPlayer) {
if (localPlayer == _localPlayer)
return;
_localPlayer = localPlayer;
if (_localPlayer != null) {
// TODO: Technically this shouldn't be needed. The RealtimeViewModel is created and locked to a user. The owner of that should progagate to children...
RealtimeTransform rootRealtimeTransform = GetComponent<RealtimeTransform>();
RealtimeTransform headRealtimeTransform = _head != null ? _head.GetComponent<RealtimeTransform>() : null;
RealtimeTransform leftHandRealtimeTransform = _leftHand != null ? _leftHand.GetComponent<RealtimeTransform>() : null;
RealtimeTransform rightHandRealtimeTransform = _rightHand != null ? _rightHand.GetComponent<RealtimeTransform>() : null;
if ( rootRealtimeTransform != null) rootRealtimeTransform.RequestOwnership();
if ( headRealtimeTransform != null) headRealtimeTransform.RequestOwnership();
if ( leftHandRealtimeTransform != null) leftHandRealtimeTransform.RequestOwnership();
if (rightHandRealtimeTransform != null) rightHandRealtimeTransform.RequestOwnership();
}
}
void ActiveStateChanged(RealtimeAvatarModel model, uint activeState) {
if (model != _model)
return;
// if (_head != null) _head.gameObject.SetActive(model.headActive); // I deprecated this because it will cause RealtimeAvatarVoice to not run when the head isn't tracking...
if ( _leftHand != null) _leftHand.gameObject.SetActive(model.leftHandActive);
if (_rightHand != null) _rightHand.gameObject.SetActive(model.rightHandActive);
}
void UpdateAvatarTransformsForLocalPlayer() {
// Make sure this avatar is a local player
if (_localPlayer == null)
return;
// Flags to fetch XRNode position/rotation state
bool updateHeadWithXRNode = false;
bool updateLeftHandWithXRNode = false;
bool updateRightHandWithXRNode = false;
// Root
if (_localPlayer.root != null) {
transform.position = _localPlayer.root.position;
transform.rotation = _localPlayer.root.rotation;
transform.localScale = _localPlayer.root.localScale;
}
// Head
if (_localPlayer.head != null) {
_model.headActive = _localPlayer.head.gameObject.activeSelf;
_head.position = _localPlayer.head.position;
_head.rotation = _localPlayer.head.rotation;
} else {
updateHeadWithXRNode = true;
}
// Left Hand
if (_leftHand != null) {
if (_localPlayer.leftHand != null) {
_model.leftHandActive = _localPlayer.leftHand.gameObject.activeSelf;
_leftHand.position = _localPlayer.leftHand.position;
_leftHand.rotation = _localPlayer.leftHand.rotation;
} else {
updateLeftHandWithXRNode = true;
}
}
// Right Hand
if (_rightHand != null) {
if (_localPlayer.rightHand != null) {
_model.rightHandActive = _localPlayer.rightHand.gameObject.activeSelf;
_rightHand.position = _localPlayer.rightHand.position;
_rightHand.rotation = _localPlayer.rightHand.rotation;
} else {
updateRightHandWithXRNode = true;
}
}
// Update head/hands using XRNode APIs if needed
if (updateHeadWithXRNode || updateLeftHandWithXRNode || updateRightHandWithXRNode) {
_nodeStates.Clear();
InputTracking.GetNodeStates(_nodeStates);
bool headActive = false;
bool leftHandActive = false;
bool rightHandActive = false;
foreach (XRNodeState nodeState in _nodeStates) {
if (nodeState.nodeType == XRNode.Head && updateHeadWithXRNode) {
headActive = nodeState.tracked;
Vector3 position;
if (nodeState.TryGetPosition(out position))
_head.localPosition = position;
Quaternion rotation;
if (nodeState.TryGetRotation(out rotation))
_head.localRotation = rotation;
} else if (nodeState.nodeType == XRNode.LeftHand && updateLeftHandWithXRNode) {
leftHandActive = nodeState.tracked;
Vector3 position;
if (nodeState.TryGetPosition(out position))
_leftHand.localPosition = position;
Quaternion rotation;
if (nodeState.TryGetRotation(out rotation))
_leftHand.localRotation = rotation;
} else if (nodeState.nodeType == XRNode.RightHand && updateRightHandWithXRNode) {
rightHandActive = nodeState.tracked;
Vector3 position;
if (nodeState.TryGetPosition(out position))
_rightHand.localPosition = position;
Quaternion rotation;
if (nodeState.TryGetRotation(out rotation))
_rightHand.localRotation = rotation;
}
}
if ( updateHeadWithXRNode) _model.headActive = headActive;
if ( updateLeftHandWithXRNode) _model.leftHandActive = leftHandActive;
if (updateRightHandWithXRNode) _model.rightHandActive = rightHandActive;
}
}
private static uint DeviceTypeToUInt(DeviceType deviceType) {
return (uint)deviceType;
}
private static DeviceType DeviceTypeFromUInt(uint deviceType) {
switch (deviceType) {
case 1:
return DeviceType.OpenVR;
case 2:
return DeviceType.Oculus;
default:
return DeviceType.Unknown;
}
}
}
public class RealtimeAvatarModel : IModel {
public bool headActive { get { return (activeState & (uint)ActiveStateFlags.HeadActive) != 0; } set { SetHeadActive(value); } }
public bool leftHandActive { get { return (activeState & (uint)ActiveStateFlags.LeftHandActive) != 0; } set { SetLeftHandActive(value); } }
public bool rightHandActive { get { return (activeState & (uint)ActiveStateFlags.RightHandActive) != 0; } set { SetRightHandActive(value); } }
[Flags]
private enum ActiveStateFlags : uint {
Default = 0,
HeadActive = 1 << 0,
LeftHandActive = 1 << 1,
RightHandActive = 1 << 2,
}
private uint _activeState = 0;
private uint activeState {
get { return _cache.LookForValueInCache(_activeState, entry => entry.activeStateSet, entry => entry.activeState); }
set { _cache.UpdateLocalCache(entry => { entry.activeStateSet = true; entry.activeState = value; return entry; }); FireActiveStateChanged(); }
}
public delegate void ActiveStateChanged(RealtimeAvatarModel model, uint activeState);
public event ActiveStateChanged activeStateChanged;
private uint _deviceType = 0;
public uint deviceType {
get { return _cache.LookForValueInCache(_deviceType, entry => entry.deviceTypeSet, entry => entry.deviceType); }
set { _cache.UpdateLocalCache(entry => { entry.deviceTypeSet = true; entry.deviceType = value; return entry; }); }
}
// Serialization
private enum Properties : uint {
ActiveState = 1,
DeviceType = 2,
}
private struct LocalCacheEntry {
public bool activeStateSet;
public uint activeState;
public bool deviceTypeSet;
public uint deviceType;
}
private LocalChangeCache<LocalCacheEntry> _cache;
public RealtimeAvatarModel() {
_cache = new LocalChangeCache<LocalCacheEntry>();
}
// Properties
void SetHeadActive(bool active) {
if (active == headActive)
return;
if (active)
activeState |= (uint)ActiveStateFlags.HeadActive;
else
activeState &= ~((uint)ActiveStateFlags.HeadActive);
}
void SetLeftHandActive(bool active) {
if (active == leftHandActive)
return;
if (active)
activeState |= (uint)ActiveStateFlags.LeftHandActive;
else
activeState &= ~((uint)ActiveStateFlags.LeftHandActive);
}
void SetRightHandActive(bool active) {
if (active == rightHandActive)
return;
if (active)
activeState |= (uint)ActiveStateFlags.RightHandActive;
else
activeState &= ~((uint)ActiveStateFlags.RightHandActive);
}
// Events
void FireActiveStateChanged() {
if (activeStateChanged != null) {
try {
activeStateChanged(this, activeState);
} catch (Exception exception) {
Debug.LogException(exception);
}
}
}
// Serialization
public int WriteLength(StreamContext context) {
int length = 0;
if (context.fullModel) {
// Flatten cache
_activeState = activeState;
_deviceType = deviceType;
_cache.Clear();
// Active state
length += WriteStream.WriteVarint32Length((uint)Properties.ActiveState, _activeState);
// Device type
length += WriteStream.WriteVarint32Length((uint)Properties.DeviceType, _deviceType);
} else {
// Active state
if (context.reliableChannel) {
LocalCacheEntry entry = _cache.localCache;
if (entry.activeStateSet)
length += WriteStream.WriteVarint32Length((uint)Properties.ActiveState, entry.activeState);
if (entry.deviceTypeSet)
length += WriteStream.WriteVarint32Length((uint)Properties.DeviceType, entry.deviceType);
}
}
return length;
}
public void Write(WriteStream stream, StreamContext context) {
if (context.fullModel) {
// Active state
stream.WriteVarint32((uint)Properties.ActiveState, _activeState);
// Device type
stream.WriteVarint32((uint)Properties.DeviceType, _deviceType);
} else {
// Active state
if (context.reliableChannel) {
// If we're going to send an update. Push the cache to inflight.
LocalCacheEntry entry = _cache.localCache;
if (entry.activeStateSet || entry.deviceTypeSet)
_cache.PushLocalCacheToInflight(context.updateID);
if (entry.activeStateSet)
stream.WriteVarint32((uint)Properties.ActiveState, entry.activeState);
if (entry.deviceTypeSet)
stream.WriteVarint32((uint)Properties.DeviceType, entry.deviceType);
}
}
}
public void Read(ReadStream stream, StreamContext context) {
// Remove from in-flight
if (context.deltaUpdatesOnly && context.reliableChannel)
_cache.RemoveUpdateFromInflight(context.updateID);
// Read properties
uint propertyID;
while (stream.ReadNextPropertyID(out propertyID)) {
switch (propertyID) {
case (uint)Properties.ActiveState:
_activeState = stream.ReadVarint32();
FireActiveStateChanged();
break;
case (uint)Properties.DeviceType:
_deviceType = stream.ReadVarint32();
break;
default:
stream.SkipProperty();
break;
}
}
}
}
}