Mirror the right glove across the palm, not along the fingers

OpenXR's grip +X is normal to the palm but points away from it on the left hand
and into it on the right, which is exactly what makes both grips carry the same
orientation when the hands hold a wheel symmetrically. The fingers therefore run
along -Y on both hands, and it is the geometry across the palm that mirrors.
Building the right hand's fingers on +Y instead left them pointing at the player
while the real hand faced forward.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
iChris4andClaude Opus 5 committed 2026-09-23 01:37:41 +02:00
1 parent fc0e927f9e
commit 4438a300b8
2 files changed
+22 -15

No files matched your search

+15 -11
View File
@@ -94,34 +94,38 @@ inline void ellipsoid(std::vector<Vertex>& vertices,V center,V radii,V color) {
}
// Rounded palm and individually articulated fingers, in the controller's grip
// space as OpenXR defines it: the origin is the palm centroid, -Z runs up the
// tube the curled fingers form (little finger towards thumb), and +X is the
// palm's outward normal, so the fingers close towards +X. +Y completes the
// right-handed frame, which leaves it along the fingers on the right hand and
// against them on the left. Building the fingers on any other axis bends them
// out of the back of the hand (seen on a Quest 3 on 2026-09-22).
// tube the curled fingers form (little finger towards thumb), and +X is normal
// to the palm - *away* from it on the left hand, *into* it on the right. That
// asymmetry is what makes both grips carry the same orientation when the hands
// hold a wheel symmetrically, so the fingers run along -Y on both, and it is
// the geometry across the palm that mirrors: fingers close towards +X on the
// left hand and -X on the right, with the thumb on the same side. Building the
// fingers on any other axis bends them out of the back of the hand (seen on a
// Quest 3 on 2026-09-22) or, for the right hand alone, points them at the
// player (seen on the PC on 2026-09-23).
inline void glove(std::vector<Vertex>& v, const AuroraCockpitHand& hand, int side) {
const size_t start=v.size();
const V white{0.91f,0.95f,1.0f};
const float forward=side==0?-1.0f:1.0f; // hand 0 is the left one
const float palm=side==0?1.0f:-1.0f; // hand 0 is the left one
const float curl=std::clamp(hand.held?0.85f:hand.squeeze,0.0f,1.0f);
// Thin through the palm's normal, a little wider across the knuckles than
// the palm is long.
ellipsoid(v,{0,0,0},{0.018f,0.043f,0.041f},white);
for(int finger=0;finger<4;++finger) {
// Index finger nearest the thumb (-Z), little finger last.
V a{0.0f,forward*0.030f,-0.025f+finger*0.017f};
V a{0.0f,-0.030f,-0.025f+finger*0.017f};
const float length=finger==0||finger==3?0.021f:0.026f;
for(int joint=0;joint<3;++joint) {
const float angle=curl*(0.55f+joint*0.8f);
V b=add(a,{std::sin(angle)*length,forward*std::cos(angle)*length,0.0f});
V b=add(a,{palm*std::sin(angle)*length,-std::cos(angle)*length,0.0f});
tube(v,a,b,0.008f,white);
ellipsoid(v,b,{0.008f,0.008f,0.008f},white);a=b;
}
}
// Thumb: out of the palm's thumb side, closing across the fingers.
const V thumbKnuckle{0.026f,forward*0.034f,-0.030f};
tube(v,{0.010f,forward*0.012f,-0.034f},thumbKnuckle,0.010f,white);
tube(v,thumbKnuckle,{0.030f+0.014f*curl,forward*(0.052f-0.016f*curl),-0.020f},0.009f,white);
const V thumbKnuckle{palm*0.026f,-0.034f,-0.030f};
tube(v,{palm*0.010f,-0.012f,-0.034f},thumbKnuckle,0.010f,white);
tube(v,thumbKnuckle,{palm*(0.030f+0.014f*curl),-(0.052f-0.016f*curl),-0.020f},0.009f,white);
for(size_t i=start;i<v.size();++i) v[i].position=point(hand.seatFromGrip,v[i].position);
}
inline void runtime_hand(std::vector<Vertex>& out, const AuroraCockpitHand& hand, const HandMesh& mesh) {
+7 -4
View File
@@ -116,8 +116,11 @@ TEST_F(CockpitGeometry, TrackedHandDrawsAGloveAtItsGrip) {
// thumb, +X out of the palm. So the fingers run along Y (+Y on the right hand,
// -Y on the left) and close towards +X, never out of the back of the hand.
TEST_F(CockpitGeometry, GloveFingersRunAlongTheHandAndCloseIntoThePalm) {
// Both grips carry the same orientation when the hands hold a wheel symmetrically, so the fingers
// run along -Y on both and it is the palm side that mirrors: +X on the left hand, -X on the right.
// Building the right hand's fingers on +Y instead pointed them at the player (PC, 2026-09-23).
for (int side = 0; side < 2; ++side) {
const float forward = side == 0 ? -1.0f : 1.0f;
const float palmSide = side == 0 ? 1.0f : -1.0f;
const auto build = [&](float squeeze) {
AuroraCockpit cockpit{};
cockpit.nativeWheel = true;
@@ -135,9 +138,9 @@ TEST_F(CockpitGeometry, GloveFingersRunAlongTheHandAndCloseIntoThePalm) {
const auto measure = [&](const std::vector<Vertex>& vertices) {
Extent e{};
for (const auto& vertex : vertices) {
e.reach = std::max(e.reach, vertex.position[1] * forward);
e.palm = std::max(e.palm, vertex.position[0]);
e.back = std::min(e.back, vertex.position[0]);
e.reach = std::max(e.reach, -vertex.position[1]);
e.palm = std::max(e.palm, vertex.position[0] * palmSide);
e.back = std::min(e.back, vertex.position[0] * palmSide);
e.across = std::max(e.across, std::abs(vertex.position[2]));
}
return e;