Skip to content

Commit 3932b76

Browse files
committed
feat(hand-pose): drive dynamic finger curl from touch and analog axis
Touching a finger's tracked control curls it to a baseline, then the analog axis (trigger for the index, grip for the bottom three fingers) drives the remaining curl up to a full fist. The thumb has no curl-depth axis, so it curls to a fixed pose and splays its proximal joint toward whichever face control it rests on (thumbstick, A, or B). Add a pointing gesture (index off the trigger, grip held, thumb on A/B) as an implicit authored pose, and exclude A/B from the thumbs-up gesture so the two no longer overlap.
1 parent e4bf99d commit 3932b76

2 files changed

Lines changed: 92 additions & 17 deletions

File tree

src/skeleton/HandPose.cpp

Lines changed: 91 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "HandPose.h"
22

33
#include <algorithm>
4+
#include <optional>
45
#include <string>
56
#include <string_view>
67

@@ -51,18 +52,57 @@ namespace
5152
}
5253

5354
/**
54-
* Map a finger to the controller input that should drive its dynamic curl.
55+
* Map a (non-thumb) finger to the controller button that should drive its dynamic curl.
56+
* The index finger follows the trigger; the bottom three fingers (middle, ring, pinky) follow the grip.
5557
*/
5658
VRButtonId getTrackedButton(const std::string& bone)
5759
{
58-
switch (boneToFingerIndex(bone)) {
59-
case 0:
60-
return k_EButton_SteamVR_Touchpad;
61-
case 1:
62-
return k_EButton_SteamVR_Trigger;
60+
return boneToFingerIndex(bone) == 1 ? k_EButton_SteamVR_Trigger : k_EButton_Grip;
61+
}
62+
63+
/**
64+
* Map a tracked finger button to its analog curl-depth axis, when the controller exposes one.
65+
* Touch-only inputs (face buttons, thumbstick) report no axis.
66+
*/
67+
std::optional<Axis> getTrackedButtonAxis(const VRButtonId button)
68+
{
69+
switch (button) { // NOLINT(clang-diagnostic-switch-enum)
70+
case k_EButton_SteamVR_Trigger:
71+
return Axis::Trigger;
72+
case k_EButton_Grip:
73+
return Axis::Grip;
6374
default:
64-
return k_EButton_Grip;
75+
return std::nullopt;
76+
}
77+
}
78+
79+
struct DynamicThumbCurl
80+
{
81+
float curl; // 0 = open, 1 = fully closed
82+
float splay; // proximal lateral splay, right-hand convention
83+
};
84+
85+
/**
86+
* Resolve the thumb's dynamic curl and proximal splay from the face control it rests on.
87+
*
88+
* The thumb has no usable curl-depth axis, so touching any of its controls curls it to a fixed
89+
* mid pose while the touched control selects a distinct splay, shifting the thumb tip toward the
90+
* thumbstick, A, or B button. Returns a neutral open thumb when nothing is touched. The A and B
91+
* buttons are checked before the thumbstick so a deliberate button reach wins over the resting
92+
* thumbstick contact.
93+
*/
94+
DynamicThumbCurl resolveDynamicThumbCurl(const Hand hand)
95+
{
96+
if (VRControllers.isTouching(hand, k_EButton_A)) {
97+
return { .curl = 0.7f, .splay = 0.2f };
98+
}
99+
if (VRControllers.isTouching(hand, k_EButton_ApplicationMenu)) {
100+
return { .curl = 0.2f, .splay = 0.1f };
65101
}
102+
if (VRControllers.isTouching(hand, k_EButton_SteamVR_Touchpad)) {
103+
return { .curl = 0.4f, .splay = 0.3f };
104+
}
105+
return { .curl = 0.0f, .splay = 0.0f };
66106
}
67107

68108
/**
@@ -374,6 +414,11 @@ namespace frik
374414
return HandPoseSource{ .kind = HandPoseSourceKind::OverridePose, .pose = &activeOverride->pose };
375415
}
376416

417+
if (shouldUsePointingPose(isLeft)) {
418+
// Pointing is treated as an implicit authored override.
419+
return HandPoseSource{ .kind = HandPoseSourceKind::OverridePose, .pose = &getPointingPose() };
420+
}
421+
377422
if (shouldUseThumbsUpPose(isLeft)) {
378423
// Thumbs-up is treated as an implicit authored override.
379424
return HandPoseSource{ .kind = HandPoseSourceKind::OverridePose, .pose = &getThumbsUpPose() };
@@ -452,21 +497,37 @@ namespace frik
452497

453498
/**
454499
* Apply hand pose by what controller buttons are touched/pressed.
500+
*
501+
* Touching a finger's tracked control curls it to a baseline; if that control exposes an analog
502+
* axis (trigger, grip) the axis drives the remaining curl up to a full fist, otherwise the curl
503+
* rests at that fixed touch baseline. The thumb has no curl-depth axis, so it instead reads which face
504+
* control it rests on (thumbstick, A, or B) and splays its proximal joint toward that control.
455505
*/
456506
void HandPose::applyDynamicHandPose(const std::string& boneName, const float frameTime)
457507
{
458-
float flex = 1.0F; // open
459508
const auto boneHand = isLeftHandBone(boneName) ? Hand::Left : Hand::Right;
460-
const auto controllerButtonForBone = getTrackedButton(boneName);
461-
if (controllerButtonForBone == k_EButton_Grip) {
462-
flex = 1.0f - VRControllers.getAxisValue(boneHand, Axis::Grip).x;
463-
} else if (controllerButtonForBone == k_EButton_SteamVR_Trigger) {
464-
flex = 1.0f - 2 * VRControllers.getAxisValue(boneHand, Axis::Trigger).x;
465-
} else if (VRControllers.isTouching(boneHand, controllerButtonForBone)) {
466-
flex = 0.0F;
509+
510+
float curl = 0.0f; // 0 = open, 1 = fully closed
511+
float splay = 0.0f;
512+
if (boneToFingerIndex(boneName) == 0) {
513+
const auto thumb = resolveDynamicThumbCurl(boneHand);
514+
curl = thumb.curl;
515+
splay = boneName.back() == '1' ? thumb.splay : 0.0f;
516+
} else {
517+
constexpr float DYNAMIC_CURL_ON_TOUCH = 0.35f;
518+
const auto button = getTrackedButton(boneName);
519+
const auto axis = getTrackedButtonAxis(button);
520+
const float axisVal = axis ? VRControllers.getAxisValue(boneHand, *axis).x : 0.0f;
521+
if (axisVal > 0.1f) {
522+
// Past the deadzone the analog axis drives curl from the touch baseline up to a full fist.
523+
curl = DYNAMIC_CURL_ON_TOUCH + (1.0f - DYNAMIC_CURL_ON_TOUCH) * std::clamp(axisVal, 0.0f, 1.0f);
524+
} else if (axisVal > 0.001f || VRControllers.isTouching(boneHand, button)) {
525+
// Resting on or lightly holding the control curls to the baseline only.
526+
curl = DYNAMIC_CURL_ON_TOUCH;
527+
}
467528
}
468529

469-
blendBoneTowardRotation(boneName, blendBoneRotation(boneName, fmax(0.0f, fmin(1.0f, flex)), 0), frameTime);
530+
blendBoneTowardRotation(boneName, blendBoneRotation(boneName, 1.0f - curl, splay), frameTime);
470531
}
471532

472533
/**
@@ -528,14 +589,27 @@ namespace frik
528589
return overrides.empty() ? nullptr : &overrides.back();
529590
}
530591

592+
/**
593+
* Detect the controller gesture that should temporarily map to pointing:
594+
* the index lifted off the trigger, the grip held, and the thumb resting on a face button (A or B).
595+
*/
596+
bool HandPose::shouldUsePointingPose(const bool isLeft)
597+
{
598+
const auto hand = isLeft ? Hand::Left : Hand::Right;
599+
return !VRControllers.isTouching(hand, vr::k_EButton_SteamVR_Trigger) &&
600+
(VRControllers.getAxisValue(hand, Axis::Grip).x > 0.01f || VRControllers.isTouching(hand, k_EButton_Grip)) &&
601+
(VRControllers.isTouching(hand, vr::k_EButton_A) || VRControllers.isTouching(hand, vr::k_EButton_ApplicationMenu));
602+
}
603+
531604
/**
532605
* Detect the controller gesture that should temporarily map to thumbs-up.
533606
*/
534607
bool HandPose::shouldUseThumbsUpPose(const bool isLeft)
535608
{
536609
const auto hand = isLeft ? Hand::Left : Hand::Right;
537610
return VRControllers.isTouching(hand, k_EButton_Grip) && VRControllers.isTouching(hand, vr::k_EButton_SteamVR_Trigger) &&
538-
!VRControllers.isTouching(hand, vr::k_EButton_SteamVR_Touchpad);
611+
!VRControllers.isTouching(hand, vr::k_EButton_SteamVR_Touchpad) && !VRControllers.isTouching(hand, vr::k_EButton_A) &&
612+
!VRControllers.isTouching(hand, vr::k_EButton_ApplicationMenu);
539613
}
540614

541615
/**

src/skeleton/HandPose.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ namespace frik
6969
void blendBoneTowardRotation(const std::string& boneName, const RE::NiMatrix3& targetRotation, float frameTime);
7070
RE::NiMatrix3 getPoseBoneRotation(const std::string& boneName, const HandFingersPose& pose) const;
7171
RE::NiMatrix3 blendBoneRotation(const std::string& boneName, float flex, float splay) const;
72+
static bool shouldUsePointingPose(bool isLeft);
7273
static bool shouldUseThumbsUpPose(bool isLeft);
7374
static void setHandPoseOverrideIntr(bool isLeft, std::string_view tag, const HandFingersPose& pose, bool forceTop);
7475
static void clearHandPoseOverrideIntr(bool isLeft, std::string_view tag);

0 commit comments

Comments
 (0)