mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-10-14 19:27:09 -04:00
VR pad hands: sticks move and aim the hands, left stick click switches the sticks to the game, d-pad pulls a held hand
This commit is contained in:
1 parent
780c42b46f
commit
b2beed6290
2 files changed
+56
-15
No files matched your search
@@ -144,11 +144,15 @@ a pose written to the shared file turns SteamVR's view. Started from the drawer
|
||||
|
||||
- **Controllers**: the driver adds two Vive-profile hands driven by the session's game pad. It reads
|
||||
the absolute-state snapshot the app already publishes in fake input ring 0
|
||||
(`FAKE_EVDEV_MEMFD_PATHS`), so the app needs no new plumbing. They are a gaze pointer: low in front
|
||||
of each hand, pointing where the head looks. Mapping, right hand: right stick = trackpad, A/R3 =
|
||||
trackpad click, RT = trigger, RB = grip, Start = menu, guide = system. Left hand: left stick, X/L3,
|
||||
LT, LB, Select. Both hands activate in SteamVR and their laser shows on the dashboard; guide, Start and
|
||||
A presses show up in the ring the driver reads.
|
||||
(`FAKE_EVDEV_MEMFD_PATHS`), so the app needs no new plumbing. They rest low in front of each side and
|
||||
point where the head looks. Each stick moves its hand sideways (40 cm), up (50 cm) and down (30 cm)
|
||||
and turns it as if from an elbow 45 cm behind, so pointers and aim follow it too; the hand eases
|
||||
back when the stick is let go. The d-pad's up and down move both hands in and out; while a hand's
|
||||
trigger or grip is held they move only that hand (a slingshot or bow pulled back), and it eases back
|
||||
on release. Clicking the left stick hands the sticks to the game as trackpads (walking, turning,
|
||||
teleport) and back. Mapping, right hand: A/R3 = trackpad click, RT = trigger, RB = grip, Start = menu, guide =
|
||||
system. Left hand: X, LT, LB, Select. Both hands activate in SteamVR and their laser shows on the
|
||||
dashboard; guide, Start and A presses show up in the ring the driver reads.
|
||||
- **Layers**: only the first layer of each frame is drawn. SteamVR's compositor composites the
|
||||
dashboard into it, so this has not mattered yet.
|
||||
- **Timing**: no reprojection. The presenter blits the frame submitted last once `vrcompositor`'s GPU
|
||||
|
||||
@@ -415,18 +415,43 @@ public:
|
||||
DriverPose_t GetPose() override { return pose_; }
|
||||
|
||||
// From the driver's pose thread: the head's orientation and the pad.
|
||||
void Update(const HmdQuaternion_t &head, const PadState *pad) {
|
||||
static constexpr float kReachSpeed = 0.6f, kReachMin = -0.2f, kReachMax = 1.0f;
|
||||
|
||||
static bool Grabbing(const PadState &pad, bool right) {
|
||||
return pad.axes[right ? 4 : 5] > kGrabTrigger || (pad.buttons & (right ? PAD_RB : PAD_LB));
|
||||
}
|
||||
|
||||
void Update(const HmdQuaternion_t &head, const PadState *pad, bool sticksMoveHands, float reach) {
|
||||
if (id_ == k_unTrackedDeviceIndexInvalid) return;
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const float dt = std::min(std::chrono::duration<float>(now - lastUpdate_).count(), 0.1f);
|
||||
lastUpdate_ = now;
|
||||
if (pad && pad->axes[7]) reach_ = std::clamp(reach_ - (pad->axes[7] > 0 ? 1.0f : -1.0f) * kReachSpeed * dt, kReachMin, kReachMax);
|
||||
const float follow = 1.0f - std::exp(-dt / kHandSmoothing);
|
||||
const bool grab = pad && Grabbing(*pad, right_);
|
||||
if (grab && pad->axes[7]) pull_ = std::clamp(pull_ - (pad->axes[7] > 0 ? 1.0f : -1.0f) * kReachSpeed * dt, kReachMin - reach, kReachMax - reach);
|
||||
else if (!grab) pull_ -= pull_ * follow;
|
||||
const float stickX = pad ? pad->axes[right_ ? 2 : 0] / 32767.0f : 0.0f, stickY = pad ? -pad->axes[right_ ? 3 : 1] / 32767.0f : 0.0f;
|
||||
float aimX = 0.0f, aimY = 0.0f;
|
||||
const float tilt = std::sqrt(stickX * stickX + stickY * stickY);
|
||||
if (sticksMoveHands && tilt > kStickDeadzone) {
|
||||
const float scale = std::min((tilt - kStickDeadzone) / (1.0f - kStickDeadzone), 1.0f) / tilt;
|
||||
aimX = stickX * scale;
|
||||
aimY = stickY * scale;
|
||||
}
|
||||
handX_ += (aimX * kHandRangeX - handX_) * follow;
|
||||
handY_ += (aimY * (aimY > 0.0f ? kHandRangeUp : kHandRangeDown) - handY_) * follow;
|
||||
DriverPose_t p = {};
|
||||
p.qWorldFromDriverRotation.w = 1;
|
||||
p.qDriverFromHeadRotation.w = 1;
|
||||
p.qRotation = head;
|
||||
const double yaw = std::atan2(-handX_, kHandPivot) / 2, pitch = std::atan2(handY_, std::hypot(kHandPivot, handX_)) / 2;
|
||||
const double cy = std::cos(yaw), sy = std::sin(yaw), cp = std::cos(pitch), sp = std::sin(pitch);
|
||||
const double lw = cy * cp, lx = cy * sp, ly = sy * cp, lz = -sy * sp;
|
||||
p.qRotation.w = head.w * lw - head.x * lx - head.y * ly - head.z * lz;
|
||||
p.qRotation.x = head.w * lx + head.x * lw + head.y * lz - head.z * ly;
|
||||
p.qRotation.y = head.w * ly - head.x * lz + head.y * lw + head.z * lx;
|
||||
p.qRotation.z = head.w * lz + head.x * ly - head.y * lx + head.z * lw;
|
||||
// Low and in front of its side, in head space, then into the room with the head's rotation.
|
||||
const double off[3] = {right_ ? 0.18 : -0.18, -0.35, -0.30 - reach_};
|
||||
const double off[3] = {(right_ ? 0.18 : -0.18) + handX_, -0.25 + handY_, -0.35 - reach - pull_};
|
||||
double m[3][3];
|
||||
const double w = head.w, x = head.x, y = head.y, z = head.z;
|
||||
m[0][0] = 1 - 2 * (y * y + z * z); m[0][1] = 2 * (x * y - z * w); m[0][2] = 2 * (x * z + y * w);
|
||||
@@ -441,9 +466,8 @@ public:
|
||||
if (!pad) return;
|
||||
auto in = VRDriverInput();
|
||||
const uint32_t b = pad->buttons;
|
||||
const float stickX = pad->axes[right_ ? 2 : 0] / 32767.0f, stickY = -pad->axes[right_ ? 3 : 1] / 32767.0f;
|
||||
const float trig = pad->axes[right_ ? 4 : 5] / 255.0f;
|
||||
const bool touch = stickX * stickX + stickY * stickY > 0.01f;
|
||||
const bool touch = !sticksMoveHands && stickX * stickX + stickY * stickY > 0.01f;
|
||||
in->UpdateBooleanComponent(system_, right_ && (b & PAD_GUIDE), 0);
|
||||
in->UpdateBooleanComponent(menu_, (b & (right_ ? PAD_START : PAD_SELECT)) != 0, 0);
|
||||
in->UpdateBooleanComponent(grip_, (b & (right_ ? PAD_RB : PAD_LB)) != 0, 0);
|
||||
@@ -452,15 +476,16 @@ public:
|
||||
in->UpdateScalarComponent(padX_, touch ? stickX : 0.0f, 0);
|
||||
in->UpdateScalarComponent(padY_, touch ? stickY : 0.0f, 0);
|
||||
in->UpdateBooleanComponent(padTouch_, touch || (b & (right_ ? PAD_A : PAD_X)), 0);
|
||||
in->UpdateBooleanComponent(padClick_, (b & (right_ ? (PAD_A | PAD_R3) : (PAD_X | PAD_L3))) != 0, 0);
|
||||
in->UpdateBooleanComponent(padClick_, (b & (right_ ? (PAD_A | PAD_R3) : PAD_X)) != 0, 0);
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr float kReachSpeed = 0.6f, kReachMin = -0.2f, kReachMax = 1.0f;
|
||||
static constexpr int16_t kGrabTrigger = 153;
|
||||
static constexpr float kStickDeadzone = 0.12f, kHandRangeX = 0.40f, kHandRangeUp = 0.50f, kHandRangeDown = 0.30f, kHandSmoothing = 0.05f, kHandPivot = 0.45f;
|
||||
bool right_;
|
||||
uint32_t id_ = k_unTrackedDeviceIndexInvalid;
|
||||
DriverPose_t pose_ = {};
|
||||
float reach_ = 0.0f;
|
||||
float pull_ = 0.0f, handX_ = 0.0f, handY_ = 0.0f;
|
||||
std::chrono::steady_clock::time_point lastUpdate_ = std::chrono::steady_clock::now();
|
||||
VRInputComponentHandle_t system_ = 0, menu_ = 0, grip_ = 0, triggerClick_ = 0, trigger_ = 0, padX_ = 0, padY_ = 0,
|
||||
padClick_ = 0, padTouch_ = 0, haptic_ = 0;
|
||||
@@ -651,7 +676,16 @@ private:
|
||||
}
|
||||
DriverPose_t head = GetPose();
|
||||
VRServerDriverHost()->TrackedDevicePoseUpdated(id_, head, sizeof(DriverPose_t));
|
||||
for (CardboardController *hand : hands_) if (hand) hand->Update(head.qRotation, havePad ? &pad : nullptr);
|
||||
if (havePad && (pad.buttons & PAD_L3) && !(lastPad_.buttons & PAD_L3)) {
|
||||
sticksMoveHands_ = !sticksMoveHands_;
|
||||
Log("sticks now move %s", sticksMoveHands_ ? "the hands" : "in the game");
|
||||
}
|
||||
const float dt = std::min(std::chrono::duration<float>(now - lastReach_).count(), 0.1f);
|
||||
lastReach_ = now;
|
||||
if (havePad && pad.axes[7] && !CardboardController::Grabbing(pad, false) && !CardboardController::Grabbing(pad, true))
|
||||
reach_ = std::clamp(reach_ - (pad.axes[7] > 0 ? 1.0f : -1.0f) * CardboardController::kReachSpeed * dt,
|
||||
CardboardController::kReachMin, CardboardController::kReachMax);
|
||||
for (CardboardController *hand : hands_) if (hand) hand->Update(head.qRotation, havePad ? &pad : nullptr, sticksMoveHands_, reach_);
|
||||
if (havePad) lastPad_ = pad;
|
||||
lastSent = now;
|
||||
}
|
||||
@@ -673,6 +707,9 @@ private:
|
||||
PadState lastPad_;
|
||||
VRInputComponentHandle_t proximity_ = 0;
|
||||
bool worn_ = true;
|
||||
bool sticksMoveHands_ = true;
|
||||
float reach_ = 0.0f;
|
||||
std::chrono::steady_clock::time_point lastReach_ = std::chrono::steady_clock::now();
|
||||
CardboardController *hands_[2] = {nullptr, nullptr};
|
||||
bool virtualDisplay_ = false, directMode_ = false;
|
||||
VirtualDisplay vd_;
|
||||
|
||||
Reference in new issue
Block a user