using System; using System.Linq; using System.Collections.Generic; using UnityEngine; using UnityEditor; using UnityEngine.Events; using DG.Tweening; using UnityEngine.Windows; using static UDE_HAND_INTERACTION.HandInteractionSetting; using static UDE_HAND_INTERACTION.InteractableObject; namespace UDE_HAND_INTERACTION { public class HandInteractor : MonoBehaviour { [HideInInspector] public FingerInteractData FingerInteractData; [HideInInspector] public HandDriver HandDriver { get; private set; } private List JointList; [SerializeField, Space] private UnityEvent WhenHandInteract; [SerializeField] private UnityEvent WhenHandDisable; private bool SeparateFinger; [HideInInspector] public float ActiveRange; [HideInInspector] public float ActiveIntense; [HideInInspector] public InteractType interactType; [HideInInspector] public KeyCode ShortCutKeyCode; private Vector3 RegularTransferPosition = Vector3.zero; private List Fingers = new(); private List FixedPoseAngle = new(); [HideInInspector] public List OriginalAngle; [HideInInspector] public List Joints; public enum Handness { Left, Right } [HideInInspector] public Handness handness; [HideInInspector] public Transform TipTrans; [HideInInspector] public InteractableObject InteractObject{ get; private set; } [HideInInspector] public InteractableObject ContactObject; [HideInInspector] public HandInteraction ChosenPose { get; private set; } private Dictionary TriggerObjects = new(); private bool[] ActiveCnt; [HideInInspector] public bool InActive { private set; get; } = false; private bool[] OnceAvtive = new bool[5] { true, true, true, true, true }; private float[] ActiveClock = new float[5] { 0, 0, 0, 0, 0 }; private bool OnceDisable = true; private bool UpdateCheckResult = false; private bool TriggerEnterCheck = false; private InteractableObject RemoveOperation; private bool FollowJointInClone = false; private HandInteractor CloneTarget; private SkinnedMeshRenderer HandSkinnedMeshRenderer; public Transform DetectArea { get; private set; } private LineRenderer lineRender; private List UsedLine = new(); private float CurveHighest = 0; public Vector3 CurveHighestVec { get; private set; } = Vector3.zero; private Ray[] DetectRays; private RaycastHit[] raycastHits = new RaycastHit[6]; private Dictionary RayDetectedTarget = new Dictionary(); private InteractableObject RayTargetObj; private InteractableObject AreaObject; private StaticPoseDetector staticPoseDetector => transform.parent?.GetComponentInChildren(); private List staticPoses; private List FullAngleList; void Awake() { HandDriver = GetComponentInChildren(); if (HandDriver.Hand == HandDriver.HandType.Right) { handness = Handness.Right; } else { handness = Handness.Left; } } void Start() { OriginalAngle = new() { HandDriver.Thumb1.localRotation, HandDriver.Thumb2.localRotation, HandDriver.Thumb3.localRotation, HandDriver.Index1.localRotation, HandDriver.Index2.localRotation, HandDriver.Index3.localRotation, HandDriver.Middle1.localRotation, HandDriver.Middle2.localRotation, HandDriver.Middle3.localRotation, HandDriver.Ring1.localRotation, HandDriver.Ring2.localRotation, HandDriver.Ring3.localRotation, HandDriver.Pinky1.localRotation, HandDriver.Pinky2.localRotation, HandDriver.Pinky3.localRotation, }; ActiveCnt = new bool[5] { false, false, false, false, false }; //ActiveRange = FingerInteractData.ActiveRange; ShortCutKeyCode = FingerInteractData.ShortcutKeyCode; DetectArea = transform.Find("DetectArea"); HandSkinnedMeshRenderer = transform.GetComponentInChildren(); lineRender = DetectArea.TryGetComponent(out var line) ? line : null; staticPoses = (handness == Handness.Left) ? staticPoseDetector?.LeftPoses : staticPoseDetector?.RightPoses; } private void OnTriggerEnter(Collider other) { Transform obj = other.transform; if (obj.TryGetComponent(out var _)) { TriggerEnterCheck = !UpdateCheckResult; return; } while (obj.parent != null) { obj = obj.parent; if (obj.TryGetComponent(out var _)) { TriggerEnterCheck = !UpdateCheckResult; return; } } } private void OnTriggerStay(Collider other) { Transform trans = other.transform; InteractableObject obj = null; if (trans.TryGetComponent(out var _obj)) { obj = _obj; } while (trans.parent != null) { trans = trans.parent; if (trans.TryGetComponent(out var __obj)) { obj = __obj; } } if (obj == null) return; ContactObject = obj; if (!TriggerEnterCheck) { TriggerEnterCheck = !UpdateCheckResult; } InteractableObject selected = InteractObject; bool ActivePermission = false; if (!InActive) { if (!TriggerObjects.ContainsKey(obj) && !FollowJointInClone) { TriggerObjects.Add(obj, other); } int Priority = int.MaxValue; List DeleteList = new(); foreach(var (interact_object, collider) in TriggerObjects) { if (collider != null && Vector3.Distance(collider.ClosestPoint(transform.Find("DetectArea").position), transform.Find("DetectArea").position) > ActiveRange) { DeleteList.Add(interact_object); continue; } if (interact_object != null && interact_object.InteractPriority < Priority) { Priority = interact_object.InteractPriority; selected = interact_object; } } foreach(var del in DeleteList) { TriggerObjects.Remove(del); } if (selected == null) return; if (selected.objectFollowType == ObjectFollowType.NoPose) { ChosenPose = null; var SettingData = selected.GetComponentInChildren(); Fingers = new() { new Finger("Thumb", SettingData.Thumb, SettingData.ThumbIntense), new Finger("Index", SettingData.Index, SettingData.IndexIntense), new Finger("Middle", SettingData.Middle, SettingData.MiddleIntense), new Finger("Ring", SettingData.Ring, SettingData.RingIntense), new Finger("Pinky", SettingData.Pinky, SettingData.PinkyIntense), }; SeparateFinger = SettingData.SeparateFinger; ActiveIntense = SettingData.ActiveIntense; interactType = SettingData.interactType; ActivePermission = ActiveChecker(); } else { var SortedDict = GetFairInteractableDict(selected); ChosenPose = SortedDict.Values.FirstOrDefault(); foreach (HandInteraction interact in SortedDict.Values) { var SettingData = interact.GetComponent(); Fingers = new() { new Finger("Thumb", SettingData.Thumb, SettingData.ThumbIntense), new Finger("Index", SettingData.Index, SettingData.IndexIntense), new Finger("Middle", SettingData.Middle, SettingData.MiddleIntense), new Finger("Ring", SettingData.Ring, SettingData.RingIntense), new Finger("Pinky", SettingData.Pinky, SettingData.PinkyIntense), }; SeparateFinger = SettingData.SeparateFinger; ActiveIntense = SettingData.ActiveIntense; interactType = SettingData.interactType; if (ActiveChecker()) { ChosenPose = interact; ActivePermission = true; break; } } } if (ChosenPose != null && ChosenPose.TryGetComponent(out var _regular)) { CalCloestPoint(TipTrans.position, _regular.RealStart, _regular.RealEnd, _regular.TipDist); } else { RegularTransferPosition = Vector3.zero; } var FingerSet = ChosenPose?.HandPose.FingersFreedom; FixedPoseAngle = new(); for (int i = 0; i < 15; ++i) { if (ChosenPose != null && selected.objectFollowType != ObjectFollowType.NoPose && (FingerSet == null || FingerSet[i / 3] != JointFreedom.Free)) { FixedPoseAngle.Add((OriginalAngle[i] * ChosenPose.HandPose.JointRotations[i]).eulerAngles); } else { FixedPoseAngle.Add(Vector3.zero); } } } //Debug.Log(selected.name + "/" + ActivePermission + " " + TriggerEnterCheck + !InActive + !selected.IsFollow); selected.interactableObjectOutline?.EnableWithHand((int)handness, !selected.IsFollow); AreaObject = selected; if (ActivePermission && TriggerEnterCheck && !InActive && (!selected.IsFollow || ChosenPose.GetComponent().OperationInsideObject)) { InteractObject = selected; selected.HandActive(gameObject, ChosenPose, RegularTransferPosition); if (ChosenPose != null && ChosenPose.TryGetComponent(out var script)) { script.ResetInteraction(); } InActive = true; Debug.Log($"Active Hand : {handness}, Active Object: {selected.name}, Interaction Pose: {ChosenPose?.name}"); WhenHandInteract.Invoke(); } } private void OnTriggerExit(Collider other) { Transform trans = other.transform; InteractableObject obj = null; AreaObject = null; if (trans.TryGetComponent(out var _obj)) { obj = _obj; } while (trans.parent != null && obj == null) { trans = trans.parent; if (trans.TryGetComponent(out var __obj)) { obj = __obj; } } if (obj == null) return; obj.interactableObjectOutline?.EnableWithHand((int)handness, false); TriggerObjects.Remove(obj); if (ChosenPose != null && ChosenPose.GetComponent().OperationInsideObject && InActive) { RemoveOperation = obj; return; } if (InteractObject == obj && !InteractObject.IsFollow) { WhenHandDisable.Invoke(); TriggerEnterCheck = false; InteractObject = null; ChosenPose = null; RegularTransferPosition = Vector3.zero; obj.HandDisable((Handedness)handness); Fingers.Clear(); } } void Update() { if (gameObject.name.Contains("(")) { return; } if (UnityEngine.Input.GetKeyDown(ShortCutKeyCode)) { RecordInteractionButton(); } JointList = new() { Math.Abs(HandDriver.thumb3.z), Math.Abs(HandDriver.index2.z), Math.Abs(HandDriver.middle2.z), Math.Abs(HandDriver.ring2.z), Math.Abs(HandDriver.pinky2.z), }; RayDetect(); if (Fingers.Count != 5) { Fingers = new() { new(""), new(""), new(""), new(""), new("") }; interactType = InteractType.AllFingers; } int Index = 0; foreach (var finger in Fingers) { ActiveCnt[Index] = false; if (SeparateFinger && finger.IsActive && JointList[Index] >= finger.MaxAngle * finger.Threshold) { ActiveCnt[Index] = true; } else if (!SeparateFinger && JointList[Index] >= finger.MaxAngle * ActiveIntense) { ActiveCnt[Index] = true; } Index++; } Joints = new() { HandDriver.Thumb1, HandDriver.Thumb2, HandDriver.Thumb3, HandDriver.Index1, HandDriver.Index2, HandDriver.Index3, HandDriver.Middle1, HandDriver.Middle2, HandDriver.Middle3, HandDriver.Ring1, HandDriver.Ring2, HandDriver.Ring3, HandDriver.Pinky1, HandDriver.Pinky2, HandDriver.Pinky3, }; FullAngleList = new() { HandDriver.thumb1, HandDriver.thumb2, HandDriver.thumb3, HandDriver.index1, HandDriver.index2, HandDriver.index3, HandDriver.middle1, HandDriver.middle2, HandDriver.middle3, HandDriver.ring1, HandDriver.ring2, HandDriver.ring3, HandDriver.pinky1, HandDriver.pinky2, HandDriver.pinky3, }; PoseDetect(); //Debug.Log(ActiveCnt + "//" + InActive); if (!ActiveChecker(true)) InActive = false; if (InteractObject == null && RayTargetObj == null) return; if(RayTargetObj != null && InteractObject == null) { InteractObject = RayTargetObj; InActive = ActiveChecker(); if(AreaObject == null || AreaObject == RayTargetObj) { RayTargetObj.interactableObjectOutline?.EnableWithHand((int)handness, !InActive); } } if(AreaObject != null && AreaObject != RayTargetObj) { InteractObject = AreaObject; } if (InActive) { InteractObject.HandActive(gameObject, ChosenPose, RegularTransferPosition); for (int i = 0; i < 5; ++i) { if (ChosenPose == null) break; ActiveClock[i] += Time.deltaTime; bool TempActive = (SeparateFinger && JointList[i] >= Fingers[i].MaxAngle * Fingers[i].Threshold) || (!SeparateFinger && JointList[i] >= Fingers[i].MaxAngle * ActiveIntense); switch ((int)ChosenPose.HandPose.FingersFreedom[i]) { case 1: if (OnceAvtive[i]) { for (int j = 3 * i; j < 3 * (i + 1); ++j) { Joints[j].DOLocalRotate(FixedPoseAngle[j], 0.4f, RotateMode.Fast); } OnceAvtive[i] = false; ActiveClock[i] = 0; } else if (ActiveClock[i] >= 0.39f) { for (int j = 3 * i; j < 3 * (i + 1); ++j) { Joints[j].DOLocalRotate(FixedPoseAngle[j], 0f, RotateMode.Fast); } } break; case 2: if (OnceAvtive[i] && TempActive) { for (int j = 3 * i; j < 3 * (i + 1); ++j) { Joints[j].DOLocalRotate(FixedPoseAngle[j], 0.4f, RotateMode.Fast); } OnceAvtive[i] = false; ActiveClock[i] = 0; } else if (ActiveClock[i] >= 0.39f && TempActive) { for (int j = 3 * i; j < 3 * (i + 1); ++j) { Joints[j].DOLocalRotate(FixedPoseAngle[j], 0f, RotateMode.Fast); } } break; case 3: var UseInteraction = ChosenPose.GetComponent(); float rate = JointList[i] / Fingers[i].MaxAngle; if (TempActive) { var UsePoseAngle = UseInteraction.FingerConfig((rate - Fingers[i].Threshold) / (1 - Fingers[i].Threshold), i); for (int j = 3 * i, k = 0; j < 3 * (i + 1); ++j) { Joints[j].DOLocalRotate((OriginalAngle[j] * UsePoseAngle[k++]).eulerAngles, 0.05f, RotateMode.Fast); } } break; } } } if (!InActive) { InteractObject.HandDisable((Handedness)handness); if (RemoveOperation != null) { TriggerObjects.Clear(); InteractObject = null; WhenHandDisable.Invoke(); ChosenPose = null; RemoveOperation = null; } RegularTransferPosition = Vector3.zero; //OnceAvtive = true; for (int i = 0; i < 5; ++i) { OnceAvtive[i] = true; } if (OnceDisable) { for (int i = 0; i < 15; ++i) { if (FixedPoseAngle[i] != Vector3.zero) { //Joints[i].DOLocalRotate(Joints[i].localEulerAngles, 0.3f, RotateMode.Fast); } } //OnceDisable = false; } } //move from late update if (FollowJointInClone && GameObject.Find("Network Manager") != null) { for (int i = 0; i < CloneTarget.Joints.Count; ++i) { CloneTarget.Joints[i].DOLocalRotate(Joints[i].localEulerAngles, 0f); } } } private bool ActiveChecker(bool IsUpdate = false) { bool[] ActiveCheck = new bool[2] { false, true }; if(ActiveCnt == null) { ActiveCnt = new bool[5]; } bool res = false; for (int i = 0; i < 5; ++i) { if (Fingers[i].IsActive != SeparateFinger) continue; if (interactType == InteractType.AnyFinger) { ActiveCheck[0] |= ActiveCnt[i]; res = ActiveCheck[0]; } else if (interactType == InteractType.AllFingers) { ActiveCheck[1] &= ActiveCnt[i]; res = ActiveCheck[1]; } } if (IsUpdate) UpdateCheckResult = res; return res; } private Dictionary GetFairInteractableDict(InteractableObject obj) { Dictionary InteractableList = new(); foreach (var interact in obj.HandInteractions) { if ((int)interact.handness == (int)handness) { float distance = Vector3.Distance(interact.transform.GetChild(0).position, TipTrans.position); float RotationDistance = Quaternion.Angle(transform.rotation, interact.transform.rotation); if (interact.TryGetComponent(out var regular)) { distance = CalCloestPoint(TipTrans.position, regular.RealStart, regular.RealEnd, regular.TipDist); } InteractableList[distance * RotationDistance] = interact; } } return InteractableList.OrderBy(item => item.Key).ToDictionary(item => item.Key, item => item.Value); } private float CalCloestPoint(Vector3 handPoint, Vector3 lineStart, Vector3 lineEnd, float radius) { Vector3 lineDirection = lineEnd - lineStart; float lineLength = lineDirection.magnitude; lineDirection.Normalize(); Vector3 CenterPoint = lineStart + Vector3.Project(handPoint - lineStart, lineDirection); if ((CenterPoint - lineStart).magnitude > lineLength || (CenterPoint - lineEnd).magnitude > lineLength) { CenterPoint = (CenterPoint - lineStart).magnitude > lineLength ? lineEnd : lineStart; RegularTransferPosition = CenterPoint; return CloestDirectionOnCircle(CenterPoint, handPoint, handPoint + lineDirection, radius); } else { RegularTransferPosition = CenterPoint; return (handPoint - CenterPoint).magnitude - radius; } } private float CloestDirectionOnCircle(Vector3 point, Vector3 lineStart, Vector3 lineEnd, float radius) { Vector3 lineDir = lineEnd - lineStart; Vector3 pontDir = point - lineStart; float Dot = Vector3.Dot(pontDir, lineDir); Vector3 centerToEdge = (pontDir - Dot * lineDir.normalized).normalized * radius; return (pontDir + centerToEdge).magnitude; } public void HandVisionControl(bool IsShow, HandInteractor clone) { HandSkinnedMeshRenderer.gameObject.SetActive(IsShow); FollowJointInClone = !IsShow; CloneTarget = clone; } public void ForceActive(InteractableObject newObj, HandInteraction newInteraction) { TriggerObjects = new(); InteractObject?.HandDisable((Handedness)handness); if (newObj != null) { TriggerObjects.Add(newObj, null); } InteractObject = newObj; ChosenPose = newInteraction; InteractObject?.HandActive(gameObject, ChosenPose, Vector3.zero); } void PoseDetect() { if (staticPoses == null || staticPoses.Count == 0) return; foreach(var pose in staticPoses) { List PoseRot = pose._handPose.JointRotations.ToList(); bool Judge = true; Judge &= (!pose.ConsiderWrist || Quaternion.Angle(transform.rotation, pose.transform.rotation) <= 30); if (Judge) { for (int i = 0; i < FullAngleList.Count; ++i) { var TotalCheck = Quaternion.Angle(Quaternion.Euler(FullAngleList[i]), PoseRot[i]) <= 25; if (!TotalCheck) { var regularPose = RegularAngle(PoseRot[i].eulerAngles); TotalCheck = Math.Abs(FullAngleList[i].x - regularPose.x) <= 10 && Math.Abs(FullAngleList[i].y - regularPose.y) <= 10 && Math.Abs(FullAngleList[i].z - regularPose.z) <= 10; } Judge &= TotalCheck; } } pose.IsPoseDetected(Judge); } } void RayDetect() { if (lineRender == null) return; int sign = handness == Handness.Left ? 1 : -1; DetectRays = new Ray[6] { new Ray(DetectArea.position, DetectArea.forward), new Ray(DetectArea.position, DetectArea.forward + sign * DetectArea.right), new Ray(DetectArea.position, sign * DetectArea.right), new Ray(DetectArea.position, sign * DetectArea.right - DetectArea.forward), new Ray(DetectArea.position, sign * DetectArea.right + (float)Math.Sqrt(3) * DetectArea.up), new Ray(DetectArea.position, sign * DetectArea.right - (float)Math.Sqrt(3) * DetectArea.up) }; RayDetectedTarget.Clear(); for (int i = 0; i < DetectRays.Length; ++i) { if (Physics.Raycast(DetectRays[i], out raycastHits[i]) && raycastHits[i].collider.gameObject.TryGetComponent(out var interactableObject)) { float Dist = Vector3.Distance(DetectArea.position, interactableObject.transform.position); if (interactableObject.objectFollowType != ObjectFollowType.Distance || Dist > interactableObject.TrackingDistance || interactableObject.IsFollow) continue; if (RayDetectedTarget.ContainsKey(interactableObject)) { RayDetectedTarget[interactableObject] = Math.Min(RayDetectedTarget[interactableObject], Dist); } else { RayDetectedTarget[interactableObject] = Dist; } } } int priority = 99; if(RayTargetObj != null && RayTargetObj != AreaObject) { RayTargetObj.interactableObjectOutline?.EnableWithHand((int)handness, false); } if (RayTargetObj == InteractObject && !InActive) InteractObject = null; RayTargetObj = null; foreach (InteractableObject obj in RayDetectedTarget.Keys) { if (priority > obj.InteractPriority) { priority = obj.InteractPriority; RayTargetObj = obj; } else if (priority == obj.InteractPriority && RayDetectedTarget[obj] < RayDetectedTarget[RayTargetObj]) { RayTargetObj = obj; } } UsedLine.Clear(); if (RayTargetObj != null && !InActive && AreaObject == null) { var SortedDict = GetFairInteractableDict(RayTargetObj); ChosenPose = SortedDict.Values.FirstOrDefault(); var SettingData = ChosenPose.GetComponent(); Fingers = new() { new Finger("Thumb", SettingData.Thumb, SettingData.ThumbIntense), new Finger("Index", SettingData.Index, SettingData.IndexIntense), new Finger("Middle", SettingData.Middle, SettingData.MiddleIntense), new Finger("Ring", SettingData.Ring, SettingData.RingIntense), new Finger("Pinky", SettingData.Pinky, SettingData.PinkyIntense), }; SeparateFinger = SettingData.SeparateFinger; ActiveIntense = SettingData.ActiveIntense; interactType = SettingData.interactType; if (ChosenPose.TryGetComponent(out var _regular)) { CalCloestPoint(TipTrans.position, _regular.RealStart, _regular.RealEnd, _regular.TipDist); } else { RegularTransferPosition = Vector3.zero; } var FingerSet = ChosenPose.HandPose.FingersFreedom; FixedPoseAngle = new(); for (int i = 0; i < 15; ++i) { if (ChosenPose != null && RayTargetObj.objectFollowType != ObjectFollowType.NoPose && (FingerSet == null || FingerSet[i / 3] != JointFreedom.Free)) { FixedPoseAngle.Add((OriginalAngle[i] * ChosenPose.HandPose.JointRotations[i]).eulerAngles); } else { FixedPoseAngle.Add(Vector3.zero); } } Vector3 HandToObj = RayTargetObj.transform.position - DetectArea.position; float Distance = HandToObj.magnitude; Vector3 project = Vector3.Project(DetectArea.forward, HandToObj); Vector3 normal = (DetectArea.forward - project).normalized; float MaxAngle = (float)Math.Atan(0.6 / Distance); float CurAngle = Vector3.Angle(DetectArea.forward, HandToObj); for (int i = 0; i <= 100; ++i) { float x = (Distance / 100) * i; float y = 0; if (CurAngle <= MaxAngle) { y = -(float)Math.Tan(CurAngle) / Distance * (float)Math.Pow(x, 2) + (float)Math.Tan(CurAngle) * x; } else { y = (-0.6f) / (float)Math.Pow(Distance, 2) * (float)Math.Pow(x, 2) + 0.6f / Distance * x; } Vector3 newPos = DetectArea.position + HandToObj.normalized * x + normal * y; CurveHighest = Math.Max(y, CurveHighest); if (i >= 15 && i <=96) { UsedLine.Add(newPos); } } CurveHighestVec = normal * CurveHighest; } lineRender.positionCount = UsedLine.Count; lineRender.SetPositions(UsedLine.ToArray()); } Vector3 RegularAngle(Vector3 euler) { var X = Math.Abs(euler.x) > 180 ? Math.Sign(euler.x) == 1 ? euler.x - 360 : euler.x + 360 : euler.x; var Y = Math.Abs(euler.y) > 180 ? Math.Sign(euler.y) == 1 ? euler.y - 360 : euler.y + 360 : euler.y; var Z = Math.Abs(euler.z) > 180 ? Math.Sign(euler.z) == 1 ? euler.z - 360 : euler.z + 360 : euler.z; return new Vector3(X, Y, Z); } public void RecordInteractionButton() { if (ContactObject == null) { Debug.LogError("Not In Runtime or Interaction Area!"); return; } #if UNITY_EDITOR GameObject virtualHand = ContactObject.DefaultVirtualHand; var RecordedTarget = Instantiate(virtualHand, ContactObject.transform); var interaction = RecordedTarget.GetComponent(); interaction._relativeTo = ContactObject.transform; interaction._handPose = new() { Handedness = (Handedness)handness, JointRotations = new Quaternion[15] { Quaternion.Inverse(OriginalAngle[0]) * Joints[0].localRotation, Quaternion.Inverse(OriginalAngle[1]) * Joints[1].localRotation, Quaternion.Inverse(OriginalAngle[2]) * Joints[2].localRotation, Quaternion.Inverse(OriginalAngle[3]) * Joints[3].localRotation, Quaternion.Inverse(OriginalAngle[4]) * Joints[4].localRotation, Quaternion.Inverse(OriginalAngle[5]) * Joints[5].localRotation, Quaternion.Inverse(OriginalAngle[6]) * Joints[6].localRotation, Quaternion.Inverse(OriginalAngle[7]) * Joints[7].localRotation, Quaternion.Inverse(OriginalAngle[8]) * Joints[8].localRotation, Quaternion.Inverse(OriginalAngle[9]) * Joints[9].localRotation, Quaternion.Inverse(OriginalAngle[10]) * Joints[10].localRotation, Quaternion.Inverse(OriginalAngle[11]) * Joints[11].localRotation, Quaternion.Inverse(OriginalAngle[12]) * Joints[12].localRotation, Quaternion.Inverse(OriginalAngle[13]) * Joints[13].localRotation, Quaternion.Inverse(OriginalAngle[14]) * Joints[14].localRotation, } }; RecordedTarget.transform.position = transform.position; if (!Directory.Exists(Application.dataPath + "/SDK/Interaction SDK/TempVirtual")) { Directory.CreateDirectory(Application.dataPath + "/SDK/Interaction SDK/TempVirtual"); } string PrefabName = "RecordedInteraction"; int index = 1; while (Directory.Exists(Application.dataPath + $"/SDK/Interaction SDK/TempVirtual/{PrefabName}.prefab")) { PrefabName = "RecordedInteraction" + index++; } PrefabUtility.SaveAsPrefabAsset(RecordedTarget, Application.dataPath + $"/SDK/Interaction SDK/TempVirtual/{PrefabName}.prefab"); #endif } public void RecordStaticPoseButton() { #if UNITY_EDITOR GameObject virtualHand = new GameObject(name + "NewStaticPose"); virtualHand.transform.SetParent(staticPoseDetector.transform); var staticPose = virtualHand.AddComponent(); Quaternion[] Target = new Quaternion[15]; if(OriginalAngle.Count == 0) { for(int i = 0; i < 15; ++i) { Target[i] = Quaternion.identity; } } else { for (int i = 0; i < 15; ++i) { Target[i] = Quaternion.Inverse(OriginalAngle[i]) * Joints[i].localRotation; } } staticPose._handPose = new() { Handedness = (Handedness)handness, JointRotations = Target }; virtualHand.transform.position = transform.position; if (!Directory.Exists(Application.dataPath + "/SDK/Interaction SDK/TempVirtual")) { Directory.CreateDirectory(Application.dataPath + "/SDK/Interaction SDK/TempVirtual"); } string PrefabName = virtualHand.name; int index = 1; while (File.Exists(Application.dataPath + $"/SDK/Interaction SDK/TempVirtual/{PrefabName}.prefab")) { PrefabName = virtualHand.name + index++; } PrefabUtility.SaveAsPrefabAsset(virtualHand, Application.dataPath + $"/SDK/Interaction SDK/TempVirtual/{PrefabName}.prefab"); #endif } } }