first commit
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
// Copyright (C) 2014-2024 Gleechi Technology AB. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System;
|
||||
|
||||
namespace VirtualGrasp.Scripts
|
||||
{
|
||||
/**
|
||||
* MyVirtualGrasp is a customizable main tutorial component.
|
||||
*
|
||||
* MyVirtualGrasp inherits from VG_MainScript, which wraps the main communication functions of the VirtualGrasp API.
|
||||
* VG_MainScript inherits from Monobehavior so you can use this as a component to a GameObject in Unity.
|
||||
* All the API functions you want to use in your own scripts can be accessed through VG_Controller.
|
||||
*/
|
||||
[LIBVIRTUALGRASP_UNITY_SCRIPT]
|
||||
[HelpURL("https://docs.virtualgrasp.com/unity_component_myvirtualgrasp." + VG_Version.__VG_VERSION__ + ".html")]
|
||||
public class MyVirtualGrasp : VG_MainScript
|
||||
{
|
||||
override public void Awake()
|
||||
{
|
||||
base.Awake(); // note: Awake can delete this component if there already is one.
|
||||
if (this != null)
|
||||
{
|
||||
VG_Controller.Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
override public void Update()
|
||||
{
|
||||
base.Update();
|
||||
}
|
||||
|
||||
override public void FixedUpdate()
|
||||
{
|
||||
base.FixedUpdate();
|
||||
}
|
||||
|
||||
void OnApplicationQuit()
|
||||
{
|
||||
SaveState();
|
||||
}
|
||||
|
||||
void OnApplicationPause()
|
||||
{
|
||||
// If linux save state
|
||||
var p = (int)Environment.OSVersion.Platform;
|
||||
if ((p == 4) || (p == 6) || (p == 128))
|
||||
SaveState();
|
||||
}
|
||||
|
||||
void SaveState()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
|
||||
if (m_graspDB != null)
|
||||
{
|
||||
string graspDBPath = AssetDatabase.GetAssetPath(m_graspDB);
|
||||
VG_Controller.SaveState(graspDBPath);
|
||||
AssetDatabase.ImportAsset(graspDBPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
VG_Controller.SaveState(null);
|
||||
}
|
||||
#else
|
||||
VG_Controller.SaveState(null);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 919761dfc44922242b6616aa92bf7903
|
||||
timeCreated: 1491831663
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,123 @@
|
||||
// Copyright (C) 2014-2024 Gleechi Technology AB. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
#if VG_ENABLE_INPUT_SYSTEM
|
||||
using UnityEngine.InputSystem;
|
||||
#else
|
||||
using UnityEngine.XR;
|
||||
#endif
|
||||
|
||||
namespace VirtualGrasp.Scripts
|
||||
{
|
||||
/**
|
||||
* VG_AnimationDriver provides a generic animation driver to drive finger and object animations
|
||||
* to achieve in-hand manipulation of articulated objects.
|
||||
*/
|
||||
[LIBVIRTUALGRASP_UNITY_SCRIPT]
|
||||
[HelpURL("https://docs.virtualgrasp.com/unity_component_vganimationdriver." + VG_Version.__VG_VERSION__ + ".html")]
|
||||
public class VG_AnimationDriver : MonoBehaviour
|
||||
{
|
||||
[SerializeField, Tooltip("Which hand is the driver")]
|
||||
public VG_HandSide m_handSide;
|
||||
#if VG_ENABLE_INPUT_SYSTEM
|
||||
[SerializeField, Tooltip("Which action drives this animation")]
|
||||
private InputActionReference m_actionReference;
|
||||
#else
|
||||
[SerializeField]
|
||||
private VG_VrButton button = VG_VrButton.TRIGGER;
|
||||
private InputDevice handInputDevice;
|
||||
#endif
|
||||
[SerializeField, Tooltip("Input value range")]
|
||||
private Vector2 m_inputRange = new Vector2(0f, 1f);
|
||||
[SerializeField, Tooltip("Optional, if unassigned this transform will be used")]
|
||||
private Transform m_interactableObject;
|
||||
|
||||
[Tooltip("Event driving animation from input")]
|
||||
public UnityEvent<float> OnDriven = new UnityEvent<float>();
|
||||
|
||||
[Tooltip("Generic animation driver events")]
|
||||
public UnityEvent OnEnabled = new UnityEvent();
|
||||
|
||||
[Tooltip("Generic animation driver events")]
|
||||
public UnityEvent OnDisabled = new UnityEvent();
|
||||
private float m_driveValue;
|
||||
private bool m_isHoldingHandRemote = false;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (this.m_interactableObject == null)
|
||||
this.m_interactableObject = transform;
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
OnDriven.Invoke(0.0f);
|
||||
OnEnabled.Invoke();
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
OnDisabled.Invoke();
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
VG_Controller.OnObjectGrasped.AddListener(OnObjectInteractionChanged);
|
||||
VG_Controller.OnObjectReleased.AddListener(OnObjectInteractionChanged);
|
||||
#if VG_ENABLE_INPUT_SYSTEM
|
||||
if(this.m_actionReference != null)
|
||||
this.m_actionReference.action.Enable();
|
||||
#endif
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
private void OnObjectInteractionChanged(VG_HandStatus status)
|
||||
{
|
||||
if (status.m_selectedObject != m_interactableObject) return;
|
||||
if (status.m_side != m_handSide) return;
|
||||
this.m_isHoldingHandRemote = status.m_isRemote;
|
||||
this.enabled = status.IsHolding();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
float inputValue = 0;
|
||||
#if VG_ENABLE_INPUT_SYSTEM
|
||||
if(this.m_actionReference != null)
|
||||
{
|
||||
inputValue = this.m_actionReference.action.ReadValue<float>();
|
||||
}
|
||||
#else
|
||||
if (m_handSide == VG_HandSide.LEFT)
|
||||
this.handInputDevice = InputDevices.GetDeviceAtXRNode(XRNode.LeftHand);
|
||||
else
|
||||
this.handInputDevice = InputDevices.GetDeviceAtXRNode(XRNode.RightHand);
|
||||
|
||||
if (!this.handInputDevice.TryGetFeatureValue(this.button == VG_VrButton.TRIGGER ?
|
||||
CommonUsages.trigger : CommonUsages.grip, out inputValue))
|
||||
{
|
||||
Debug.LogError($"Could not read button {this.button} on device {this.handInputDevice}");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (this.m_isHoldingHandRemote == false)
|
||||
{
|
||||
this.m_driveValue = Mathf.InverseLerp(m_inputRange.x, m_inputRange.y, inputValue);
|
||||
}
|
||||
OnDriven.Invoke(this.m_driveValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drives animation from other components, rather than the input reference
|
||||
/// </summary>
|
||||
public void Drive(float driveValue)
|
||||
{
|
||||
this.m_driveValue = driveValue;
|
||||
}
|
||||
|
||||
public float GetDriveValue() => this.m_driveValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 30c547db6b5e6864c9c3c17f41d99fba
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,356 @@
|
||||
// Copyright (C) 2014-2024 Gleechi Technology AB. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using VirtualGrasp;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace VirtualGrasp.Scripts
|
||||
{
|
||||
/**
|
||||
* VG_Assemble provides a tool to assemble / dissemble an object through VG_Articulation.
|
||||
* The MonoBehavior provides a tutorial on the VG API functions VG_Controller.ChangeObjectJoint and RecoverObjectJoint.
|
||||
*/
|
||||
[LIBVIRTUALGRASP_UNITY_SCRIPT]
|
||||
[HelpURL("https://docs.virtualgrasp.com/unity_component_vgassemble." + VG_Version.__VG_VERSION__ + ".html")]
|
||||
public class VG_Assemble : MonoBehaviour
|
||||
{
|
||||
public enum AxisType
|
||||
{
|
||||
[Tooltip("Not match any axis, i.e. no rotation match")]
|
||||
NoAxis = 0,
|
||||
[Tooltip("Match X axis")]
|
||||
XAxis = 1,
|
||||
[Tooltip("Match Y axis")]
|
||||
YAxis = 2,
|
||||
[Tooltip("Match Z axis")]
|
||||
ZAxis = 3,
|
||||
[Tooltip("Match X axis in both direction")]
|
||||
XAxisSymmetric = 4,
|
||||
[Tooltip("Match Y axis in both direction")]
|
||||
YAxisSymmetric = 5,
|
||||
[Tooltip("Match Z axis in both direction")]
|
||||
ZAxisSymmetric = 6
|
||||
}
|
||||
|
||||
|
||||
[Tooltip("If this object will be reparented to the parent of the desired pose transform when it is assembled.")]
|
||||
public bool m_assembleToParent = true;
|
||||
[Tooltip("The target pose(s) of the assembled object (or assemble anchor if provided).")]
|
||||
public List<Transform> m_desiredPoses = new List<Transform>();
|
||||
[Tooltip("Threshold to assemble when object (or assemble anchor) to desired pose is smaller than this value (m).")]
|
||||
public float m_assembleDistance = 0.05f;
|
||||
[Tooltip("Threshold to assemble when object (or assemble anchor) to desired rotation is smaller than this value (deg).")]
|
||||
public float m_assembleAngle = 45;
|
||||
[Tooltip("The axis of the Assemble Anchor if assigned (otherwise of the object) to be aligned for assembling to a desired rotation. If NoAxis then no match to target rotation.")]
|
||||
public AxisType m_assembleAxis = AxisType.YAxis;
|
||||
[Tooltip("The number of discrete steps across 360 deg around Assemble Axis to match the rotation. 0 means rotational symmetric.")]
|
||||
public int m_assembleSymmetrySteps = 0;
|
||||
[Tooltip("If provided will match this anchor to the desired pose, otherwise match this object.")]
|
||||
public Transform m_assembleAnchor = null;
|
||||
|
||||
[Tooltip("Threshold to disassemble when object (or assemble anchor) distance to desired pose is bigger than this value (m).")]
|
||||
public float m_disassembleDistance = 0.25f;
|
||||
[Tooltip("If only allow dissasemble when object is at the zero joint state (most relevant for screw joint).")]
|
||||
public bool m_disassembleOnZeroState = false;
|
||||
|
||||
[Tooltip("The VG Articulation of constrained (non-FLOATING) joint type to switch to when assembling an object. If null will use Fixed joint.")]
|
||||
public VG_Articulation m_assembleArticulation = null;
|
||||
|
||||
[Tooltip("The VG Articulation of floating joint type to switch to when disassembling an object. Must be provided if original joint is non-Floating.")]
|
||||
public VG_Articulation m_disassembleArticulation = null;
|
||||
|
||||
[Tooltip("If force the disassembled object to become physical. Only relevant if original joint is non-Floating.")]
|
||||
public bool m_forceDisassembledPhysical = false;
|
||||
|
||||
[Tooltip("Event triggered when object is to be assembled (satisfying assembling criterior but before joint and parent change), return the matched target transform.")]
|
||||
public UnityEvent<Transform> OnBeforeAssembled = new UnityEvent<Transform>();
|
||||
[Tooltip("Event triggered when object is assembled, return the assembled target transform.")]
|
||||
public UnityEvent<Transform> OnAssembled = new UnityEvent<Transform>();
|
||||
[Tooltip("Event triggered when object is disassembled, return the target transform from which the object is disassembled.")]
|
||||
public UnityEvent<Transform> OnDisassembled = new UnityEvent<Transform>();
|
||||
|
||||
private float m_timeAtDisassemble = 0.0F;
|
||||
private float m_assembleDelay = 2.0F;
|
||||
|
||||
public Transform m_disassembleParent = null;
|
||||
private Transform m_desiredPose = null;
|
||||
|
||||
void Start()
|
||||
{
|
||||
if (m_assembleArticulation == null)
|
||||
Debug.LogWarning("Assemble Articulation is not assigned, so assemble will use Fixed joint for " + transform.name, transform);
|
||||
else if (m_assembleArticulation.m_type == VG_JointType.FLOATING)
|
||||
{
|
||||
Debug.LogError(transform.name + "'s Assemble Articulation can not be FLOATING joint type.", transform);
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
if(VG_Controller.GetObjectJointType(transform, true, out VG_JointType jointType) == VG_ReturnCode.SUCCESS && jointType == VG_JointType.FLOATING
|
||||
&& m_disassembleParent == null)
|
||||
m_disassembleParent = transform.parent;
|
||||
|
||||
if (VG_Controller.GetObjectJointType(transform, true, out VG_JointType originalJointType) == VG_ReturnCode.SUCCESS
|
||||
&& originalJointType != VG_JointType.FLOATING)
|
||||
{
|
||||
if (m_disassembleArticulation == null)
|
||||
{
|
||||
Debug.LogError(transform.name + "'s initial joint is constrained type " + originalJointType + ", Disassemble Articulation with FLOATING joint type needs to be assigned.", transform);
|
||||
this.enabled = false;
|
||||
}
|
||||
else if (m_disassembleArticulation.m_type != VG_JointType.FLOATING)
|
||||
{
|
||||
Debug.LogError(transform.name + "'s Disassemble Articulation has to be FLOATING joint type.", transform);
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
// If originally is non-floating means disassemble parent should be original parent's parent
|
||||
if(m_disassembleParent == null)
|
||||
m_disassembleParent = transform.parent.parent;
|
||||
}
|
||||
|
||||
if (m_assembleAnchor == null)
|
||||
m_assembleAnchor = transform;
|
||||
}
|
||||
|
||||
public void SetTargetTransformActive(bool active)
|
||||
{
|
||||
if(m_desiredPose != null)
|
||||
m_desiredPose.gameObject.SetActive(active);
|
||||
}
|
||||
void LateUpdate()
|
||||
{
|
||||
assembleByJointChange();
|
||||
disassembleByJointChange();
|
||||
}
|
||||
|
||||
void assembleByJointChange()
|
||||
{
|
||||
Quaternion relRot = Quaternion.identity;
|
||||
if (!findTarget(ref relRot))
|
||||
return;
|
||||
|
||||
VG_JointType jointType;
|
||||
if ((Time.timeSinceLevelLoad - m_timeAtDisassemble) > m_assembleDelay
|
||||
&& VG_Controller.GetObjectJointType(transform, false, out jointType) == VG_ReturnCode.SUCCESS &&
|
||||
jointType == VG_JointType.FLOATING)
|
||||
{
|
||||
OnBeforeAssembled.Invoke(m_desiredPose);
|
||||
|
||||
// Project object rotation axis to align to desired rotation axis.
|
||||
transform.SetPositionAndRotation(transform.position, relRot * transform.rotation);
|
||||
Vector3 offset = m_desiredPose.position - m_assembleAnchor.position;
|
||||
transform.SetPositionAndRotation(transform.position + offset, transform.rotation);
|
||||
|
||||
if (m_assembleToParent)
|
||||
transform.SetParent(m_desiredPose.parent);
|
||||
|
||||
VG_ReturnCode ret = m_assembleArticulation ? VG_Controller.ChangeObjectJoint(transform, m_assembleArticulation) : VG_Controller.ChangeObjectJoint(transform, VG_JointType.FIXED);
|
||||
if (ret != VG_ReturnCode.SUCCESS)
|
||||
Debug.LogError("Failed to ChangeObjectJoint() on " + transform.name + " with return code " + ret, transform);
|
||||
|
||||
OnAssembled.Invoke(m_desiredPose);
|
||||
}
|
||||
}
|
||||
|
||||
void disassembleByJointChange()
|
||||
{
|
||||
// When the object with simulated weight is in "heaving lifting" phase, disallow distance-based disassemble
|
||||
if (VG_Controller.IsLiftingObject(transform))
|
||||
return;
|
||||
|
||||
foreach (VG_HandStatus hand in VG_Controller.GetHands())
|
||||
{
|
||||
VG_JointType jointType;
|
||||
if (hand.m_selectedObject == transform && hand.IsHolding()
|
||||
&& VG_Controller.GetObjectJointType(transform, false, out jointType) == VG_ReturnCode.SUCCESS
|
||||
&& jointType != VG_JointType.FLOATING)
|
||||
{
|
||||
getSensorControlledAnchorPose(hand, out Vector3 sensor_anchor_pos, out Quaternion sensor_anchor_rot);
|
||||
|
||||
if (isZeroState(jointType)
|
||||
&& (sensor_anchor_pos - m_assembleAnchor.position).magnitude > m_disassembleDistance
|
||||
)
|
||||
{
|
||||
if (m_assembleToParent || m_disassembleParent == null)
|
||||
transform.SetParent(transform.parent.parent);
|
||||
else
|
||||
transform.SetParent(m_disassembleParent);
|
||||
|
||||
VG_Controller.GetObjectJointType(transform, true, out VG_JointType originalJointType);
|
||||
if (originalJointType == VG_JointType.FLOATING)
|
||||
{
|
||||
if (VG_Controller.RecoverObjectJoint(transform) != VG_ReturnCode.SUCCESS)
|
||||
{
|
||||
Debug.LogError("Failed to disassemble with RecoverObjectJoint() on " + transform.name);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
else if (m_disassembleArticulation != null)
|
||||
{
|
||||
if (VG_Controller.ChangeObjectJoint(transform, m_disassembleArticulation) != VG_ReturnCode.SUCCESS)
|
||||
{
|
||||
Debug.LogError("Failed to disassemble with ChangeObjectJoint() on " + transform.name);
|
||||
return;
|
||||
}
|
||||
// When object originally has VG constrained joint type (non-Floating) it has to be a non-physical object,
|
||||
// therefore disassemble will not recover its original physical property, so here we add rigid body
|
||||
// if user choose to m_makeDisassembledPhysical.
|
||||
if (m_forceDisassembledPhysical && !transform.gameObject.TryGetComponent<Rigidbody>(out Rigidbody rb))
|
||||
{
|
||||
rb = transform.gameObject.AddComponent<Rigidbody>();
|
||||
rb.useGravity = true;
|
||||
if (!transform.TryGetComponent<Collider>(out _))
|
||||
{
|
||||
MeshCollider collider = transform.gameObject.AddComponent<MeshCollider>();
|
||||
collider.convex = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("Failed to disassemble with ChangeObjectJoint() since Disassemble Articulation on " + transform.name + " is not assigned.");
|
||||
return;
|
||||
}
|
||||
|
||||
m_timeAtDisassemble = Time.timeSinceLevelLoad;
|
||||
|
||||
OnDisassembled.Invoke(m_desiredPose);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool isZeroState(VG_JointType jointType)
|
||||
{
|
||||
if (!m_disassembleOnZeroState)
|
||||
return true;
|
||||
|
||||
// If object is of a joint type that has no relevant joint states, then no zero state control for disassemble so return true
|
||||
VG_Controller.GetObjectJointState(transform, out float jointState);
|
||||
if (jointType == VG_JointType.REVOLUTE || jointType == VG_JointType.PRISMATIC || jointType == VG_JointType.CONE)
|
||||
return jointState == 0.0F;
|
||||
else if (jointType == VG_JointType.PLANAR)
|
||||
{
|
||||
VG_Controller.GetObjectSecondaryJointState(transform, out float jointState2);
|
||||
return (jointState == 0 && jointState2 == 0);
|
||||
}
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
void getSensorControlledAnchorPose(VG_HandStatus hand, out Vector3 anchorPos, out Quaternion anchorRot)
|
||||
{
|
||||
// Compute relative pose of anchor to grasping hand pose
|
||||
Vector3 lp = Quaternion.Inverse(hand.m_hand.rotation) * (m_assembleAnchor.position - hand.m_hand.position);
|
||||
Quaternion lq = Quaternion.Inverse(hand.m_hand.rotation) * m_assembleAnchor.rotation;
|
||||
|
||||
// Then evaluate anchor rotation corresponding to hand pose determined by sensor
|
||||
VG_Controller.GetSensorPose(hand.m_avatarID, hand.m_side, out Vector3 sensor_pos, out Quaternion sensor_rot);
|
||||
anchorPos = sensor_rot * lp + sensor_pos;
|
||||
anchorRot = sensor_rot * lq;
|
||||
}
|
||||
|
||||
bool findTarget(ref Quaternion relRot)
|
||||
{
|
||||
// If object is already assembled, don't need to find target.
|
||||
if (VG_Controller.GetObjectJointType(transform, false, out VG_JointType jointType) == VG_ReturnCode.SUCCESS
|
||||
&& jointType != VG_JointType.FLOATING)
|
||||
return false;
|
||||
m_desiredPose = null;
|
||||
foreach (Transform pose in m_desiredPoses)
|
||||
{
|
||||
if (closeToTargetPose(m_assembleAnchor, pose, m_assembleAxis, m_assembleSymmetrySteps, ref relRot))
|
||||
{
|
||||
m_desiredPose = pose;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool closeToTargetPose(Transform anchor, Transform target, AxisType assembleAxis, int angleSteps, ref Quaternion relRot)
|
||||
{
|
||||
return (target.position - anchor.position).magnitude < m_assembleDistance &&
|
||||
closeToTargetRotation(anchor, target, assembleAxis, angleSteps, ref relRot);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if rotationally close to target and compute relRot to rotate anchor to target
|
||||
/// </summary>
|
||||
/// <param name="anchor">Anchor whose rotation is to match to target</param>
|
||||
/// <param name="target">Target to map anchor rotation to</param>
|
||||
/// <param name="assembleAxis">Axis type to map rotation on a plane defined by this</param>
|
||||
/// <param name="angleSteps">number of steps of angles to define a set of evenly distributed axes on the plane defined by assembleAxis</param>
|
||||
/// <param name="relRot">Output, the relative rotation to apply to anchor to map to target</param>
|
||||
/// <returns></returns>
|
||||
bool closeToTargetRotation(Transform anchor, Transform target, AxisType assembleAxis, int angleSteps, ref Quaternion relRot)
|
||||
{
|
||||
relRot = Quaternion.identity;
|
||||
if (assembleAxis == AxisType.NoAxis)
|
||||
return true;
|
||||
float angle = 0.0F;
|
||||
Quaternion relRot2 = Quaternion.identity;
|
||||
float angle2 = 0.0F;
|
||||
switch (assembleAxis)
|
||||
{
|
||||
case AxisType.XAxis:
|
||||
case AxisType.XAxisSymmetric:
|
||||
computeRelativeRotationAngle(anchor.right, target.right, target.forward, (assembleAxis == AxisType.XAxisSymmetric)? 2 : 1, ref relRot, ref angle);
|
||||
if(angleSteps >0)
|
||||
computeRelativeRotationAngle((relRot * anchor.rotation) * Vector3.up, target.up, target.right, angleSteps, ref relRot2, ref angle2);
|
||||
relRot = relRot2 * relRot;
|
||||
break;
|
||||
case AxisType.YAxis:
|
||||
case AxisType.YAxisSymmetric:
|
||||
computeRelativeRotationAngle(anchor.up, target.up, target.forward, (assembleAxis == AxisType.YAxisSymmetric) ? 2 : 1, ref relRot, ref angle);
|
||||
if(angleSteps >0)
|
||||
computeRelativeRotationAngle((relRot * anchor.rotation) * Vector3.right, target.right, target.up, angleSteps, ref relRot2, ref angle2);
|
||||
relRot = relRot2 * relRot;
|
||||
break;
|
||||
case AxisType.ZAxis:
|
||||
case AxisType.ZAxisSymmetric:
|
||||
computeRelativeRotationAngle(anchor.forward, target.forward, target.right, (assembleAxis == AxisType.ZAxisSymmetric) ? 2 : 1, ref relRot, ref angle);
|
||||
if(angleSteps >0)
|
||||
computeRelativeRotationAngle((relRot * anchor.rotation) * Vector3.right, target.right, target.forward, angleSteps, ref relRot2, ref angle2);
|
||||
relRot = relRot2 * relRot;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return (angle < m_assembleAngle && angle2 < m_assembleAngle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute relative rotation and angle from anchor axis to a set of axis starting form target axis following a number of steps, will find smallest relRot / angle
|
||||
/// </summary>
|
||||
/// <param name="anchorAxis">The axis to rotate to one of the targetAxis</param>
|
||||
/// <param name="targetAxis">The target axis as starting axis to rotate to</param>
|
||||
/// <param name="axis">The axis orthorganal to targetAxis around which will rotate a step from starting targetAxis</param>
|
||||
/// <param name="angleSteps">The number of steps around 360 deg to rotate target axis, if 0 means rotational symmetric</param>
|
||||
/// <param name="relRot">Output, the minimum relative rotation to map anchor axis to target axis</param>
|
||||
/// <param name="angle">Output, angle corresponding to relRot</param>
|
||||
void computeRelativeRotationAngle(Vector3 anchorAxis, Vector3 targetAxis, Vector3 axis, int angleSteps, ref Quaternion relRot, ref float angle)
|
||||
{
|
||||
relRot = Quaternion.identity;
|
||||
angle = 360.0f;
|
||||
Quaternion rot = Quaternion.AngleAxis(360.0F / angleSteps, axis);
|
||||
float rel_angle = 0.0f;
|
||||
Quaternion rel_rot = Quaternion.identity;
|
||||
for (int i = 0; i < angleSteps; i++)
|
||||
{
|
||||
rel_rot = Quaternion.FromToRotation(anchorAxis, targetAxis);
|
||||
rel_rot.ToAngleAxis(out rel_angle, out _);
|
||||
if (rel_angle < angle)
|
||||
{
|
||||
angle = rel_angle;
|
||||
relRot = rel_rot;
|
||||
}
|
||||
targetAxis = rot * targetAxis;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bcb8902ac694dbe4f9e61e77c4d5e9ec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aae1162d0c164084398d6b52b1ec64aa
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright (C) 2014-2024 Gleechi Technology AB. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VirtualGrasp.Controllers
|
||||
{
|
||||
/**
|
||||
* This is an external controller class that supports a Mouse controller as an external controller.
|
||||
* Please refer to https://docs.virtualgrasp.com/controllers.html for the definition of an external controller for VG.
|
||||
*/
|
||||
[LIBVIRTUALGRASP_UNITY_SCRIPT]
|
||||
[HelpURL("https://docs.virtualgrasp.com/unity_vg_cp_generic." + VG_Version.__VG_VERSION__ + ".html")]
|
||||
public class VG_EC_Generic : VG_ExternalController
|
||||
{
|
||||
[Serializable]
|
||||
public class HandMapping : VG_BoneMapping
|
||||
{
|
||||
public override void Initialize(int avatarID, VG_HandSide side)
|
||||
{
|
||||
base.Initialize(avatarID, side);
|
||||
m_BoneToTransform = new Dictionary<int, Transform>()
|
||||
{
|
||||
{ 0, Hand_WristRoot },
|
||||
{ 1, Hand_Thumb1 },
|
||||
{ 2, Hand_Thumb2 },
|
||||
{ 3, Hand_Thumb3 },
|
||||
{ 4, Hand_Index1 },
|
||||
{ 5, Hand_Index2 },
|
||||
{ 6, Hand_Index3 },
|
||||
{ 7, Hand_Middle1 },
|
||||
{ 8, Hand_Middle2 },
|
||||
{ 9, Hand_Middle3 },
|
||||
{ 10, Hand_Ring1 },
|
||||
{ 11, Hand_Ring2 },
|
||||
{ 12, Hand_Ring3 },
|
||||
{ 13, Hand_Pinky1 },
|
||||
{ 14, Hand_Pinky2 },
|
||||
{ 15, Hand_Pinky3 }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public VG_EC_Generic(int avatarID, VG_HandSide side, Transform origin)
|
||||
{
|
||||
m_avatarID = avatarID;
|
||||
m_handType = side;
|
||||
m_origin = origin;
|
||||
m_zeroOffsets = true; // the generic hand works on the Unity transforms, so it can't use offsets.
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public new void Initialize()
|
||||
{
|
||||
m_mapping = new HandMapping();
|
||||
base.Initialize();
|
||||
m_enabled = true;
|
||||
}
|
||||
|
||||
public override bool Compute()
|
||||
{
|
||||
for (int bone = 0; bone < m_mapping.GetNumBones(); bone++)
|
||||
{
|
||||
if (!m_mapping.GetTransform(bone, out Transform pose)) continue;
|
||||
SetPose(bone, Matrix4x4.TRS(pose.position, pose.rotation, Vector3.one));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override float GetGrabStrength()
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
public override Color GetConfidence()
|
||||
{
|
||||
return Color.yellow;
|
||||
}
|
||||
|
||||
public override void HapticPulse(VG_HandStatus hand, float amplitude = 0.5F, float duration = 0.015F, int finger = 5)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ba28b8b076ea2a45a4f03b9f332ec23
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,205 @@
|
||||
// Copyright (C) 2014-2024 Gleechi Technology AB. All rights reserved.
|
||||
|
||||
//#define VG_USE_LEAP_CONTROLLER
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
#if VG_USE_LEAP_CONTROLLER
|
||||
using Leap.Unity;
|
||||
#endif
|
||||
|
||||
namespace VirtualGrasp.Controllers
|
||||
{
|
||||
/**
|
||||
* This is an external controller class that supports the LeapMotion controller as an external controller.
|
||||
* Please refer to https://docs.virtualgrasp.com/controllers.html for the definition of an external controller for VG.
|
||||
*
|
||||
* The following requirements have to be met to be able to enable the #define VG_USE_LEAP_CONTROLLER above and use the controller:
|
||||
* - You have a Core Assets plugin from https://developer.leapmotion.com/unity imported into your Unity project.
|
||||
* - Note that Core Assets > 4.4.0 are for LeapMotion SDK 4, older are for LeapMotion SDK 3 (lastest CA 4.3.4).
|
||||
* - You have the corresponding LeapMotion SDK (https://developer.leapmotion.com/sdk-leap-motion-controller/) installed on your computer.
|
||||
*/
|
||||
[LIBVIRTUALGRASP_UNITY_SCRIPT]
|
||||
[HelpURL("https://docs.virtualgrasp.com/unity_vg_ec_leap." + VG_Version.__VG_VERSION__ + ".html")]
|
||||
public class VG_EC_Leap : VG_ExternalController
|
||||
{
|
||||
#if VG_USE_LEAP_CONTROLLER
|
||||
static LeapProvider m_provider = null;
|
||||
Leap.Hand m_hand = null;
|
||||
|
||||
static public int LeapBoneToInt(int finger, int bone)
|
||||
{
|
||||
return 4 * finger + bone + 1;
|
||||
}
|
||||
|
||||
static public int LeapBoneToInt(Leap.Finger.FingerType finger, Leap.Bone.BoneType bone)
|
||||
{
|
||||
return LeapBoneToInt((int)finger, (int)bone);
|
||||
}
|
||||
|
||||
static public void IntToLeapBone(int id, out int finger, out Leap.Bone.BoneType bone)
|
||||
{
|
||||
bone = (Leap.Bone.BoneType)((id - 1) % 4);
|
||||
finger = (id - (int)bone) / 4;
|
||||
}
|
||||
#endif
|
||||
|
||||
[Serializable]
|
||||
public class HandMapping : VG_BoneMapping
|
||||
{
|
||||
public override void Initialize(int avatarID, VG_HandSide side)
|
||||
{
|
||||
base.Initialize(avatarID, side);
|
||||
m_BoneToTransform = new Dictionary<int, Transform>()
|
||||
{
|
||||
#if VG_USE_LEAP_CONTROLLER
|
||||
{ 0, Hand_WristRoot },
|
||||
{ LeapBoneToInt(0, 0), null },
|
||||
{ LeapBoneToInt(0, 1), Hand_Thumb1 },
|
||||
{ LeapBoneToInt(0, 2), Hand_Thumb2 },
|
||||
{ LeapBoneToInt(0, 3), Hand_Thumb3 },
|
||||
{ LeapBoneToInt(1, 0), null },
|
||||
{ LeapBoneToInt(1, 1), Hand_Index1 },
|
||||
{ LeapBoneToInt(1, 2), Hand_Index2 },
|
||||
{ LeapBoneToInt(1, 3), Hand_Index3 },
|
||||
{ LeapBoneToInt(2, 0), null },
|
||||
{ LeapBoneToInt(2, 1), Hand_Middle1 },
|
||||
{ LeapBoneToInt(2, 2), Hand_Middle2 },
|
||||
{ LeapBoneToInt(2, 3), Hand_Middle3 },
|
||||
{ LeapBoneToInt(3, 0), null },
|
||||
{ LeapBoneToInt(3, 1), Hand_Ring1 },
|
||||
{ LeapBoneToInt(3, 2), Hand_Ring2 },
|
||||
{ LeapBoneToInt(3, 3), Hand_Ring3 },
|
||||
{ LeapBoneToInt(4, 0), null },
|
||||
{ LeapBoneToInt(4, 1), Hand_Pinky1 },
|
||||
{ LeapBoneToInt(4, 2), Hand_Pinky2 },
|
||||
{ LeapBoneToInt(4, 3), Hand_Pinky3 }
|
||||
#endif
|
||||
};
|
||||
|
||||
m_BoneToParent = new Dictionary<int, int>()
|
||||
{
|
||||
};
|
||||
#if VG_USE_LEAP_CONTROLLER
|
||||
m_BoneToParent[LeapBoneToInt(0, 0)] = 0;
|
||||
m_BoneToParent[LeapBoneToInt(0, 1)] = LeapBoneToInt(0, 0);
|
||||
m_BoneToParent[LeapBoneToInt(0, 2)] = LeapBoneToInt(0, 1);
|
||||
m_BoneToParent[LeapBoneToInt(0, 3)] = LeapBoneToInt(0, 2);
|
||||
m_BoneToParent[LeapBoneToInt(1, 0)] = 0;
|
||||
m_BoneToParent[LeapBoneToInt(1, 1)] = LeapBoneToInt(1, 0);
|
||||
m_BoneToParent[LeapBoneToInt(1, 2)] = LeapBoneToInt(1, 1);
|
||||
m_BoneToParent[LeapBoneToInt(1, 3)] = LeapBoneToInt(1, 2);
|
||||
m_BoneToParent[LeapBoneToInt(2, 0)] = 0;
|
||||
m_BoneToParent[LeapBoneToInt(2, 1)] = LeapBoneToInt(2, 0);
|
||||
m_BoneToParent[LeapBoneToInt(2, 2)] = LeapBoneToInt(2, 1);
|
||||
m_BoneToParent[LeapBoneToInt(2, 3)] = LeapBoneToInt(2, 2);
|
||||
m_BoneToParent[LeapBoneToInt(3, 0)] = 0;
|
||||
m_BoneToParent[LeapBoneToInt(3, 1)] = LeapBoneToInt(3, 0);
|
||||
m_BoneToParent[LeapBoneToInt(3, 2)] = LeapBoneToInt(3, 1);
|
||||
m_BoneToParent[LeapBoneToInt(3, 3)] = LeapBoneToInt(3, 2);
|
||||
m_BoneToParent[LeapBoneToInt(4, 0)] = 0;
|
||||
m_BoneToParent[LeapBoneToInt(4, 1)] = LeapBoneToInt(4, 0);
|
||||
m_BoneToParent[LeapBoneToInt(4, 2)] = LeapBoneToInt(4, 1);
|
||||
m_BoneToParent[LeapBoneToInt(4, 3)] = LeapBoneToInt(4, 2);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public VG_EC_Leap(int avatarID, VG_HandSide side, Transform origin)
|
||||
{
|
||||
m_avatarID = avatarID;
|
||||
m_handType = side;
|
||||
m_origin = origin;
|
||||
m_enablingDefine = "VG_USE_LEAP_CONTROLLER";
|
||||
|
||||
#if VG_USE_LEAP_CONTROLLER
|
||||
m_enabled = true;
|
||||
#else
|
||||
PrintNotEnabledError();
|
||||
m_enabled = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public new void Initialize()
|
||||
{
|
||||
#if VG_USE_LEAP_CONTROLLER
|
||||
if (m_provider == null)
|
||||
{
|
||||
m_provider = GameObject.FindObjectOfType<VG_MainScript>().gameObject.AddComponent<LeapServiceProvider>();
|
||||
}
|
||||
|
||||
if (m_provider != null)
|
||||
{
|
||||
m_mapping = new HandMapping();
|
||||
base.Initialize();
|
||||
}
|
||||
|
||||
m_initialized = (m_provider != null);
|
||||
#endif
|
||||
}
|
||||
|
||||
public override bool Compute()
|
||||
{
|
||||
#if VG_USE_LEAP_CONTROLLER
|
||||
if (!m_enabled) return false;
|
||||
if (!m_initialized || m_mapping == null) { Initialize(); return false; }
|
||||
if (m_provider == null) return false;
|
||||
|
||||
Leap.Frame frame = m_provider.CurrentFrame;
|
||||
if (frame == null) return false;
|
||||
|
||||
m_hand = null;
|
||||
foreach (Leap.Hand hand in frame.Hands)
|
||||
{
|
||||
if (hand.IsLeft && m_handType == VG_HandSide.LEFT) { m_hand = hand; break; }
|
||||
if (!hand.IsLeft && m_handType == VG_HandSide.RIGHT) { m_hand = hand; break; }
|
||||
}
|
||||
if (m_hand == null) return false;
|
||||
|
||||
for (int boneId = 0; boneId < GetNumBones(); boneId++)
|
||||
{
|
||||
if (boneId == 0)
|
||||
{
|
||||
SetPose(boneId, Matrix4x4.TRS(m_hand.WristPosition, m_hand.Rotation, Vector3.one));
|
||||
}
|
||||
else
|
||||
{
|
||||
IntToLeapBone(boneId, out int finger, out Leap.Bone.BoneType bone);
|
||||
Leap.Bone b = m_hand.Fingers[finger].Bone(bone);
|
||||
SetPose(boneId, Matrix4x4.TRS(b.NextJoint, b.Rotation, Vector3.one));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public override float GetGrabStrength()
|
||||
{
|
||||
#if VG_USE_LEAP_CONTROLLER
|
||||
// Get grab strength from Leap if available
|
||||
//return m_hand != null ? m_hand.GrabStrength : 0.0f;
|
||||
return -1.0f; // let VG decide from full DOF
|
||||
#else
|
||||
return 0.0f;
|
||||
#endif
|
||||
}
|
||||
|
||||
public override Color GetConfidence()
|
||||
{
|
||||
#if VG_USE_LEAP_CONTROLLER
|
||||
if (m_hand != null) return Color.black;
|
||||
return m_hand.Confidence > 0.5f ? Color.green : Color.red;
|
||||
#else
|
||||
return Color.yellow;
|
||||
#endif
|
||||
}
|
||||
|
||||
public override void HapticPulse(VG_HandStatus hand, float amplitude = 0.5F, float duration = 0.015F, int finger = 5)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c735f357c234e0b4e9a5c8db5e88f37c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,413 @@
|
||||
// Copyright (C) 2014-2024 Gleechi Technology AB. All rights reserved.
|
||||
|
||||
//#define VG_USE_MANUS_CONTROLLER
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
#if VG_USE_MANUS_CONTROLLER
|
||||
using System.Linq;
|
||||
using Manus;
|
||||
using Manus.Skeletons;
|
||||
using Manus.Utility;
|
||||
#endif
|
||||
|
||||
namespace VirtualGrasp.Controllers
|
||||
{
|
||||
/**
|
||||
* This is an external controller class that supports the Manus Glove controller as an external controller.
|
||||
* Please refer to https://docs.virtualgrasp.com/controllers.html for the definition of an external controller for VG.
|
||||
*
|
||||
* The following requirements have to be met to be able to enable the #define VG_USE_MANUS_CONTROLLER above and use the controller:
|
||||
* - You have the corresponding Manus Core SDK (https://resources.manus-meta.com/downloads) installed on your computer.
|
||||
* - You have the Unity Plugin for Manus Core from https://resources.manus-meta.com/downloads imported into your Unity project.
|
||||
* - You have a Manus Pro License assigned to your SDK to use the Unity Plugin.
|
||||
*/
|
||||
[LIBVIRTUALGRASP_UNITY_SCRIPT]
|
||||
[HelpURL("https://docs.virtualgrasp.com/unity_vg_ec_manus." + VG_Version.__VG_VERSION__ + ".html")]
|
||||
public class VG_EC_Manus : VG_ExternalController
|
||||
{
|
||||
#if VG_USE_MANUS_CONTROLLER
|
||||
private Skeleton m_skeleton = null;
|
||||
private CoreSDK.SkeletonStream m_skeletonData;
|
||||
private int m_id = -1;
|
||||
private static uint MANUS_SKELETON_ID = 0;
|
||||
|
||||
private void onSkeletonData(CoreSDK.SkeletonStream data)
|
||||
{
|
||||
m_skeletonData = data;
|
||||
}
|
||||
|
||||
protected void IntFromManusBone(uint nodeId, out int boneId)
|
||||
{
|
||||
boneId = (int)nodeId;
|
||||
}
|
||||
|
||||
static private void PrintNode(CoreSDK.SkeletonNode node)
|
||||
{
|
||||
Debug.Log("CNode " + node.ToString());
|
||||
}
|
||||
|
||||
static private void PrintNode(Node node)
|
||||
{
|
||||
Debug.Log("Node " + node.id + " (" + node.name + "|" + node.nodeName + ")" +
|
||||
node.unityTransform.name + " (type: " + node.type + "; parent: " + node.parentID + ")"
|
||||
);
|
||||
}
|
||||
static private void PrintChain(Chain chain)
|
||||
{
|
||||
string str = "";
|
||||
foreach (uint id in chain.nodeIds)
|
||||
str += id + ",";
|
||||
Debug.Log("Chain " + chain.id + " (" + chain.name + "|" + chain.dataSide + ")"
|
||||
+ " (type: " + chain.type + "; hand: " + chain.settings.finger.handChainId + "); meta " + chain.settings.finger.metacarpalBoneId
|
||||
+ "; " + str);
|
||||
}
|
||||
#endif
|
||||
|
||||
[Serializable]
|
||||
public class HandMapping : VG_BoneMapping
|
||||
{
|
||||
public override void Initialize(int avatarID, VG_HandSide side)
|
||||
{
|
||||
base.Initialize(avatarID, side);
|
||||
m_BoneToTransform = new Dictionary<int, Transform>()
|
||||
{
|
||||
#if VG_USE_MANUS_CONTROLLER
|
||||
{ 0, Hand_WristRoot },
|
||||
|
||||
{ 1, Hand_Thumb1 },
|
||||
{ 2, Hand_Thumb2 },
|
||||
{ 3, Hand_Thumb3 },
|
||||
{ 4, null },
|
||||
|
||||
{ 5, null },
|
||||
{ 6, Hand_Index1 },
|
||||
{ 7, Hand_Index2 },
|
||||
{ 8, Hand_Index3 },
|
||||
{ 9, null },
|
||||
|
||||
{ 10, null },
|
||||
{ 11, Hand_Middle1 },
|
||||
{ 12, Hand_Middle2 },
|
||||
{ 13, Hand_Middle3 },
|
||||
{ 14, null },
|
||||
|
||||
{ 15, null },
|
||||
{ 16, Hand_Ring1 },
|
||||
{ 17, Hand_Ring2 },
|
||||
{ 18, Hand_Ring3 },
|
||||
{ 19, null },
|
||||
|
||||
{ 20, null },
|
||||
{ 21, Hand_Pinky1 },
|
||||
{ 22, Hand_Pinky2 },
|
||||
{ 23, Hand_Pinky3 },
|
||||
{ 24, null },
|
||||
#endif
|
||||
};
|
||||
|
||||
m_BoneToParent = new Dictionary<int, int>()
|
||||
{
|
||||
};
|
||||
#if VG_USE_MANUS_CONTROLLER
|
||||
m_BoneToParent[0] = -1;
|
||||
|
||||
m_BoneToParent[1] = 0;
|
||||
m_BoneToParent[2] = 1;
|
||||
m_BoneToParent[3] = 2;
|
||||
m_BoneToParent[4] = 3;
|
||||
|
||||
m_BoneToParent[5] = 0;
|
||||
m_BoneToParent[6] = 5;
|
||||
m_BoneToParent[7] = 6;
|
||||
m_BoneToParent[8] = 7;
|
||||
m_BoneToParent[9] = 8;
|
||||
|
||||
m_BoneToParent[10] = 0;
|
||||
m_BoneToParent[11] = 10;
|
||||
m_BoneToParent[12] = 11;
|
||||
m_BoneToParent[13] = 12;
|
||||
m_BoneToParent[14] = 13;
|
||||
|
||||
m_BoneToParent[15] = 0;
|
||||
m_BoneToParent[16] = 15;
|
||||
m_BoneToParent[17] = 16;
|
||||
m_BoneToParent[18] = 17;
|
||||
m_BoneToParent[19] = 18;
|
||||
|
||||
m_BoneToParent[20] = 0;
|
||||
m_BoneToParent[21] = 20;
|
||||
m_BoneToParent[22] = 21;
|
||||
m_BoneToParent[23] = 22;
|
||||
m_BoneToParent[24] = 23;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if VG_USE_MANUS_CONTROLLER
|
||||
// Function to setup nodes for metacarpals and tips properly in Manus SDK.
|
||||
// (They are not considered in VG controllers.)
|
||||
private void FillUnassignedNodes(List<Node> nodes)
|
||||
{
|
||||
for (int nodeId = 0; nodeId < nodes.Count; nodeId++)
|
||||
{
|
||||
Node node = nodes[nodeId];
|
||||
if (node.unityTransform == null)
|
||||
{
|
||||
// metacarpals
|
||||
if (new List<int>() { 5, 10, 15, 20 }.Contains(nodeId))
|
||||
{
|
||||
Node next_node = nodes[nodeId + 1];
|
||||
node.unityTransform = next_node.unityTransform.parent;
|
||||
node.nodeName = (node.unityTransform == null) ? "n/a" : node.unityTransform.name;
|
||||
node.name = node.nodeName;
|
||||
}
|
||||
else // tips
|
||||
{
|
||||
Node prev_node = nodes[nodeId - 1];
|
||||
node.unityTransform = prev_node.unityTransform.GetChild(0);
|
||||
node.nodeName = (node.unityTransform == null) ? "n/a" : node.unityTransform.name;
|
||||
node.name = node.nodeName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool ComputeFingerChainIds(out List<uint> chainIds)
|
||||
{
|
||||
chainIds = new List<uint>();
|
||||
foreach (Transform child in Hand_WristRoot.transform)
|
||||
{
|
||||
List<Transform> children = child.GetComponentsInChildren<Transform>().ToList<Transform>();
|
||||
if (children.Contains(Hand_Thumb1))
|
||||
chainIds.Add(2);
|
||||
else if (children.Contains(Hand_Index1))
|
||||
chainIds.Add(3);
|
||||
else if (children.Contains(Hand_Middle1))
|
||||
chainIds.Add(4);
|
||||
else if (children.Contains(Hand_Ring1))
|
||||
chainIds.Add(5);
|
||||
else if (children.Contains(Hand_Pinky1))
|
||||
chainIds.Add(6);
|
||||
|
||||
// Check if we have properly ordered fingers.
|
||||
int count = chainIds.Count;
|
||||
if (count > 1 && chainIds[count - 1] < chainIds[count - 2])
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool FillSkeletonFromMapping(Skeleton skeleton, VG_HandSide side)
|
||||
{
|
||||
skeleton.skeletonData = new SkeletonData();
|
||||
skeleton.skeletonData.type = CoreSDK.SkeletonType.Hand;
|
||||
skeleton.skeletonData.name = skeleton.name;
|
||||
skeleton.skeletonData.settings.targetType = CoreSDK.SkeletonTargetType.UserIndexData;
|
||||
skeleton.skeletonData.nodes.Clear();
|
||||
skeleton.skeletonData.chains.Clear();
|
||||
|
||||
// Hand chain
|
||||
Chain chain1 = new Chain();
|
||||
chain1.id = 1;
|
||||
chain1.type = CoreSDK.ChainType.Hand;
|
||||
chain1.appliedDataType = chain1.type;
|
||||
chain1.dataSide = (side == VG_HandSide.LEFT) ? CoreSDK.Side.Left : CoreSDK.Side.Right;
|
||||
chain1.nodeIds.Add(0);
|
||||
chain1.settings.finger.handChainId = 0;
|
||||
chain1.settings.finger.metacarpalBoneId = 0;
|
||||
chain1.settings.hand.fingerChainIds = new[] { 2, 3, 4, 5, 6 };
|
||||
skeleton.skeletonData.chains.Add(chain1);
|
||||
|
||||
// Add all nodes from VG mapping.
|
||||
foreach (KeyValuePair<int, Transform> boneTransform in m_BoneToTransform)
|
||||
{
|
||||
Node node = new Node();
|
||||
node.unityTransform = boneTransform.Value;
|
||||
node.nodeName = (node.unityTransform == null) ? "n/a" : node.unityTransform.name;
|
||||
node.name = node.nodeName;
|
||||
node.type = CoreSDK.NodeType.Joint;
|
||||
node.id = (uint)boneTransform.Key;
|
||||
node.parentID = m_BoneToParent.ContainsKey(boneTransform.Key) ?
|
||||
(uint)m_BoneToParent[boneTransform.Key] : 0;
|
||||
node.transform = new TransformValues();
|
||||
node.transform.scale = Vector3.one;
|
||||
skeleton.skeletonData.nodes.Add(node);
|
||||
}
|
||||
|
||||
// Fill in nodes for bones that don't exist in VG skeleton
|
||||
FillUnassignedNodes(skeleton.skeletonData.nodes);
|
||||
|
||||
// Finger chains (chain to bones)
|
||||
// Note: the chains need to be exactly ordered as in the hierarchy.
|
||||
|
||||
if (!ComputeFingerChainIds(out List<uint> chainIds))
|
||||
{
|
||||
Debug.LogError($"Please assure that the finger chains (children of {Hand_WristRoot.name}) are sorted as:" +
|
||||
"(Thumb, Index, Middle, Ring, Pinky)", Hand_WristRoot);
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (uint chainId in chainIds)
|
||||
{
|
||||
Chain chain = new Chain();
|
||||
chain.name = "Finger " + chainId;
|
||||
chain.id = chainId;
|
||||
chain.type = (CoreSDK.ChainType)chainId + 3;
|
||||
chain.appliedDataType = chain.type;
|
||||
chain.dataSide = (side == VG_HandSide.LEFT) ? CoreSDK.Side.Left : CoreSDK.Side.Right;
|
||||
chain.nodeIds = new List<uint>();
|
||||
chain.settings.finger.handChainId = 1;
|
||||
if (chainId == 2) // thumb
|
||||
{
|
||||
chain.settings.finger.metacarpalBoneId = -1;
|
||||
chain.nodeIds = new List<uint> { 1, 2, 3, 4 };
|
||||
}
|
||||
else
|
||||
{
|
||||
chain.settings.finger.metacarpalBoneId = (int)(5 * (chainId - 2));
|
||||
chain.nodeIds = new List<uint>();
|
||||
for (int i = chain.settings.finger.metacarpalBoneId; i < chain.settings.finger.metacarpalBoneId + 5; i++)
|
||||
chain.nodeIds.Add((uint)i);
|
||||
}
|
||||
|
||||
skeleton.skeletonData.chains.Add(chain);
|
||||
}
|
||||
|
||||
/*
|
||||
// Just for debugging
|
||||
foreach (Node node in skeleton.skeletonData.nodes)
|
||||
PrintNode(node);
|
||||
foreach (Chain chain in skeleton.skeletonData.chains)
|
||||
PrintChain(chain);
|
||||
*/
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public VG_EC_Manus(int avatarID, VG_HandSide side, Transform origin)
|
||||
{
|
||||
m_avatarID = avatarID;
|
||||
m_handType = side;
|
||||
m_origin = origin;
|
||||
m_enablingDefine = "VG_USE_MANUS_CONTROLLER";
|
||||
|
||||
#if VG_USE_MANUS_CONTROLLER
|
||||
m_enabled = true;
|
||||
#else
|
||||
PrintNotEnabledError();
|
||||
m_enabled = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public new void Initialize()
|
||||
{
|
||||
#if VG_USE_MANUS_CONTROLLER
|
||||
if (m_mapping == null)
|
||||
{
|
||||
m_mapping = new HandMapping();
|
||||
base.Initialize();
|
||||
}
|
||||
|
||||
// Note: need to disable GO first so Skeleton constructor does not fail
|
||||
// due to uninitialized data structures (FillSkeletonFromMapping).
|
||||
GameObject go = m_mapping.Hand_WristRoot.gameObject;
|
||||
bool oldActive = go.activeSelf;
|
||||
go.SetActive(false);
|
||||
if (!go.TryGetComponent<Skeleton>(out m_skeleton))
|
||||
m_skeleton = go.AddComponent<Skeleton>();
|
||||
|
||||
// Note: paused needs to be modified to public so Manus API does not apply bone poses,
|
||||
// but let's VG do this.
|
||||
m_skeleton.m_Paused = true;
|
||||
|
||||
m_initialized = false;
|
||||
if (!(m_mapping as HandMapping).FillSkeletonFromMapping(m_skeleton, m_handType))
|
||||
{
|
||||
this.m_enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try { go.SetActive(oldActive); }
|
||||
catch (Exception) { return; }
|
||||
|
||||
// Note: ManusManager is static and managing the gloves
|
||||
m_skeleton.SetupMeshes();
|
||||
m_skeleton.SetupNodes();
|
||||
m_skeleton.SendSkeleton();
|
||||
m_skeleton.skeletonData.id = MANUS_SKELETON_ID++;
|
||||
m_id = (int)m_skeleton.skeletonData.id;
|
||||
ManusManager.communicationHub?.onSkeletonData.AddListener(this.onSkeletonData);
|
||||
|
||||
m_initialized = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
public override bool Compute()
|
||||
{
|
||||
#if VG_USE_MANUS_CONTROLLER
|
||||
if (!m_enabled) return false;
|
||||
if (!m_initialized || m_mapping == null) { Initialize(); return false; }
|
||||
if (m_skeletonData.skeletons == null || m_skeletonData.skeletons.Count == 0) return false;
|
||||
|
||||
foreach (CoreSDK.SkeletonNode node in m_skeletonData.skeletons[m_id].nodes)
|
||||
{
|
||||
IntFromManusBone(node.id, out int boneId);
|
||||
//Debug.Log("VG " + m_handType + ":" + m_id + " (" + boneId + "/" + GetNumBones() + "); MAN: " + node.id + "/" + m_skeletonData.skeletons[0].nodes.Length + "):\n" + node.transform.rotation.FromManus());
|
||||
|
||||
if (boneId == 0)
|
||||
{
|
||||
SetPose(boneId, Matrix4x4.TRS(
|
||||
node.transform.position.FromManus(),
|
||||
node.transform.rotation.FromManus().normalized,
|
||||
Vector3.one));
|
||||
}
|
||||
else
|
||||
{
|
||||
SetPose(boneId, m_poses[m_mapping.GetParent(boneId)] *
|
||||
Matrix4x4.TRS(
|
||||
node.transform.position.FromManus(),
|
||||
node.transform.rotation.FromManus().normalized,
|
||||
Vector3.one));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public override float GetGrabStrength()
|
||||
{
|
||||
#if VG_USE_MANUS_CONTROLLER
|
||||
// No grab strength from Manus API is available, so
|
||||
// let VG decide from full DOF.
|
||||
return -1.0f;
|
||||
#else
|
||||
return 0.0f;
|
||||
#endif
|
||||
}
|
||||
|
||||
public override Color GetConfidence()
|
||||
{
|
||||
#if VG_USE_MANUS_CONTROLLER
|
||||
// No confidence value of the tracked data in Manus API.
|
||||
return Color.yellow;
|
||||
#else
|
||||
return Color.yellow;
|
||||
#endif
|
||||
}
|
||||
|
||||
public override void HapticPulse(VG_HandStatus hand, float amplitude = 0.5F, float duration = 0.015F, int finger = 5)
|
||||
{
|
||||
#if VG_USE_MANUS_CONTROLLER
|
||||
float[] amplitudes = new float[] { amplitude, amplitude, amplitude, amplitude, amplitude};
|
||||
ManusManager.communicationHub?.SendHapticDataForSkeleton(m_skeleton.skeletonData.id, m_handType == VG_HandSide.LEFT ? CoreSDK.Side.Left : CoreSDK.Side.Right, amplitudes);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 506abf992f64c0845b2a9c27d0f3e8fc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,109 @@
|
||||
// Copyright (C) 2014-2024 Gleechi Technology AB. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
#if VG_ENABLE_INPUT_SYSTEM // && !ENABLE_LEGACY_INPUT_MANAGER
|
||||
using UnityEngine.InputSystem;
|
||||
#endif
|
||||
|
||||
namespace VirtualGrasp.Controllers
|
||||
{
|
||||
/**
|
||||
* This is an external controller class that supports a Mouse controller as an external controller.
|
||||
* Please refer to https://docs.virtualgrasp.com/controllers.html for the definition of an external controller for VG.
|
||||
*/
|
||||
[LIBVIRTUALGRASP_UNITY_SCRIPT]
|
||||
[HelpURL("https://docs.virtualgrasp.com/unity_vg_ec_mouse." + VG_Version.__VG_VERSION__ + ".html")]
|
||||
public class VG_EC_Mouse : VG_ExternalController
|
||||
{
|
||||
private int mouse_held = 0;
|
||||
private int filter = 15;
|
||||
private float depth = .5f;
|
||||
private Vector3 rotation = Vector3.zero;
|
||||
#if VG_ENABLE_INPUT_SYSTEM
|
||||
private float rotationSpeed = 0.5f;
|
||||
#endif
|
||||
|
||||
[Serializable]
|
||||
public class HandMapping : VG_BoneMapping
|
||||
{
|
||||
public override void Initialize(int avatarID, VG_HandSide side)
|
||||
{
|
||||
base.Initialize(avatarID, side);
|
||||
m_BoneToTransform = new Dictionary<int, Transform>()
|
||||
{
|
||||
{ 0, Hand_WristRoot }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public VG_EC_Mouse(int avatarID, VG_HandSide side, Transform origin)
|
||||
{
|
||||
m_avatarID = avatarID;
|
||||
m_handType = side;
|
||||
m_origin = origin;
|
||||
|
||||
m_enabled = true;
|
||||
}
|
||||
|
||||
public new void Initialize()
|
||||
{
|
||||
m_initialized = (Camera.main != null);
|
||||
if (!m_initialized) return;
|
||||
|
||||
m_mapping = new HandMapping();
|
||||
base.Initialize();
|
||||
if (Camera.main.stereoTargetEye != StereoTargetEyeMask.None)
|
||||
{
|
||||
Debug.LogWarning("VG_EC_MouseHand uses single GameView camera, but a stereo camera is activated. Deactivating Stereo view.");
|
||||
Camera.main.stereoTargetEye = StereoTargetEyeMask.None;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Compute()
|
||||
{
|
||||
if (!m_enabled || !Application.isFocused) return false;
|
||||
if (!m_initialized || m_mapping == null) { Initialize(); return false; }
|
||||
|
||||
#if VG_ENABLE_INPUT_SYSTEM // && !ENABLE_LEGACY_INPUT_MANAGER
|
||||
if (Keyboard.current.leftShiftKey.isPressed && m_handType == VG_HandSide.RIGHT ||
|
||||
Keyboard.current.rightShiftKey.isPressed && m_handType == VG_HandSide.LEFT)
|
||||
return true;
|
||||
|
||||
if (Keyboard.current.xKey.IsPressed()) rotation.x += rotationSpeed;
|
||||
if (Keyboard.current.yKey.IsPressed()) rotation.y += rotationSpeed;
|
||||
if (Keyboard.current.zKey.IsPressed()) rotation.z += rotationSpeed;
|
||||
|
||||
if (m_handType == VirtualGrasp.VG_HandSide.LEFT ? Mouse.current.leftButton.isPressed : Mouse.current.rightButton.isPressed)
|
||||
mouse_held = Mathf.Min(filter, mouse_held + 1);
|
||||
else mouse_held = Mathf.Max(0, mouse_held - 1);
|
||||
depth = Mathf.Clamp(depth + Mouse.current.scroll.y.ReadValue() / 100.0f, .01f, 1.0f);
|
||||
Ray ray = Camera.main.ScreenPointToRay(Mouse.current.position.ReadValue());
|
||||
#else
|
||||
if (Input.GetMouseButton(m_handType == VirtualGrasp.VG_HandSide.LEFT ? 0 : 1)) mouse_held = Mathf.Min(filter, mouse_held + 1);
|
||||
else mouse_held = Mathf.Max(0, mouse_held - 1);
|
||||
depth = Mathf.Clamp(depth + Input.mouseScrollDelta.y / 10.0f, .5f, 3.0f);
|
||||
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
|
||||
#endif
|
||||
Quaternion q = Camera.main.transform.rotation;
|
||||
Vector3 p = ray.origin + depth * ray.direction + q * (m_handType == VirtualGrasp.VG_HandSide.LEFT ? Vector3.left : Vector3.right) * .1f;
|
||||
q = q * Quaternion.Euler(rotation);
|
||||
SetPose(0, Matrix4x4.TRS(p, q, Vector3.one));
|
||||
return true;
|
||||
}
|
||||
|
||||
public override float GetGrabStrength()
|
||||
{
|
||||
return (float)mouse_held / filter;
|
||||
}
|
||||
|
||||
public override Color GetConfidence()
|
||||
{
|
||||
return Color.yellow;
|
||||
}
|
||||
public override void HapticPulse(VG_HandStatus hand, float amplitude = 0.5F, float duration = 0.015F, int finger = 5)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fb4ba4cb1ca48424ca180c73ac0d42cc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,115 @@
|
||||
// Copyright (C) 2014-2024 Gleechi Technology AB. All rights reserved.
|
||||
|
||||
//#define VG_USE_OVRHAND_CONTROLLER // Please read below instructions and requirements before activating.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace VirtualGrasp.Controllers
|
||||
{
|
||||
/**
|
||||
* NOTE: this is an experimental controller only for the OculusIntegration sample.
|
||||
* We recommend to not use OVRHand / OVRCustomSkeleton but use one of the
|
||||
* various finger controllers that come with VirtualGrasp.
|
||||
*
|
||||
* This is an external controller class that supports a generic overlay controller for the OVRHand class as an external controller.
|
||||
* Please refer to https://docs.virtualgrasp.com/controllers.html for the definition of an external controller for VG.
|
||||
*
|
||||
* The following requirements have to be met to be able to enable the #define VG_USE_OVRHAND_CONTROLLER above and use the controller:
|
||||
* - You have the Oculus SDK (https://www.oculus.com/setup/) installed on your computer.
|
||||
* - You have the Oculus Integration plugin from https://developer.oculus.com/downloads/package/unity-integration/ imported into your Unity project.
|
||||
* - You are using a handmodel / rig that is based on the OVRHand / OVRCustomSkeleton classes.
|
||||
*/
|
||||
|
||||
[LIBVIRTUALGRASP_UNITY_SCRIPT]
|
||||
[HelpURL("https://docs.virtualgrasp.com/unity_vg_ec_ovr." + VG_Version.__VG_VERSION__ + ".html")]
|
||||
public class VG_EC_OVR : VG_ExternalController
|
||||
{
|
||||
#if VG_USE_OVRHAND_CONTROLLER
|
||||
private OVRCustomSkeleton m_skeleton = null;
|
||||
private OVRHand m_hand = null;
|
||||
#endif
|
||||
|
||||
public VG_EC_OVR(int avatarID, VG_HandSide side, Transform origin)
|
||||
{
|
||||
m_avatarID = avatarID;
|
||||
m_handType = side;
|
||||
m_zeroOffsets = true; // the generic hand works on the Unity transforms, so it can't use offsets.
|
||||
m_origin = origin;
|
||||
m_enablingDefine = "VG_USE_OVRHAND_CONTROLLER";
|
||||
|
||||
#if VG_USE_OVRHAND_CONTROLLER
|
||||
Initialize();
|
||||
m_enabled = true;
|
||||
#else
|
||||
PrintNotEnabledError();
|
||||
m_enabled = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public new void Initialize()
|
||||
{
|
||||
m_mapping = new VG_EC_Oculus.HandMapping();
|
||||
|
||||
#if VG_USE_OVRHAND_CONTROLLER
|
||||
foreach (OVRCustomSkeleton hand in GameObject.FindObjectsOfType<OVRCustomSkeleton>()) {
|
||||
if (hand.GetSkeletonType() == OVRSkeleton.SkeletonType.HandLeft && m_handType == VG_HandSide.LEFT ||
|
||||
hand.GetSkeletonType() == OVRSkeleton.SkeletonType.HandRight && m_handType == VG_HandSide.RIGHT) {
|
||||
m_skeleton = hand;
|
||||
m_hand = hand.gameObject.GetComponent<OVRHand>();
|
||||
}
|
||||
}
|
||||
|
||||
if (m_skeleton == null)
|
||||
{
|
||||
m_initialized = false;
|
||||
return;
|
||||
}
|
||||
|
||||
base.Initialize();
|
||||
m_initialized = true;
|
||||
#else
|
||||
m_initialized = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public override bool Compute()
|
||||
{
|
||||
if (!m_enabled) return false;
|
||||
if (!m_initialized) { Initialize(); return false; }
|
||||
|
||||
#if VG_USE_OVRHAND_CONTROLLER
|
||||
if (!m_skeleton.IsDataValid) return false;
|
||||
int offset = (int)m_skeleton.GetCurrentStartBoneId();
|
||||
for (int bone = 0; bone < m_mapping.GetNumBones(); bone++)
|
||||
{
|
||||
Transform pose = (bone != 1) ?
|
||||
m_skeleton.CustomBones[offset + bone].transform :
|
||||
m_skeleton.CustomBones[offset].transform; // we map forearm stub to wrist
|
||||
SetPose(bone, Matrix4x4.TRS(pose.position, pose.rotation, Vector3.one));
|
||||
}
|
||||
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public override float GetGrabStrength() {
|
||||
|
||||
#if VG_USE_OVRHAND_CONTROLLER
|
||||
return -1.0f;
|
||||
//return m_hand.GetFingerPinchStrength(OVRHand.HandFinger.Index);
|
||||
#else
|
||||
return -1.0f;
|
||||
#endif
|
||||
}
|
||||
|
||||
public override Color GetConfidence()
|
||||
{
|
||||
return Color.yellow;
|
||||
}
|
||||
|
||||
public override void HapticPulse(VG_HandStatus hand, float amplitude = 0.5F, float duration = 0.015F, int finger = 5) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 65dd4ebfbffd7b740a91055a7bf4353f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,200 @@
|
||||
// Copyright (C) 2014-2024 Gleechi Technology AB. All rights reserved.
|
||||
|
||||
//#define VG_USE_OCULUS_CONTROLLER // Please read below instructions and requirements before activating.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VirtualGrasp.Controllers
|
||||
{
|
||||
/**
|
||||
* This is an external controller class that supports the Oculus Finger Tracking controller as an external controller.
|
||||
* Please refer to https://docs.virtualgrasp.com/controllers.html for the definition of an external controller for VG.
|
||||
*
|
||||
* The following requirements have to be met to be able to enable the #define VG_USE_OCULUS_CONTROLLER above and use the controller:
|
||||
* - You have the Oculus SDK (https://www.oculus.com/setup/) installed on your computer.
|
||||
* - You have the Oculus Integration plugin from https://developer.oculus.com/downloads/package/unity-integration/ imported into your Unity project.
|
||||
* - You have the same Oculus Integration plugin version as the one on your headset AND Oculus App.
|
||||
* - You use Oculus through "Legacy OVR" (Oculus -> Tools -> OVR Utilitites Plugin -> Set OVR to Legacy LibOVR)
|
||||
* - You have setup the AndroidManifest.xml properly, i.e. they need to include
|
||||
* <uses-permission android:name="com.oculus.permission.HAND_TRACKING" />
|
||||
* <uses-feature android:name="oculus.software.handtracking" android:required="false" />
|
||||
*/
|
||||
|
||||
[LIBVIRTUALGRASP_UNITY_SCRIPT]
|
||||
[HelpURL("https://docs.virtualgrasp.com/unity_vg_ec_oculus." + VG_Version.__VG_VERSION__ + ".html")]
|
||||
public class VG_EC_Oculus : VG_ExternalController
|
||||
{
|
||||
#if VG_USE_OCULUS_CONTROLLER
|
||||
private OVRPlugin.Skeleton m_skeleton = new OVRPlugin.Skeleton();
|
||||
private OVRPlugin.HandState m_currentState = new OVRPlugin.HandState();
|
||||
#endif
|
||||
|
||||
[Serializable]
|
||||
public class HandMapping : VG_BoneMapping
|
||||
{
|
||||
public override void Initialize(int avatarID, VG_HandSide side)
|
||||
{
|
||||
base.Initialize(avatarID, side);
|
||||
m_BoneToTransform = new Dictionary<int, Transform>() {
|
||||
#if VG_USE_OCULUS_CONTROLLER
|
||||
{ (int)OVRPlugin.BoneId.Hand_WristRoot, Hand_WristRoot },
|
||||
{ (int)OVRPlugin.BoneId.Hand_ForearmStub, null }, // this is a child of wrist, but towards the arm
|
||||
{ (int)OVRPlugin.BoneId.Hand_Thumb0, null },
|
||||
{ (int)OVRPlugin.BoneId.Hand_Thumb1, Hand_Thumb1 },
|
||||
{ (int)OVRPlugin.BoneId.Hand_Thumb2, Hand_Thumb2 },
|
||||
{ (int)OVRPlugin.BoneId.Hand_Thumb3, Hand_Thumb3 },
|
||||
{ (int)OVRPlugin.BoneId.Hand_Index1, Hand_Index1 },
|
||||
{ (int)OVRPlugin.BoneId.Hand_Index2, Hand_Index2 },
|
||||
{ (int)OVRPlugin.BoneId.Hand_Index3, Hand_Index3 },
|
||||
{ (int)OVRPlugin.BoneId.Hand_Middle1, Hand_Middle1 },
|
||||
{ (int)OVRPlugin.BoneId.Hand_Middle2, Hand_Middle2 },
|
||||
{ (int)OVRPlugin.BoneId.Hand_Middle3, Hand_Middle3 },
|
||||
{ (int)OVRPlugin.BoneId.Hand_Ring1, Hand_Ring1 },
|
||||
{ (int)OVRPlugin.BoneId.Hand_Ring2, Hand_Ring2 },
|
||||
{ (int)OVRPlugin.BoneId.Hand_Ring3, Hand_Ring3 },
|
||||
{ (int)OVRPlugin.BoneId.Hand_Pinky0, null },
|
||||
{ (int)OVRPlugin.BoneId.Hand_Pinky1, Hand_Pinky1 },
|
||||
{ (int)OVRPlugin.BoneId.Hand_Pinky2, Hand_Pinky2 },
|
||||
{ (int)OVRPlugin.BoneId.Hand_Pinky3, Hand_Pinky3 },
|
||||
{ (int)OVRPlugin.BoneId.Hand_ThumbTip, null },
|
||||
{ (int)OVRPlugin.BoneId.Hand_IndexTip, null },
|
||||
{ (int)OVRPlugin.BoneId.Hand_MiddleTip, null },
|
||||
{ (int)OVRPlugin.BoneId.Hand_RingTip, null },
|
||||
{ (int)OVRPlugin.BoneId.Hand_PinkyTip, null }
|
||||
#endif
|
||||
};
|
||||
|
||||
m_BoneToParent = new Dictionary<int, int>();
|
||||
#if VG_USE_OCULUS_CONTROLLER
|
||||
m_BoneToParent[(int)OVRPlugin.BoneId.Hand_ForearmStub] = (int)OVRPlugin.BoneId.Hand_WristRoot;
|
||||
m_BoneToParent[(int)OVRPlugin.BoneId.Hand_Thumb0] = (int)OVRPlugin.BoneId.Hand_WristRoot;
|
||||
m_BoneToParent[(int)OVRPlugin.BoneId.Hand_Thumb1] = (int)OVRPlugin.BoneId.Hand_Thumb0;
|
||||
m_BoneToParent[(int)OVRPlugin.BoneId.Hand_Thumb2] = (int)OVRPlugin.BoneId.Hand_Thumb1;
|
||||
m_BoneToParent[(int)OVRPlugin.BoneId.Hand_Thumb3] = (int)OVRPlugin.BoneId.Hand_Thumb2;
|
||||
m_BoneToParent[(int)OVRPlugin.BoneId.Hand_ThumbTip] = (int)OVRPlugin.BoneId.Hand_Thumb3;
|
||||
m_BoneToParent[(int)OVRPlugin.BoneId.Hand_Index1] = (int)OVRPlugin.BoneId.Hand_WristRoot;
|
||||
m_BoneToParent[(int)OVRPlugin.BoneId.Hand_Index2] = (int)OVRPlugin.BoneId.Hand_Index1;
|
||||
m_BoneToParent[(int)OVRPlugin.BoneId.Hand_Index3] = (int)OVRPlugin.BoneId.Hand_Index2;
|
||||
m_BoneToParent[(int)OVRPlugin.BoneId.Hand_IndexTip] = (int)OVRPlugin.BoneId.Hand_Index3;
|
||||
m_BoneToParent[(int)OVRPlugin.BoneId.Hand_Middle1] = (int)OVRPlugin.BoneId.Hand_WristRoot;
|
||||
m_BoneToParent[(int)OVRPlugin.BoneId.Hand_Middle2] = (int)OVRPlugin.BoneId.Hand_Middle1;
|
||||
m_BoneToParent[(int)OVRPlugin.BoneId.Hand_Middle3] = (int)OVRPlugin.BoneId.Hand_Middle2;
|
||||
m_BoneToParent[(int)OVRPlugin.BoneId.Hand_MiddleTip] = (int)OVRPlugin.BoneId.Hand_Middle3;
|
||||
m_BoneToParent[(int)OVRPlugin.BoneId.Hand_Ring1] = (int)OVRPlugin.BoneId.Hand_WristRoot;
|
||||
m_BoneToParent[(int)OVRPlugin.BoneId.Hand_Ring2] = (int)OVRPlugin.BoneId.Hand_Ring1;
|
||||
m_BoneToParent[(int)OVRPlugin.BoneId.Hand_Ring3] = (int)OVRPlugin.BoneId.Hand_Ring2;
|
||||
m_BoneToParent[(int)OVRPlugin.BoneId.Hand_RingTip] = (int)OVRPlugin.BoneId.Hand_Ring3;
|
||||
m_BoneToParent[(int)OVRPlugin.BoneId.Hand_Pinky0] = (int)OVRPlugin.BoneId.Hand_WristRoot;
|
||||
m_BoneToParent[(int)OVRPlugin.BoneId.Hand_Pinky1] = (int)OVRPlugin.BoneId.Hand_Pinky0;
|
||||
m_BoneToParent[(int)OVRPlugin.BoneId.Hand_Pinky2] = (int)OVRPlugin.BoneId.Hand_Pinky1;
|
||||
m_BoneToParent[(int)OVRPlugin.BoneId.Hand_Pinky3] = (int)OVRPlugin.BoneId.Hand_Pinky2;
|
||||
m_BoneToParent[(int)OVRPlugin.BoneId.Hand_PinkyTip] = (int)OVRPlugin.BoneId.Hand_Pinky3;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public VG_EC_Oculus(int avatarID, VG_HandSide side, Transform origin)
|
||||
{
|
||||
m_avatarID = avatarID;
|
||||
m_handType = side;
|
||||
m_origin = origin;
|
||||
m_enablingDefine = "VG_USE_OCULUS_CONTROLLER";
|
||||
|
||||
#if VG_USE_OCULUS_CONTROLLER
|
||||
m_enabled = true;
|
||||
#else
|
||||
PrintNotEnabledError();
|
||||
m_enabled = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public new void Initialize()
|
||||
{
|
||||
#if VG_USE_OCULUS_CONTROLLER
|
||||
m_mapping = new HandMapping();
|
||||
|
||||
if (OVRPlugin.GetSkeleton(
|
||||
(m_handType == VG_HandSide.LEFT) ? OVRPlugin.SkeletonType.HandLeft :
|
||||
(m_handType == VG_HandSide.RIGHT) ? OVRPlugin.SkeletonType.HandRight : OVRPlugin.SkeletonType.None,
|
||||
out m_skeleton))
|
||||
{
|
||||
base.Initialize();
|
||||
m_initialized = true;
|
||||
}
|
||||
else m_initialized = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public override float GetGrabStrength()
|
||||
{
|
||||
#if VG_USE_OCULUS_CONTROLLER
|
||||
if (m_initialized && m_currentState.Status.HasFlag(OVRPlugin.HandStatus.HandTracked))
|
||||
return -1.0f; // let VG decide from full DOF
|
||||
//return (m_currentState.PinchStrength[0] + m_currentState.PinchStrength[1] + m_currentState.PinchStrength[2]) / 3.0f;
|
||||
#endif
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
private bool IsTracking()
|
||||
{
|
||||
#if VG_USE_OCULUS_CONTROLLER
|
||||
return OVRPlugin.GetHandState(OVRPlugin.Step.Render, m_handType == VG_HandSide.LEFT ? OVRPlugin.Hand.HandLeft : OVRPlugin.Hand.HandRight, ref m_currentState)
|
||||
&& m_currentState.Status.HasFlag(OVRPlugin.HandStatus.HandTracked);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public override bool Compute()
|
||||
{
|
||||
if (!m_enabled) return false;
|
||||
if (!m_initialized) { Initialize(); return false; }
|
||||
|
||||
#if VG_USE_OCULUS_CONTROLLER
|
||||
if (!IsTracking()) return false;
|
||||
|
||||
for (int boneId = 0; boneId < GetNumBones(); ++boneId)
|
||||
{
|
||||
if (boneId == 0)
|
||||
{
|
||||
SetPose(boneId, Matrix4x4.TRS(
|
||||
m_currentState.RootPose.Position.FromFlippedZVector3f(),
|
||||
m_currentState.RootPose.Orientation.FromFlippedZQuatf(),
|
||||
Vector3.one));
|
||||
}
|
||||
else SetPose(boneId, m_poses[m_mapping.GetParent(boneId)] *
|
||||
Matrix4x4.TRS(
|
||||
m_skeleton.Bones[boneId].Pose.Position.FromFlippedZVector3f(),
|
||||
m_currentState.BoneRotations[boneId].FromFlippedZQuatf(),
|
||||
Vector3.one));
|
||||
}
|
||||
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public override Color GetConfidence()
|
||||
{
|
||||
#if VG_USE_OCULUS_CONTROLLER
|
||||
if (!m_initialized) return Color.black;
|
||||
|
||||
switch (m_currentState.HandConfidence)
|
||||
{
|
||||
case OVRPlugin.TrackingConfidence.High:
|
||||
return Color.green;
|
||||
case OVRPlugin.TrackingConfidence.Low:
|
||||
return Color.red;
|
||||
}
|
||||
#endif
|
||||
return Color.black;
|
||||
}
|
||||
|
||||
public override void HapticPulse(VG_HandStatus hand, float amplitude = 0.5F, float duration = 0.015F, int finger = 5)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a6adfc75fbb3e8944a213eb006c326c6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,90 @@
|
||||
// Copyright (C) 2014-2024 Gleechi Technology AB. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VirtualGrasp.Controllers
|
||||
{
|
||||
/**
|
||||
* This is an external controller class that supports a Script/GUI controller as an external controller.
|
||||
* Please refer to https://docs.virtualgrasp.com/controllers.html for the definition of an external controller for VG.
|
||||
*/
|
||||
[LIBVIRTUALGRASP_UNITY_SCRIPT]
|
||||
[HelpURL("https://docs.virtualgrasp.com/unity_vg_ec_script." + VG_Version.__VG_VERSION__ + ".html")]
|
||||
public class VG_EC_Script : VG_ExternalController
|
||||
{
|
||||
private Transform m_wrist = null;
|
||||
private float m_grabStrength = 0.0f;
|
||||
|
||||
/**
|
||||
* The wrist transform strength can be set and received from another Script/GUI.
|
||||
* Changes in the wrist transform will affect the controller through Compute().
|
||||
*/
|
||||
public Transform WristTransform
|
||||
{
|
||||
get { return m_wrist; }
|
||||
set { m_wrist = value; }
|
||||
}
|
||||
|
||||
/**
|
||||
* The grab strength can be set and received from another Script/GUI.
|
||||
* Changes in the grab strength will affect the controller through GetGrabStrength().
|
||||
*/
|
||||
public float GrabStrength
|
||||
{
|
||||
get { return m_grabStrength; }
|
||||
set { m_grabStrength = Mathf.Clamp01(value); }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class HandMapping : VG_BoneMapping
|
||||
{
|
||||
public override void Initialize(int avatarID, VG_HandSide side)
|
||||
{
|
||||
base.Initialize(avatarID, side);
|
||||
m_BoneToTransform = new Dictionary<int, Transform>()
|
||||
{
|
||||
{ 0, Hand_WristRoot }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public VG_EC_Script(int avatarID, VG_HandSide side, Transform origin)
|
||||
{
|
||||
m_avatarID = avatarID;
|
||||
m_handType = side;
|
||||
m_origin = origin;
|
||||
m_enabled = true;
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public new void Initialize()
|
||||
{
|
||||
m_mapping = new HandMapping();
|
||||
base.Initialize();
|
||||
}
|
||||
|
||||
public override bool Compute()
|
||||
{
|
||||
if (!m_enabled || m_wrist == null) return false;
|
||||
SetPose(0, Matrix4x4.TRS(m_wrist.position, m_wrist.rotation, Vector3.one));
|
||||
return true;
|
||||
}
|
||||
|
||||
public override float GetGrabStrength()
|
||||
{
|
||||
return m_grabStrength;
|
||||
}
|
||||
|
||||
public override Color GetConfidence()
|
||||
{
|
||||
return Color.yellow;
|
||||
}
|
||||
|
||||
public override void HapticPulse(VG_HandStatus hand, float amplitude = 0.5F, float duration = 0.015F, int finger = 5)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7187da2e8079f68459c7737612322b4e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,214 @@
|
||||
// Copyright (C) 2014-2024 Gleechi Technology AB. All rights reserved.
|
||||
|
||||
//#define VG_USE_STEAMVR_CONTROLLER
|
||||
|
||||
#if UNITY_ANDROID && !UNITY_EDITOR // SteamVR is not supported on Android
|
||||
#undef VG_USE_STEAMVR_CONTROLLER
|
||||
#endif
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
#if VG_USE_STEAMVR_CONTROLLER
|
||||
using Valve.VR;
|
||||
#endif
|
||||
|
||||
namespace VirtualGrasp.Controllers
|
||||
{
|
||||
/**
|
||||
* This is an external controller class that supports the Steam Knuckles controller as an external controller.
|
||||
* Please refer to https://docs.virtualgrasp.com/controllers.html for the definition of an external controller for VG.
|
||||
*
|
||||
* The following requirements have to be met to be able to enable the #define VG_USE_STEAMVR_CONTROLLER above and use the controller:
|
||||
* - You have the SteamVR Unity plugin from https://assetstore.unity.com/packages/tools/integration/steamvr-plugin-32647 imported into your Unity project.
|
||||
* - You have Steam and the corresponding SteamVR SDK (https://store.steampowered.com/app/250820/SteamVR/) installed on your computer.
|
||||
* - You have OpenVR Loader selected in Unity XR Management Project Settings.
|
||||
*/
|
||||
|
||||
[LIBVIRTUALGRASP_UNITY_SCRIPT]
|
||||
[HelpURL("https://docs.virtualgrasp.com/unity_vg_ec_steam." + VG_Version.__VG_VERSION__ + ".html")]
|
||||
|
||||
public class VG_EC_Steam : VG_ExternalController
|
||||
{
|
||||
#if VG_USE_STEAMVR_CONTROLLER
|
||||
private SteamVR_Action_Skeleton m_skeleton = null;
|
||||
private bool m_skeletonActive = false;
|
||||
#endif
|
||||
|
||||
[Serializable]
|
||||
public class SteamHandMapping : VG_BoneMapping
|
||||
{
|
||||
public override void Initialize(int avatarID, VG_HandSide side)
|
||||
{
|
||||
base.Initialize(avatarID, side);
|
||||
m_BoneToTransform = new Dictionary<int, Transform>()
|
||||
{
|
||||
#if VG_USE_STEAMVR_CONTROLLER
|
||||
{ SteamVR_Skeleton_JointIndexes.root, null },
|
||||
{ SteamVR_Skeleton_JointIndexes.wrist, Hand_WristRoot },
|
||||
{ SteamVR_Skeleton_JointIndexes.thumbProximal, Hand_Thumb1 },
|
||||
{ SteamVR_Skeleton_JointIndexes.thumbMiddle, Hand_Thumb2 },
|
||||
{ SteamVR_Skeleton_JointIndexes.thumbDistal, Hand_Thumb3 },
|
||||
{ SteamVR_Skeleton_JointIndexes.thumbTip, null },
|
||||
{ SteamVR_Skeleton_JointIndexes.indexMetacarpal, null },
|
||||
{ SteamVR_Skeleton_JointIndexes.indexProximal, Hand_Index1 },
|
||||
{ SteamVR_Skeleton_JointIndexes.indexMiddle, Hand_Index2 },
|
||||
{ SteamVR_Skeleton_JointIndexes.indexDistal, Hand_Index3 },
|
||||
{ SteamVR_Skeleton_JointIndexes.indexTip, null },
|
||||
{ SteamVR_Skeleton_JointIndexes.middleMetacarpal, null },
|
||||
{ SteamVR_Skeleton_JointIndexes.middleProximal, Hand_Middle1 },
|
||||
{ SteamVR_Skeleton_JointIndexes.middleMiddle, Hand_Middle2 },
|
||||
{ SteamVR_Skeleton_JointIndexes.middleDistal, Hand_Middle3 },
|
||||
{ SteamVR_Skeleton_JointIndexes.middleTip, null },
|
||||
{ SteamVR_Skeleton_JointIndexes.ringMetacarpal, null },
|
||||
{ SteamVR_Skeleton_JointIndexes.ringProximal, Hand_Ring1 },
|
||||
{ SteamVR_Skeleton_JointIndexes.ringMiddle, Hand_Ring2 },
|
||||
{ SteamVR_Skeleton_JointIndexes.ringDistal, Hand_Ring3 },
|
||||
{ SteamVR_Skeleton_JointIndexes.ringTip, null },
|
||||
{ SteamVR_Skeleton_JointIndexes.pinkyMetacarpal, null },
|
||||
{ SteamVR_Skeleton_JointIndexes.pinkyProximal, Hand_Pinky1 },
|
||||
{ SteamVR_Skeleton_JointIndexes.pinkyMiddle, Hand_Pinky2 },
|
||||
{ SteamVR_Skeleton_JointIndexes.pinkyDistal, Hand_Pinky3 },
|
||||
{ SteamVR_Skeleton_JointIndexes.pinkyTip, null }
|
||||
#endif
|
||||
};
|
||||
|
||||
m_BoneToParent = new Dictionary<int, int>()
|
||||
{
|
||||
};
|
||||
#if VG_USE_STEAMVR_CONTROLLER
|
||||
m_BoneToParent[SteamVR_Skeleton_JointIndexes.wrist] = SteamVR_Skeleton_JointIndexes.root;
|
||||
m_BoneToParent[SteamVR_Skeleton_JointIndexes.thumbProximal] = SteamVR_Skeleton_JointIndexes.wrist;
|
||||
m_BoneToParent[SteamVR_Skeleton_JointIndexes.thumbMiddle] = SteamVR_Skeleton_JointIndexes.thumbProximal;
|
||||
m_BoneToParent[SteamVR_Skeleton_JointIndexes.thumbDistal] = SteamVR_Skeleton_JointIndexes.thumbMiddle;
|
||||
m_BoneToParent[SteamVR_Skeleton_JointIndexes.thumbTip] = SteamVR_Skeleton_JointIndexes.thumbDistal;
|
||||
m_BoneToParent[SteamVR_Skeleton_JointIndexes.indexMetacarpal] = SteamVR_Skeleton_JointIndexes.wrist;
|
||||
m_BoneToParent[SteamVR_Skeleton_JointIndexes.indexProximal] = SteamVR_Skeleton_JointIndexes.indexMetacarpal;
|
||||
m_BoneToParent[SteamVR_Skeleton_JointIndexes.indexMiddle] = SteamVR_Skeleton_JointIndexes.indexProximal;
|
||||
m_BoneToParent[SteamVR_Skeleton_JointIndexes.indexDistal] = SteamVR_Skeleton_JointIndexes.indexMiddle;
|
||||
m_BoneToParent[SteamVR_Skeleton_JointIndexes.indexTip] = SteamVR_Skeleton_JointIndexes.indexDistal;
|
||||
m_BoneToParent[SteamVR_Skeleton_JointIndexes.middleMetacarpal] = SteamVR_Skeleton_JointIndexes.wrist;
|
||||
m_BoneToParent[SteamVR_Skeleton_JointIndexes.middleProximal] = SteamVR_Skeleton_JointIndexes.middleMetacarpal;
|
||||
m_BoneToParent[SteamVR_Skeleton_JointIndexes.middleMiddle] = SteamVR_Skeleton_JointIndexes.middleProximal;
|
||||
m_BoneToParent[SteamVR_Skeleton_JointIndexes.middleDistal] = SteamVR_Skeleton_JointIndexes.middleMiddle;
|
||||
m_BoneToParent[SteamVR_Skeleton_JointIndexes.middleTip] = SteamVR_Skeleton_JointIndexes.middleDistal;
|
||||
m_BoneToParent[SteamVR_Skeleton_JointIndexes.ringMetacarpal] = SteamVR_Skeleton_JointIndexes.wrist;
|
||||
m_BoneToParent[SteamVR_Skeleton_JointIndexes.ringProximal] = SteamVR_Skeleton_JointIndexes.ringMetacarpal;
|
||||
m_BoneToParent[SteamVR_Skeleton_JointIndexes.ringMiddle] = SteamVR_Skeleton_JointIndexes.ringProximal;
|
||||
m_BoneToParent[SteamVR_Skeleton_JointIndexes.ringDistal] = SteamVR_Skeleton_JointIndexes.ringMiddle;
|
||||
m_BoneToParent[SteamVR_Skeleton_JointIndexes.ringTip] = SteamVR_Skeleton_JointIndexes.ringDistal;
|
||||
m_BoneToParent[SteamVR_Skeleton_JointIndexes.pinkyMetacarpal] = SteamVR_Skeleton_JointIndexes.wrist;
|
||||
m_BoneToParent[SteamVR_Skeleton_JointIndexes.pinkyProximal] = SteamVR_Skeleton_JointIndexes.pinkyMetacarpal;
|
||||
m_BoneToParent[SteamVR_Skeleton_JointIndexes.pinkyMiddle] = SteamVR_Skeleton_JointIndexes.pinkyProximal;
|
||||
m_BoneToParent[SteamVR_Skeleton_JointIndexes.pinkyDistal] = SteamVR_Skeleton_JointIndexes.pinkyMiddle;
|
||||
m_BoneToParent[SteamVR_Skeleton_JointIndexes.pinkyTip] = SteamVR_Skeleton_JointIndexes.pinkyDistal;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public VG_EC_Steam(int avatarID, VG_HandSide side, Transform origin)
|
||||
{
|
||||
m_avatarID = avatarID;
|
||||
m_handType = side;
|
||||
m_origin = origin;
|
||||
m_enablingDefine = "VG_USE_STEAMVR_CONTROLLER";
|
||||
|
||||
#if VG_USE_STEAMVR_CONTROLLER
|
||||
m_enabled = true;
|
||||
#else
|
||||
PrintNotEnabledError();
|
||||
m_enabled = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public new void Initialize()
|
||||
{
|
||||
#if VG_USE_STEAMVR_CONTROLLER
|
||||
m_mapping = new SteamHandMapping();
|
||||
base.Initialize();
|
||||
|
||||
SteamVR.Initialize();
|
||||
m_skeleton = SteamVR_Input.GetAction<SteamVR_Action_Skeleton>(m_handType == VG_HandSide.LEFT ? "SkeletonLeftHand" : "SkeletonRightHand");
|
||||
m_skeleton.SetRangeOfMotion(EVRSkeletalMotionRange.WithoutController);
|
||||
//m_skeleton.SetSkeletalTransformSpace(Valve.VR.EVRSkeletalTransformSpace.Model);
|
||||
|
||||
m_initialized = true;
|
||||
if (GetNumBones() == 0)
|
||||
{
|
||||
Debug.LogError("Could not find bone skeleton root (boneCount=" + GetNumBones() + "; skeleton active=" + m_skeletonActive + ").");
|
||||
m_initialized = false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public override float GetGrabStrength()
|
||||
{
|
||||
#if VG_USE_STEAMVR_CONTROLLER
|
||||
if (m_skeleton == null) return 0.0f;
|
||||
return (m_skeleton.fingerCurls[0] + m_skeleton.fingerCurls[1] + m_skeleton.fingerCurls[2]) / 3.0f;
|
||||
#else
|
||||
return 0.0f;
|
||||
#endif
|
||||
}
|
||||
|
||||
public override bool Compute()
|
||||
{
|
||||
|
||||
#if VG_USE_STEAMVR_CONTROLLER
|
||||
if (!m_enabled) return false;
|
||||
if (!m_initialized) { Initialize(); return false; }
|
||||
|
||||
ETrackingResult res = m_skeleton.GetTrackingResult(m_handType == VG_HandSide.LEFT ? SteamVR_Input_Sources.LeftHand : SteamVR_Input_Sources.RightHand);
|
||||
if (res == 0) return false;
|
||||
m_skeletonActive = m_skeleton.GetActive();
|
||||
|
||||
for (int boneId = 0; boneId < (m_skeletonActive ? GetNumBones() : 2); ++boneId)
|
||||
{
|
||||
if (boneId == 0) // root
|
||||
{
|
||||
SetPose(boneId, Matrix4x4.identity);
|
||||
}
|
||||
else if (boneId == 1) // wrist
|
||||
{
|
||||
SetPose(boneId, Matrix4x4.TRS(m_skeleton.GetLocalPosition(), m_skeleton.GetLocalRotation(), Vector3.one));
|
||||
}
|
||||
else
|
||||
{
|
||||
SetPose(boneId, m_poses[m_mapping.GetParent(boneId)] * // When transform space is local / Parent
|
||||
Matrix4x4.TRS(m_skeleton.bonePositions[boneId], m_skeleton.boneRotations[boneId], Vector3.one));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void HapticPulse(VG_HandStatus hand, float amplitude = 0.5F, float duration = 0.01F, int finger = 5)
|
||||
{
|
||||
#if VG_USE_STEAMVR_CONTROLLER
|
||||
SteamVR_Actions.default_Haptic[hand.m_side == VG_HandSide.LEFT ? SteamVR_Input_Sources.LeftHand : SteamVR_Input_Sources.RightHand].Execute(0, duration, 10, amplitude);
|
||||
#endif
|
||||
}
|
||||
|
||||
public override Color GetConfidence()
|
||||
{
|
||||
#if VG_USE_STEAMVR_CONTROLLER
|
||||
EVRSkeletalTrackingLevel skeletalTrackingLevel = EVRSkeletalTrackingLevel.VRSkeletalTracking_Estimated;
|
||||
if (m_skeleton == null || OpenVR.Input.GetSkeletalTrackingLevel(m_skeleton.handle, ref skeletalTrackingLevel) != EVRInputError.None)
|
||||
return Color.black;
|
||||
|
||||
switch (skeletalTrackingLevel)
|
||||
{
|
||||
case EVRSkeletalTrackingLevel.VRSkeletalTracking_Full:
|
||||
return Color.green;
|
||||
case EVRSkeletalTrackingLevel.VRSkeletalTracking_Partial:
|
||||
return Color.yellow;
|
||||
case EVRSkeletalTrackingLevel.VRSkeletalTracking_Estimated:
|
||||
return Color.red;
|
||||
}
|
||||
#endif
|
||||
return Color.black;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 35987637c5dd86e4082679c5c048d825
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,107 @@
|
||||
// Copyright (C) 2014-2024 Gleechi Technology AB. All rights reserved.
|
||||
|
||||
//#define VG_USE_UNITYXR_CONTROLLER
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.XR;
|
||||
|
||||
namespace VirtualGrasp.Controllers
|
||||
{
|
||||
/**
|
||||
* This is an external controller class that supports the UnityXR controller (such as provided by Pico or Oculus integrations) as an external controller.
|
||||
* Please refer to https://docs.virtualgrasp.com/controllers.html for the definition of an external controller for VG.
|
||||
*
|
||||
* The following requirements have to be met to be able to enable the #define VG_USE_UNITYXR_CONTROLLER above and use the controller:
|
||||
* - You have the Unity XR Management package installed into your Unity project.
|
||||
*/
|
||||
[LIBVIRTUALGRASP_UNITY_SCRIPT]
|
||||
[HelpURL("https://docs.virtualgrasp.com/unity_vg_ec_unityxr." + VG_Version.__VG_VERSION__ + ".html")]
|
||||
public class VG_EC_UnityXR : VG_ExternalController
|
||||
{
|
||||
private InputDevice m_device;
|
||||
|
||||
[Serializable]
|
||||
public class HandMapping : VG_BoneMapping
|
||||
{
|
||||
public override void Initialize(int avatarID, VG_HandSide side)
|
||||
{
|
||||
base.Initialize(avatarID, side);
|
||||
m_BoneToTransform = new Dictionary<int, Transform>()
|
||||
{
|
||||
{ 0, Hand_WristRoot }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public VG_EC_UnityXR(int avatarID, VG_HandSide side, Transform origin)
|
||||
{
|
||||
m_avatarID = avatarID;
|
||||
m_handType = side;
|
||||
m_origin = origin;
|
||||
m_enabled = true;
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public new void Initialize()
|
||||
{
|
||||
m_mapping = new HandMapping();
|
||||
base.Initialize();
|
||||
m_initialized = true;
|
||||
}
|
||||
|
||||
public override bool Compute()
|
||||
{
|
||||
if (!m_enabled) return false;
|
||||
if (!m_initialized) { Initialize(); return false; }
|
||||
|
||||
if (!m_device.isValid)
|
||||
{
|
||||
m_device = InputDevices.GetDeviceAtXRNode(m_handType == VG_HandSide.LEFT ? XRNode.LeftHand : XRNode.RightHand);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_device.TryGetFeatureValue(CommonUsages.devicePosition, out Vector3 p) &&
|
||||
m_device.TryGetFeatureValue(CommonUsages.deviceRotation, out Quaternion q))
|
||||
{
|
||||
SetPose(0, Matrix4x4.TRS(p, q, Vector3.one));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override float GetGrabStrength()
|
||||
{
|
||||
if (!m_initialized || !m_device.isValid) return 0.0f;
|
||||
float trigger = 0.0f;
|
||||
switch (VG_Controller.GetGraspButton())
|
||||
{
|
||||
case VG_VrButton.TRIGGER:
|
||||
m_device.TryGetFeatureValue(CommonUsages.trigger, out trigger); break;
|
||||
case VG_VrButton.GRIP:
|
||||
m_device.TryGetFeatureValue(CommonUsages.grip, out trigger); break;
|
||||
case VG_VrButton.GRIP_OR_TRIGGER:
|
||||
m_device.TryGetFeatureValue(CommonUsages.trigger, out trigger);
|
||||
m_device.TryGetFeatureValue(CommonUsages.grip, out float trigger2);
|
||||
trigger = Mathf.Max(trigger, trigger2);
|
||||
break;
|
||||
}
|
||||
return trigger;
|
||||
}
|
||||
|
||||
public override Color GetConfidence()
|
||||
{
|
||||
return Color.yellow;
|
||||
}
|
||||
|
||||
public override void HapticPulse(VG_HandStatus hand, float amplitude = 0.5F, float duration = 0.015F, int finger = 5)
|
||||
{
|
||||
if (!m_initialized || !m_device.isValid) return;
|
||||
HapticCapabilities capabilities;
|
||||
if (m_device.TryGetHapticCapabilities(out capabilities) && capabilities.supportsImpulse)
|
||||
m_device.SendHapticImpulse(0, amplitude, duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 482d610d95177f04a9bb38d20d6539c8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,193 @@
|
||||
// Copyright (C) 2014-2024 Gleechi Technology AB. All rights reserved.
|
||||
|
||||
//#define VG_USE_XRHANDS_CONTROLLER
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
#if VG_USE_XRHANDS_CONTROLLER
|
||||
using UnityEngine.XR.Hands;
|
||||
using UnityEngine.XR.Management;
|
||||
#endif
|
||||
|
||||
namespace VirtualGrasp.Controllers
|
||||
{
|
||||
/**
|
||||
* This is an external controller class that supports the Unity XRHands controller as an external controller.
|
||||
* Please refer to https://docs.virtualgrasp.com/controllers.html for the definition of an external controller for VG.
|
||||
*
|
||||
* The following requirements have to be met to be able to enable the #define VG_USE_XRHANDS_CONTROLLER above and use the controller:
|
||||
* - You have followed the installation instructions of the Unity XRHands package,
|
||||
* from https://docs.unity3d.com/Packages/com.unity.xr.hands@1.1/manual/
|
||||
*/
|
||||
[LIBVIRTUALGRASP_UNITY_SCRIPT]
|
||||
[HelpURL("https://docs.virtualgrasp.com/unity_vg_ec_unityxrhands." + VG_Version.__VG_VERSION__ + ".html")]
|
||||
|
||||
public class VG_EC_UnityXRHands : VG_ExternalController
|
||||
{
|
||||
#if VG_USE_XRHANDS_CONTROLLER
|
||||
XRHandSubsystem m_Subsystem = null;
|
||||
XRHand m_hand;
|
||||
XRHandSubsystem.UpdateSuccessFlags m_updateSuccessFlags = XRHandSubsystem.UpdateSuccessFlags.None;
|
||||
#endif
|
||||
|
||||
[Serializable]
|
||||
public class OpenXRHandMapping : VG_BoneMapping
|
||||
{
|
||||
public override void Initialize(int avatarID, VG_HandSide side)
|
||||
{
|
||||
base.Initialize(avatarID, side);
|
||||
m_BoneToTransform = new Dictionary<int, Transform>()
|
||||
{
|
||||
#if VG_USE_XRHANDS_CONTROLLER
|
||||
{ XRHandJointID.Wrist.ToIndex(), Hand_WristRoot },
|
||||
{ XRHandJointID.Palm.ToIndex(), null },
|
||||
{ XRHandJointID.ThumbMetacarpal.ToIndex(), Hand_Thumb1 },
|
||||
{ XRHandJointID.ThumbProximal.ToIndex(), Hand_Thumb2 },
|
||||
{ XRHandJointID.ThumbDistal.ToIndex(), Hand_Thumb3 },
|
||||
{ XRHandJointID.ThumbTip.ToIndex(), null },
|
||||
{ XRHandJointID.IndexMetacarpal.ToIndex(), null },
|
||||
{ XRHandJointID.IndexProximal.ToIndex(), Hand_Index1 },
|
||||
{ XRHandJointID.IndexIntermediate.ToIndex(), Hand_Index2 },
|
||||
{ XRHandJointID.IndexDistal.ToIndex(), Hand_Index3 },
|
||||
{ XRHandJointID.IndexTip.ToIndex(), null },
|
||||
{ XRHandJointID.MiddleMetacarpal.ToIndex(), null },
|
||||
{ XRHandJointID.MiddleProximal.ToIndex(), Hand_Middle1 },
|
||||
{ XRHandJointID.MiddleIntermediate.ToIndex(), Hand_Middle2 },
|
||||
{ XRHandJointID.MiddleDistal.ToIndex(), Hand_Middle3 },
|
||||
{ XRHandJointID.MiddleTip.ToIndex(), null },
|
||||
{ XRHandJointID.RingMetacarpal.ToIndex(), null },
|
||||
{ XRHandJointID.RingProximal.ToIndex(), Hand_Ring1 },
|
||||
{ XRHandJointID.RingIntermediate.ToIndex(), Hand_Ring2 },
|
||||
{ XRHandJointID.RingDistal.ToIndex(), Hand_Ring3 },
|
||||
{ XRHandJointID.RingTip.ToIndex(), null },
|
||||
{ XRHandJointID.LittleMetacarpal.ToIndex(), null },
|
||||
{ XRHandJointID.LittleProximal.ToIndex(), Hand_Pinky1 },
|
||||
{ XRHandJointID.LittleIntermediate.ToIndex(), Hand_Pinky2 },
|
||||
{ XRHandJointID.LittleDistal.ToIndex(), Hand_Pinky3 },
|
||||
{ XRHandJointID.LittleTip.ToIndex(), null }
|
||||
#endif
|
||||
};
|
||||
|
||||
m_BoneToParent = new Dictionary<int, int>()
|
||||
{
|
||||
};
|
||||
|
||||
#if VG_USE_XRHANDS_CONTROLLER
|
||||
m_BoneToParent[XRHandJointID.Palm.ToIndex()] = XRHandJointID.Wrist.ToIndex();
|
||||
m_BoneToParent[XRHandJointID.ThumbMetacarpal.ToIndex()] = XRHandJointID.Wrist.ToIndex();
|
||||
m_BoneToParent[XRHandJointID.ThumbProximal.ToIndex()] = XRHandJointID.ThumbMetacarpal.ToIndex();
|
||||
m_BoneToParent[XRHandJointID.ThumbDistal.ToIndex()] = XRHandJointID.ThumbProximal.ToIndex();
|
||||
m_BoneToParent[XRHandJointID.ThumbTip.ToIndex()] = XRHandJointID.ThumbDistal.ToIndex();
|
||||
m_BoneToParent[XRHandJointID.IndexMetacarpal.ToIndex()] = XRHandJointID.Wrist.ToIndex();
|
||||
m_BoneToParent[XRHandJointID.IndexProximal.ToIndex()] = XRHandJointID.IndexMetacarpal.ToIndex();
|
||||
m_BoneToParent[XRHandJointID.IndexIntermediate.ToIndex()] = XRHandJointID.IndexProximal.ToIndex();
|
||||
m_BoneToParent[XRHandJointID.IndexDistal.ToIndex()] = XRHandJointID.IndexIntermediate.ToIndex();
|
||||
m_BoneToParent[XRHandJointID.IndexTip.ToIndex()] = XRHandJointID.IndexDistal.ToIndex();
|
||||
m_BoneToParent[XRHandJointID.MiddleMetacarpal.ToIndex()] = XRHandJointID.Wrist.ToIndex();
|
||||
m_BoneToParent[XRHandJointID.MiddleProximal.ToIndex()] = XRHandJointID.MiddleMetacarpal.ToIndex();
|
||||
m_BoneToParent[XRHandJointID.MiddleIntermediate.ToIndex()] = XRHandJointID.MiddleProximal.ToIndex();
|
||||
m_BoneToParent[XRHandJointID.MiddleDistal.ToIndex()] = XRHandJointID.MiddleIntermediate.ToIndex();
|
||||
m_BoneToParent[XRHandJointID.MiddleTip.ToIndex()] = XRHandJointID.MiddleDistal.ToIndex();
|
||||
m_BoneToParent[XRHandJointID.RingMetacarpal.ToIndex()] = XRHandJointID.Wrist.ToIndex();
|
||||
m_BoneToParent[XRHandJointID.RingProximal.ToIndex()] = XRHandJointID.RingMetacarpal.ToIndex();
|
||||
m_BoneToParent[XRHandJointID.RingIntermediate.ToIndex()] = XRHandJointID.RingProximal.ToIndex();
|
||||
m_BoneToParent[XRHandJointID.RingDistal.ToIndex()] = XRHandJointID.RingIntermediate.ToIndex();
|
||||
m_BoneToParent[XRHandJointID.RingTip.ToIndex()] = XRHandJointID.RingDistal.ToIndex();
|
||||
m_BoneToParent[XRHandJointID.LittleMetacarpal.ToIndex()] = XRHandJointID.Wrist.ToIndex();
|
||||
m_BoneToParent[XRHandJointID.LittleProximal.ToIndex()] = XRHandJointID.LittleMetacarpal.ToIndex();
|
||||
m_BoneToParent[XRHandJointID.LittleIntermediate.ToIndex()] = XRHandJointID.LittleProximal.ToIndex();
|
||||
m_BoneToParent[XRHandJointID.LittleDistal.ToIndex()] = XRHandJointID.LittleIntermediate.ToIndex();
|
||||
m_BoneToParent[XRHandJointID.LittleTip.ToIndex()] = XRHandJointID.LittleDistal.ToIndex();
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public VG_EC_UnityXRHands(int avatarID, VG_HandSide side, Transform origin)
|
||||
{
|
||||
m_avatarID = avatarID;
|
||||
m_handType = side;
|
||||
m_origin = origin;
|
||||
m_enablingDefine = "VG_USE_XRHANDS_CONTROLLER";
|
||||
|
||||
#if VG_USE_XRHANDS_CONTROLLER
|
||||
m_enabled = true;
|
||||
#else
|
||||
PrintNotEnabledError();
|
||||
m_enabled = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public new void Initialize()
|
||||
{
|
||||
#if VG_USE_XRHANDS_CONTROLLER
|
||||
m_mapping = new OpenXRHandMapping();
|
||||
base.Initialize();
|
||||
|
||||
m_Subsystem = XRGeneralSettings.Instance?.Manager?.activeLoader?.GetLoadedSubsystem<XRHandSubsystem>();
|
||||
if (m_Subsystem != null)
|
||||
{
|
||||
if (m_handType == VG_HandSide.LEFT)
|
||||
{
|
||||
m_hand = m_Subsystem.leftHand;
|
||||
m_updateSuccessFlags = XRHandSubsystem.UpdateSuccessFlags.LeftHandRootPose | XRHandSubsystem.UpdateSuccessFlags.LeftHandJoints;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_hand = m_Subsystem.rightHand;
|
||||
m_updateSuccessFlags = XRHandSubsystem.UpdateSuccessFlags.RightHandRootPose | XRHandSubsystem.UpdateSuccessFlags.RightHandJoints;
|
||||
}
|
||||
|
||||
if (!m_Subsystem.running) m_Subsystem.Start();
|
||||
|
||||
base.Initialize();
|
||||
m_initialized = true;
|
||||
}
|
||||
else m_initialized = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public override float GetGrabStrength()
|
||||
{
|
||||
return -1.0f; // let VG decide from full DOF
|
||||
}
|
||||
|
||||
public override bool Compute()
|
||||
{
|
||||
if (!m_enabled) return false;
|
||||
if (!m_initialized) { Initialize(); return false; }
|
||||
#if VG_USE_XRHANDS_CONTROLLER
|
||||
if (!m_Subsystem.running) return false;
|
||||
if (!m_Subsystem.TryUpdateHands(XRHandSubsystem.UpdateType.Dynamic).HasFlag(m_updateSuccessFlags))
|
||||
return false;
|
||||
|
||||
for (int boneId = 0; boneId < GetNumBones(); ++boneId)
|
||||
{
|
||||
if (m_hand.GetJoint(XRHandJointIDUtility.FromIndex(boneId)).TryGetPose(out Pose pose))
|
||||
{
|
||||
//if (m_origin != null) pose = pose.GetTransformedBy(m_origin);
|
||||
SetPose(boneId, Matrix4x4.TRS(
|
||||
pose.position,
|
||||
pose.rotation, Vector3.one));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public override void HapticPulse(VG_HandStatus hand, float amplitude = 0.5F, float duration = 0.01F, int finger = 5)
|
||||
{
|
||||
}
|
||||
|
||||
public override Color GetConfidence()
|
||||
{
|
||||
if (!m_initialized) return Color.black;
|
||||
|
||||
return Color.yellow;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f9e2dc4556c290f4a88a6b4573b61556
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,201 @@
|
||||
// Copyright (C) 2014-2024 Gleechi Technology AB. All rights reserved.
|
||||
|
||||
//#define VG_USE_UNITYXRINTERACTION_HAND
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
#if VG_USE_UNITYXRINTERACTION_HAND
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.XR.Interaction.Toolkit;
|
||||
using UnityEngine.XR.Interaction.Toolkit.Inputs;
|
||||
#endif
|
||||
|
||||
namespace VirtualGrasp.Controllers
|
||||
{
|
||||
/**
|
||||
* This is an external controller class that supports the action-based Unity XR Interaction toolkit controller as an external controller.
|
||||
* Please refer to https://docs.virtualgrasp.com/controllers.html for the definition of an external controller for VG, and to
|
||||
* https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@2.0/manual/index.html for the plugin itself.
|
||||
*
|
||||
* The following requirements have to be met to be able to enable the #define VG_USE_UNITYINTERACTION_HAND above and use the controller:
|
||||
* - You have the "XR Plugin Management" package installed into your Unity project.
|
||||
* - You have the "XR Interaction Toolkit" package installed into your Unity project.
|
||||
* - The current setup does not seem to work for OpenXR as an XR plugin provider, you must have Oculus XR package installed into your Unity project.
|
||||
* - if you use Oculus, you use it through "OpenXR" (Oculus -> Tools -> OVR Utilities Plugin -> Set OVR to OpenXR)
|
||||
*/
|
||||
|
||||
[LIBVIRTUALGRASP_UNITY_SCRIPT]
|
||||
[HelpURL("https://docs.virtualgrasp.com/unity_vg_ec_unityxrinteraction." + VG_Version.__VG_VERSION__ + ".html")]
|
||||
public class VG_EC_UnityXRInteraction : VG_ExternalController
|
||||
{
|
||||
|
||||
#if VG_USE_UNITYXRINTERACTION_HAND
|
||||
private const string XRI_RESOURCE = "VG_XRI_Entries"; //"XRI Default Input Actions";
|
||||
private const string XRI_ACTIONMAP = "Player";
|
||||
private static VG_XRI_Entries m_xriEntries = null;
|
||||
static InputActionManager m_provider = null;
|
||||
private ActionBasedController m_controller = null;
|
||||
#endif
|
||||
|
||||
[Serializable]
|
||||
public class HandMapping : VG_BoneMapping
|
||||
{
|
||||
public override void Initialize(int avatarID, VG_HandSide side)
|
||||
{
|
||||
base.Initialize(avatarID, side);
|
||||
m_BoneToTransform = new Dictionary<int, Transform>()
|
||||
{
|
||||
{ 0, Hand_WristRoot }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public VG_EC_UnityXRInteraction(int avatarID, VG_HandSide side, Transform origin)
|
||||
{
|
||||
m_avatarID = avatarID;
|
||||
m_handType = side;
|
||||
m_origin = origin;
|
||||
m_enabled = true;
|
||||
m_enablingDefine = "VG_USE_UNITYXRINTERACTION_HAND";
|
||||
|
||||
#if VG_USE_UNITYXRINTERACTION_HAND
|
||||
m_enabled = true;
|
||||
#else
|
||||
PrintNotEnabledError();
|
||||
m_enabled = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public void DisposeController()
|
||||
{
|
||||
#if VG_USE_UNITYXRINTERACTION_HAND
|
||||
if (m_controller != null)
|
||||
GameObject.Destroy(m_controller.gameObject);
|
||||
#endif
|
||||
}
|
||||
|
||||
public new void Initialize()
|
||||
{
|
||||
#if VG_USE_UNITYXRINTERACTION_HAND
|
||||
if (m_provider == null) m_provider = GameObject.FindObjectOfType<InputActionManager>();
|
||||
if (m_xriEntries == null)
|
||||
m_xriEntries = Resources.Load<VG_XRI_Entries>(XRI_RESOURCE);
|
||||
if (m_xriEntries == null)
|
||||
{
|
||||
Debug.LogError("Could not find " + XRI_RESOURCE);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_xriEntries.Initialize();
|
||||
//Debug.Log(m_xriEntries);
|
||||
}
|
||||
|
||||
InputActionAsset inputActionAsset = null;
|
||||
if (m_provider == null)
|
||||
{
|
||||
m_provider = GameObject.FindObjectOfType<VG_MainScript>().gameObject.AddComponent<InputActionManager>();
|
||||
inputActionAsset = Resources.Load<InputActionAsset>(m_xriEntries.Get(VG_XRI_Entries.XRI_Entry.RESOURCE));
|
||||
if (inputActionAsset != null)
|
||||
{
|
||||
m_provider.actionAssets = new List<InputActionAsset>{ inputActionAsset };
|
||||
m_provider.EnableInput();
|
||||
}
|
||||
else Debug.Log("Could not load input actions.");
|
||||
}
|
||||
|
||||
if (m_provider != null && m_provider.actionAssets.Count > 0 && m_provider.actionAssets[0] != null)
|
||||
{
|
||||
m_mapping = new HandMapping();
|
||||
base.Initialize();
|
||||
|
||||
string handSide = (m_handType == VG_HandSide.LEFT) ? "Left" : "Right";
|
||||
|
||||
// We put the ActionBasedController components on dummy GameObjects so they do not
|
||||
// affect the wrist transforms per se (this can't be disabled in the XRController it seems).
|
||||
GameObject controller = new(handSide + "Controller_ID" + m_avatarID);
|
||||
controller.transform.SetParent(m_provider.transform);
|
||||
m_controller = controller.AddComponent<ActionBasedController>();
|
||||
|
||||
// Bind Position and Rotation Signals
|
||||
InputActionMap inputMap = m_provider.actionAssets[0].FindActionMap(XRI_ACTIONMAP);
|
||||
if (inputMap == null) Debug.LogError("Could not find map " + XRI_ACTIONMAP);
|
||||
else
|
||||
{
|
||||
m_controller.enableInputTracking = true;
|
||||
m_controller.updateTrackingType = XRBaseController.UpdateType.Update;
|
||||
|
||||
foreach (VG_XRI_Entries.XRI_Entry entry in new List<VG_XRI_Entries.XRI_Entry> { VG_XRI_Entries.XRI_Entry.POSITION, VG_XRI_Entries.XRI_Entry.ROTATION, VG_XRI_Entries.XRI_Entry.TRIGGER, VG_XRI_Entries.XRI_Entry.GRAB, VG_XRI_Entries.XRI_Entry.HAPTICS })
|
||||
{
|
||||
string actionName = m_xriEntries.Get(entry, handSide);
|
||||
InputAction inputAction = inputMap.FindAction(actionName);
|
||||
if (inputAction == null)
|
||||
{
|
||||
Debug.LogWarning("Could not find action " + actionName + " in " + XRI_ACTIONMAP + ".");
|
||||
continue;
|
||||
}
|
||||
switch (entry)
|
||||
{
|
||||
case VG_XRI_Entries.XRI_Entry.POSITION:
|
||||
m_controller.positionAction = new InputActionProperty(inputAction); break;
|
||||
case VG_XRI_Entries.XRI_Entry.ROTATION:
|
||||
m_controller.rotationAction = new InputActionProperty(inputAction); break;
|
||||
case VG_XRI_Entries.XRI_Entry.TRIGGER:
|
||||
m_controller.activateAction = new InputActionProperty(inputAction); break;
|
||||
case VG_XRI_Entries.XRI_Entry.GRAB:
|
||||
m_controller.selectAction = new InputActionProperty(inputAction); break;
|
||||
case VG_XRI_Entries.XRI_Entry.HAPTICS:
|
||||
m_controller.hapticDeviceAction = new InputActionProperty(inputAction); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_initialized = (m_provider != null && m_controller != null);
|
||||
#endif
|
||||
}
|
||||
|
||||
public override bool Compute()
|
||||
{
|
||||
#if VG_USE_UNITYXRINTERACTION_HAND
|
||||
if (!m_enabled) return false;
|
||||
if (!m_initialized) { Initialize(); return false; }
|
||||
SetPose(0, Matrix4x4.TRS(m_controller.currentControllerState.position, m_controller.currentControllerState.rotation, Vector3.one));
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
public override float GetGrabStrength()
|
||||
{
|
||||
float trigger = 0.0f;
|
||||
#if VG_USE_UNITYXRINTERACTION_HAND
|
||||
if (!m_initialized) return 0.0f;
|
||||
switch (VG_Controller.GetGraspButton())
|
||||
{
|
||||
case VG_VrButton.TRIGGER:
|
||||
trigger = m_controller.currentControllerState.activateInteractionState.value; break;
|
||||
case VG_VrButton.GRIP:
|
||||
trigger = m_controller.currentControllerState.selectInteractionState.value; break;
|
||||
case VG_VrButton.GRIP_OR_TRIGGER:
|
||||
trigger = Mathf.Max(m_controller.currentControllerState.activateInteractionState.value,
|
||||
m_controller.currentControllerState.selectInteractionState.value);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
return trigger;
|
||||
}
|
||||
|
||||
public override Color GetConfidence()
|
||||
{
|
||||
return Color.yellow;
|
||||
}
|
||||
|
||||
public override void HapticPulse(VG_HandStatus hand, float amplitude = 0.5F, float duration = 0.015F, int finger = 5)
|
||||
{
|
||||
#if VG_USE_UNITYXRINTERACTION_HAND
|
||||
m_controller.SendHapticImpulse(amplitude, duration);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ec2bbae109cbf34b994dac2f101fb30
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright (C) 2014-2024 Gleechi Technology AB. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VirtualGrasp.Controllers
|
||||
{
|
||||
|
||||
[Serializable, CreateAssetMenu(fileName = "VG_XRI_Entries", menuName = "VirtualGrasp/VG_XRI_Entries")]
|
||||
public class VG_XRI_Entries : ScriptableObject
|
||||
{
|
||||
public enum XRI_Entry
|
||||
{
|
||||
RESOURCE,
|
||||
POSITION,
|
||||
ROTATION,
|
||||
TRIGGER,
|
||||
GRAB,
|
||||
HAPTICS
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
m_entries.Clear();
|
||||
TryAdd(XRI_Entry.RESOURCE, m_resourceName);
|
||||
TryAdd(XRI_Entry.POSITION, m_positionAction);
|
||||
TryAdd(XRI_Entry.ROTATION, m_rotationAction);
|
||||
TryAdd(XRI_Entry.TRIGGER, m_triggerAction);
|
||||
TryAdd(XRI_Entry.GRAB, m_grabAction);
|
||||
TryAdd(XRI_Entry.HAPTICS, m_hapticsAction);
|
||||
}
|
||||
|
||||
public void TryAdd(XRI_Entry key, string value)
|
||||
{
|
||||
if (value != "")
|
||||
m_entries.Add(key, value);
|
||||
}
|
||||
|
||||
public string Get(XRI_Entry key, string suffix = "")
|
||||
{
|
||||
if (!m_entries.TryGetValue(key, out string value))
|
||||
return "null";
|
||||
return value + suffix;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
string str = "Valid Entries:\n";
|
||||
foreach (var entry in m_entries) { str += entry.ToString() + "\n"; }
|
||||
return str;
|
||||
}
|
||||
|
||||
public string m_resourceName = "";
|
||||
public string m_positionAction = "";
|
||||
public string m_rotationAction = "";
|
||||
public string m_triggerAction = "";
|
||||
public string m_grabAction = "";
|
||||
public string m_hapticsAction = "";
|
||||
|
||||
private Dictionary<XRI_Entry, string> m_entries = new();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7f5008351c8d5fa4590d077c163cd0cc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,218 @@
|
||||
// Copyright (C) 2014-2024 Gleechi Technology AB. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace VirtualGrasp.Scripts
|
||||
{
|
||||
using static VG_FingerAnimator.VG_FingerAnimationData;
|
||||
|
||||
/**
|
||||
* VG_FingerAnimator animates the fingers on a selected hand side using a list of animation clips.
|
||||
* Useful for making manual corrections on primary grasps,
|
||||
* or animating fingers during inhand manipulation of articulated objects.
|
||||
*/
|
||||
[LIBVIRTUALGRASP_UNITY_SCRIPT]
|
||||
[HelpURL("https://docs.virtualgrasp.com/unity_component_vgfingeranimator." + VG_Version.__VG_VERSION__ + ".html")]
|
||||
|
||||
public partial class VG_FingerAnimator : MonoBehaviour
|
||||
{
|
||||
[SerializeField, Tooltip("Hand side to animate")]
|
||||
private HandSide m_hand = HandSide.LEFT;
|
||||
[SerializeField, Tooltip("Animation clips for directing finger animation")]
|
||||
private List<VG_FingerAnimationData> m_fingerAnimations = new List<VG_FingerAnimationData>();
|
||||
[SerializeField, Tooltip("Optional, only enable animation when this object is grasped. If unassigned this transform will be used.")]
|
||||
private Transform m_interactableObject = null;
|
||||
[SerializeField]
|
||||
private AnimationEvents m_events = new AnimationEvents();
|
||||
private Transform m_holdingHand = null;
|
||||
private float m_animationDrive = 1.0f;
|
||||
|
||||
private Dictionary<Transform, Vector3> m_startRotation= new Dictionary<Transform, Vector3>();
|
||||
private Dictionary<Transform, Vector3> m_targetRotation = new Dictionary<Transform, Vector3>();
|
||||
|
||||
/// <summary>
|
||||
/// Pass in a float in range [0,1] to drive the animation
|
||||
/// </summary>
|
||||
public void DriveAnimation(float animationDrive)
|
||||
{
|
||||
this.m_animationDrive = Mathf.Clamp01(animationDrive);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops animation drive, bones will be rotated according to values inside the animation clips
|
||||
/// </summary>
|
||||
public void StopAnimationDrive()
|
||||
{
|
||||
this.m_animationDrive = 1f;
|
||||
}
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (m_interactableObject == null)
|
||||
m_interactableObject = this.transform;
|
||||
VG_Controller.OnObjectGrasped.AddListener(OnObjectGrasped);
|
||||
VG_Controller.OnObjectReleased.AddListener(OnObjectReleased);
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
private void OnObjectGrasped(VG_HandStatus status)
|
||||
{
|
||||
if (status.m_selectedObject != m_interactableObject) return;
|
||||
m_holdingHand = status.m_hand;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
private void OnObjectReleased(VG_HandStatus status)
|
||||
{
|
||||
if (status.m_selectedObject != m_interactableObject) return;
|
||||
if (m_holdingHand == status.m_hand)
|
||||
{
|
||||
m_holdingHand = null;
|
||||
VG_Controller.GetGraspingAvatars(m_interactableObject, out var hands);
|
||||
foreach (var hand in hands)
|
||||
{
|
||||
m_holdingHand = VG_Controller.GetHand(hand.Key, hand.Value).m_hand;
|
||||
}
|
||||
this.enabled = m_holdingHand != null;
|
||||
}
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
m_events.OnAnimationStarted?.Invoke();
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
m_events.OnAnimationStopped?.Invoke();
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
for (int i = 0; i < m_fingerAnimations.Count; i++)
|
||||
{
|
||||
VG_FingerAnimationData animation = m_fingerAnimations[i];
|
||||
if (animation.disabled) continue;
|
||||
|
||||
for (int fingerIndex = 0; fingerIndex < 5; fingerIndex++)
|
||||
{
|
||||
Finger fingerEnum = FingerEnumFromIndex(fingerIndex);
|
||||
if (animation.finger.HasFlag(fingerEnum))
|
||||
{
|
||||
for (int boneIndex = 0; boneIndex < 3; boneIndex++)
|
||||
{
|
||||
Bone boneEnum = BoneEnumFromIndex(boneIndex);
|
||||
if (animation.bone.HasFlag(boneEnum))
|
||||
{
|
||||
int avatarID = VG_Controller.GetHand(m_holdingHand).m_avatarID;
|
||||
AnimateFingerBone(avatarID, fingerIndex, boneIndex, animation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AnimateFingerBone(int avatarID, int fingerIndex, int boneIndex, VG_FingerAnimationData animation)
|
||||
{
|
||||
if (VG_Controller.GetFingerBone(avatarID, (VG_HandSide)this.m_hand, fingerIndex, boneIndex, out Transform bone) != VG_ReturnCode.SUCCESS)
|
||||
return;
|
||||
|
||||
Vector3 startRotationEuler = bone.localRotation.eulerAngles;
|
||||
Vector3 targetRotationEuler = startRotationEuler + animation.rotation;
|
||||
if (m_startRotation.ContainsKey(bone))
|
||||
startRotationEuler = m_startRotation[bone];
|
||||
else
|
||||
m_startRotation.Add(bone, startRotationEuler);
|
||||
|
||||
if (m_targetRotation.ContainsKey(bone))
|
||||
targetRotationEuler = m_targetRotation[bone];
|
||||
else
|
||||
m_targetRotation.Add(bone, targetRotationEuler);
|
||||
|
||||
Quaternion startRotation = Quaternion.Euler(startRotationEuler);
|
||||
Quaternion targetRotation = Quaternion.Euler(targetRotationEuler);
|
||||
float targetAnimationDrive = this.m_animationDrive;
|
||||
if (animation.ignoreDrive)
|
||||
{
|
||||
targetAnimationDrive = 1f;
|
||||
}
|
||||
else if (animation.invertDrive)
|
||||
{
|
||||
targetAnimationDrive = 1f - targetAnimationDrive;
|
||||
}
|
||||
|
||||
bone.localRotation = Quaternion.Slerp(startRotation, targetRotation, targetAnimationDrive);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class VG_FingerAnimator
|
||||
{
|
||||
public enum HandSide
|
||||
{
|
||||
LEFT = VG_HandSide.LEFT,
|
||||
RIGHT = VG_HandSide.RIGHT
|
||||
}
|
||||
[Serializable]
|
||||
public class VG_FingerAnimationData
|
||||
{
|
||||
[Tooltip("Disables this animation clip")]
|
||||
public bool disabled = true;
|
||||
[Tooltip("Makes this animation clip always reach target rotation, effectively ignoring any animation drive")]
|
||||
public bool ignoreDrive = false;
|
||||
[Tooltip("Makes this animation clip reach target rotation when the animation drive value is 0 rather than 1")]
|
||||
public bool invertDrive = false;
|
||||
[System.Flags]
|
||||
public enum Finger
|
||||
{
|
||||
Thumb = 1, Index = 2, Middle = 4, Ring = 8, Pinky = 16
|
||||
};
|
||||
[Tooltip("Fingers to animate in this clip, multiple can be selected")]
|
||||
public Finger finger = Finger.Thumb;
|
||||
[System.Flags]
|
||||
public enum Bone
|
||||
{
|
||||
Proximal = 1, Middle = 2, Distal = 4
|
||||
};
|
||||
[Tooltip("Bones to animate in this clip, multiple can be selected")]
|
||||
public Bone bone = Bone.Proximal;
|
||||
[Tooltip("Target rotation relative to the initial rotation for selected bones")]
|
||||
public Vector3 rotation;
|
||||
|
||||
public static Bone BoneEnumFromIndex(int boneIndex)
|
||||
{
|
||||
return boneIndex switch
|
||||
{
|
||||
0 => Bone.Proximal,
|
||||
1 => Bone.Middle,
|
||||
2 => Bone.Distal,
|
||||
_ => throw new ArgumentException($"Couldn't map index {boneIndex} to bone enum")
|
||||
};
|
||||
}
|
||||
|
||||
public static Finger FingerEnumFromIndex(int fingerIndex)
|
||||
{
|
||||
return fingerIndex switch
|
||||
{
|
||||
0 => Finger.Thumb,
|
||||
1 => Finger.Index,
|
||||
2 => Finger.Middle,
|
||||
3 => Finger.Ring,
|
||||
4 => Finger.Pinky,
|
||||
_ => throw new ArgumentException($"Couldn't map index {fingerIndex} to finger enum")
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class AnimationEvents
|
||||
{
|
||||
[SerializeField]
|
||||
public UnityEvent OnAnimationStarted, OnAnimationStopped;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3b785d2c01f3df24ab03aecb68556bd2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,296 @@
|
||||
// Copyright (C) 2014-2024 Gleechi Technology AB. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI; // for Text UI
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace VirtualGrasp.Scripts
|
||||
{
|
||||
/**
|
||||
* VG_Highlighter exemplifies how you could enable runtime grasp editing into your application.
|
||||
* The MonoBehavior provides a tutorial on the VG API functions for some of the VG_Controller event functions,
|
||||
* such as EditGrasp, GetInteractionTypeForObject and SetInteractionTypeForObject.
|
||||
*/
|
||||
[LIBVIRTUALGRASP_UNITY_SCRIPT]
|
||||
[HelpURL("https://docs.virtualgrasp.com/unity_component_vggraspannotator." + VG_Version.__VG_VERSION__ + ".html")]
|
||||
public class VG_GraspEditor : MonoBehaviour
|
||||
{
|
||||
/// An enum to describe a hand interaction type allowed for grasp editing (adding primary grasps)
|
||||
public enum VG_EditingInteractionType
|
||||
{
|
||||
TRIGGER_GRASP = 0, // Default, hand goes to object at grasp position
|
||||
PREVIEW_GRASP = 1, // Grasp is always previewed once object is selected, trigger will pick up the object
|
||||
JUMP_GRASP = 3, // Object jumps to hand when grasp is triggered
|
||||
}
|
||||
public Transform m_pad = null;
|
||||
public Transform m_addGraspButton = null;
|
||||
public Transform m_toggleInteractionButton = null;
|
||||
public Transform m_stepGraspButton = null;
|
||||
public Transform m_deleteGraspButton = null;
|
||||
public Transform m_deleteAllGraspsButton = null;
|
||||
[Tooltip("Interaction type used to add grasps.")]
|
||||
public VG_EditingInteractionType m_editingInteractionType = VG_EditingInteractionType.TRIGGER_GRASP;
|
||||
|
||||
|
||||
private bool m_sensorAvatarFound = false;
|
||||
private int m_sensorAvatarIDLeft = 0;
|
||||
private int m_sensorAvatarIDRight = 0;
|
||||
|
||||
|
||||
private List<ButtonContainer> m_containers = new List<ButtonContainer>();
|
||||
|
||||
// Keep track of intersections
|
||||
private Dictionary<Transform, bool> m_intersections = new Dictionary<Transform, bool>();
|
||||
|
||||
private delegate void EditFunction(VG_HandStatus hand);
|
||||
private delegate bool ValidateFunction(VG_HandStatus hand, out string text);
|
||||
private class ButtonContainer
|
||||
{
|
||||
public Transform m_root = null;
|
||||
public EditFunction m_editFunction = null;
|
||||
public ValidateFunction m_validateFunction = null;
|
||||
private Text m_text = null;
|
||||
private MeshRenderer m_renderer = null;
|
||||
private VG_Articulation m_articulation = null;
|
||||
public static HashSet<Transform> BUTTON_TRANSFORMS = new HashSet<Transform>();
|
||||
public ButtonContainer(Transform button, ValidateFunction validateFunc, EditFunction editFunc)
|
||||
{
|
||||
m_root = button;
|
||||
m_text = button.GetComponentInChildren<Text>();
|
||||
m_renderer = button.GetComponentInChildren<MeshRenderer>();
|
||||
m_articulation = button.GetComponentInChildren<VG_Articulation>();
|
||||
BUTTON_TRANSFORMS.Add(m_articulation.transform);
|
||||
m_editFunction = editFunc;
|
||||
m_validateFunction = validateFunc;
|
||||
}
|
||||
|
||||
public bool Validate(VG_HandStatus hand)
|
||||
{
|
||||
bool valid = m_validateFunction(hand, out string text);
|
||||
if (m_text != null)
|
||||
{
|
||||
m_text.text = text;
|
||||
m_text.color = (valid && hand.IsHolding()) ? Color.black : Color.grey;
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
|
||||
public bool Trigger(VG_HandStatus hand, bool hasIntersection)
|
||||
{
|
||||
if (m_renderer == null || !hand.IsHolding())
|
||||
return hasIntersection;
|
||||
|
||||
VG_Controller.GetObjectJointState(m_root, out float state);
|
||||
float threshold = m_articulation.m_min + (m_articulation.m_max - m_articulation.m_min) * 0.2f;
|
||||
|
||||
bool isIntersecting = state > threshold;
|
||||
if (hasIntersection != isIntersecting)
|
||||
{
|
||||
VG_Controller.OnObjectCollided.Invoke(hand); // just to trigger haptics
|
||||
if (isIntersecting) m_editFunction(hand);
|
||||
}
|
||||
|
||||
return isIntersecting;
|
||||
}
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
m_containers.Add(new ButtonContainer(m_addGraspButton, ValidateAddGrasp, AddGrasp));
|
||||
m_containers.Add(new ButtonContainer(m_toggleInteractionButton, ValidateToggleInteraction, ToggleInteraction));
|
||||
m_containers.Add(new ButtonContainer(m_stepGraspButton, ValidateStepGrasp, StepGrasp));
|
||||
m_containers.Add(new ButtonContainer(m_deleteGraspButton, ValidateDeleteGrasp, DeleteGrasp));
|
||||
m_containers.Add(new ButtonContainer(m_deleteAllGraspsButton, ValidateDeleteAllGrasp, DeleteAllGrasp));
|
||||
|
||||
if (VG_Controller.GetSensorControlledAvatarID(out m_sensorAvatarIDLeft, out m_sensorAvatarIDRight) == VG_ReturnCode.SUCCESS)
|
||||
m_sensorAvatarFound = true;
|
||||
}
|
||||
|
||||
#region ContainerFunctions
|
||||
|
||||
private bool IsValidObject(VG_HandStatus hand)
|
||||
{
|
||||
return hand != null && hand.m_selectedObject != null && hand.m_selectedObject.TryGetComponent<MeshRenderer>(out _);
|
||||
}
|
||||
|
||||
private void AddGrasp(VG_HandStatus hand)
|
||||
{
|
||||
VG_Controller.EditGrasp(hand.m_avatarID, hand.m_side, VG_EditorAction.ADD_CURRENT);
|
||||
}
|
||||
|
||||
private bool ValidateAddGrasp(VG_HandStatus hand, out string text)
|
||||
{
|
||||
if (!IsValidObject(hand))
|
||||
{
|
||||
text = "Add Grasp";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (VG_Controller.GetInteractionTypeForObject(hand.m_selectedObject) == VG_InteractionType.JUMP_PRIMARY_GRASP)
|
||||
{
|
||||
text = "No add grasp\ninteraction is JUMP_PRIMARY_GRASP!";
|
||||
return false;
|
||||
}
|
||||
|
||||
text = "Add Grasp\n(" + hand.GetNumGraspsInDB() + ")";
|
||||
return true;
|
||||
}
|
||||
private void ToggleInteraction(VG_HandStatus hand)
|
||||
{
|
||||
VG_InteractionType current_type = VG_Controller.GetInteractionTypeForObject(hand.m_selectedObject);
|
||||
// Initially if object has non ideal grasp editing interaction type, first toggle allow switch to ideal editing interaction type
|
||||
if (current_type != (VG_InteractionType)m_editingInteractionType && current_type != VG_InteractionType.JUMP_PRIMARY_GRASP)
|
||||
VG_Controller.SetInteractionTypeForObject(hand.m_selectedObject, (VG_InteractionType)m_editingInteractionType);
|
||||
// Then later will always toggle between the ideal editing type and jump primary grasp type
|
||||
else
|
||||
VG_Controller.SetInteractionTypeForObject(hand.m_selectedObject, current_type != VG_InteractionType.JUMP_PRIMARY_GRASP ?
|
||||
VG_InteractionType.JUMP_PRIMARY_GRASP : (VG_InteractionType)m_editingInteractionType);
|
||||
}
|
||||
|
||||
private void StepGrasp(VG_HandStatus hand)
|
||||
{
|
||||
VG_Controller.TogglePrimaryGraspOnObject(hand.m_avatarID, hand.m_side, hand.m_selectedObject);
|
||||
}
|
||||
|
||||
private bool ValidateToggleInteraction(VG_HandStatus hand, out string text)
|
||||
{
|
||||
if (!IsValidObject(hand))
|
||||
{
|
||||
text = "Toggle interaction";
|
||||
return false;
|
||||
}
|
||||
|
||||
VG_InteractionType current_type = VG_Controller.GetInteractionTypeForObject(hand.m_selectedObject);
|
||||
if (current_type == (VG_InteractionType)m_editingInteractionType && hand.GetNumPrimaryGraspsInDB() == 0)
|
||||
{
|
||||
text = "No toggle interaction\nno grasp!";
|
||||
return false;
|
||||
}
|
||||
|
||||
VG_InteractionType target_type = (current_type == (VG_InteractionType)m_editingInteractionType) ? VG_InteractionType.JUMP_PRIMARY_GRASP : (VG_InteractionType)m_editingInteractionType;
|
||||
text = "Toggle interaction To\n" + target_type + "";
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ValidateStepGrasp(VG_HandStatus hand, out string text)
|
||||
{
|
||||
if (!IsValidObject(hand))
|
||||
{
|
||||
text = "Step grasp";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hand.GetNumGraspsInDB() == 0)
|
||||
{
|
||||
text = "No step grasp\nno grasp!";
|
||||
return false;
|
||||
}
|
||||
|
||||
VG_InteractionType currentInteractionType = VG_Controller.GetInteractionTypeForObject(hand.m_selectedObject);
|
||||
|
||||
if (currentInteractionType != VG_InteractionType.JUMP_PRIMARY_GRASP)
|
||||
{
|
||||
text = "No step grasp\ninteraction is not JUMP_PRIMARY_GRASP!";
|
||||
return false;
|
||||
}
|
||||
|
||||
text = "Step grasp";
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ValidateDeleteGrasp(VG_HandStatus hand, out string text)
|
||||
{
|
||||
if (!IsValidObject(hand))
|
||||
{
|
||||
text = "Delete grasp";
|
||||
return false;
|
||||
}
|
||||
int numGrasps = hand.GetNumGraspsInDB();
|
||||
if (numGrasps == 0)
|
||||
{
|
||||
text = "No delete grasp";
|
||||
return false;
|
||||
}
|
||||
text = "Delete grasp\n(" + numGrasps + ")";
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ValidateDeleteAllGrasp(VG_HandStatus hand, out string text)
|
||||
{
|
||||
if (!IsValidObject(hand))
|
||||
{
|
||||
text = "Delete all grasps";
|
||||
return false;
|
||||
}
|
||||
int numGrasps = hand.GetNumGraspsInDB();
|
||||
if (numGrasps == 0)
|
||||
{
|
||||
text = "No delete all grasps";
|
||||
return false;
|
||||
}
|
||||
text = "Delete all grasps\n(" + numGrasps + ")";
|
||||
return true;
|
||||
}
|
||||
|
||||
private void DeleteGrasp(VG_HandStatus hand)
|
||||
{
|
||||
VG_Controller.EditGrasp(hand.m_avatarID, hand.m_side, VG_EditorAction.DELETE_CURRENT);
|
||||
if (hand.GetNumGraspsInDB() == 0)
|
||||
VG_Controller.SetInteractionTypeForObject(hand.m_selectedObject, (VG_InteractionType)m_editingInteractionType);
|
||||
}
|
||||
|
||||
private void DeleteAllGrasp(VG_HandStatus hand)
|
||||
{
|
||||
VG_Controller.EditGrasp(hand.m_avatarID, hand.m_side, VG_EditorAction.DELETE_ALL_HAND_GRASPS);
|
||||
if (VG_Controller.GetNumGraspsInDB(hand.m_selectedObject, hand.m_avatarID, hand.m_side) == 0)
|
||||
VG_Controller.SetInteractionTypeForObject(hand.m_selectedObject, (VG_InteractionType)m_editingInteractionType);
|
||||
else
|
||||
Debug.LogError("After remove all hand grasp, num grasps still non zero");
|
||||
}
|
||||
|
||||
#endregion // ContainerFunctions
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (!m_sensorAvatarFound)
|
||||
{
|
||||
if (VG_Controller.GetSensorControlledAvatarID(out m_sensorAvatarIDLeft, out m_sensorAvatarIDRight) == VG_ReturnCode.SUCCESS)
|
||||
m_sensorAvatarFound = true;
|
||||
}
|
||||
|
||||
if (!m_sensorAvatarFound)
|
||||
return;
|
||||
|
||||
|
||||
// Find selected object for left and right hand, but ignore if selected object is the pad or a button for this annotator
|
||||
Transform leftSelected = null;
|
||||
Transform rightSelected = null;
|
||||
VG_HandStatus status = VG_Controller.GetHand(m_sensorAvatarIDLeft, VG_HandSide.LEFT);
|
||||
if (status != null)
|
||||
leftSelected = ButtonContainer.BUTTON_TRANSFORMS.Contains(status.m_selectedObject) || status.m_selectedObject == m_pad ? null : status.m_selectedObject;
|
||||
status = VG_Controller.GetHand(m_sensorAvatarIDRight, VG_HandSide.RIGHT);
|
||||
if (status != null)
|
||||
rightSelected = ButtonContainer.BUTTON_TRANSFORMS.Contains(status.m_selectedObject) || status.m_selectedObject == m_pad ? null : status.m_selectedObject;
|
||||
|
||||
// If no object selected or if both hand selected different objects, not allow annotating
|
||||
if ((leftSelected == null && rightSelected == null) ||
|
||||
(leftSelected != null && rightSelected != null && leftSelected != rightSelected))
|
||||
{
|
||||
foreach (ButtonContainer container in m_containers)
|
||||
if (!container.Validate(null)) continue;
|
||||
return;
|
||||
}
|
||||
|
||||
bool hasIntersection;
|
||||
VG_HandStatus hand = VG_Controller.GetHand((leftSelected != null) ? m_sensorAvatarIDLeft : m_sensorAvatarIDRight,
|
||||
(leftSelected != null) ? VG_HandSide.LEFT : VG_HandSide.RIGHT);
|
||||
foreach (ButtonContainer container in m_containers)
|
||||
{
|
||||
if (!container.Validate(hand)) continue;
|
||||
// cache intersecting value in map so function only triggers in entering/exiting frame
|
||||
hasIntersection = (m_intersections.ContainsKey(container.m_root)) ? m_intersections[container.m_root] : false;
|
||||
m_intersections[container.m_root] = container.Trigger(hand, hasIntersection);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c42d88de4e4fe1f458f3290d3e5c80d9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright (C) 2014-2024 Gleechi Technology AB. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace VirtualGrasp.Scripts
|
||||
{
|
||||
/**
|
||||
* VG_HandStatus_Debugger provides a tool to show the VG_HandStatus members during runtime in editor mode.
|
||||
* The MonoBehavior provides a tutorial on the VG API functions for using the VG_HandStatus.
|
||||
*/
|
||||
[LIBVIRTUALGRASP_UNITY_SCRIPT]
|
||||
[HelpURL("https://docs.virtualgrasp.com/unity_component_vghandstatusdebugger." + VG_Version.__VG_VERSION__ + ".html")]
|
||||
public class VG_HandStatusDebugger : MonoBehaviour
|
||||
{
|
||||
[Tooltip("This list will be updated during runtime with the VG_HandStatus of all hands.")]
|
||||
public List<VG_HandStatus> m_hands = new List<VG_HandStatus>();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public void Start()
|
||||
{
|
||||
this.hideFlags = HideFlags.NotEditable;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
m_hands.Clear();
|
||||
foreach (VG_HandStatus hand in VG_Controller.GetHands())
|
||||
m_hands.Add(hand);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 02b0b5507498e6244984414f4eedfeaf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright (C) 2014-2024 Gleechi Technology AB. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System.Linq;
|
||||
|
||||
namespace VirtualGrasp.Scripts
|
||||
{
|
||||
/**
|
||||
* VG_HandVisualizer provides a tool to visualize the hand bones in Unity.
|
||||
* The MonoBehavior provides a tutorial on the VG API functions for accessing specific bones / elements of the hands.
|
||||
*/
|
||||
[LIBVIRTUALGRASP_UNITY_SCRIPT]
|
||||
[HelpURL("https://docs.virtualgrasp.com/unity_component_vghandvisualizer." + VG_Version.__VG_VERSION__ + ".html")]
|
||||
public class VG_HandVisualizer : MonoBehaviour
|
||||
{
|
||||
private Dictionary<int, GameObject> m_limbs = new();
|
||||
private Dictionary<int, LineRenderer> m_lines = new();
|
||||
private Transform m_root = null;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
m_root = new GameObject("DebugVis").transform;
|
||||
VG_Controller.OnPostUpdate.AddListener(Visualize);
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
VG_Controller.OnPostUpdate.RemoveListener(Visualize);
|
||||
if (m_root != null)
|
||||
DestroyImmediate(m_root.gameObject);
|
||||
m_limbs.Clear();
|
||||
m_lines.Clear();
|
||||
}
|
||||
|
||||
void Visualize()
|
||||
{
|
||||
foreach (VG_HandStatus hand in VG_Controller.GetHands())
|
||||
{
|
||||
if (VG_Controller.GetBone(hand.m_avatarID, hand.m_side, VG_BoneType.WRIST, out int wrist_iid, out Vector3 pw, out Quaternion qw) == VG_ReturnCode.SUCCESS)
|
||||
{
|
||||
if (!m_limbs.ContainsKey(wrist_iid))
|
||||
{
|
||||
m_limbs[wrist_iid] = new GameObject("Avatar" + hand.m_avatarID + "_" + hand.m_side + "_wrist");
|
||||
m_limbs[wrist_iid].transform.SetParent(m_root, true);
|
||||
Destroy(m_limbs[wrist_iid].GetComponent<Collider>());
|
||||
}
|
||||
m_limbs[wrist_iid].transform.SetPositionAndRotation(pw, qw);
|
||||
}
|
||||
else continue;
|
||||
|
||||
foreach (int fingerId in new List<int>() { 0, 1, 2, 3, 4 })
|
||||
{
|
||||
foreach (int boneId in new List<int>() { 0, 1, 2, -1 })
|
||||
{
|
||||
if (VG_Controller.GetFingerBone(hand.m_avatarID, hand.m_side, fingerId, boneId, out int iid, out Vector3 pf, out Quaternion qf) == VG_ReturnCode.SUCCESS)
|
||||
{
|
||||
if (!m_limbs.ContainsKey(iid))
|
||||
{
|
||||
Transform last_bone = m_limbs.Last().Value.transform;
|
||||
m_limbs[iid] = new GameObject(hand.m_side + "_" + VG_Controller.GetBone(iid).name + "_" + fingerId.ToString() + boneId.ToString());
|
||||
m_limbs[iid].transform.SetParent(boneId == 0 ? m_limbs[wrist_iid].transform : last_bone, true);
|
||||
Destroy(m_limbs[iid].GetComponent<Collider>());
|
||||
m_lines[iid] = m_limbs[iid].AddComponent<LineRenderer>();
|
||||
m_lines[iid].widthMultiplier = 0.002f;
|
||||
m_lines[iid].positionCount = 2;
|
||||
m_lines[iid].useWorldSpace = true;
|
||||
}
|
||||
|
||||
m_limbs[iid].transform.SetPositionAndRotation(pf, qf);
|
||||
m_lines[iid].SetPosition(0, m_limbs[iid].transform.parent.position);
|
||||
m_lines[iid].SetPosition(1, pf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0c2b4c87853da2f459f1d87e98981a1b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,278 @@
|
||||
// Copyright (C) 2014-2024 Gleechi Technology AB. All rights reserved.
|
||||
|
||||
//#define HIGHLIGHT_PLUS
|
||||
//#define USE_CAKESLICE_OUTLINE // https://github.com/cakeslice/Outline-Effect
|
||||
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
#if HIGHLIGHT_PLUS
|
||||
using HighlightPlus;
|
||||
#endif
|
||||
#if USE_CAKESLICE_OUTLINE
|
||||
using cakeslice;
|
||||
#endif
|
||||
|
||||
namespace VirtualGrasp.Scripts
|
||||
{
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
|
||||
[CustomEditor(typeof(VG_Highlighter))]
|
||||
class VG_Highlighter_GUI : Editor
|
||||
{
|
||||
private class State
|
||||
{
|
||||
public bool enabled = true;
|
||||
public string text = "";
|
||||
public string tooltip = "";
|
||||
public State(string _text, string _tooltip, bool _enabled = true)
|
||||
{
|
||||
text = _text;
|
||||
tooltip = _tooltip;
|
||||
enabled = _enabled;
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<VG_ReturnCode, State> m_states = new Dictionary<VG_ReturnCode, State>() {
|
||||
{ VG_ReturnCode.OBJECT_NO_BAKE, new State("No bakes.", "These objects have not been baked.") },
|
||||
{ VG_ReturnCode.OBJECT_NO_GRASPS, new State("Dynamic Grasps", "These objects have been baked and will be available for dynamic grasping.") },
|
||||
{ VG_ReturnCode.SUCCESS, new State("+ Static Grasps", "These objects have been baked and grasps have been added in GraspStudio.") }
|
||||
};
|
||||
|
||||
private List<VG_ReturnCode> state_copy = new List<VG_ReturnCode>();
|
||||
private HashSet<VG_ReturnCode> state_selected = new HashSet<VG_ReturnCode>();
|
||||
|
||||
public void OnEnable()
|
||||
{
|
||||
foreach (var state in m_states)
|
||||
{
|
||||
state_copy.Add(state.Key);
|
||||
if (state.Value.enabled) state_selected.Add(state.Key);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
base.OnInspectorGUI();
|
||||
|
||||
if (!VG_Controller.IsEnabled())
|
||||
{
|
||||
GUILayout.Label(
|
||||
"Play scene to highlight the state of objects in the scene.",
|
||||
EditorStyles.wordWrappedLabel);
|
||||
return;
|
||||
}
|
||||
|
||||
GUILayout.Label(
|
||||
"Select to highlight the state of objects in the scene.",
|
||||
EditorStyles.wordWrappedLabel);
|
||||
|
||||
Color color;
|
||||
bool state_changed = false;
|
||||
GUIStyle myStyle = new GUIStyle(GUI.skin.button);
|
||||
VG_Highlighter highlighter = target as VG_Highlighter;
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
foreach (VG_ReturnCode code in state_copy)
|
||||
{
|
||||
color = highlighter.GetColor(code);
|
||||
myStyle.normal.textColor = color;
|
||||
myStyle.onNormal.textColor = color;
|
||||
myStyle.onHover.textColor = color;
|
||||
m_states[code].enabled = GUILayout.Toggle(m_states[code].enabled, new GUIContent(m_states[code].text, m_states[code].tooltip), myStyle);
|
||||
bool this_enabled = !m_states[code].enabled;
|
||||
if (this_enabled ^ state_selected.Contains(code)) state_changed |= true;
|
||||
if (this_enabled) state_selected.Add(code);
|
||||
else state_selected.Remove(code);
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
if (state_changed) highlighter.HighlightObjectStatus(state_selected);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* VG_Highlighter exemplifies how you could enable object highlighting based on the current hand status.
|
||||
* The MonoBehavior provides a tutorial on the VG API functions for some of the VG_Controller event functions, such as OnObjectSelected and OnObjectDeselected.
|
||||
*/
|
||||
[LIBVIRTUALGRASP_UNITY_SCRIPT]
|
||||
[HelpURL("https://docs.virtualgrasp.com/unity_component_vghighlighter." + VG_Version.__VG_VERSION__ + ".html")]
|
||||
public class VG_Highlighter : MonoBehaviour
|
||||
{
|
||||
[Tooltip("Set the shader that is used for highlighting.")]
|
||||
public Shader m_shader = null;
|
||||
|
||||
#if !HIGHLIGHT_PLUS && !USE_CAKESLICE_OUTLINE
|
||||
private List<Material>[] m_unhighlightedMaterials = new List<Material>[2];
|
||||
private List<Material>[] m_highlightedMaterials = new List<Material>[2];
|
||||
#endif
|
||||
[Tooltip("Set the color that are used for highlighting objects selected by the left hand.")]
|
||||
public Color m_leftHandColor = Color.green;
|
||||
[Tooltip("Set the color that are used for highlighting objects selected by the right hand.")]
|
||||
public Color m_rightHandColor = Color.green;
|
||||
|
||||
// Dictionary to keep track of highlighted objects.
|
||||
private Dictionary<VG_HandSide, Transform> m_highlightedObjects = new Dictionary<VG_HandSide, Transform>();
|
||||
|
||||
#if USE_CAKESLICE_OUTLINE
|
||||
private Outline[] m_outlines = new Outline[2] { new Outline(), new Outline() };
|
||||
#endif
|
||||
#if HIGHLIGHT_PLUS
|
||||
|
||||
#endif
|
||||
|
||||
public Color GetColor(VG_ReturnCode code)
|
||||
{
|
||||
switch (code)
|
||||
{
|
||||
case VG_ReturnCode.OBJECT_NO_BAKE: return Color.red;
|
||||
case VG_ReturnCode.OBJECT_NO_GRASPS: return Color.green;
|
||||
case VG_ReturnCode.SUCCESS: return Color.cyan;
|
||||
default: return Color.black;
|
||||
}
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
m_shader = Resources.Load<Shader>("SH_RimLight");
|
||||
if (m_shader == null) Debug.LogWarning("No shader found/assigned to VG_Highlighter. Please assign a shader.", gameObject);
|
||||
|
||||
// Initialize the highlighted objects dictionary.
|
||||
m_highlightedObjects[VG_HandSide.LEFT] = null;
|
||||
m_highlightedObjects[VG_HandSide.RIGHT] = null;
|
||||
|
||||
m_leftHandColor.a = 0.5f;
|
||||
m_rightHandColor.a = 0.5f;
|
||||
|
||||
VG_Controller.OnObjectSelected.AddListener(Highlight);
|
||||
VG_Controller.OnObjectDeselected.AddListener(Unhighlight);
|
||||
VG_Controller.OnObjectGrasped.AddListener(Unhighlight);
|
||||
}
|
||||
|
||||
public void HighlightObjectStatus(HashSet<VG_ReturnCode> states)
|
||||
{
|
||||
bool isSelected;
|
||||
int numSelected = 0;
|
||||
int numAll = 0;
|
||||
|
||||
Color color;
|
||||
foreach (Transform t in VG_Controller.GetSelectableObjects())
|
||||
{
|
||||
color = Color.black;
|
||||
|
||||
isSelected = false;
|
||||
foreach (VG_ReturnCode state in states)
|
||||
{
|
||||
isSelected = VG_Controller.GetUnbakedObjects(state).Contains(t);
|
||||
if (state == VG_ReturnCode.SUCCESS) isSelected = !isSelected;
|
||||
if (isSelected) { color = GetColor(state); break; }
|
||||
}
|
||||
color.a = 0.5f;
|
||||
|
||||
if (t != null && t.GetComponentInChildren<MeshRenderer>() != null)
|
||||
{
|
||||
foreach (Material m in t.GetComponentInChildren<MeshRenderer>().materials)
|
||||
{
|
||||
m.shader = isSelected ? m_shader : Shader.Find("Legacy Shaders/Specular");
|
||||
m.SetFloat("_RimPower", 0.25f);
|
||||
m.SetColor("_RimColor", color);
|
||||
}
|
||||
}
|
||||
|
||||
if (isSelected) numSelected++;
|
||||
numAll++;
|
||||
}
|
||||
|
||||
if (states.Count > 0)
|
||||
Debug.Log("Highlighting " + numSelected + " out of " + numAll + " interactable objects.");
|
||||
}
|
||||
|
||||
private void Highlight(VG_HandStatus hand)
|
||||
{
|
||||
//if (VG_Controller.IsPushable(obj) &&
|
||||
// !VG_Controller.IsGraspable(obj))
|
||||
//return;
|
||||
|
||||
// If the selected object is already highlighted
|
||||
if (hand.m_selectedObject == m_highlightedObjects[hand.m_side])
|
||||
return;
|
||||
|
||||
// If the selected object is the same
|
||||
if (hand.m_selectedObject == m_highlightedObjects[hand.m_side == VG_HandSide.LEFT ? VG_HandSide.RIGHT : VG_HandSide.LEFT])
|
||||
return;
|
||||
|
||||
m_highlightedObjects[hand.m_side] = hand.m_selectedObject;
|
||||
|
||||
int id = hand.m_side < 0 ? 0 : 1;
|
||||
Color color = id == 0 ? m_leftHandColor : m_rightHandColor;
|
||||
#if HIGHLIGHT_PLUS
|
||||
HighlightEffect highlight = hand.m_selectedObject.GetComponent<HighlightEffect>();
|
||||
if (highlight == null)
|
||||
{
|
||||
highlight = hand.m_selectedObject.gameObject.AddComponent<HighlightPlus.HighlightEffect>();
|
||||
highlight.highlighted = true;
|
||||
highlight.glow = 1.0f;
|
||||
highlight.overlayAnimationSpeed = 0.0f;
|
||||
highlight.overlay = 0.0f;
|
||||
highlight.seeThrough = SeeThroughMode.Never;
|
||||
highlight.outlineVisibility = Visibility.AlwaysOnTop;
|
||||
}
|
||||
else highlight.highlighted = true;
|
||||
for (int gp = 0; gp < highlight.glowPasses.Length; gp++)
|
||||
highlight.glowPasses[gp].color = color;
|
||||
highlight.outlineColor = color;
|
||||
#else
|
||||
#if USE_CAKESLICE_OUTLINE
|
||||
m_outlines[id].Renderer = hand.m_selectedObject.GetComponent<Renderer>();
|
||||
m_outlines[id].Enable();
|
||||
#else
|
||||
Material[] objectMaterials = hand.m_selectedObject.GetComponentInChildren<MeshRenderer>().sharedMaterials;
|
||||
m_unhighlightedMaterials[id] = new List<Material>(hand.m_selectedObject.GetComponentInChildren<MeshRenderer>().sharedMaterials);
|
||||
for (int i = 0, count = m_unhighlightedMaterials[id].Count; i < count; i++)
|
||||
m_unhighlightedMaterials[id][i] = new Material(m_unhighlightedMaterials[id][i]);
|
||||
|
||||
m_highlightedMaterials[id] = new List<Material>();
|
||||
for (int i = 0, count = m_unhighlightedMaterials[id].Count; i < count; i++)
|
||||
{
|
||||
m_highlightedMaterials[id].Add(new Material(m_unhighlightedMaterials[id][i]));
|
||||
m_highlightedMaterials[id][i].shader = m_shader;
|
||||
}
|
||||
|
||||
MeshRenderer objectRenderer = m_highlightedObjects[hand.m_side].GetComponentInChildren<MeshRenderer>();
|
||||
for (int i = 0, count = objectRenderer.materials.Length; i < count; i++)
|
||||
{
|
||||
objectMaterials[i] = m_highlightedMaterials[id][i];
|
||||
objectMaterials[i].SetColor("_RimColor", color);
|
||||
}
|
||||
|
||||
objectRenderer.sharedMaterials = objectMaterials;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
private void Unhighlight(VG_HandStatus hand)
|
||||
{
|
||||
int id = hand.m_side < 0 ? 0 : 1;
|
||||
if (!m_highlightedObjects.ContainsKey(hand.m_side))
|
||||
return;
|
||||
|
||||
// Got no object (or the same object as before), got no unhighlight
|
||||
Transform highlightedObject = m_highlightedObjects[hand.m_side];
|
||||
if (highlightedObject == null) return;
|
||||
|
||||
#if HIGHLIGHT_PLUS
|
||||
if (hand.m_formerSelectedObject.GetComponent<HighlightPlus.HighlightEffect>() != null)
|
||||
hand.m_formerSelectedObject.GetComponent<HighlightPlus.HighlightEffect>().highlighted = false;
|
||||
#else
|
||||
#if USE_CAKESLICE_OUTLINE
|
||||
m_outlines[id].Disable();
|
||||
m_outlines[id].Renderer = null;
|
||||
#else
|
||||
highlightedObject.GetComponentInChildren<MeshRenderer>().sharedMaterials = m_unhighlightedMaterials[id].ToArray();
|
||||
#endif
|
||||
#endif
|
||||
m_highlightedObjects[hand.m_side] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: da4078d632a9ee2459dbc2ad09811c6f
|
||||
timeCreated: 1463495798
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,115 @@
|
||||
// Copyright (C) 2014-2024 Gleechi Technology AB. All rights reserved.
|
||||
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VirtualGrasp.Scripts
|
||||
{
|
||||
/**
|
||||
* VG_HintVisualizer provides a tool to visualize some hints such as a selection sphere to debug object selection or a push sphere to guide pushing interactions.
|
||||
* The MonoBehavior provides a tutorial on the VG API functions for accessing the push state (GetPushCircle).
|
||||
*/
|
||||
[LIBVIRTUALGRASP_UNITY_SCRIPT]
|
||||
[HelpURL("https://docs.virtualgrasp.com/unity_component_vghintvisualizer." + VG_Version.__VG_VERSION__ + ".html")]
|
||||
public class VG_HintVisualizer : MonoBehaviour
|
||||
{
|
||||
[Tooltip("Enable push hint visualization. For each hand, a circular hint will appear when the hand is in PUSHING mode.")]
|
||||
public bool m_enablePushHints = true;
|
||||
[Tooltip("Enable grasp hint visualization. For each hand, a sphere will appear as a hint of the current selection state.")]
|
||||
public bool m_enableGraspHints = true;
|
||||
|
||||
[Tooltip("A push hint visualization (e.g. put a sphere here) when push mode is PUSHING.")]
|
||||
public List<Transform> pushHints = new List<Transform> { };
|
||||
[Tooltip("A selection hint visualization (e.g. put a sphere here) where the grasp selector is placed.")]
|
||||
public List<Transform> graspHints = new List<Transform> { };
|
||||
|
||||
/// Add a collider-less sphere as a marker
|
||||
private void AddHintObject(List<Transform> hintList, string name)
|
||||
{
|
||||
GameObject go = GameObject.CreatePrimitive(PrimitiveType.Sphere);
|
||||
DestroyImmediate(go.GetComponent<Collider>());
|
||||
go.name = name;
|
||||
go.transform.localScale = Vector3.zero;
|
||||
go.transform.SetParent(transform);
|
||||
hintList.Add(go.transform);
|
||||
}
|
||||
|
||||
private void RemoveHintObjects()
|
||||
{
|
||||
foreach (var hint in pushHints)
|
||||
DestroyImmediate(hint.gameObject);
|
||||
foreach (var hint in graspHints)
|
||||
DestroyImmediate(hint.gameObject);
|
||||
pushHints.Clear();
|
||||
graspHints.Clear();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
VG_Controller.OnPostUpdate.AddListener(HintUpdate);
|
||||
if (pushHints.Count == 0) AddHintObject(pushHints, "PushHint");
|
||||
if (graspHints.Count == 0) AddHintObject(graspHints, "SelectionHint");
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
VG_Controller.OnPostUpdate.RemoveListener(HintUpdate);
|
||||
RemoveHintObjects();
|
||||
}
|
||||
|
||||
void HintUpdate()
|
||||
{
|
||||
if (m_enablePushHints)
|
||||
{
|
||||
// Fill up the push hints assuming each avatar has 2 hands
|
||||
while (pushHints.Count < VG_Controller.GetHands().Count())
|
||||
{
|
||||
pushHints.Add(GameObject.Instantiate(pushHints.Last()));
|
||||
pushHints.Last().SetParent(transform);
|
||||
}
|
||||
|
||||
int num = 0;
|
||||
foreach (VG_HandStatus hand in VG_Controller.GetHands())
|
||||
{
|
||||
Transform t = VG_Controller.GetPushCircle(hand.m_avatarID, hand.m_side, out Vector3 p, out Quaternion q, out float radius, out bool inContact);
|
||||
Transform hint = pushHints[num];
|
||||
hint.gameObject.SetActive(t != null);
|
||||
if (t != null)
|
||||
{
|
||||
hint.SetPositionAndRotation(p + q * new Vector3(0, 0, -0.001f), q);
|
||||
hint.localScale = new Vector3(2 * radius, 2 * radius, 0.001f);
|
||||
if (inContact) VG_Controller.OnObjectCollided.Invoke(hand);
|
||||
}
|
||||
num++;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_enableGraspHints)
|
||||
{
|
||||
// Fill up the push hints assuming each avatar has 2 hands
|
||||
while (graspHints.Count < VG_Controller.GetHands().Count())
|
||||
{
|
||||
graspHints.Add(GameObject.Instantiate(graspHints.Last()));
|
||||
graspHints.Last().SetParent(transform);
|
||||
}
|
||||
|
||||
int num = 0;
|
||||
foreach (VG_HandStatus hand in VG_Controller.GetHands())
|
||||
{
|
||||
Transform t = VG_Controller.GetBone(hand.m_avatarID, hand.m_side, VG_BoneType.APPROACH, out _, out Matrix4x4 m);
|
||||
Transform hint = graspHints[num];
|
||||
hint.gameObject.SetActive(t != null);
|
||||
if (t != null)
|
||||
{
|
||||
hint.SetPositionAndRotation(m.GetColumn(3), Quaternion.LookRotation(m.GetColumn(2), m.GetColumn(1)));
|
||||
if (m.GetRow(3) != new Vector4(0, 0, 0, 1)) hint.localScale = m.GetRow(3);
|
||||
hint.GetComponent<Renderer>().material.SetColor("_Color",
|
||||
VG_Controller.GetHand(hand.m_avatarID, hand.m_side).m_selectedObject == null ? Color.red : Color.green);
|
||||
}
|
||||
num++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ddc51de3da0736a42a8db4f56ef97198
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright (C) 2014-2024 Gleechi Technology AB. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.XR;
|
||||
|
||||
namespace VirtualGrasp.Scripts
|
||||
{
|
||||
/**
|
||||
* VG_Locomotion does not depends on VG library, and is a script that provides a convenient tool for locomation.
|
||||
*/
|
||||
[LIBVIRTUALGRASP_UNITY_SCRIPT]
|
||||
[HelpURL("https://docs.virtualgrasp.com/unity_component_vglocomotion." + VG_Version.__VG_VERSION__ + ".html")]
|
||||
public class VG_Locomotion : MonoBehaviour
|
||||
{
|
||||
public Transform m_character = null;
|
||||
private Vector2 m_axisL = Vector2.zero;
|
||||
private Vector2 m_axisR = Vector2.zero;
|
||||
private Camera m_camera = null;
|
||||
|
||||
public float speed = 1.0f;
|
||||
public float rotationSpeed = 60f;
|
||||
|
||||
void TryAssignCamera()
|
||||
{
|
||||
if (m_character == null) m_character = transform;
|
||||
m_camera = GetComponentInChildren<Camera>();
|
||||
if (m_camera == null) m_camera = Camera.main;
|
||||
}
|
||||
|
||||
void FixedUpdate()
|
||||
{
|
||||
if (m_camera == null)
|
||||
{
|
||||
TryAssignCamera();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!InputDevices.GetDeviceAtXRNode(XRNode.LeftHand).TryGetFeatureValue(CommonUsages.primary2DAxis, out m_axisL))
|
||||
m_axisL = Vector2.zero;
|
||||
|
||||
if (!InputDevices.GetDeviceAtXRNode(XRNode.RightHand).TryGetFeatureValue(CommonUsages.primary2DAxis, out m_axisR))
|
||||
m_axisR = Vector2.zero;
|
||||
|
||||
// Key board control
|
||||
if (Input.GetKey(KeyCode.W))
|
||||
{
|
||||
m_axisL.y += 1f;
|
||||
}
|
||||
if (Input.GetKey(KeyCode.S))
|
||||
{
|
||||
m_axisL.y -= 1f;
|
||||
}
|
||||
if (Input.GetKey(KeyCode.A))
|
||||
{
|
||||
m_axisL.x -= 1f;
|
||||
}
|
||||
if (Input.GetKey(KeyCode.D))
|
||||
{
|
||||
m_axisL.x += 1f;
|
||||
}
|
||||
if (Input.GetKey(KeyCode.Q))
|
||||
{
|
||||
m_character.transform.Translate(new Vector3(0f, -speed * Time.deltaTime, 0f));
|
||||
}
|
||||
if (Input.GetKey(KeyCode.E))
|
||||
{
|
||||
m_character.transform.Translate(new Vector3(0f, speed * Time.deltaTime, 0f));
|
||||
}
|
||||
|
||||
// Joint stick control from controllers
|
||||
|
||||
//float x = Mathf.Abs(m_axisL.x) > Mathf.Abs(m_axisR.x) ? m_axisL.x : m_axisR.x;
|
||||
//float y = Mathf.Abs(m_axisL.y) > Mathf.Abs(m_axisR.y) ? m_axisL.y : m_axisR.y;
|
||||
//if (Mathf.Abs(x) > 0.1f) m_character.Rotate(new Vector3(0, 2.0f * x, 0), Space.Self);
|
||||
//if (Mathf.Abs(y) > 0.1f) m_character.Translate(0.03f * y * (m_camera.transform.rotation * Vector3.forward), Space.World);
|
||||
|
||||
// Below has speed control
|
||||
float x = Mathf.Abs(m_axisL.x) > Mathf.Abs(m_axisR.x) ? m_axisL.x : m_axisR.x;
|
||||
float y = Mathf.Abs(m_axisL.y) > Mathf.Abs(m_axisR.y) ? m_axisL.y : m_axisR.y;
|
||||
m_character.Rotate(new Vector3(0, rotationSpeed * x * Time.deltaTime, 0), Space.Self);
|
||||
m_character.Translate(speed * y * Time.deltaTime * (m_camera.transform.rotation * Vector3.forward), Space.World);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
fileFormatVersion: 2
|
||||
<<<<<<< HEAD:Assets/ChangeSelectionWeight.cs.meta
|
||||
guid: c21c93d43a515fc4bb64444425bc4d54
|
||||
=======
|
||||
guid: f86eec4236f99b944a3f49c98e34cf45
|
||||
>>>>>>> master:Assets/ThirdParty/VirtualGrasp/Scripts/Dev/Move.cs.meta
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,106 @@
|
||||
// Copyright (C) 2014-2024 Gleechi Technology AB. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace VirtualGrasp.Scripts
|
||||
{
|
||||
/**
|
||||
* VG_ObjectAnimator animates a selected object by either rotating around an axis or translate along an axis.
|
||||
* Useful for to achieve in-hand manipulation of articulated objects.
|
||||
*/
|
||||
[LIBVIRTUALGRASP_UNITY_SCRIPT]
|
||||
[HelpURL("https://docs.virtualgrasp.com/unity_component_vgobjectanimator." + VG_Version.__VG_VERSION__ + ".html")]
|
||||
public class VG_ObjectAnimator : MonoBehaviour
|
||||
{
|
||||
[SerializeField, Tooltip("Optional, if not assigned this transform will be used")]
|
||||
private Transform m_object;
|
||||
[SerializeField, Tooltip("Which axis to rotate around or translate along")]
|
||||
private SnapAxis m_axis = SnapAxis.X;
|
||||
[SerializeField, Tooltip("How much degree to rotate around the axis")]
|
||||
private float m_angle = 0f;
|
||||
[SerializeField, Tooltip("How much distance (m) to translate along the axis")]
|
||||
private float m_distance = 0f;
|
||||
|
||||
private Vector3 m_initialLocalPosition = Vector3.zero;
|
||||
private Quaternion m_initialLocalRotation = Quaternion.identity;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (this.m_object == null)
|
||||
this.m_object = transform;
|
||||
}
|
||||
private void Start()
|
||||
{
|
||||
m_initialLocalPosition = this.m_object.localPosition;
|
||||
m_initialLocalRotation = this.m_object.localRotation;
|
||||
}
|
||||
/// <summary>
|
||||
/// Pass in a float in range [0,1] to drive the translation animation of the object
|
||||
/// </summary>
|
||||
public void Translate(float inputValue)
|
||||
{
|
||||
inputValue = Mathf.Clamp01(inputValue);
|
||||
float targetDist = 0f;
|
||||
float maxDistanceToMove = this.m_distance;
|
||||
if (inputValue < 0f)
|
||||
{
|
||||
inputValue = Mathf.Abs(inputValue);
|
||||
maxDistanceToMove *= -1f;
|
||||
}
|
||||
targetDist = Mathf.Lerp(0f, maxDistanceToMove, inputValue);
|
||||
|
||||
var targetPosition = this.m_object.localPosition;
|
||||
switch (this.m_axis)
|
||||
{
|
||||
case SnapAxis.X:
|
||||
targetPosition.x = targetDist;
|
||||
break;
|
||||
case SnapAxis.Y:
|
||||
targetPosition.y = targetDist;
|
||||
break;
|
||||
case SnapAxis.Z:
|
||||
targetPosition.z = targetDist;
|
||||
break;
|
||||
default:
|
||||
throw new System.NotImplementedException("Axis to rotate not defined");
|
||||
}
|
||||
|
||||
this.m_object.localPosition = m_initialLocalPosition + targetPosition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pass in a float in range [0,1] to drive the rotation animation of the object
|
||||
/// </summary>
|
||||
public void Rotate(float inputValue)
|
||||
{
|
||||
inputValue = Mathf.Clamp01(inputValue);
|
||||
|
||||
float targetAngle = 0f;
|
||||
float maxDegreesToRotate = this.m_angle;
|
||||
if (inputValue < 0f)
|
||||
{
|
||||
inputValue = Mathf.Abs(inputValue);
|
||||
maxDegreesToRotate *= -1f;
|
||||
}
|
||||
targetAngle = Mathf.Lerp(0f, maxDegreesToRotate, inputValue);
|
||||
|
||||
var targetRotation = this.m_object.localRotation.eulerAngles;
|
||||
switch (this.m_axis)
|
||||
{
|
||||
case SnapAxis.X:
|
||||
targetRotation.x = targetAngle;
|
||||
break;
|
||||
case SnapAxis.Y:
|
||||
targetRotation.y = targetAngle;
|
||||
break;
|
||||
case SnapAxis.Z:
|
||||
targetRotation.z = targetAngle;
|
||||
break;
|
||||
default:
|
||||
throw new System.NotImplementedException("Axis to rotate not defined");
|
||||
}
|
||||
|
||||
this.m_object.localRotation = Quaternion.Euler(targetRotation) * m_initialLocalRotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f698d1ee5005d8e4bbb24ecfa7e023b1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,303 @@
|
||||
// Copyright (C) 2014-2024 Gleechi Technology AB. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
#if VG_ENABLE_INPUT_SYSTEM && !ENABLE_LEGACY_INPUT_MANAGER
|
||||
using UnityEngine.InputSystem;
|
||||
#endif
|
||||
|
||||
namespace VirtualGrasp.Scripts
|
||||
{
|
||||
|
||||
/**
|
||||
* VG_Recorder provides a tool to record and replay hand interactions in an object-independent manner.
|
||||
* The MonoBehavior provides a tutorial on the VG API functions for recording and replaying interactions.
|
||||
*/
|
||||
[LIBVIRTUALGRASP_UNITY_SCRIPT]
|
||||
[HelpURL("https://docs.virtualgrasp.com/unity_component_vgrecorder." + VG_Version.__VG_VERSION__ + ".html")]
|
||||
public class VG_Recorder : MonoBehaviour
|
||||
{
|
||||
public enum RecordingMode
|
||||
{
|
||||
RECORD_ON_PLAY,
|
||||
REPLAY_ON_PLAY,
|
||||
MANUAL
|
||||
}
|
||||
|
||||
[Tooltip("How to start recording or replaying a sequence; MANUAL means record and replay will be initiated through Keycodes specified.")]
|
||||
public RecordingMode m_recordingMode = RecordingMode.MANUAL;
|
||||
//[Tooltip("Reset scene when replay.")]
|
||||
//public bool m_resetWhenReplay = false;
|
||||
[Tooltip("Path to save the new Recording asset in. E.g. Assets/Recordings/example.sdb.")]
|
||||
public string m_newRecordingPath = "Assets/Recordings/example.sdb";
|
||||
[Tooltip("Recording(s) used for replay")]
|
||||
public List<VG_Recording> m_replayRecordings = new List<VG_Recording>();
|
||||
|
||||
[Tooltip("Avatar used to replay a sensor recording. Has to be the avatar with Replay checked.")]
|
||||
public List<SkinnedMeshRenderer> m_replayAvatars = null;
|
||||
|
||||
[Header("Keycodes")]
|
||||
#if VG_ENABLE_INPUT_SYSTEM && !ENABLE_LEGACY_INPUT_MANAGER
|
||||
[Tooltip("Key to press to start and stop recording.")]
|
||||
public Key m_recordingKey = Key.R;
|
||||
[Tooltip("Key to press to replay a recording. Shift + Key to Pause/Resume.")]
|
||||
public Key m_replaySequenceKey = Key.P;
|
||||
[Tooltip("Key to press to replay a single recording element. Shift + Key to Pause/Resume.")]
|
||||
public Key m_replaySegmentKey = Key.Y;
|
||||
#else
|
||||
[Tooltip("Key to press to start and stop recording. When stop recording most recent recording will be saved to the New Recording Path.")]
|
||||
public KeyCode m_recordingKey = KeyCode.R;
|
||||
[Tooltip("Key to press to replay a recording. Shift + Key to Pause/Resume.")]
|
||||
public KeyCode m_replaySequenceKey = KeyCode.P;
|
||||
[Tooltip("Key to press to replay a single recording element. Shift + Key to Pause/Resume.")]
|
||||
public KeyCode m_replaySegmentKey = KeyCode.Y;
|
||||
#endif
|
||||
|
||||
[Header("Segment Replay Options")]
|
||||
[Tooltip("Avatar's hand to replay a specific interaction segment with.")]
|
||||
public VG_HandSide m_side = VG_HandSide.LEFT;
|
||||
[Tooltip("Specific segment to replay (see console output for segment ID, and has to corresond to Side and Replay Object).")]
|
||||
public int m_segmentID = 5;
|
||||
[Tooltip("Provide object for segment replay to be centered on this object's current pose.")]
|
||||
public Transform m_replayObject = null;
|
||||
|
||||
private bool m_isRecording = false;
|
||||
private bool m_isReplaying = false;
|
||||
private Dictionary<VG_HandSide, bool> m_primaryPushed = new Dictionary<VG_HandSide, bool>() { { VG_HandSide.LEFT, false }, { VG_HandSide.RIGHT, false } };
|
||||
private Dictionary<VG_HandSide, bool> m_secondaryPushed = new Dictionary<VG_HandSide, bool>() { { VG_HandSide.LEFT, false }, { VG_HandSide.RIGHT, false } };
|
||||
private Dictionary<VG_HandSide, int> m_joystickPushed = new Dictionary<VG_HandSide, int>() { { VG_HandSide.LEFT, -1 }, { VG_HandSide.RIGHT, -1 } };
|
||||
|
||||
private VG_Recording m_tempRecording;
|
||||
private int m_replayID = 0;
|
||||
|
||||
void Start()
|
||||
{
|
||||
VG_Controller.OnPostUpdate.AddListener(CheckStopReplay);
|
||||
|
||||
switch (m_recordingMode)
|
||||
{
|
||||
case RecordingMode.RECORD_ON_PLAY:
|
||||
TryToggleRecording();
|
||||
break;
|
||||
case RecordingMode.REPLAY_ON_PLAY:
|
||||
StartReplaySequence();
|
||||
break;
|
||||
}
|
||||
if (m_newRecordingPath!=null && m_newRecordingPath.Length > 0)
|
||||
enforceAppending(ref m_newRecordingPath, ".sdb");
|
||||
}
|
||||
|
||||
private void OnApplicationQuit()
|
||||
{
|
||||
if (m_isRecording) VG_Controller.StopRecording();
|
||||
}
|
||||
|
||||
private void enforceAppending(ref string target, string appendingStr)
|
||||
{
|
||||
if (target.LastIndexOf(appendingStr) == -1)
|
||||
{
|
||||
Debug.LogWarning(target + " is appended with " + appendingStr);
|
||||
target = target + appendingStr;
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure appendingStr is at the end of target
|
||||
string sub_str = target.Substring(target.LastIndexOf(appendingStr), target.Length - target.LastIndexOf(appendingStr));
|
||||
if(sub_str.Length != appendingStr.Length)
|
||||
{
|
||||
Debug.LogWarning(target + " is appended with " + appendingStr);
|
||||
target = target + appendingStr;
|
||||
}
|
||||
}
|
||||
|
||||
void Update() {
|
||||
|
||||
#if VG_ENABLE_INPUT_SYSTEM && !ENABLE_LEGACY_INPUT_MANAGER
|
||||
if (Keyboard.current[m_recordingKey].wasPressedThisFrame) TryToggleRecording();
|
||||
|
||||
if (Keyboard.current[m_replaySequenceKey].wasPressedThisFrame) {
|
||||
if (Keyboard.current[Key.LeftShift].isPressed) ResumePauseReplay();
|
||||
else StartReplaySequence();
|
||||
}
|
||||
|
||||
if (Keyboard.current[m_replaySegmentKey].wasPressedThisFrame) {
|
||||
if (Keyboard.current[Key.LeftShift].isPressed) ResumePauseReplay();
|
||||
else StartReplaySegment();
|
||||
}
|
||||
#else
|
||||
if (Input.GetKeyDown(m_recordingKey)) TryToggleRecording();
|
||||
|
||||
if (Input.GetKeyDown(m_replaySequenceKey))
|
||||
{
|
||||
if (Input.GetKey(KeyCode.LeftShift)) ResumePauseReplay();
|
||||
else StartReplaySequence();
|
||||
}
|
||||
|
||||
if (Input.GetKeyDown(m_replaySegmentKey))
|
||||
{
|
||||
if (Input.GetKey(KeyCode.LeftShift)) ResumePauseReplay();
|
||||
else StartReplaySegment();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public void TryToggleRecording()
|
||||
{
|
||||
if (m_isReplaying)
|
||||
{
|
||||
Debug.Log("VG is replaying. Stopping the replay before recording. Try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
ToggleRecording();
|
||||
}
|
||||
|
||||
private void ToggleRecording()
|
||||
{
|
||||
if (!m_isRecording)
|
||||
{
|
||||
|
||||
VG_Controller.StartRecording();
|
||||
m_isRecording = true;
|
||||
Debug.Log("Started VG sensor recording, Press Recording Key again will stop recording.");
|
||||
|
||||
if (m_newRecordingPath == null || m_newRecordingPath.Length == 0)
|
||||
Debug.LogWarning("When stop recording, data won't be save to a file because New Recording Path is not set.");
|
||||
}
|
||||
else
|
||||
{
|
||||
VG_Controller.StopRecording();
|
||||
m_isRecording = false;
|
||||
VG_Controller.CollectRecording(out m_tempRecording);
|
||||
m_replayRecordings.Add(m_tempRecording);
|
||||
Debug.Log("Stopped VG sensor recording.");
|
||||
|
||||
if (m_newRecordingPath == null || m_newRecordingPath.Length == 0)
|
||||
{
|
||||
Debug.LogWarning("Recorded sensor data can not be saved to a file because New Recording Path is not set.");
|
||||
}
|
||||
else
|
||||
{
|
||||
VG_Controller.SaveRecording(m_newRecordingPath);
|
||||
Debug.Log("Saved VG sensor recording as " + m_newRecordingPath);
|
||||
#if UNITY_EDITOR
|
||||
AssetDatabase.ImportAsset(m_newRecordingPath);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckStopReplay()
|
||||
{
|
||||
if (!m_isReplaying) return;
|
||||
foreach (int replayAvatarID in GetReplayAvatars())
|
||||
{
|
||||
if (!VG_Controller.IsReplaying(replayAvatarID))
|
||||
{
|
||||
m_isReplaying = false;
|
||||
m_replayID++;
|
||||
if (m_replayID >= m_replayRecordings.Count) m_replayID = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool PrepareReplay()
|
||||
{
|
||||
if (m_isRecording)
|
||||
{
|
||||
Debug.Log("VG is recording. Stop the recording before replay. Try again.");
|
||||
return false;
|
||||
}
|
||||
|
||||
VG_ReturnCode res;
|
||||
|
||||
res = VG_Controller.LoadRecording(m_replayRecordings[m_replayID]);
|
||||
if (res == VG_ReturnCode.SUCCESS)
|
||||
{
|
||||
m_isReplaying = true;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("Loading recording failed");
|
||||
m_isReplaying = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void StartReplaySequence()
|
||||
{
|
||||
if (!PrepareReplay()) return;
|
||||
|
||||
foreach (int replayAvatarID in GetReplayAvatars())
|
||||
VG_Controller.StartReplay(replayAvatarID);
|
||||
}
|
||||
|
||||
public VG_ReturnCode StartReplaySegment()
|
||||
{
|
||||
if (m_replayObject == null)
|
||||
{
|
||||
Debug.LogWarning("You need to provide a replay object for StartSingleReplay().");
|
||||
return VG_ReturnCode.INVALID_TARGET;
|
||||
}
|
||||
if (!PrepareReplay()) return VG_ReturnCode.DLL_FUNCTION_FAILED;
|
||||
|
||||
foreach (int replayAvatarID in GetReplayAvatars())
|
||||
if (VG_Controller.StartReplayOnObject(m_replayObject, replayAvatarID, m_side, m_segmentID) != VG_ReturnCode.SUCCESS)
|
||||
return VG_ReturnCode.ARGUMENT_ERROR;
|
||||
|
||||
return VG_ReturnCode.SUCCESS;
|
||||
}
|
||||
|
||||
public void ResumePauseReplay()
|
||||
{
|
||||
if (m_isRecording)
|
||||
{
|
||||
Debug.Log("VG is recording. Stop the recording before replay. Try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_isReplaying)
|
||||
{
|
||||
foreach (int replayAvatarID in GetReplayAvatars())
|
||||
VG_Controller.StopReplay(replayAvatarID);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (int replayAvatarID in GetReplayAvatars())
|
||||
VG_Controller.ResumeReplay(replayAvatarID);
|
||||
}
|
||||
|
||||
m_isReplaying = !m_isReplaying;
|
||||
}
|
||||
|
||||
private List<int> GetReplayAvatars()
|
||||
{
|
||||
List<int> res = new List<int>();
|
||||
int replayAvatarIDLeft, replayAvatarIDRight;
|
||||
if (m_replayAvatars == null || m_replayAvatars.Count == 0)
|
||||
{
|
||||
if (VG_Controller.GetReplayAvatarID(out replayAvatarIDLeft, out replayAvatarIDRight) != VG_ReturnCode.SUCCESS)
|
||||
return res;
|
||||
if(replayAvatarIDLeft == replayAvatarIDRight)
|
||||
res.Add(replayAvatarIDLeft);
|
||||
else
|
||||
{
|
||||
if(replayAvatarIDLeft != -1)
|
||||
res.Add(replayAvatarIDLeft);
|
||||
if(replayAvatarIDRight != -1)
|
||||
res.Add(replayAvatarIDRight);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (SkinnedMeshRenderer sm in m_replayAvatars)
|
||||
res.Add(sm.GetInstanceID());
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8e95a9637ccf9754f986ff603a18cca1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,191 @@
|
||||
// Copyright (C) 2014-2024 Gleechi Technology AB. All rights reserved.
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.XR;
|
||||
|
||||
|
||||
namespace VirtualGrasp.Scripts
|
||||
{
|
||||
[LIBVIRTUALGRASP_UNITY_SCRIPT]
|
||||
[HelpURL("https://docs.virtualgrasp.com/unity_component_vgruntimeregister." + VG_Version.__VG_VERSION__ + ".html")]
|
||||
|
||||
public class VG_RunTimeRegister : MonoBehaviour
|
||||
{
|
||||
[Serializable]
|
||||
public class RigRegister
|
||||
{
|
||||
[Tooltip("The key to press to clone and register the rig from rigClone.")]
|
||||
public KeyCode m_registerRigKey = KeyCode.R;
|
||||
[Tooltip("The rig transform to test rig clone registering.")]
|
||||
public Transform m_rigClone = null;
|
||||
[Tooltip("Scale the rig uniformly.")]
|
||||
public float m_scale = 1.0f;
|
||||
[Tooltip("The new external controller sensor setup for any new avatar.")]
|
||||
public VG_SensorSetup m_sensorSetup = null;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class PrefabRegister
|
||||
{
|
||||
[Tooltip("The key to press to clone and register the rig from rigClone.")]
|
||||
public KeyCode m_registerPrefabKey = KeyCode.P;
|
||||
[Tooltip("The prefab to test rig clone registering.")]
|
||||
public GameObject m_prefab = null;
|
||||
[Tooltip("Scale the prefab uniformly.")]
|
||||
public float m_scale = 1.0f;
|
||||
[Tooltip("The new external controller profile for the new avatar.")]
|
||||
public VG_ControllerProfile m_profile;
|
||||
[Tooltip("Override the origin of the controller profile. Try to find it by name in the prefab.")]
|
||||
public string m_originName;
|
||||
}
|
||||
|
||||
[Tooltip("The rigs to register at certain key press events.")]
|
||||
private List<RigRegister> m_registerRigs = new();
|
||||
[Tooltip("The rigs to register at certain key press events.")]
|
||||
public List<PrefabRegister> m_prefabRigs = new();
|
||||
private Dictionary<int, GameObject> m_prefabClones = new();
|
||||
[Tooltip("Register the first prefab at start. In case we have no other in the scene.")]
|
||||
public bool m_registerFirstAvatarAtStart = true;
|
||||
[Tooltip("If an avatar is instantiated while another is there, remove the older one.")]
|
||||
public bool m_allowOnlyOneAvatar = false;
|
||||
private bool m_vrButtonWasPressed = false;
|
||||
|
||||
[Header("Register Object")]
|
||||
[Tooltip("The key to press to clone and register the object from objectClone.")]
|
||||
public KeyCode m_registerObjectKey = KeyCode.O;
|
||||
[Tooltip("The object transform to test object clone registering.")]
|
||||
public Transform m_objectClone = null;
|
||||
[Header("Register Scene")]
|
||||
[Tooltip("The key to press to load another scene additively")]
|
||||
public KeyCode m_registerSceneKey = KeyCode.S;
|
||||
[Tooltip("The scene name to test scene loading. ActiveScene if left empty.")]
|
||||
public string m_sceneName = "";
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (m_sceneName == "")
|
||||
m_sceneName = SceneManager.GetActiveScene().name;
|
||||
|
||||
if (m_registerFirstAvatarAtStart && m_prefabRigs.Count > 0)
|
||||
RegisterAvatarAndController(m_prefabRigs[0]);
|
||||
}
|
||||
|
||||
private void RegisterAvatarAndController(RigRegister rig)
|
||||
{
|
||||
if (rig.m_rigClone == null || rig.m_rigClone.GetComponentInChildren<SkinnedMeshRenderer>() == null)
|
||||
return;
|
||||
|
||||
GameObject clone = GameObject.Instantiate(rig.m_rigClone.gameObject);
|
||||
clone.transform.localScale = rig.m_scale * Vector3.one;
|
||||
if (VG_Controller.RegisterSensorAvatar(clone.GetComponentInChildren<SkinnedMeshRenderer>(), out int id, rig.m_sensorSetup) != VG_ReturnCode.SUCCESS)
|
||||
{
|
||||
Destroy(clone);
|
||||
return;
|
||||
}
|
||||
clone.name = clone.name + "_ID" + id;
|
||||
}
|
||||
|
||||
private void UnregisterControlledAvatar()
|
||||
{
|
||||
VG_Controller.GetSensorControlledAvatarID(out int avatarID);
|
||||
if (avatarID != -1)
|
||||
{
|
||||
VG_Controller.UnRegisterAvatar(avatarID);
|
||||
GameObject.Destroy(m_prefabClones[avatarID]);
|
||||
m_prefabClones.Remove(avatarID);
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterAvatarAndController(PrefabRegister prefab)
|
||||
{
|
||||
if (m_allowOnlyOneAvatar)
|
||||
UnregisterControlledAvatar();
|
||||
|
||||
GameObject clone = Instantiate(prefab.m_prefab);
|
||||
if (clone == null || clone.GetComponentInChildren<SkinnedMeshRenderer>() == null)
|
||||
{
|
||||
Debug.LogError("Prefab " + prefab.m_prefab.name + " could not be found or has no SkinnedMeshRenderer.", this);
|
||||
Destroy(clone);
|
||||
return;
|
||||
}
|
||||
clone.transform.localScale = prefab.m_scale * Vector3.one;
|
||||
VG_SensorSetup sensorSetup = new()
|
||||
{
|
||||
m_profile = prefab.m_profile,
|
||||
m_origin = clone.transform.Find(prefab.m_originName)
|
||||
};
|
||||
|
||||
if (VG_Controller.RegisterSensorAvatar(clone.GetComponentInChildren<SkinnedMeshRenderer>(), out int id, sensorSetup) != VG_ReturnCode.SUCCESS)
|
||||
{
|
||||
Destroy(clone);
|
||||
return;
|
||||
}
|
||||
|
||||
clone.name = prefab.m_prefab.name + "_ID" + id;
|
||||
m_prefabClones.Add(id, clone);
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
// Clone an object and register it in runtime to the VG library.
|
||||
if (Input.GetKeyDown(m_registerObjectKey) && m_objectClone != null)
|
||||
{
|
||||
GameObject clone = GameObject.Instantiate(m_objectClone.gameObject, m_objectClone.parent, true);
|
||||
clone.name = m_objectClone.name + "_clone";
|
||||
clone.transform.position += 0.05f * Vector3.up;
|
||||
m_objectClone = clone.transform;
|
||||
}
|
||||
|
||||
// Clone an avatar rig (existing in the scene) and register it in runtime to the VG library.
|
||||
foreach (RigRegister rig in m_registerRigs)
|
||||
{
|
||||
if (Input.GetKeyDown(rig.m_registerRigKey))
|
||||
RegisterAvatarAndController(rig);
|
||||
}
|
||||
|
||||
// Clone an avatar rig (existing in the scene) and register it in runtime to the VG library.
|
||||
InputDevice inputDevice = InputDevices.GetDeviceAtXRNode(XRNode.LeftHand);
|
||||
foreach (PrefabRegister rig in m_prefabRigs)
|
||||
{
|
||||
if (Input.GetKeyDown(rig.m_registerPrefabKey))
|
||||
RegisterAvatarAndController(rig);
|
||||
|
||||
// Register specific avatars with XR X/Y buttons
|
||||
if (inputDevice.isValid)
|
||||
{
|
||||
bool vrButtonIsPressed = false;
|
||||
if (inputDevice.TryGetFeatureValue(CommonUsages.primaryButton, out vrButtonIsPressed))
|
||||
{
|
||||
// Register the one marked with "1" as KeyCode with X
|
||||
if (rig.m_registerPrefabKey == KeyCode.Alpha1 &&
|
||||
vrButtonIsPressed && !m_vrButtonWasPressed)
|
||||
RegisterAvatarAndController(rig);
|
||||
m_vrButtonWasPressed = vrButtonIsPressed;
|
||||
}
|
||||
if (!m_vrButtonWasPressed && inputDevice.TryGetFeatureValue(CommonUsages.secondaryButton, out vrButtonIsPressed))
|
||||
{
|
||||
// Register the one marked with "2" as KeyCode with Y
|
||||
if (rig.m_registerPrefabKey == KeyCode.Alpha2 &&
|
||||
vrButtonIsPressed && !m_vrButtonWasPressed)
|
||||
RegisterAvatarAndController(rig);
|
||||
m_vrButtonWasPressed = vrButtonIsPressed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Input.GetKeyDown(KeyCode.U))
|
||||
{
|
||||
VG_Controller.GetSensorControlledAvatarID(out int avatarID);
|
||||
VG_Controller.UnRegisterAvatar(avatarID);
|
||||
}
|
||||
|
||||
if (Input.GetKeyDown(m_registerSceneKey))
|
||||
{
|
||||
SceneManager.LoadScene(m_sceneName, LoadSceneMode.Additive);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3fa66cdc96070cd4eb019b39fcad1c61
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,442 @@
|
||||
// Copyright (C) 2014-2024 Gleechi Technology AB. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using VirtualGrasp;
|
||||
|
||||
namespace VirtualGrasp.Scripts
|
||||
{
|
||||
[CreateAssetMenu(menuName = "VirtualGrasp/VG_Utility")]
|
||||
public class VG_Utility : ScriptableObject
|
||||
{
|
||||
private Transform m_selectedTransform;
|
||||
private Dictionary<Transform, ArticulationDrive> cachedJointByTransform = new Dictionary<Transform, ArticulationDrive>();
|
||||
private static VG_HandSide BoolToHandSide(bool isLeft) => isLeft ? VG_HandSide.LEFT : VG_HandSide.RIGHT;
|
||||
private static int SensorAvatarIDLeft
|
||||
{
|
||||
get
|
||||
{
|
||||
VG_Controller.GetSensorControlledAvatarID(out var sensorAvatarIDLeft, out var sensorAvatarIDRight);
|
||||
if (sensorAvatarIDLeft != -1 || sensorAvatarIDRight != -1)
|
||||
return sensorAvatarIDLeft;
|
||||
return ReplayAvatarIDLeft;
|
||||
}
|
||||
}
|
||||
private static int SensorAvatarIDRight
|
||||
{
|
||||
get
|
||||
{
|
||||
VG_Controller.GetSensorControlledAvatarID(out var sensorAvatarIDLeft, out var sensorAvatarIDRight);
|
||||
if (sensorAvatarIDLeft != -1 || sensorAvatarIDRight != -1)
|
||||
return sensorAvatarIDRight;
|
||||
return ReplayAvatarIDRight;
|
||||
}
|
||||
}
|
||||
private static int ReplayAvatarIDLeft
|
||||
{
|
||||
get
|
||||
{
|
||||
VG_Controller.GetReplayAvatarID(out var replayAvatarIDLeft, out var replayAvatarIDRight);
|
||||
return replayAvatarIDLeft;
|
||||
}
|
||||
}
|
||||
|
||||
private static int ReplayAvatarIDRight
|
||||
{
|
||||
get
|
||||
{
|
||||
VG_Controller.GetReplayAvatarID(out var replayAvatarIDLeft, out var replayAvatarIDRight);
|
||||
return replayAvatarIDRight;
|
||||
}
|
||||
}
|
||||
|
||||
public void SelectObject(Transform transform) => m_selectedTransform = transform;
|
||||
|
||||
public void SetSelectionWeight(float weight)
|
||||
{
|
||||
if (m_selectedTransform == null)
|
||||
{
|
||||
Debug.LogError("No transform select in VGUtility");
|
||||
}
|
||||
SetSelectionWeight(m_selectedTransform, weight);
|
||||
}
|
||||
|
||||
private void SetSelectionWeight(Transform transform, float weight) => VG_Controller.SetObjectSelectionWeight(transform, weight);
|
||||
|
||||
public void SetObjectIsDualHandsOnly(Transform transform)
|
||||
{
|
||||
VG_Controller.SetDualHandsOnly(transform, true);
|
||||
}
|
||||
public void SetObjectNotDualHandsOnly(Transform transform)
|
||||
{
|
||||
VG_Controller.SetDualHandsOnly(transform, false);
|
||||
}
|
||||
public void SetJointStateNormalized(float normalizedState)
|
||||
{
|
||||
normalizedState = Mathf.Clamp01(normalizedState);
|
||||
var articulation = FindActiveArticulation(m_selectedTransform);
|
||||
if (articulation == null)
|
||||
{
|
||||
Debug.LogError("No articulation on object, can't set joint state", m_selectedTransform);
|
||||
return;
|
||||
}
|
||||
var state = Mathf.Lerp(articulation.m_min, articulation.m_max, normalizedState);
|
||||
VG_Controller.SetObjectJointState(m_selectedTransform, state);
|
||||
}
|
||||
|
||||
private static VG_Articulation FindActiveArticulation(Transform transform)
|
||||
{
|
||||
var articulations = transform.GetComponents<VG_Articulation>();
|
||||
foreach (var articulation in articulations)
|
||||
{
|
||||
if (articulation.enabled)
|
||||
{
|
||||
return articulation;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void SetJointState(float state)
|
||||
{
|
||||
if (m_selectedTransform == null)
|
||||
{
|
||||
Debug.LogError("No transform selected, can't set joint state", this);
|
||||
return;
|
||||
}
|
||||
VG_Controller.SetObjectJointState(m_selectedTransform, state);
|
||||
}
|
||||
|
||||
public void StopSettingJointState()
|
||||
{
|
||||
if (m_selectedTransform == null)
|
||||
{
|
||||
Debug.LogError("No transform selected, can't set stop set joint state", this);
|
||||
return;
|
||||
}
|
||||
VG_Controller.StopSettingObjectJointState(m_selectedTransform);
|
||||
}
|
||||
|
||||
public void SetGlobalInteractionType(int type)
|
||||
{
|
||||
Debug.Log($"Set global interaction type to {(VG_InteractionType)type}");
|
||||
VG_Controller.SetGlobalInteractionType((VG_InteractionType)type);
|
||||
}
|
||||
|
||||
public void SetInteractionTypeOnSelectedObject(int type)
|
||||
{
|
||||
if (this.m_selectedTransform == null)
|
||||
{
|
||||
Debug.LogError("No articulation selected, selected one with VGUtility.SelectObject first", this);
|
||||
return;
|
||||
}
|
||||
Debug.Log($"Set interaction type to {(VG_InteractionType)type}");
|
||||
VG_Controller.SetInteractionTypeForObject(this.m_selectedTransform, (VG_InteractionType)type);
|
||||
}
|
||||
|
||||
public void LockArticulation(Transform transform)
|
||||
{
|
||||
if (transform.TryGetComponent<ArticulationBody>(out var articulationBody))
|
||||
{
|
||||
if (articulationBody.jointType == ArticulationJointType.FixedJoint) return;
|
||||
|
||||
var xDrive = articulationBody.xDrive;
|
||||
cachedJointByTransform.Add(transform, xDrive);
|
||||
xDrive.lowerLimit = xDrive.upperLimit = articulationBody.jointPosition[0];
|
||||
articulationBody.xDrive = xDrive;
|
||||
}
|
||||
else
|
||||
{
|
||||
VG_Controller.ChangeObjectJoint(transform, VG_JointType.FIXED);
|
||||
}
|
||||
}
|
||||
|
||||
public void UnLockArticulation(Transform transform)
|
||||
{
|
||||
if (transform.TryGetComponent<ArticulationBody>(out var articulationBody))
|
||||
{
|
||||
if (cachedJointByTransform.TryGetValue(transform, out var cachedJointType))
|
||||
{
|
||||
articulationBody.xDrive = cachedJointType;
|
||||
cachedJointByTransform.Remove(transform);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("No cached joint found for articulationBody", transform);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (VG_Controller.RecoverObjectJoint(transform) != VG_ReturnCode.SUCCESS)
|
||||
{
|
||||
Debug.LogWarning("Couldn't recover articulation");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeObjectJoint(VG_Articulation articulation) => VG_Controller.ChangeObjectJoint(articulation.transform, articulation);
|
||||
|
||||
public void ChangeObjectJointOnSelectedObject(VG_Articulation articulation)
|
||||
{
|
||||
VG_Controller.ChangeObjectJoint(m_selectedTransform, articulation);
|
||||
}
|
||||
|
||||
public void ForceReleaseObject(Transform transform) => VG_Controller.ForceReleaseObject(transform);
|
||||
|
||||
public void ForceReleaseObjectLeft()
|
||||
{
|
||||
int left_id = SensorAvatarIDLeft;
|
||||
if (left_id == -1)
|
||||
{
|
||||
Debug.LogError("No avatar with left hand is registered, can not ForceReleaseObjectLeft!");
|
||||
return;
|
||||
}
|
||||
|
||||
VG_Controller.ForceReleaseObject(left_id, VG_HandSide.LEFT);
|
||||
}
|
||||
|
||||
public void ForceReleaseObjectRight()
|
||||
{
|
||||
int right_id = SensorAvatarIDRight;
|
||||
if (right_id == -1)
|
||||
{
|
||||
Debug.LogError("No avatar with right hand is registered, can not ForceReleaseObjectRight!");
|
||||
return;
|
||||
}
|
||||
|
||||
VG_Controller.ForceReleaseObject(right_id, VG_HandSide.RIGHT);
|
||||
}
|
||||
|
||||
public void ForceReleaseObject()
|
||||
{
|
||||
int left_id = SensorAvatarIDLeft;
|
||||
int right_id = SensorAvatarIDRight;
|
||||
if (left_id != -1)
|
||||
VG_Controller.ForceReleaseObject(left_id);
|
||||
if (right_id != left_id && right_id != -1)
|
||||
VG_Controller.ForceReleaseObject(right_id);
|
||||
}
|
||||
|
||||
public void MakeObjectUngraspable(Transform transform)
|
||||
{
|
||||
VG_Controller.SetObjectSelectionWeight(transform, 0f);
|
||||
}
|
||||
|
||||
public void MakeObjectGraspable(Transform transform)
|
||||
{
|
||||
VG_Controller.SetObjectSelectionWeight(transform, 1f);
|
||||
}
|
||||
public void JumpGraspObjectLeft(Transform transform)
|
||||
{
|
||||
int left_id = SensorAvatarIDLeft;
|
||||
if (left_id == -1)
|
||||
{
|
||||
Debug.LogError("No avatar with left hand is registered, can not JumpGraspObjectLeft!");
|
||||
return;
|
||||
}
|
||||
VG_Controller.JumpGraspObject(left_id, VG_HandSide.LEFT, transform);
|
||||
}
|
||||
|
||||
public void JumpGraspObjectRight(Transform transform)
|
||||
{
|
||||
int right_id = SensorAvatarIDRight;
|
||||
if (right_id == -1)
|
||||
{
|
||||
Debug.LogError("No avatar with right hand is registered, can not JumpGraspObjectRight!");
|
||||
return;
|
||||
}
|
||||
VG_Controller.JumpGraspObject(right_id, VG_HandSide.RIGHT, transform);
|
||||
}
|
||||
|
||||
public void SwitchGraspObjectLeft(Transform transform)
|
||||
{
|
||||
int left_id = SensorAvatarIDLeft;
|
||||
if (left_id == -1)
|
||||
{
|
||||
Debug.LogError("No avatar with left hand is registered, can not SwitchGraspObjectLeft!");
|
||||
return;
|
||||
}
|
||||
VG_Controller.SwitchGraspObject(left_id, VG_HandSide.LEFT, transform);
|
||||
}
|
||||
|
||||
public void SwitchGraspObjectRight(Transform transform)
|
||||
{
|
||||
int right_id = SensorAvatarIDRight;
|
||||
if (right_id == -1)
|
||||
{
|
||||
Debug.LogError("No avatar with right hand is registered, can not SwitchGraspObjectRight!");
|
||||
return;
|
||||
}
|
||||
VG_Controller.SwitchGraspObject(right_id, VG_HandSide.RIGHT, transform);
|
||||
}
|
||||
|
||||
public void StartReplay(string replayName)
|
||||
{
|
||||
VG_Controller.LoadRecording(replayName);
|
||||
int left_id = ReplayAvatarIDLeft;
|
||||
int right_id = ReplayAvatarIDRight;
|
||||
if (left_id != -1)
|
||||
VG_Controller.StartReplay(left_id);
|
||||
if (right_id != left_id && right_id != -1)
|
||||
VG_Controller.StartReplay(right_id);
|
||||
}
|
||||
|
||||
public void StopReplay()
|
||||
{
|
||||
int left_id = ReplayAvatarIDLeft;
|
||||
int right_id = ReplayAvatarIDRight;
|
||||
if (left_id != -1)
|
||||
VG_Controller.StopReplay(left_id);
|
||||
if (right_id != left_id && right_id != -1)
|
||||
VG_Controller.StopReplay(right_id);
|
||||
|
||||
}
|
||||
|
||||
public void SetAvatarMirrorHand(bool mirrorHand)
|
||||
{
|
||||
int left_id = SensorAvatarIDLeft;
|
||||
int right_id = SensorAvatarIDRight;
|
||||
if (left_id != -1)
|
||||
VG_Controller.SetAvatarMirrorHandControl(left_id, mirrorHand);
|
||||
if (right_id != left_id && right_id != -1)
|
||||
VG_Controller.SetAvatarMirrorHandControl(right_id, mirrorHand);
|
||||
}
|
||||
public void SetSensorAvatarActive(bool isActive)
|
||||
{
|
||||
int left_id = SensorAvatarIDLeft;
|
||||
int right_id = SensorAvatarIDRight;
|
||||
if (left_id != -1)
|
||||
VG_Controller.SetAvatarActive(left_id, true, isActive, Vector3.down);
|
||||
if (right_id != left_id && right_id != -1)
|
||||
VG_Controller.SetAvatarActive(right_id, true, isActive, Vector3.down);
|
||||
}
|
||||
|
||||
public void SetReplayAvatarActive(bool isActive)
|
||||
{
|
||||
int left_id = ReplayAvatarIDLeft;
|
||||
int right_id = ReplayAvatarIDRight;
|
||||
if (left_id != -1)
|
||||
VG_Controller.SetAvatarActive(left_id, true, isActive);
|
||||
if (right_id != left_id && right_id != -1)
|
||||
VG_Controller.SetAvatarActive(right_id, true, isActive);
|
||||
}
|
||||
|
||||
public void MakeGestureLeft(int gestureID)
|
||||
{
|
||||
int left_id = SensorAvatarIDLeft;
|
||||
if (left_id == -1)
|
||||
{
|
||||
Debug.LogError("No avatar with left hand is registered, can not MakeGestureLeft!");
|
||||
return;
|
||||
}
|
||||
VG_Controller.MakeGesture(left_id, VG_HandSide.LEFT, (VG_GestureType)gestureID);
|
||||
}
|
||||
|
||||
public void MakeGestureRight(int gestureID)
|
||||
{
|
||||
int right_id = SensorAvatarIDRight;
|
||||
if (right_id == -1)
|
||||
{
|
||||
Debug.LogError("No avatar with right hand is registered, can not MakeGestureRight!");
|
||||
return;
|
||||
}
|
||||
VG_Controller.MakeGesture(right_id, VG_HandSide.RIGHT, (VG_GestureType)gestureID);
|
||||
}
|
||||
|
||||
public void ReleaseGestureLeft()
|
||||
{
|
||||
int left_id = SensorAvatarIDLeft;
|
||||
if (left_id == -1)
|
||||
{
|
||||
Debug.LogError("No avatar with left hand is registered, can not ReleaseGestureLeft!");
|
||||
return;
|
||||
}
|
||||
VG_Controller.ReleaseGesture(left_id, VG_HandSide.LEFT);
|
||||
}
|
||||
|
||||
public void ReleaseGestureRight()
|
||||
{
|
||||
int right_id = SensorAvatarIDRight;
|
||||
if (right_id == -1)
|
||||
{
|
||||
Debug.LogError("No avatar with right hand is registered, can not ReleaseGestureRight!");
|
||||
return;
|
||||
}
|
||||
VG_Controller.ReleaseGesture(right_id, VG_HandSide.RIGHT);
|
||||
}
|
||||
|
||||
public void ReleaseGesture()
|
||||
{
|
||||
int left_id = SensorAvatarIDLeft;
|
||||
int right_id = SensorAvatarIDRight;
|
||||
if (left_id != -1)
|
||||
VG_Controller.ReleaseGesture(left_id, VG_HandSide.LEFT);
|
||||
if (right_id != -1)
|
||||
VG_Controller.ReleaseGesture(right_id, VG_HandSide.RIGHT);
|
||||
}
|
||||
|
||||
public void BlockReleaseObjectLeft()
|
||||
{
|
||||
int left_id = SensorAvatarIDLeft;
|
||||
if (left_id == -1)
|
||||
{
|
||||
Debug.LogError("No avatar with left hand is registered, can not BlockReleaseObjectLeft()!");
|
||||
return;
|
||||
}
|
||||
VG_Controller.SetBlockReleaseObject(left_id, VG_HandSide.LEFT, true);
|
||||
}
|
||||
|
||||
public void BlockReleaseObjectRight()
|
||||
{
|
||||
int right_id = SensorAvatarIDRight;
|
||||
if (right_id == -1)
|
||||
{
|
||||
Debug.LogError("No avatar with right hand is registered, can not BlockReleaseObjectRight!");
|
||||
return;
|
||||
}
|
||||
VG_Controller.SetBlockReleaseObject(right_id, VG_HandSide.RIGHT, true);
|
||||
}
|
||||
|
||||
public void BlockReleaseObject()
|
||||
{
|
||||
int left_id = SensorAvatarIDLeft;
|
||||
int right_id = SensorAvatarIDRight;
|
||||
if (left_id != -1)
|
||||
VG_Controller.SetBlockReleaseObject(left_id, VG_HandSide.LEFT, true);
|
||||
if (right_id != -1)
|
||||
VG_Controller.SetBlockReleaseObject(right_id, VG_HandSide.RIGHT, true);
|
||||
}
|
||||
|
||||
public void AllowReleaseObjectLeft()
|
||||
{
|
||||
int left_id = SensorAvatarIDLeft;
|
||||
if (left_id == -1)
|
||||
{
|
||||
Debug.LogError("No avatar with left hand is registered, can not AllowReleaseObjectLeft()!");
|
||||
return;
|
||||
}
|
||||
VG_Controller.SetBlockReleaseObject(left_id, VG_HandSide.LEFT, false);
|
||||
}
|
||||
|
||||
public void AllowReleaseObjectRight()
|
||||
{
|
||||
int right_id = SensorAvatarIDRight;
|
||||
if (right_id == -1)
|
||||
{
|
||||
Debug.LogError("No avatar with right hand is registered, can not AllowReleaseObjectRight!");
|
||||
return;
|
||||
}
|
||||
VG_Controller.SetBlockReleaseObject(right_id, VG_HandSide.RIGHT, false);
|
||||
}
|
||||
|
||||
public void AllowReleaseObject()
|
||||
{
|
||||
int left_id = SensorAvatarIDLeft;
|
||||
int right_id = SensorAvatarIDRight;
|
||||
if (left_id != -1)
|
||||
VG_Controller.SetBlockReleaseObject(left_id, VG_HandSide.LEFT, false);
|
||||
if (right_id != -1)
|
||||
VG_Controller.SetBlockReleaseObject(right_id, VG_HandSide.RIGHT, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e613f8a174259dd479c580bdd04e018f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user