SGCore Nova 2.0 Functions
There are two ways to interface with a Nova 2.0 Glove: Via it’s specific Nova2Glove Device Class, or via a more Generic HandLayer class. We recommend interfacing through the HandLayer class, at least for the finger tracking, so as to be compatible with other SenseGlove devices. It also does not require you to cache any object(s).
Finger Tracking Data
The Nova Glove has on-board sensors to determine the finger tracking. You can work with the raw values of these sensors, or with post-processed data, whichever you prefer:
SGCore.HandPose
Contains any data you might need to render a hand in 3D space. Includes the positions and rotations of each joint (relative to the wrist) and also the hand angles used to calculate them. Full reference can be found in our C++ HandPose Page.
bool rightHand = true;
HandPose handPose;
if (HandLayer::GetHandPose(rightHand, handPose)) {
std::cout << (handPose.ToString()) << std::endl;
} else {
std::cout << ("We couldn't grab a hand pose. That can happen because sensor data was corrupted, or because the glove is (no longer) connected. Try again later..") << std::endl;
}
bool rightHand = true;
Nova2Glove myGlove;
if (Nova2Glove::GetNova2Glove(rightHand, myGlove)) {
HandPose handPose;
if (myGlove.GetHandPose(handPose)) {
std::cout << (handPose.ToString()) << std::endl;
} else {
std::cout << ("We couldn't grab a hand pose. That can happen because sensor data was corrupted, or because the glove is (no longer) connected. Try again later..") << std::endl;
}
}
Sensor Data
bool rightHand = true;
Nova2Glove myGlove;
if (Nova2Glove::GetNova2Glove(rightHand, myGlove))
{
Nova2GloveSensorData sData;
if (myGlove.GetSensorData(sData)) {
std::cout << (sData.ToString()) << std::endl;
} else {
std::cout << ("We couldn't grab a hand pose. That can happen because sensor data was corrupted, or because the glove is (no longer) connected. Try again later..") << std::endl;
}
}
Hand / Wrist Tracking Data
The Nova 2.0 Glove has no method to sense its position in 3D space. Instead, it relies on 3rd party tracking devices, such as tracker and controllers, to determine position in 3D space.
If you’re using one such tracker and a mounting bracket that came with your glove, it is possible to grab the wrist location of the hand through the C++ API.
The API assumes the input and output are given in world space. The position will be given as values in millimeters, usign the SenseGlove coordinate system.
bool rightHand = true;
// Since our Gloves do not have their own on-board tracking, we rely on another Tracking Source, like a Quest 2 controller:
EPositionalTrackingHardware trackingHardware = EPositionalTrackingHardware::Quest2Controller;
Vect3D trackerPosition = Vect3D(0.0f, 0.0f, 0.0f);
Quat trackerRotation = Quat::FromEuler(0.0f, 0.0f, 0.0f);
// We then calculate the wrist location as follows:
Vect3D wristPosition;
Quat wristRotation;
HandLayer::GetWristLocation(rightHand, trackerPosition, trackerRotation, trackingHardware, wristPosition, wristRotation);
std::cout << ("Wrist position (" + trackerPosition.ToString() + " mm, " + trackerRotation.ToEuler().ToString() + " rad) for "
+ Tracking::ToString( trackingHardware ) + ": => " + wristPosition.ToString() + " mm, " + wristRotation.ToEuler().ToString() + " rad.") << std::endl;
bool rightHand = true;
Nova2Glove myGlove;
if (Nova2Glove::GetNova2Glove(rightHand, myGlove))
{
// Since our Gloves do not have their own on-board tracking, we rely on another Tracking Source, like a Quest 2 controller:
EPositionalTrackingHardware trackingHardware = EPositionalTrackingHardware::Quest2Controller;
Vect3D trackerPosition = Vect3D(0.0f, 0.0f, 0.0f);
Quat trackerRotation = Quat::FromEuler(0.0f, 0.0f, 0.0f);
// We then calculate the wrist location as follows:
Vect3D wristPosition;
Quat wristRotation;
myGlove.GetWristLocation(trackerPosition, trackerRotation, trackingHardware, wristPosition, wristRotation);
std::cout << ("Wrist position (" + trackerPosition.ToString() + " mm, " + trackerRotation.ToEuler().ToString() + " rad) for "
+ Tracking::ToString( trackingHardware ) + ": => " + wristPosition.ToString() + " mm, " + wristRotation.ToEuler().ToString() + " rad.") << std::endl;
}
Sending Haptics
At any point during a simulation, you might have multiple commands setting Force-Feedback or Wrist Squeeze levels.
To help manage this, the Force-Feedback and Squeeze commands aren’t sent directly to the glove, but end up in a queue. For instance, one effect wants so squeeze the wirst at 100% force, and another (send later in the code) wants to squeeze it at 50%. In that case, the target should be set to 100%.
Once all commands have been received, you can call the SendHaptics() function, which flushes all of these in a single command
Through the HandLayer, you can also immedeately send the haptics to the glove with the ‘sendImmediate’ parameter.
Force-Feedback
Force feedback of the Nova 2.0 can be set to a level between 0 … 1, where 0 is “no force-feedback” and 1.0 is “100% of the Force”. The range is divided into 100 steps, resulting in a resolution of 0.01.
You can choose to set the force-feedback of specific fingers, or to set it directly for all fingers. When setting the force-level for an individual finger, the other finger forces will remain at their last value. In that case, it may be easier to stick to this method, as opposed to setting them as a floating point array.
bool rightHand = true;
// Set index finger FFB to 100%, and immedeately send the command!
float ffbLevel = 1.0f;
int32_t finger = 1; //0 = thumb, 1= index finger, 2 = middle, 3 = ring, 4 = pinky.
HandLayer::QueueCmd_FFBLevel(rightHand, ffbLevel, finger, true); //sent immedeately
// Set the force feedback of the thumb and middle finger to 100%, every thing else to 'off'
std::vector<float> ffbLevels = { 1.0f, 0.0f, 1.0f, 0.0f, 0.0f }; //index in array represents the finger.
HandLayer::QueueCmd_FFBLevel(rightHand, ffbLevels, false); //false to not send it _yet_
//We can also set the finger and anything else we want before sending
HandLayer::QueueCmd_FFBLevel(rightHand, ffbLevel, finger, false);
//finally, send all effects as a single command to the Nova Glove:
HandLayer::SendHaptics(rightHand);
//End haprics at the end of this test, or they'll stay that way
HandLayer::StopAllHaptics(rightHand);
bool rightHand = true;
Nova2Glove myGlove;
if (Nova2Glove::GetNova2Glove(rightHand, myGlove))
{
// Set index finger FFB to 100%, and immedeately send the command!
float ffbLevel = 1.0f;
int32_t finger = 1; //0 = thumb, 1= index finger, 2 = middle, 3 = ring, 4 = pinky.
myGlove.QueueCmd_FFBLevel(rightHand, ffbLevel, finger);
myGlove.SendHaptics(); //for glove instances you have to always send the Haptics, too
// Set the force feedback of the thumb and middle finger to 100%, every thing else to 'off'
std::vector<float> ffbLevels = { 1.0f, 0.0f, 1.0f, 0.0f, 0.0f }; //index in array represents the finger.
myGlove.QueueCmd_FFBLevel(ffbLevels, false); //false to not send it _yet_
//We can also set the finger and anything else we want before sending
myGlove.QueueCmd_FFBLevel(ffbLevel, finger, false);
//finally, send all effects as a single command to the Nova Glove:
myGlove.SendHaptics();
//End haprics at the end of this test, or they'll stay that way.
myGlove.StopAllHaptics();
}
Vibration Commands
The Nova 2.0 supports sending vibrations via a CustomWaveform command. This is a data class containing all sorts of parameters that are used to generate a waveform on the glove to play. These effects are considered “fire and forget”: After sending them to the device, they will play until the effect ends, after which the motor stops vibrating. If a new effect is sent, any currently platying effect is discarded. The location of the vibratio is determined by an EHapticLocation enumerator
/// <summary> All of the locations supported by the SenseGlove API </summary>
enum class SGCORE_API EHapticLocation : int8_t
{
/// <summary> Unknown location. Calling haptic function(s) with this parameter will not play anything </summary>
Unknown = 0,
/// <summary> Fingertip of the Thumb </summary>
ThumbTip = 1,
/// <summary> Fingertip of the Index Finger </summary>
IndexTip = 2,
/// <summary> Vibration on the hand palm, close to the index finger. </summary>
PalmIndexSide = 6,
/// <summary> Vibration on the hand palm close to the pinky finger. </summary>
PalmPinkySide = 7,
/// <summary> Play this Haptic Effect on the whole hand. For General Hand Feedback. </summary>
WholeHand = 8
};
Creating a Custom Waveform
float amplitude = 0.9f; //90% amplitude
float duration= 0.2f; //200ms
float frequency= 170.0f; //170Hz is the resonance frequency of the Nova 2.0 actuators.
CustomWaveform myWaveform = CustomWaveform(amplitude, duration, frequency);
Sending a Custom Waveform
bool rightHand = true;
HandLayer::SendCustomWaveform(rightHand, myWaveform, EHapticLocation::WholeHand);
bool rightHand = true;
Nova2Glove myGlove;
if (Nova2Glove::GetNova2Glove(rightHand, myGlove))
{
myGlove.SendCustomWaveform(rightHand, myWaveform, EHapticLocation::WholeHand);
}
Wrist Squeeze / Active Contact Feedback
The Nova 2.0 has an actuator connected to its front strap, that you can tighten to simulate a contact, handshake, or force on the hand palm. Due to the mechanical implementation, this strap is not controlled by force, but rather by position.
Activating the strap is done by setting a level between 0 … 1, there 0 represents the strap at its ‘base location’, and a value of 1.0 represents the strap at its maximum lightened position.
Like with the Force-Feedback commands, you have the option to send the command immediately or to wait for other commands to come in.
bool rightHand = true;
HandLayer::QueueCmd_WristSqueeze(rightHand, 1.0f, true); //send it immedeately to be 100%
HandLayer::QueueCmd_WristSqueeze(rightHand, 0.8f, true); //send it immedeately to be 80%
//combining effects
HandLayer::QueueCmd_WristSqueeze(rightHand, 0.7f, false); //would be 70% when you send it now
HandLayer::QueueCmd_WristSqueeze(rightHand, 0.5f, false); //this 50% is ignored becasue another effects wants it to be 70%
HandLayer::SendHaptics(rightHand); //this will send a command to set strap tightness to 70%.
bool rightHand = true;
Nova2Glove myGlove;
if (Nova2Glove::GetNova2Glove(rightHand, myGlove))
{
myGlove.QueueCmd_WristSqueeze(1.0f); //send it immedeately to be 100%
myGlove.SendHaptics(); //this will send a command to set strap tightness to 100%.
myGlove.QueueCmd_WristSqueeze(0.8f); //send it immedeately to be 80%
myGlove.SendHaptics(); //this will send a command to set strap tightness to 80%.
//combining effects
myGlove.QueueCmd_WristSqueeze(0.7f); //would be 70% when you send it now
myGlove.QueueCmd_WristSqueeze(0.5f); //this 50% is ignored becasue another effects wants it to be 70%
myGlove.SendHaptics(); //this will send a command to set strap tightness to 70%.
}