Face right-wrist panel inward without mirroring text

This commit is contained in:
baketnk committed 2026-09-24 14:54:13 -04:00
1 parent deb8f84591
commit 29f3729df7
3 files changed
+34 -4

No files matched your search

+4 -2
View File
@@ -173,10 +173,12 @@ rather than placing a menu at the world origin. A tracking-origin reset requests
a fresh placement. Settings → Recenter in front deliberately resamples the pose. a fresh placement. Settings → Recenter in front deliberately resamples the pose.
Settings offers World space, Left wrist, Right wrist and Head on that same canvas. Settings offers World space, Left wrist, Right wrist and Head on that same canvas.
World/head width is 0.85 m; wrist width defaults to 0.30 m. Both wrists use World/head width is 0.85 m; wrist width defaults to 0.30 m. The left wrist uses
VR Workspace's fallback watch-face axes: panel-right points toward the fingers VR Workspace's fallback watch-face axes: panel-right points toward the fingers
(controller -Z), panel-up points out of the back of the hand (controller +Y), (controller -Z), panel-up points out of the back of the hand (controller +Y),
and panel-front points toward controller +X. Its controller-relative center is and panel-front points toward controller +X. The right wrist reverses panel-right
and panel-front (controller +Z and -X), keeping panel-up unchanged so it faces
inward with upright, unmirrored text. The controller-relative center is
(0, 0.18, 0.089) m, approximating the compact HUD's surface center: its (0, 0.18, 0.089) m, approximating the compact HUD's surface center: its
0.12 m wrist lift, 0.09 m bottom anchor and ~0.03 m panel-center correction; 0.12 m wrist lift, 0.09 m bottom anchor and ~0.03 m panel-center correction;
Z combines the fallback 0.054 m wrist calibration and 0.035 m finger-back offset. This copies placement geometry, not Z combines the fallback 0.054 m wrist calibration and 0.035 m finger-back offset. This copies placement geometry, not
+6
View File
@@ -78,6 +78,12 @@ Matrix34 relative_mount_pose(Mount mount, const WristPlacement& wrist) {
pose[0] = {0.f, s, c, c * wrist.x + s * wrist.y}; pose[0] = {0.f, s, c, c * wrist.x + s * wrist.y};
pose[1] = {0.f, c, -s, -s * wrist.x + c * wrist.y}; pose[1] = {0.f, c, -s, -s * wrist.x + c * wrist.y};
pose[2] = {-1.f, 0.f, 0.f, wrist.z}; pose[2] = {-1.f, 0.f, 0.f, wrist.z};
if (mount == Mount::RightWrist) {
// The wearer views the right wrist from the opposite side. Rotate
// 180 degrees about panel-up, not a reflection or upside-down roll:
// both right and front reverse, while up and center stay unchanged.
for (auto& row : pose) { row[0] = -row[0]; row[2] = -row[2]; }
}
break; break;
} }
case Mount::World: break; case Mount::World: break;
+24 -2
View File
@@ -66,7 +66,13 @@ int main() {
check_basis(head, 1, 0, 0, 1); check_basis(head, 1, 0, 0, 1);
CHECK(near(head[0][3], 0)); CHECK(near(head[1][3], -.16f)); CHECK(near(head[2][3], -1.05f)); CHECK(near(head[0][3], 0)); CHECK(near(head[1][3], -.16f)); CHECK(near(head[2][3], -1.05f));
const auto left = relative_mount_pose(Mount::LeftWrist), right = relative_mount_pose(Mount::RightWrist); const auto left = relative_mount_pose(Mount::LeftWrist), right = relative_mount_pose(Mount::RightWrist);
CHECK(left == right); // VR Workspace fallback calibration uses the same controller-local axes for both hands. check_basis(right, 0, 1, -1, 0); // right wrist faces inward, not away from wearer
for (int r = 0; r < 3; ++r) {
CHECK(near(right[r][3], left[r][3])); // position and up unchanged
CHECK(near(right[r][1], left[r][1]));
CHECK(near(right[r][0], -left[r][0]));
CHECK(near(right[r][2], -left[r][2]));
}
CHECK(near(left[0][0], 0)); CHECK(near(left[1][0], 0)); CHECK(near(left[2][0], -1)); CHECK(near(left[0][0], 0)); CHECK(near(left[1][0], 0)); CHECK(near(left[2][0], -1));
CHECK(near(left[0][1], 0)); CHECK(near(left[1][1], 1)); CHECK(near(left[2][1], 0)); CHECK(near(left[0][1], 0)); CHECK(near(left[1][1], 1)); CHECK(near(left[2][1], 0));
CHECK(near(left[0][2], 1)); CHECK(near(left[1][2], 0)); CHECK(near(left[2][2], 0)); CHECK(near(left[0][2], 1)); CHECK(near(left[1][2], 0)); CHECK(near(left[2][2], 0));
@@ -74,7 +80,23 @@ int main() {
WristPlacement tuned{.02f, .1f, .07f, .25f, 90.f}; WristPlacement tuned{.02f, .1f, .07f, .25f, 90.f};
const auto rotated = relative_mount_pose(Mount::RightWrist, tuned); const auto rotated = relative_mount_pose(Mount::RightWrist, tuned);
CHECK(near(rotated[0][1], 1)); CHECK(near(rotated[1][1], 0)); CHECK(near(rotated[0][1], 1)); CHECK(near(rotated[1][1], 0));
CHECK(near(rotated[0][2], 0)); CHECK(near(rotated[1][2], -1)); CHECK(near(rotated[0][2], 0)); CHECK(near(rotated[1][2], 1));
// Every roll remains an orthonormal, positive-determinant rotation: no mirror.
for (float roll : {-180.f, -90.f, -35.f, 0.f, 90.f, 180.f}) {
tuned.roll_degrees = roll;
for (auto hand : {Mount::LeftWrist, Mount::RightWrist}) {
const auto m = relative_mount_pose(hand, tuned);
for (int a = 0; a < 3; ++a) for (int b = 0; b < 3; ++b) {
float dot = 0;
for (int r = 0; r < 3; ++r) dot += m[r][a] * m[r][b];
CHECK(near(dot, a == b ? 1.f : 0.f));
}
for (int r = 0; r < 3; ++r) {
const int a = (r + 1) % 3, b = (r + 2) % 3;
CHECK(near(m[a][0] * m[b][1] - m[b][0] * m[a][1], m[r][2]));
}
}
}
CHECK(near(rotated[0][3], .1f)); CHECK(near(rotated[1][3], -.02f)); CHECK(near(rotated[0][3], .1f)); CHECK(near(rotated[1][3], -.02f));
CHECK(near(rotated[2][3], .07f)); CHECK(near(mount_width(Mount::RightWrist, tuned), .25f)); CHECK(near(rotated[2][3], .07f)); CHECK(near(mount_width(Mount::RightWrist, tuned), .25f));
CHECK(relative_mount_pose(Mount::Head, tuned) == head); CHECK(relative_mount_pose(Mount::Head, tuned) == head);