using System.Collections; using UnityEngine; namespace Passer.Humanoid { using Humanoid.Tracking; public class HeadMovements : Movements { protected HeadTarget headTarget; #region Start public override void Start(HumanoidControl _humanoid, HumanoidTarget _target) { base.Start(_humanoid, _target); headTarget = (HeadTarget)_target; if (headTarget.neck.bone.transform != null) headTarget.neck.bone.transform.rotation = headTarget.neck.target.transform.rotation * headTarget.neck.target.toBoneRotation; if (headTarget.head.bone.transform != null) headTarget.head.bone.transform.rotation = headTarget.head.target.transform.rotation * headTarget.head.target.toBoneRotation; } #endregion #region Update public static void Update(HeadTarget headTarget) { if (headTarget.head.bone.transform == null || !headTarget.humanoid.calculateBodyPose) return; #if hFACE if (headTarget.neck.target.confidence.rotation < 0.2F && headTarget.head.target.confidence.rotation < 0.2F && headTarget.face.leftEye.target.confidence.rotation > 0.2F) { UpdateHeadBonesFromGazeDirection(headTarget); } else { #endif if (Application.isPlaying && headTarget.humanoid.primaryTarget == HumanoidControl.PrimaryTarget.Hips) { UpdateNeckRotation(headTarget); UpdateNeckPositionFromHips(headTarget); UpdateHeadPositionFromNeck(headTarget); } else { UpdateHead(headTarget); UpdateNeck(headTarget); } #if hFACE } headTarget.face.UpdateMovements(); #endif } private static void UpdateHead(HeadTarget headTarget) { if (headTarget.head.target.transform == null) return; headTarget.head.SetBoneRotation(headTarget.head.target.transform.rotation); // Commented out the requirement for position confidence > 0 because of Oculus Go/GearVR positional issue if (!Application.isPlaying || (headTarget.humanoid.targetsRig.runtimeAnimatorController == null || headTarget.head.target.confidence.position > 0) ) headTarget.head.SetBonePosition(headTarget.head.target.transform.position); } private static void UpdateHeadPositionFromNeck(HeadTarget headTarget) { headTarget.head.SetBoneRotation(headTarget.head.target.transform.rotation); Vector3 neckPosition = headTarget.neck.bone.transform.position; Vector3 headPosition = neckPosition + headTarget.head.target.transform.rotation * Vector3.up * headTarget.neck.bone.length; headTarget.head.SetBonePosition(headPosition); } private static void UpdateNeck(HeadTarget headTarget) { if (headTarget.neck.bone.transform == null) return; Vector3 headPosition = headTarget.head.bone.transform.position; Quaternion headRotation = headTarget.head.bone.transform.rotation; if (headTarget.neck.target.confidence.rotation > headTarget.head.target.confidence.rotation) { Quaternion neckRotation = headTarget.neck.target.transform.rotation; headTarget.neck.SetBoneRotation(neckRotation); if (headTarget.humanoid.targetsRig.runtimeAnimatorController == null || headTarget.head.target.confidence.position > 0) { Vector3 neckPosition = headPosition - neckRotation * Vector3.up * headTarget.neck.bone.length; headTarget.neck.SetBonePosition(neckPosition); } } else { Quaternion hipsTargetRotation = headTarget.humanoid.hipsTarget.hips.bone.targetRotation * Quaternion.Inverse(headTarget.humanoid.hipsTarget.hips.bone.baseRotation); Quaternion neckRotation = Quaternion.Slerp(headTarget.head.bone.targetRotation, hipsTargetRotation, 0.3F) * headTarget.neck.bone.baseRotation; headTarget.neck.SetBoneRotation(neckRotation); // Commented out the requirement for position confidence > 0 because of Oculus Go/GearVR positional issue if (!Application.isPlaying || ( headTarget.humanoid.targetsRig.runtimeAnimatorController == null || headTarget.head.target.confidence.position > 0 )) { Vector3 neckPosition = headPosition - neckRotation * Vector3.up * headTarget.neck.bone.length; headTarget.neck.SetBonePosition(neckPosition); } } headTarget.head.bone.transform.position = headPosition; headTarget.head.bone.transform.rotation = headRotation; } private static void UpdateNeckRotation(HeadTarget headTarget) { Quaternion headRotation = headTarget.head.bone.transform.rotation; if (headTarget.neck.target.confidence.rotation > headTarget.head.target.confidence.rotation) { Quaternion neckRotation = headTarget.neck.target.transform.rotation; headTarget.neck.SetBoneRotation(neckRotation); } else { Quaternion hipsTargetRotation = headTarget.humanoid.hipsTarget.hips.bone.targetRotation * Quaternion.Inverse(headTarget.humanoid.hipsTarget.hips.bone.baseRotation); Quaternion neckRotation = Quaternion.Slerp(headTarget.head.bone.targetRotation, hipsTargetRotation, 0.3F) * headTarget.neck.bone.baseRotation; headTarget.neck.SetBoneRotation(neckRotation); } headTarget.head.bone.transform.rotation = headRotation; } private static void UpdateNeckPositionFromHips(HeadTarget headTarget) { HipsTarget hipsTarget = headTarget.humanoid.hipsTarget; Vector3 hipsPosition = hipsTarget.hips.bone.transform.position; if (hipsTarget.chest.bone.transform != null) { Vector3 chestTopPosition = hipsTarget.chest.bone.transform.position + hipsTarget.chest.bone.targetRotation * Vector3.up * hipsTarget.chest.bone.length; //Vector3 spineVector = chestTopPosition - hipsPosition; headTarget.neck.SetBonePosition(chestTopPosition); } else if (hipsTarget.spine.bone.transform != null) { Vector3 spineTopPosition = hipsTarget.spine.bone.transform.position + hipsTarget.spine.bone.targetRotation * Vector3.up * hipsTarget.spine.bone.length; //Vector3 spineVector = spineTopPosition - hipsPosition; headTarget.neck.SetBonePosition(spineTopPosition); } } public static Quaternion CalculateNeckRotation(Quaternion hipRotation, Quaternion headRotation) { Vector3 headAnglesCharacterSpace = (Quaternion.Inverse(hipRotation) * headRotation).eulerAngles; float neckYRotation = UnityAngles.Normalize(headAnglesCharacterSpace.y) * 0.6F; Quaternion neckRotation = hipRotation * Quaternion.Euler(headAnglesCharacterSpace.x, neckYRotation, headAnglesCharacterSpace.z); return neckRotation; } public static Vector3 CalculateNeckPositionFromEyes(Vector3 eyePosition, Quaternion eyeRotation, Vector3 eye2neck) { Vector3 neckPosition = eyePosition + eyeRotation * eye2neck; return neckPosition; } public static Vector3 CalculateNeckPositionFromHead(Vector3 headPosition, Quaternion headRotation, float neckBoneLength) { Vector3 neckPosition = headPosition - headRotation * Vector3.up * neckBoneLength; return neckPosition; } static float lastTime; private static void UpdateHeadBonesFromGazeDirection(HeadTarget headTarget) { if (headTarget.humanoid.hipsTarget.hips.bone.transform == null) return; #if hFACE Quaternion neckParentRotation = headTarget.humanoid.hipsTarget.hips.bone.transform.rotation * headTarget.humanoid.hipsTarget.hips.bone.toTargetRotation; Quaternion oldNeckRotation = headTarget.neck.bone.transform.rotation * headTarget.neck.bone.toTargetRotation; Vector3 localNeckAngles = (Quaternion.Inverse(neckParentRotation) * oldNeckRotation).eulerAngles; Quaternion predictedRotation = Quaternion.Slerp(Quaternion.identity, headTarget.neck.bone.rotationVelocity, Time.deltaTime); Quaternion eyeRotation = headTarget.face.leftEye.target.transform.rotation; Vector3 localEyeAngles = (Quaternion.Inverse(oldNeckRotation) * eyeRotation).eulerAngles; if (Quaternion.Angle(oldNeckRotation, eyeRotation) < 2) { lastTime = Time.time; return; } float headTensionY = CalculateTension(localNeckAngles, headTarget.neck.bone.maxAngles); float eyeTensionY = CalculateTension(localEyeAngles, headTarget.face.leftEye.bone.maxAngles); float f = Mathf.Clamp01(1 - (headTensionY / eyeTensionY)); Quaternion targetRotation = Quaternion.LookRotation(headTarget.face.gazeDirection, Vector3.up); // this will overshoot, because the lookdirection itself is affected... Quaternion neckRotation = Quaternion.RotateTowards(oldNeckRotation, targetRotation, f * 2); Quaternion desiredRotation = Quaternion.Inverse(oldNeckRotation) * neckRotation; // This limits the speed changes float deltaTime = Time.time - lastTime; Quaternion resultRotation = Quaternion.RotateTowards(predictedRotation, desiredRotation, deltaTime * 100); neckRotation = oldNeckRotation * resultRotation; headTarget.neck.bone.transform.rotation = neckRotation * headTarget.neck.target.toBoneRotation; lastTime = Time.time; #endif } private void UpdateHeadBonesFromLookDirection() { Quaternion neckParentRotation = headTarget.humanoid.hipsTarget.hips.bone.transform.rotation * headTarget.humanoid.hipsTarget.hips.bone.toTargetRotation; Quaternion oldNeckRotation = headTarget.neck.bone.transform.rotation * headTarget.neck.bone.toTargetRotation; Vector3 localNeckAngles = (Quaternion.Inverse(neckParentRotation) * oldNeckRotation).eulerAngles; Quaternion predictedRotation = Quaternion.Slerp(Quaternion.identity, headTarget.neck.bone.rotationVelocity, Time.deltaTime); Quaternion targetRotation = Quaternion.LookRotation(headTarget.lookDirection, Vector3.up); // this will overshoot, because the lookdirection itself is affected... Quaternion neckRotation = targetRotation; Quaternion desiredRotation = Quaternion.Inverse(oldNeckRotation) * neckRotation; // This limits the speed changes Quaternion resultRotation = Quaternion.RotateTowards(predictedRotation, desiredRotation, Time.deltaTime * 6); neckRotation = oldNeckRotation * resultRotation; headTarget.neck.bone.transform.rotation = neckRotation * headTarget.neck.target.toBoneRotation; } #endregion private static float CalculateTension(Vector3 angle, Vector3 maxAngle) { float dX = CalculateTension(angle.x, maxAngle.x); float dY = CalculateTension(angle.y, maxAngle.y); float dZ = CalculateTension(angle.z, maxAngle.z); return (dX + dY + dZ); } private static float CalculateTension(float angle, float maxAngle) { return (maxAngle != 0) ? Mathf.Abs(Angle.Normalize(angle) / maxAngle) : 0; } } public class HeadCollisionHandler : MonoBehaviour { static public Collider AddHeadCollider(GameObject headObject) { HeadTarget headTarget = headObject.GetComponent(); if (headTarget.headRigidbody == null) { headTarget.headRigidbody = headObject.AddComponent(); if (headTarget.headRigidbody != null) { headTarget.headRigidbody.mass = 1; headTarget.headRigidbody.useGravity = false; headTarget.headRigidbody.isKinematic = true; } } Collider collider = headObject.GetComponent(); if (collider != null) return collider; SphereCollider sphereCollider = headObject.AddComponent(); if (sphereCollider != null) { sphereCollider.isTrigger = true; sphereCollider.radius = 0.1F; sphereCollider.center = new Vector3(0, 0, 0.05F); } return sphereCollider; } private HumanoidControl humanoid; private Material fadeMaterial; public void Initialize(HumanoidControl _humanoid) { humanoid = _humanoid; //if (humanoid.headTarget.collisionFader) // FindFadeMaterial(); } private void FindFadeMaterial() { #if pUNITYXR if (humanoid.unityXR == null || humanoid.unityXR.trackerComponent == null) return; Passer.Tracking.UnityXR unityXRtracker = humanoid.unityXR.trackerComponent as Passer.Tracking.UnityXR; if (unityXRtracker.hmd == null || unityXRtracker.hmd.unityCamera == null) #endif return; #if pUNITYXR Transform plane = unityXRtracker.hmd.unityCamera.transform.Find("Fader"); #endif #if pUNITYXR || hLEGACYXR if (plane != null) { Renderer renderer = plane.GetComponent(); if (renderer != null) { renderer.enabled = true; fadeMaterial = renderer.sharedMaterial; Color color = fadeMaterial.color; color.a = 0.0F; fadeMaterial.color = color; } } #endif } void OnTriggerEnter(Collider otherCollider) { if (fadeMaterial == null || otherCollider.isTrigger) return; if (otherCollider.attachedRigidbody == null) { DoFadeOut(); humanoid.headTarget.isInsideCollider = true; } } void OnTriggerExit(Collider otherCollider) { if (fadeMaterial == null || otherCollider.isTrigger) return; if (otherCollider.attachedRigidbody == null) { DoFadeIn(); humanoid.headTarget.isInsideCollider = false; } } private void DoFadeOut() { StartCoroutine(FadeOut(fadeMaterial)); } private void DoFadeIn() { StartCoroutine(FadeIn(fadeMaterial)); } private bool faded = false; public float fadeTime = 0.3F; public IEnumerator FadeOut(Material fadeMaterial) { if (!faded) { float elapsedTime = 0.0f; Color color = fadeMaterial.color; color.a = 0.0f; fadeMaterial.color = color; while (elapsedTime < fadeTime) { yield return new WaitForEndOfFrame(); elapsedTime += Time.deltaTime; color.a = Mathf.Clamp01(elapsedTime / fadeTime); fadeMaterial.color = color; } } faded = true; } public IEnumerator FadeIn(Material fadeMaterial) { if (faded) { float elapsedTime = 0.0f; Color color = fadeMaterial.color; while (elapsedTime < fadeTime) { yield return new WaitForEndOfFrame(); elapsedTime += Time.deltaTime; color.a = 1.0f - Mathf.Clamp01(elapsedTime / fadeTime); fadeMaterial.color = color; } } faded = false; } } }