|
1 | 1 | #include "HandPose.h" |
2 | 2 |
|
3 | 3 | #include <algorithm> |
| 4 | +#include <optional> |
4 | 5 | #include <string> |
5 | 6 | #include <string_view> |
6 | 7 |
|
@@ -51,18 +52,57 @@ namespace |
51 | 52 | } |
52 | 53 |
|
53 | 54 | /** |
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. |
55 | 57 | */ |
56 | 58 | VRButtonId getTrackedButton(const std::string& bone) |
57 | 59 | { |
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; |
63 | 74 | 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 }; |
65 | 101 | } |
| 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 }; |
66 | 106 | } |
67 | 107 |
|
68 | 108 | /** |
@@ -374,6 +414,11 @@ namespace frik |
374 | 414 | return HandPoseSource{ .kind = HandPoseSourceKind::OverridePose, .pose = &activeOverride->pose }; |
375 | 415 | } |
376 | 416 |
|
| 417 | + if (shouldUsePointingPose(isLeft)) { |
| 418 | + // Pointing is treated as an implicit authored override. |
| 419 | + return HandPoseSource{ .kind = HandPoseSourceKind::OverridePose, .pose = &getPointingPose() }; |
| 420 | + } |
| 421 | + |
377 | 422 | if (shouldUseThumbsUpPose(isLeft)) { |
378 | 423 | // Thumbs-up is treated as an implicit authored override. |
379 | 424 | return HandPoseSource{ .kind = HandPoseSourceKind::OverridePose, .pose = &getThumbsUpPose() }; |
@@ -452,21 +497,37 @@ namespace frik |
452 | 497 |
|
453 | 498 | /** |
454 | 499 | * 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. |
455 | 505 | */ |
456 | 506 | void HandPose::applyDynamicHandPose(const std::string& boneName, const float frameTime) |
457 | 507 | { |
458 | | - float flex = 1.0F; // open |
459 | 508 | 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 | + } |
467 | 528 | } |
468 | 529 |
|
469 | | - blendBoneTowardRotation(boneName, blendBoneRotation(boneName, fmax(0.0f, fmin(1.0f, flex)), 0), frameTime); |
| 530 | + blendBoneTowardRotation(boneName, blendBoneRotation(boneName, 1.0f - curl, splay), frameTime); |
470 | 531 | } |
471 | 532 |
|
472 | 533 | /** |
@@ -528,14 +589,27 @@ namespace frik |
528 | 589 | return overrides.empty() ? nullptr : &overrides.back(); |
529 | 590 | } |
530 | 591 |
|
| 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 | + |
531 | 604 | /** |
532 | 605 | * Detect the controller gesture that should temporarily map to thumbs-up. |
533 | 606 | */ |
534 | 607 | bool HandPose::shouldUseThumbsUpPose(const bool isLeft) |
535 | 608 | { |
536 | 609 | const auto hand = isLeft ? Hand::Left : Hand::Right; |
537 | 610 | 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); |
539 | 613 | } |
540 | 614 |
|
541 | 615 | /** |
|
0 commit comments