mirror of
https://github.com/mitch030504/Wiicompiled_VR_Frame.git
synced 2026-10-06 03:00:14 +02:00
Curl the runtime hand mesh's fingers towards the palm
The Quest offers XR_FB_hand_tracking_mesh without the app declaring hand tracking, so a Quest 3 draws the runtime's own hand mesh rather than the procedural gloves, and its fingers bent backwards on grip: the mesh's joints point -Z towards the fingertip and +Y out of the back of the hand, so flexion is negative about the joint's own X, on both hands. Fix and test by the repository owner; OPENXR.md said the Quest always drew the gloves, which was wrong. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
90a09aec0b
commit
248bd6787d
3 files changed
+76
-3
No files matched your search
@@ -442,8 +442,11 @@ aside while it drives.
|
|||||||
|
|
||||||
**Hands and the separate wheel.** Hands are drawn while hand steering is on: the runtime's own hand
|
**Hands and the separate wheel.** Hands are drawn while hand steering is on: the runtime's own hand
|
||||||
mesh where it offers one (`XR_EXT_hand_tracking` and `XR_FB_hand_tracking_mesh`, requested only when
|
mesh where it offers one (`XR_EXT_hand_tracking` and `XR_FB_hand_tracking_mesh`, requested only when
|
||||||
hand steering is on at launch), otherwise procedural gloves that curl with the squeeze. The Quest
|
hand steering is on at launch), otherwise procedural gloves that curl with the squeeze. A Quest 3
|
||||||
build declares no hand-tracking permission, so it always draws the gloves. They and the
|
offers that mesh without the app declaring hand tracking, and the log says which is drawn
|
||||||
|
(`[mkw-vr] cockpit hands:`). Both close their fingers towards the palm: the mesh's joints point
|
||||||
|
-Z towards the fingertip and +Y out of the back of the hand, so flexion is negative about the
|
||||||
|
joint's own X, on both hands. They and the
|
||||||
separate VR wheel or handlebar travel with the stereo packet in metres in the seated frame, and each
|
separate VR wheel or handlebar travel with the stereo packet in metres in the seated frame, and each
|
||||||
eye draws them inside the scene's pass just before the first 2D-layer draw, depth-tested with the
|
eye draws them inside the scene's pass just before the first 2D-layer draw, depth-tested with the
|
||||||
world's own depth mapping, so the kart and the track hide them and the HUD cannot
|
world's own depth mapping, so the kart and the track hide them and the HUD cannot
|
||||||
|
|||||||
@@ -137,7 +137,10 @@ inline void runtime_hand(std::vector<Vertex>& out, const AuroraCockpitHand& hand
|
|||||||
M local=parent>=0&&parent<26?compose(mesh.inverseBind[parent],mesh.bind[j]):mesh.bind[j];
|
M local=parent>=0&&parent<26?compose(mesh.inverseBind[parent],mesh.bind[j]):mesh.bind[j];
|
||||||
const bool fingerJoint=j>=2 && j!=6 && j!=11 && j!=16 && j!=21;
|
const bool fingerJoint=j>=2 && j!=6 && j!=11 && j!=16 && j!=21;
|
||||||
if(fingerJoint) {
|
if(fingerJoint) {
|
||||||
const float a=curl*(j<6?0.3f:0.75f),c=std::cos(a),s=std::sin(a);
|
// OpenXR joints point -Z toward the fingertip and +Y out of the back
|
||||||
|
// of the hand. Flexion is therefore negative about local X, for both
|
||||||
|
// hands; positive angles bend the fingers backward on runtime meshes.
|
||||||
|
const float a=-curl*(j<6?0.3f:0.75f),c=std::cos(a),s=std::sin(a);
|
||||||
local=compose(local,M{1,0,0,0,0,c,-s,0,0,s,c,0});
|
local=compose(local,M{1,0,0,0,0,c,-s,0,0,s,c,0});
|
||||||
}
|
}
|
||||||
posed[j]=parent>=0&&parent<26?compose(posed[parent],local):local;
|
posed[j]=parent>=0&&parent<26?compose(posed[parent],local):local;
|
||||||
|
|||||||
@@ -153,6 +153,73 @@ TEST_F(CockpitGeometry, GloveFingersRunAlongTheHandAndCloseIntoThePalm) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(CockpitGeometry, RuntimeFingersCurlTowardPalmForSqueezeAndWheelGrab) {
|
||||||
|
using namespace aurora::gfx::cockpit;
|
||||||
|
// OpenXR joint space: -Z runs toward the fingertip, +Y out of the back
|
||||||
|
// of the hand, for BOTH hands. Mirror positions, not the curl direction.
|
||||||
|
for (int side = 0; side < 2; ++side) {
|
||||||
|
SCOPED_TRACE(side);
|
||||||
|
HandMesh mesh;
|
||||||
|
mesh.parents.fill(1);
|
||||||
|
mesh.parents[1] = -1;
|
||||||
|
const float rootPose[7]{0, 0, 0.70710678f, 0.70710678f, 0.12f, -0.08f, 0.03f};
|
||||||
|
const M root = from_pose(rootPose);
|
||||||
|
mesh.bind.fill(root);
|
||||||
|
const int bases[]{2, 6, 11, 16, 21};
|
||||||
|
for (int finger = 0; finger < 5; ++finger) {
|
||||||
|
const int base = bases[finger];
|
||||||
|
const int count = finger == 0 ? 4 : 5;
|
||||||
|
for (int bone = 0; bone < count; ++bone) {
|
||||||
|
M bind = identity();
|
||||||
|
bind[3] = (side == 0 ? -1.0f : 1.0f) * (finger - 2) * 0.018f;
|
||||||
|
bind[11] = -0.025f * (bone + 1);
|
||||||
|
mesh.bind[base + bone] = compose(root, bind);
|
||||||
|
mesh.parents[base + bone] = bone == 0 ? 1 : base + bone - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int j = 0; j < 26; ++j) mesh.inverseBind[j] = inverse(mesh.bind[j]);
|
||||||
|
// A tiny triangle rigidly weighted to each joint, including each fingertip.
|
||||||
|
for (int j = 0; j < 26; ++j) {
|
||||||
|
for (V offset : {V{0, 0, 0}, V{0.001f, 0, 0}, V{0, 0, 0.001f}}) {
|
||||||
|
AuroraVRHandVertex vertex{};
|
||||||
|
const V p = point(mesh.bind[j].data(), offset);
|
||||||
|
std::memcpy(vertex.position, p.data(), sizeof(vertex.position));
|
||||||
|
vertex.joints[0] = j;
|
||||||
|
vertex.weights[0] = 1;
|
||||||
|
mesh.indices.push_back(static_cast<uint16_t>(mesh.vertices.size()));
|
||||||
|
mesh.vertices.push_back(vertex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AuroraCockpitHand hand{};
|
||||||
|
set_identity(hand.seatFromGrip, {0, 0, 0});
|
||||||
|
const auto build = [&](float squeeze, bool held) {
|
||||||
|
hand.squeeze = squeeze;
|
||||||
|
hand.held = held;
|
||||||
|
std::vector<Vertex> vertices;
|
||||||
|
runtime_hand(vertices, hand, mesh);
|
||||||
|
return vertices;
|
||||||
|
};
|
||||||
|
const auto open = build(0, false);
|
||||||
|
for (int j = 0; j < 26; ++j) {
|
||||||
|
const V bind = point(mesh.inverseBind[1].data(), point(mesh.bind[j].data(), {0, 0, 0}));
|
||||||
|
for (int axis = 0; axis < 3; ++axis)
|
||||||
|
EXPECT_NEAR(open[j * 3].position[axis], bind[axis] + (axis == 2 ? 0.04f : 0), 1e-6f);
|
||||||
|
}
|
||||||
|
for (const auto& closed : {build(0.5f, false), build(1, false), build(0, true)}) {
|
||||||
|
ASSERT_TRUE(all_finite(closed));
|
||||||
|
for (int tip : {5, 10, 15, 20, 25}) {
|
||||||
|
EXPECT_LT(closed[tip * 3].position[1], open[tip * 3].position[1] - 0.005f)
|
||||||
|
<< "fingertip must move toward palm (-Y), joint " << tip;
|
||||||
|
EXPECT_GT(closed[tip * 3].position[2], open[tip * 3].position[2])
|
||||||
|
<< "curl must shorten finger reach, joint " << tip;
|
||||||
|
}
|
||||||
|
for (int rigid : {0, 1, 6, 11, 16, 21})
|
||||||
|
for (int axis = 0; axis < 3; ++axis)
|
||||||
|
EXPECT_NEAR(closed[rigid * 3].position[axis], open[rigid * 3].position[axis], 1e-6f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(CockpitGeometry, RuntimeHandMeshIsSkinnedWithoutNans) {
|
TEST_F(CockpitGeometry, RuntimeHandMeshIsSkinnedWithoutNans) {
|
||||||
using namespace aurora::gfx::cockpit;
|
using namespace aurora::gfx::cockpit;
|
||||||
auto mesh = std::make_shared<HandMesh>();
|
auto mesh = std::make_shared<HandMesh>();
|
||||||
|
|||||||
Reference in new issue
Block a user