using UnityEngine; using System.Collections; using System; using System.Linq; using Oculus.Avatar; using System.Runtime.InteropServices; using System.Collections.Generic; using UnityEngine.Events; #if UNITY_EDITOR using UnityEditor; #endif [System.Serializable] public class AvatarLayer { public int layerIndex; } #if UNITY_EDITOR [CustomPropertyDrawer(typeof(AvatarLayer))] public class AvatarLayerPropertyDrawer : PropertyDrawer { public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { EditorGUI.BeginProperty(position, GUIContent.none, property); SerializedProperty layerIndex = property.FindPropertyRelative("layerIndex"); position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label); layerIndex.intValue = EditorGUI.LayerField(position, layerIndex.intValue); EditorGUI.EndProperty(); } } #endif [System.Serializable] public class PacketRecordSettings { internal bool RecordingFrames = false; public float UpdateRate = 1f / 30f; // 30 hz update of packets internal float AccumulatedTime; }; public class OvrAvatar : MonoBehaviour { public OvrAvatarMaterialManager DefaultBodyMaterialManager; public OvrAvatarMaterialManager DefaultHandMaterialManager; public OvrAvatarDriver Driver; public OvrAvatarBase Base; public OvrAvatarBody Body; public OvrAvatarTouchController ControllerLeft; public OvrAvatarTouchController ControllerRight; public OvrAvatarHand HandLeft; public OvrAvatarHand HandRight; public bool RecordPackets; public bool UseSDKPackets = true; public bool StartWithControllers; public AvatarLayer FirstPersonLayer; public AvatarLayer ThirdPersonLayer; public bool ShowFirstPerson = true; public bool ShowThirdPerson; public ovrAvatarCapabilities Capabilities = ovrAvatarCapabilities.All; public Shader SurfaceShader; public Shader SurfaceShaderSelfOccluding; public Shader SurfaceShaderPBS; public Shader SurfaceShaderPBSV2Single; public Shader SurfaceShaderPBSV2Combined; public Shader SurfaceShaderPBSV2Simple; public Shader SurfaceShaderPBSV2Loading; int renderPartCount = 0; bool showLeftController; bool showRightController; List voiceUpdates = new List(); public string oculusUserID; internal UInt64 oculusUserIDInternal; #if UNITY_ANDROID && UNITY_5_5_OR_NEWER && !UNITY_EDITOR bool CombineMeshes = true; #else bool CombineMeshes = false; #endif #if UNITY_EDITOR && UNITY_ANDROID bool ForceMobileTextureFormat = true; #else bool ForceMobileTextureFormat = false; #endif private bool WaitingForCombinedMesh = false; public IntPtr sdkAvatar = IntPtr.Zero; private HashSet assetLoadingIds = new HashSet(); private Dictionary trackedComponents = new Dictionary(); private UnityEvent AssetsDoneLoading = new UnityEvent(); bool assetsFinishedLoading = false; public Transform LeftHandCustomPose; public Transform RightHandCustomPose; Transform cachedLeftHandCustomPose; Transform[] cachedCustomLeftHandJoints; ovrAvatarTransform[] cachedLeftHandTransforms; Transform cachedRightHandCustomPose; Transform[] cachedCustomRightHandJoints; ovrAvatarTransform[] cachedRightHandTransforms; private Vector4 clothingAlphaOffset = new Vector4(0f, 0f, 0f, 1f); private UInt64 clothingAlphaTexture = 0; public class PacketEventArgs : EventArgs { public readonly OvrAvatarPacket Packet; public PacketEventArgs(OvrAvatarPacket packet) { Packet = packet; } } public PacketRecordSettings PacketSettings = new PacketRecordSettings(); OvrAvatarPacket CurrentUnityPacket; public enum HandType { Right, Left, Max }; public enum HandJoint { HandBase, IndexBase, IndexTip, ThumbBase, ThumbTip, Max, } private static string[,] HandJoints = new string[(int)HandType.Max, (int)HandJoint.Max] { { "hands:r_hand_world", "hands:r_hand_world/hands:b_r_hand/hands:b_r_index1", "hands:r_hand_world/hands:b_r_hand/hands:b_r_index1/hands:b_r_index2/hands:b_r_index3/hands:b_r_index_ignore", "hands:r_hand_world/hands:b_r_hand/hands:b_r_thumb1/hands:b_r_thumb2", "hands:r_hand_world/hands:b_r_hand/hands:b_r_thumb1/hands:b_r_thumb2/hands:b_r_thumb3/hands:b_r_thumb_ignore" }, { "hands:l_hand_world", "hands:l_hand_world/hands:b_l_hand/hands:b_l_index1", "hands:l_hand_world/hands:b_l_hand/hands:b_l_index1/hands:b_l_index2/hands:b_l_index3/hands:b_l_index_ignore", "hands:l_hand_world/hands:b_l_hand/hands:b_l_thumb1/hands:b_l_thumb2", "hands:l_hand_world/hands:b_l_hand/hands:b_l_thumb1/hands:b_l_thumb2/hands:b_l_thumb3/hands:b_l_thumb_ignore" } }; #if UNITY_ANDROID internal ovrAvatarAssetLevelOfDetail LevelOfDetail = ovrAvatarAssetLevelOfDetail.Medium; #else internal ovrAvatarAssetLevelOfDetail LevelOfDetail = ovrAvatarAssetLevelOfDetail.Highest; #endif void OnDestroy() { if (sdkAvatar != IntPtr.Zero) { CAPI.ovrAvatar_Destroy(sdkAvatar); } } public EventHandler PacketRecorded; public void AssetLoadedCallback(OvrAvatarAsset asset) { assetLoadingIds.Remove(asset.assetID); } public void CombinedMeshLoadedCallback(IntPtr assetPtr) { if (!WaitingForCombinedMesh) { return; } var meshIDs = CAPI.ovrAvatarAsset_GetCombinedMeshIDs(assetPtr); foreach (var id in meshIDs) { assetLoadingIds.Remove(id); } CAPI.ovrAvatar_GetCombinedMeshAlphaData(sdkAvatar, ref clothingAlphaTexture, ref clothingAlphaOffset); WaitingForCombinedMesh = false; } private void AddAvatarComponent(GameObject componentObject, ovrAvatarComponent component) { OvrAvatarComponent ovrComponent = componentObject.AddComponent(); trackedComponents.Add(component.name, ovrComponent); if (ovrComponent.name == "body") { ovrComponent.ClothingAlphaOffset = clothingAlphaOffset; ovrComponent.ClothingAlphaTexture = clothingAlphaTexture; } AddRenderParts(ovrComponent, component, componentObject.transform); } private OvrAvatarSkinnedMeshRenderComponent AddSkinnedMeshRenderComponent(GameObject gameObject, ovrAvatarRenderPart_SkinnedMeshRender skinnedMeshRender) { OvrAvatarSkinnedMeshRenderComponent skinnedMeshRenderer = gameObject.AddComponent(); skinnedMeshRenderer.Initialize(skinnedMeshRender, SurfaceShader, SurfaceShaderSelfOccluding, ThirdPersonLayer.layerIndex, FirstPersonLayer.layerIndex, renderPartCount++); return skinnedMeshRenderer; } private OvrAvatarSkinnedMeshRenderPBSComponent AddSkinnedMeshRenderPBSComponent(GameObject gameObject, ovrAvatarRenderPart_SkinnedMeshRenderPBS skinnedMeshRenderPBS) { OvrAvatarSkinnedMeshRenderPBSComponent skinnedMeshRenderer = gameObject.AddComponent(); skinnedMeshRenderer.Initialize(skinnedMeshRenderPBS, SurfaceShaderPBS, ThirdPersonLayer.layerIndex, FirstPersonLayer.layerIndex, renderPartCount++); return skinnedMeshRenderer; } private OvrAvatarSkinnedMeshPBSV2RenderComponent AddSkinnedMeshRenderPBSV2Component( IntPtr renderPart, GameObject gameObject, ovrAvatarRenderPart_SkinnedMeshRenderPBS_V2 skinnedMeshRenderPBSV2, OvrAvatarMaterialManager materialManager) { OvrAvatarSkinnedMeshPBSV2RenderComponent skinnedMeshRenderer = gameObject.AddComponent(); skinnedMeshRenderer.Initialize( renderPart, skinnedMeshRenderPBSV2, materialManager, ThirdPersonLayer.layerIndex, FirstPersonLayer.layerIndex, renderPartCount++, gameObject.name.Contains("body") && CombineMeshes, LevelOfDetail); return skinnedMeshRenderer; } private OvrAvatarProjectorRenderComponent AddProjectorRenderComponent(GameObject gameObject, ovrAvatarRenderPart_ProjectorRender projectorRender) { ovrAvatarComponent component = CAPI.ovrAvatarComponent_Get(sdkAvatar, projectorRender.componentIndex); OvrAvatarComponent ovrComponent; if (trackedComponents.TryGetValue(component.name, out ovrComponent)) { if (projectorRender.renderPartIndex < ovrComponent.RenderParts.Count) { OvrAvatarRenderComponent targetRenderPart = ovrComponent.RenderParts[(int)projectorRender.renderPartIndex]; OvrAvatarProjectorRenderComponent projectorComponent = gameObject.AddComponent(); projectorComponent.InitializeProjectorRender(projectorRender, SurfaceShader, targetRenderPart); return projectorComponent; } } return null; } static public IntPtr GetRenderPart(ovrAvatarComponent component, UInt32 renderPartIndex) { long offset = Marshal.SizeOf(typeof(IntPtr)) * renderPartIndex; IntPtr marshalPtr = new IntPtr(component.renderParts.ToInt64() + offset); return (IntPtr)Marshal.PtrToStructure(marshalPtr, typeof(IntPtr)); } private void UpdateAvatarComponent(ovrAvatarComponent component) { OvrAvatarComponent ovrComponent; if (!trackedComponents.TryGetValue(component.name, out ovrComponent)) { throw new Exception(string.Format("trackedComponents didn't have {0}", component.name)); } ovrComponent.UpdateAvatar(component, this); } private static string GetRenderPartName(ovrAvatarComponent component, uint renderPartIndex) { return component.name + "_renderPart_" + (int)renderPartIndex; } internal static void ConvertTransform(ovrAvatarTransform transform, Transform target) { Vector3 position = transform.position; position.z = -position.z; Quaternion orientation = transform.orientation; orientation.x = -orientation.x; orientation.y = -orientation.y; target.localPosition = position; target.localRotation = orientation; target.localScale = transform.scale; } public static ovrAvatarTransform CreateOvrAvatarTransform(Vector3 position, Quaternion orientation) { return new ovrAvatarTransform { position = new Vector3(position.x, position.y, -position.z), orientation = new Quaternion(-orientation.x, -orientation.y, orientation.z, orientation.w), scale = Vector3.one }; } private void RemoveAvatarComponent(string name) { OvrAvatarComponent componentObject; trackedComponents.TryGetValue(name, out componentObject); Destroy(componentObject.gameObject); trackedComponents.Remove(name); } private void UpdateSDKAvatarUnityState() { //Iterate through all the render components UInt32 componentCount = CAPI.ovrAvatarComponent_Count(sdkAvatar); HashSet componentsThisRun = new HashSet(); for (UInt32 i = 0; i < componentCount; i++) { IntPtr ptr = CAPI.ovrAvatarComponent_Get_Native(sdkAvatar, i); ovrAvatarComponent component = (ovrAvatarComponent)Marshal.PtrToStructure(ptr, typeof(ovrAvatarComponent)); componentsThisRun.Add(component.name); if (!trackedComponents.ContainsKey(component.name)) { GameObject componentObject = null; Type specificType = null; if ((Capabilities & ovrAvatarCapabilities.Base) != 0) { ovrAvatarBaseComponent? baseComponent = CAPI.ovrAvatarPose_GetBaseComponent(sdkAvatar); if (baseComponent.HasValue && ptr == baseComponent.Value.renderComponent) { specificType = typeof(OvrAvatarBase); if (Base != null) { componentObject = Base.gameObject; } } } if (specificType == null && (Capabilities & ovrAvatarCapabilities.Body) != 0) { ovrAvatarBodyComponent? bodyComponent = CAPI.ovrAvatarPose_GetBodyComponent(sdkAvatar); if (bodyComponent.HasValue && ptr == bodyComponent.Value.renderComponent) { specificType = typeof(OvrAvatarBody); if (Body != null) { componentObject = Body.gameObject; } } } if (specificType == null && (Capabilities & ovrAvatarCapabilities.Hands) != 0) { ovrAvatarControllerComponent? controllerComponent = CAPI.ovrAvatarPose_GetLeftControllerComponent(sdkAvatar); if (specificType == null && controllerComponent.HasValue && ptr == controllerComponent.Value.renderComponent) { specificType = typeof(OvrAvatarTouchController); if (ControllerLeft != null) { componentObject = ControllerLeft.gameObject; } } controllerComponent = CAPI.ovrAvatarPose_GetRightControllerComponent(sdkAvatar); if (specificType == null && controllerComponent.HasValue && ptr == controllerComponent.Value.renderComponent) { specificType = typeof(OvrAvatarTouchController); if (ControllerRight != null) { componentObject = ControllerRight.gameObject; } } ovrAvatarHandComponent? handComponent = CAPI.ovrAvatarPose_GetLeftHandComponent(sdkAvatar); if (specificType == null && handComponent.HasValue && ptr == handComponent.Value.renderComponent) { specificType = typeof(OvrAvatarHand); if (HandLeft != null) { componentObject = HandLeft.gameObject; } } handComponent = CAPI.ovrAvatarPose_GetRightHandComponent(sdkAvatar); if (specificType == null && handComponent.HasValue && ptr == handComponent.Value.renderComponent) { specificType = typeof(OvrAvatarHand); if (HandRight != null) { componentObject = HandRight.gameObject; } } } // If this is an unknown type, just create an object for the rendering if (componentObject == null && specificType == null) { componentObject = new GameObject(); componentObject.name = component.name; componentObject.transform.SetParent(transform); } if (componentObject != null) { AddAvatarComponent(componentObject, component); } } UpdateAvatarComponent(component); } HashSet deletableNames = new HashSet(trackedComponents.Keys); deletableNames.ExceptWith(componentsThisRun); //deletableNames contains the name of all components which are tracked and were //not present in this run foreach (var name in deletableNames) { RemoveAvatarComponent(name); } UpdateVoiceBehavior(); } void UpdateCustomPoses() { // Check to see if the pose roots changed if (UpdatePoseRoot(LeftHandCustomPose, ref cachedLeftHandCustomPose, ref cachedCustomLeftHandJoints, ref cachedLeftHandTransforms)) { if (cachedLeftHandCustomPose == null && sdkAvatar != IntPtr.Zero) { CAPI.ovrAvatar_SetLeftHandGesture(sdkAvatar, ovrAvatarHandGesture.Default); } } if (UpdatePoseRoot(RightHandCustomPose, ref cachedRightHandCustomPose, ref cachedCustomRightHandJoints, ref cachedRightHandTransforms)) { if (cachedRightHandCustomPose == null && sdkAvatar != IntPtr.Zero) { CAPI.ovrAvatar_SetRightHandGesture(sdkAvatar, ovrAvatarHandGesture.Default); } } // Check to see if the custom gestures need to be updated if (sdkAvatar != IntPtr.Zero) { if (cachedLeftHandCustomPose != null && UpdateTransforms(cachedCustomLeftHandJoints, cachedLeftHandTransforms)) { CAPI.ovrAvatar_SetLeftHandCustomGesture(sdkAvatar, (uint)cachedLeftHandTransforms.Length, cachedLeftHandTransforms); } if (cachedRightHandCustomPose != null && UpdateTransforms(cachedCustomRightHandJoints, cachedRightHandTransforms)) { CAPI.ovrAvatar_SetRightHandCustomGesture(sdkAvatar, (uint)cachedRightHandTransforms.Length, cachedRightHandTransforms); } } } static bool UpdatePoseRoot(Transform poseRoot, ref Transform cachedPoseRoot, ref Transform[] cachedPoseJoints, ref ovrAvatarTransform[] transforms) { if (poseRoot == cachedPoseRoot) { return false; } if (!poseRoot) { cachedPoseRoot = null; cachedPoseJoints = null; transforms = null; } else { List joints = new List(); OrderJoints(poseRoot, joints); cachedPoseRoot = poseRoot; cachedPoseJoints = joints.ToArray(); transforms = new ovrAvatarTransform[joints.Count]; } return true; } static bool UpdateTransforms(Transform[] joints, ovrAvatarTransform[] transforms) { bool updated = false; for (int i = 0; i < joints.Length; ++i) { Transform joint = joints[i]; ovrAvatarTransform transform = CreateOvrAvatarTransform(joint.localPosition, joint.localRotation); if (transform.position != transforms[i].position || transform.orientation != transforms[i].orientation) { transforms[i] = transform; updated = true; } } return updated; } private static void OrderJoints(Transform transform, List joints) { joints.Add(transform); for (int i = 0; i < transform.childCount; ++i) { Transform child = transform.GetChild(i); OrderJoints(child, joints); } } void AvatarSpecificationCallback(IntPtr avatarSpecification) { #if UNITY_ANDROID Capabilities &= ~ovrAvatarCapabilities.BodyTilt; #endif sdkAvatar = CAPI.ovrAvatar_Create(avatarSpecification, Capabilities); ShowLeftController(showLeftController); ShowRightController(showRightController); //Fetch all the assets that this avatar uses. UInt32 assetCount = CAPI.ovrAvatar_GetReferencedAssetCount(sdkAvatar); for (UInt32 i = 0; i < assetCount; ++i) { UInt64 id = CAPI.ovrAvatar_GetReferencedAsset(sdkAvatar, i); if (OvrAvatarSDKManager.Instance.GetAsset(id) == null) { OvrAvatarSDKManager.Instance.BeginLoadingAsset( id, LevelOfDetail, AssetLoadedCallback); assetLoadingIds.Add(id); } } if (CombineMeshes) { OvrAvatarSDKManager.Instance.RegisterCombinedMeshCallback( sdkAvatar, CombinedMeshLoadedCallback); } } void Start() { #if !UNITY_ANDROID if (CombineMeshes) { CombineMeshes = false; AvatarLogger.Log("Combine Meshes Currently Only Supported On Android"); } #endif #if !UNITY_5_5_OR_NEWER if (CombineMeshes) { CombineMeshes = false; AvatarLogger.LogWarning("Unity Version too old to use Combined Mesh Shader, required 5.5.0+"); } #endif try { oculusUserIDInternal = UInt64.Parse(oculusUserID); } catch (Exception) { oculusUserIDInternal = 0; AvatarLogger.LogWarning("Invalid Oculus User ID Format"); } AvatarLogger.Log("Starting OvrAvatar " + gameObject.name); AvatarLogger.Log(AvatarLogger.Tab + "LOD: " + LevelOfDetail.ToString()); AvatarLogger.Log(AvatarLogger.Tab + "Combine Meshes: " + CombineMeshes); AvatarLogger.Log(AvatarLogger.Tab + "Force Mobile Textures: " + ForceMobileTextureFormat); AvatarLogger.Log(AvatarLogger.Tab + "Oculus User ID: " + oculusUserIDInternal); ShowLeftController(StartWithControllers); ShowRightController(StartWithControllers); OvrAvatarSDKManager.Instance.RequestAvatarSpecification( oculusUserIDInternal, this.AvatarSpecificationCallback, CombineMeshes, LevelOfDetail, ForceMobileTextureFormat); WaitingForCombinedMesh = CombineMeshes; Driver.Mode = UseSDKPackets ? OvrAvatarDriver.PacketMode.SDK : OvrAvatarDriver.PacketMode.Unity; } void Update() { if (sdkAvatar == IntPtr.Zero) { return; } if (Driver != null) { Driver.UpdateTransforms(sdkAvatar); foreach (float[] voiceUpdate in voiceUpdates) { CAPI.ovrAvatarPose_UpdateVoiceVisualization(sdkAvatar, voiceUpdate); } voiceUpdates.Clear(); CAPI.ovrAvatarPose_Finalize(sdkAvatar, Time.deltaTime); } if (RecordPackets) { RecordFrame(); } if (assetLoadingIds.Count == 0) { UpdateSDKAvatarUnityState(); UpdateCustomPoses(); if (!assetsFinishedLoading) { AssetsDoneLoading.Invoke(); assetsFinishedLoading = true; } } } public static ovrAvatarHandInputState CreateInputState(ovrAvatarTransform transform, OvrAvatarDriver.ControllerPose pose) { ovrAvatarHandInputState inputState = new ovrAvatarHandInputState(); inputState.transform = transform; inputState.buttonMask = pose.buttons; inputState.touchMask = pose.touches; inputState.joystickX = pose.joystickPosition.x; inputState.joystickY = pose.joystickPosition.y; inputState.indexTrigger = pose.indexTrigger; inputState.handTrigger = pose.handTrigger; inputState.isActive = pose.isActive; return inputState; } public void ShowControllers(bool show) { ShowLeftController(show); ShowRightController(show); } public void ShowLeftController(bool show) { if (sdkAvatar != IntPtr.Zero) { CAPI.ovrAvatar_SetLeftControllerVisibility(sdkAvatar, show); } showLeftController = show; } public void ShowRightController(bool show) { if (sdkAvatar != IntPtr.Zero) { CAPI.ovrAvatar_SetRightControllerVisibility(sdkAvatar, show); } showRightController = show; } public void UpdateVoiceVisualization(float[] voiceSamples) { voiceUpdates.Add(voiceSamples); } void RecordFrame() { if(UseSDKPackets) { RecordSDKFrame(); } else { RecordUnityFrame(); } } // Meant to be used mutually exclusively with RecordSDKFrame to give user more options to optimize or tweak packet data private void RecordUnityFrame() { var deltaSeconds = Time.deltaTime; var frame = Driver.GetCurrentPose(); // If this is our first packet, store the pose as the initial frame if (CurrentUnityPacket == null) { CurrentUnityPacket = new OvrAvatarPacket(frame); deltaSeconds = 0; } float recordedSeconds = 0; while (recordedSeconds < deltaSeconds) { float remainingSeconds = deltaSeconds - recordedSeconds; float remainingPacketSeconds = PacketSettings.UpdateRate - CurrentUnityPacket.Duration; // If we're not going to fill the packet, just add the frame if (remainingSeconds < remainingPacketSeconds) { CurrentUnityPacket.AddFrame(frame, remainingSeconds); recordedSeconds += remainingSeconds; } // If we're going to fill the packet, interpolate the pose, send the packet, // and open a new one else { // Interpolate between the packet's last frame and our target pose // to compute a pose at the end of the packet time. OvrAvatarDriver.PoseFrame a = CurrentUnityPacket.FinalFrame; OvrAvatarDriver.PoseFrame b = frame; float t = remainingPacketSeconds / remainingSeconds; OvrAvatarDriver.PoseFrame intermediatePose = OvrAvatarDriver.PoseFrame.Interpolate(a, b, t); CurrentUnityPacket.AddFrame(intermediatePose, remainingPacketSeconds); recordedSeconds += remainingPacketSeconds; // Broadcast the recorded packet if (PacketRecorded != null) { PacketRecorded(this, new PacketEventArgs(CurrentUnityPacket)); } // Open a new packet CurrentUnityPacket = new OvrAvatarPacket(intermediatePose); } } } private void RecordSDKFrame() { if (sdkAvatar == IntPtr.Zero) { return; } if (!PacketSettings.RecordingFrames) { CAPI.ovrAvatarPacket_BeginRecording(sdkAvatar); PacketSettings.AccumulatedTime = 0.0f; PacketSettings.RecordingFrames = true; } PacketSettings.AccumulatedTime += Time.deltaTime; if (PacketSettings.AccumulatedTime >= PacketSettings.UpdateRate) { PacketSettings.AccumulatedTime = 0.0f; var packet = CAPI.ovrAvatarPacket_EndRecording(sdkAvatar); CAPI.ovrAvatarPacket_BeginRecording(sdkAvatar); if (PacketRecorded != null) { PacketRecorded(this, new PacketEventArgs(new OvrAvatarPacket { ovrNativePacket = packet })); } CAPI.ovrAvatarPacket_Free(packet); } } private void AddRenderParts( OvrAvatarComponent ovrComponent, ovrAvatarComponent component, Transform parent) { for (UInt32 renderPartIndex = 0; renderPartIndex < component.renderPartCount; renderPartIndex++) { GameObject renderPartObject = new GameObject(); renderPartObject.name = GetRenderPartName(component, renderPartIndex); renderPartObject.transform.SetParent(parent); IntPtr renderPart = GetRenderPart(component, renderPartIndex); ovrAvatarRenderPartType type = CAPI.ovrAvatarRenderPart_GetType(renderPart); OvrAvatarRenderComponent ovrRenderPart; switch (type) { case ovrAvatarRenderPartType.SkinnedMeshRender: ovrRenderPart = AddSkinnedMeshRenderComponent(renderPartObject, CAPI.ovrAvatarRenderPart_GetSkinnedMeshRender(renderPart)); break; case ovrAvatarRenderPartType.SkinnedMeshRenderPBS: ovrRenderPart = AddSkinnedMeshRenderPBSComponent(renderPartObject, CAPI.ovrAvatarRenderPart_GetSkinnedMeshRenderPBS(renderPart)); break; case ovrAvatarRenderPartType.ProjectorRender: ovrRenderPart = AddProjectorRenderComponent(renderPartObject, CAPI.ovrAvatarRenderPart_GetProjectorRender(renderPart)); break; case ovrAvatarRenderPartType.SkinnedMeshRenderPBS_V2: { OvrAvatarMaterialManager materialManager = null; if (ovrComponent.name == "body") { materialManager = DefaultBodyMaterialManager; } else if( ovrComponent.name.Contains("hand")) { materialManager = DefaultHandMaterialManager; } ovrRenderPart = AddSkinnedMeshRenderPBSV2Component( renderPart, renderPartObject, CAPI.ovrAvatarRenderPart_GetSkinnedMeshRenderPBSV2(renderPart), materialManager); } break; default: throw new NotImplementedException(string.Format("Unsupported render part type: {0}", type.ToString())); } ovrComponent.RenderParts.Add(ovrRenderPart); } } public void RefreshBodyParts() { OvrAvatarComponent component; if (trackedComponents.TryGetValue("body", out component) && Body != null) { foreach (var part in component.RenderParts) { Destroy(part.gameObject); } component.RenderParts.Clear(); ovrAvatarBodyComponent? sdkBodyComponent = CAPI.ovrAvatarPose_GetBodyComponent(sdkAvatar); if (sdkBodyComponent != null) { ovrAvatarComponent sdKComponent = (ovrAvatarComponent)Marshal.PtrToStructure(sdkBodyComponent.Value.renderComponent, typeof(ovrAvatarComponent)); AddRenderParts(component, sdKComponent, Body.gameObject.transform); } else { throw new Exception("Destroyed the body component, but didn't find a new one in the SDK"); } } } public ovrAvatarBodyComponent? GetBodyComponent() { return CAPI.ovrAvatarPose_GetBodyComponent(sdkAvatar); } public Transform GetHandTransform(HandType hand, HandJoint joint) { if (hand >= HandType.Max || joint >= HandJoint.Max) { return null; } var HandObject = hand == HandType.Left ? HandLeft : HandRight; if (HandObject != null) { var AvatarComponent = HandObject.GetComponent(); if (AvatarComponent != null && AvatarComponent.RenderParts.Count > 0) { var SkinnedMesh = AvatarComponent.RenderParts[0]; return SkinnedMesh.transform.Find(HandJoints[(int)hand, (int)joint]); } } return null; } public void GetPointingDirection(HandType hand, ref Vector3 forward, ref Vector3 up) { Transform handBase = GetHandTransform(hand, HandJoint.HandBase); if (handBase != null) { forward = handBase.forward; up = handBase.up; } } public Transform GetMouthTransform() { OvrAvatarComponent component; if (trackedComponents.TryGetValue("voice", out component)) { if (component.RenderParts.Count > 0) { return component.RenderParts[0].transform; } } return null; } static Vector3 MOUTH_POSITION_OFFSET = new Vector3(0, -0.018f, 0.1051f); static string VOICE_PROPERTY = "_Voice"; static string MOUTH_POSITION_PROPERTY = "_MouthPosition"; static string MOUTH_DIRECTION_PROPERTY = "_MouthDirection"; static string MOUTH_SCALE_PROPERTY = "_MouthEffectScale"; static float MOUTH_SCALE_GLOBAL = 0.007f; static float MOUTH_MAX_GLOBAL = 0.007f; static string NECK_JONT = "root_JNT/body_JNT/chest_JNT/neckBase_JNT/neck_JNT"; public float VoiceAmplitude = 0f; public bool EnableMouthVertexAnimation = false; private void UpdateVoiceBehavior() { if (!EnableMouthVertexAnimation) { return; } OvrAvatarComponent component; if (trackedComponents.TryGetValue("body", out component)) { VoiceAmplitude = Mathf.Clamp(VoiceAmplitude, 0f, 1f); if (component.RenderParts.Count > 0) { var material = component.RenderParts[0].mesh.sharedMaterial; var neckJoint = component.RenderParts[0].mesh.transform.Find(NECK_JONT); var scaleDiff = neckJoint.TransformPoint(Vector3.up) - neckJoint.position; material.SetFloat(MOUTH_SCALE_PROPERTY, scaleDiff.magnitude); material.SetFloat( VOICE_PROPERTY, Mathf.Min(scaleDiff.magnitude * MOUTH_MAX_GLOBAL, scaleDiff.magnitude * VoiceAmplitude * MOUTH_SCALE_GLOBAL)); material.SetVector( MOUTH_POSITION_PROPERTY, neckJoint.TransformPoint(MOUTH_POSITION_OFFSET)); material.SetVector(MOUTH_DIRECTION_PROPERTY, neckJoint.up); } } } }