Compare commits

...
4 Commits
Author SHA1 Message Date
Stefan MaierandClaude Opus 5.5 5530d19769 Merge fix/vr-weapon-arm: each weapon held so that its hand lands on the real one, the scope's disc with it
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 18:00:14 +02:00
Stefan MaierandClaude Opus 5.5 603ad6b543 Roadmap: each weapon held so that its hand lands on the real one
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 18:00:13 +02:00
Stefan MaierandClaude Opus 5.5 322146f3cb Scope: the disc moves with the weapon from its old fixed place in the hand
The disc was placed for the weapons at the old fixed place (7 cm above
the hand, 22 cm ahead of it): with the pistol now held 20 cm further
back it sat at the muzzle. It moves as the weapon did (forward and up;
sideways the barrel places it, as before).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 17:54:43 +02:00
Stefan MaierandClaude Opus 5.5 a31c482d5a Hold each weapon so that its hand lands on the real one
The first-person model hung from a camera at one fixed place from the
weapon hand's controller (18 cm behind, 10 cm left, 14 cm above), but
each weapon's animations hold the hand elsewhere from that camera: the
pistol's wrist came out 15 cm before the controller (22 cm before where
the free hand's wrist is, 7 cm behind its grip), the plasma pistol's 11,
the assault rifle's 8 cm aside and 10 cm low; the weapon arm looked
longer, reaching forward. Measured on the Quest 3 (2026-10-17).

Now the wrist as the weapon is held ready is kept for each weapon
(eased, the last eight weapons), and the camera put so that it lands
where the free hand's does: 7 cm behind the grip, along the aim. The
model, its barrel, the laser and the shots move with the camera, as
before. A weapon not yet seen is held at the old place for its first
frame. vr.weapon_hand_forward/_right/_down (0 by default) move the hand
on the weapon; vr.weapon_forward/_right/_down, the old fixed place, are
gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 17:41:48 +02:00
5 changed files with 155 additions and 25 deletions

No files matched your search

+1 -1
View File
@@ -18,7 +18,7 @@ named where code is taken over.
| --- | --- |
| Headsets | Quest 3; Steam Frame (Lepton): its controllers' layout, a Steam library tile that installs, starts, updates and quits the game, data kept in `~/Documents/RingfallMjolnirVR` |
| Rendering | The eyes drawn one by one (both at once, multiview, only with `debug.multiview`: its eyes did not always fuse, see the open points); lens flares at their depth in stereo; HUD, menus, scope, crosshair and names as layers of their own |
| Aiming | The weapon in the hand, its shots from the muzzle, laser and crosshair options |
| Aiming | The weapon in the hand, its shots from the muzzle, laser and crosshair options; each weapon held so that its model's hand lands on the real one, as the free hand's (it learns where each weapon's animations hold the hand; the pistol's had been 15 cm before the real hand, the assault rifle's 8 cm aside), the scope's disc moved with it; `vr.weapon_hand_*` to adjust (2026-10-17) |
| Body | Physical crouch (`vr.crouch_limit`) beside the crouch button, leaning (`vr.lean_limit`), melee by swinging, grenades thrown with the left hand, two-handed weapons |
| Vehicles | The Warthog's wheel turned with the hands, right trigger throttle, left brake; first-person seats; turrets with handles |
| Comfort | Snap or smooth turning, a comfort vignette in vehicles, VR options in the main and pause menus (Y), a page of every controller button |
+9 -1
View File
@@ -103,7 +103,15 @@ int halo_vr_weapon_origin_offset(short local_player_index, float offset[3]);
(as the drawn frame had it): shots start there too, not from an offset to
the unit's own eye point, which need not be the drawn camera's */
int halo_vr_weapon_origin(short local_player_index, float origin[3]);
int halo_vr_weapon_camera(short local_player_index, float position[3], float forward[3], float up[3]);
/* The first-person model's camera: where the model's weapon hand lands on
the weapon hand's controller, as the free hand does (graph_index: the
weapon's first-person animations, whose hand halo_vr_weapon_wrist says
where it is, in the camera's axes: forward, right, up, world units, from
the weapon held ready; any other, the weapon as one held at the old fixed
place) */
int halo_vr_weapon_camera(short local_player_index, long graph_index, float position[3], float forward[3],
float up[3]);
void halo_vr_weapon_wrist(long graph_index, const float wrist[3]);
int halo_vr_laser(short local_player_index, float start[3], float direction[3]);
/* Model space: the first-person model (first_person_weapons.c) is always
posed as the right hand's. Left-handed, it is posed in a mirror image of
+11 -8
View File
@@ -286,14 +286,17 @@ static const struct config_setting config_settings[] =
{ "vr.crosshair", _config_boolean, "true", "HALO_VR_CROSSHAIR", _environment_value, _platform_android,
"The weapon's crosshair where it aims, at the distance of what it aims at\n"
"(with aim_with = \"controller\"); false leaves it out." },
{ "vr.weapon_forward", _config_real, "0.18", "HALO_VR_WEAPON_FORWARD", _environment_value, _platform_android,
"Where the weapon lies in your hand: how far the first-person model's\n"
"camera is behind your hand, in metres (weapon_right: to its left,\n"
"weapon_down: above it)." },
{ "vr.weapon_right", _config_real, "0.10", "HALO_VR_WEAPON_RIGHT", _environment_value, _platform_android,
"See weapon_forward." },
{ "vr.weapon_down", _config_real, "0.14", "HALO_VR_WEAPON_DOWN", _environment_value, _platform_android,
"See weapon_forward." },
{ "vr.weapon_hand_forward", _config_real, "0.0", "HALO_VR_WEAPON_HAND_FORWARD", _environment_value,
_platform_android,
"The weapon in your hand: each one's model is held so that its hand is\n"
"where yours is, as the free hand's; this moves your hand on it, in\n"
"metres (weapon_hand_right: to its right, weapon_hand_down: down)." },
{ "vr.weapon_hand_right", _config_real, "0.0", "HALO_VR_WEAPON_HAND_RIGHT", _environment_value,
_platform_android,
"See weapon_hand_forward." },
{ "vr.weapon_hand_down", _config_real, "0.0", "HALO_VR_WEAPON_HAND_DOWN", _environment_value,
_platform_android,
"See weapon_hand_forward." },
{ "vr.two_hand_reach", _config_real, "0.2", "HALO_VR_TWO_HAND_REACH", _environment_value, _platform_android,
"How near, in metres, the free hand must be to the weapon's foregrip for\n"
"its grip button to take hold of it with both hands." },
+112 -14
View File
@@ -223,7 +223,8 @@ static struct
float motion_tracker_down;
int aim_with_controller;
int laser;
float weapon_offset[3]; /* forward, right, down, metres: the weapon model's camera from the hand */
float weapon_adjust[3]; /* forward, right, down, metres: vr.weapon_hand_*, the hand moved on the weapon */
float weapon_shift[3]; /* forward, right, up, metres: the weapon moved from the old fixed place (the scope) */
float muzzle_distance;
/* the free hand on the weapon (halo_vr_two_hand) */
int two_hand_gripping;
@@ -638,9 +639,9 @@ void halo_vr_initialize(void)
xgpu_draw_timing(vr.log_performance);
vr.resolution_scale = (float)config_real("vr.resolution_scale");
vr.aim_with_controller = strcmp(config_string("vr.aim_with"), "head") != 0;
vr.weapon_offset[0] = (float)config_real("vr.weapon_forward");
vr.weapon_offset[1] = (float)config_real("vr.weapon_right");
vr.weapon_offset[2] = (float)config_real("vr.weapon_down");
vr.weapon_adjust[0] = (float)config_real("vr.weapon_hand_forward");
vr.weapon_adjust[1] = (float)config_real("vr.weapon_hand_right");
vr.weapon_adjust[2] = (float)config_real("vr.weapon_hand_down");
vr.muzzle_distance = 0.3f;
vr.two_hand_reach = (float)config_real("vr.two_hand_reach");
vr.melee_swing_speed = (float)config_real("vr.melee_swing_speed");
@@ -2770,9 +2771,80 @@ int halo_vr_weapon_origin(short local_player_index, float origin[3])
return 1;
}
int halo_vr_weapon_camera(short local_player_index, float position[3], float forward[3], float up[3])
/* The weapon's hand in its first-person model (halo_vr_weapon_wrist): each
weapon's animations hold the hand elsewhere from the model's camera, the
pistol's some 25 cm further forward than the assault rifle's, so a camera
at one fixed place from the hand (as before: WEAPON_FIXED_*) held the
pistol's hand 15 cm before the real one and the assault rifle's 8 cm aside.
The wrist as the weapon is held ready, eased, is kept for each weapon (a
few at once, the last ones held), and the camera put where it lands as the
free hand's does: WRIST_BEHIND_GRIP behind the controller's grip, along
its aim. */
#define WEAPON_WRISTS 8
#define WRIST_BEHIND_GRIP 0.07f
#define WEAPON_FIXED_FORWARD 0.18f
#define WEAPON_FIXED_RIGHT 0.10f
#define WEAPON_FIXED_DOWN 0.14f
static struct
{
float right[3], hand[3];
long graph_index;
float wrist[3];
unsigned long used;
} weapon_wrists[WEAPON_WRISTS];
static unsigned long weapon_wrists_used;
static float *weapon_wrist_for(long graph_index, int make)
{
int index, oldest = 0;
for (index = 0; index < WEAPON_WRISTS; index++)
{
if (weapon_wrists[index].used && weapon_wrists[index].graph_index == graph_index)
{
weapon_wrists[index].used = ++weapon_wrists_used;
return weapon_wrists[index].wrist;
}
if (weapon_wrists[index].used < weapon_wrists[oldest].used)
oldest = index;
}
if (!make)
return NULL;
weapon_wrists[oldest].graph_index = graph_index;
weapon_wrists[oldest].used = 0;
return NULL;
}
void halo_vr_weapon_wrist(long graph_index, const float wrist[3])
{
float *kept = weapon_wrist_for(graph_index, 1);
int index, axis;
if (kept)
{
for (axis = 0; axis < 3; axis++)
kept[axis] += (wrist[axis] - kept[axis]) * 0.1f;
return;
}
for (index = 0; index < WEAPON_WRISTS; index++)
{
if (weapon_wrists[index].graph_index == graph_index && !weapon_wrists[index].used)
{
memcpy(weapon_wrists[index].wrist, wrist, sizeof(weapon_wrists[index].wrist));
weapon_wrists[index].used = ++weapon_wrists_used;
platform_log("VR: weapon hand found %.0f cm forward, %.0f right, %.0f up of its model's camera",
wrist[0] * METRES_PER_WORLD_UNIT * 100.0f, wrist[1] * METRES_PER_WORLD_UNIT * 100.0f,
wrist[2] * METRES_PER_WORLD_UNIT * 100.0f);
return;
}
}
}
int halo_vr_weapon_camera(short local_player_index, long graph_index, float position[3], float forward[3],
float up[3])
{
float right[3], hand[3], from_hand[3];
const float *wrist;
int axis;
if (!weapon_hand_tracked(local_player_index))
@@ -2794,13 +2866,33 @@ int halo_vr_weapon_camera(short local_player_index, float position[3], float for
}
}
/* the first-person model hangs from a camera: put that camera where the
model's grip lands in the hand */
model's hand lands on the real one (the camera from the hand: forward,
right, up, world units), moved as vr.weapon_hand_* says */
wrist = graph_index != -1 ? weapon_wrist_for(graph_index, 0) : NULL;
if (wrist)
{
from_hand[0] = -WRIST_BEHIND_GRIP / METRES_PER_WORLD_UNIT - wrist[0];
from_hand[1] = -wrist[1];
from_hand[2] = -wrist[2];
}
else
{
from_hand[0] = -WEAPON_FIXED_FORWARD / METRES_PER_WORLD_UNIT;
from_hand[1] = -WEAPON_FIXED_RIGHT / METRES_PER_WORLD_UNIT;
from_hand[2] = WEAPON_FIXED_DOWN / METRES_PER_WORLD_UNIT;
}
from_hand[0] -= vr.weapon_adjust[0] / METRES_PER_WORLD_UNIT;
from_hand[1] -= vr.weapon_adjust[1] / METRES_PER_WORLD_UNIT;
from_hand[2] += vr.weapon_adjust[2] / METRES_PER_WORLD_UNIT;
for (axis = 0; axis < 3; axis++)
{
position[axis] = vr.game_camera[axis] + hand[axis] -
(forward[axis] * vr.weapon_offset[0] + right[axis] * vr.weapon_offset[1] -
up[axis] * vr.weapon_offset[2]) / METRES_PER_WORLD_UNIT;
position[axis] = vr.game_camera[axis] + hand[axis] +
forward[axis] * from_hand[0] + right[axis] * from_hand[1] + up[axis] * from_hand[2];
}
/* (the scope's disc, placed for the old fixed place, moves with it) */
vr.weapon_shift[0] = from_hand[0] * METRES_PER_WORLD_UNIT + WEAPON_FIXED_FORWARD;
vr.weapon_shift[1] = from_hand[1] * METRES_PER_WORLD_UNIT + WEAPON_FIXED_RIGHT;
vr.weapon_shift[2] = from_hand[2] * METRES_PER_WORLD_UNIT - WEAPON_FIXED_DOWN;
return 1;
}
@@ -3366,10 +3458,14 @@ int halo_vr_scope(short local_player_index, float magnification, float position[
game_to_local(forward, local_forward);
game_to_local(up, local_up);
game_to_local(right, local_right);
/* (placed for the weapons at the old fixed place in the hand: moved as
the weapon is from it, halo_vr_weapon_camera; sideways by the barrel,
below) */
for (axis = 0; axis < 3; axis++)
{
vr.scope_pose.position[axis] = hand->aim.position[axis] + local_up[axis] * (SCOPE_ABOVE_HAND + scope_raise) +
local_forward[axis] * SCOPE_AHEAD_OF_HAND;
vr.scope_pose.position[axis] = hand->aim.position[axis] +
local_up[axis] * (SCOPE_ABOVE_HAND + scope_raise + vr.weapon_shift[2]) +
local_forward[axis] * (SCOPE_AHEAD_OF_HAND + vr.weapon_shift[0]);
}
if (barrel_known())
{
@@ -3653,9 +3749,11 @@ void halo_vr_present(unsigned int back_buffer_texture) { (void)back_buffer_textu
int halo_vr_eye_size(int *width, int *height) { (void)width; (void)height; return 0; }
int halo_vr_aim_vector(short local_player_index, float vector[3]) { (void)local_player_index; (void)vector; return 0; }
int halo_vr_weapon_origin_offset(short local_player_index, float offset[3]) { (void)local_player_index; (void)offset; return 0; }
int halo_vr_weapon_camera(short local_player_index, float position[3], float forward[3], float up[3])
void halo_vr_weapon_wrist(long graph_index, const float wrist[3]) { (void)graph_index; (void)wrist; }
int halo_vr_weapon_camera(short local_player_index, long graph_index, float position[3], float forward[3],
float up[3])
{
(void)local_player_index; (void)position; (void)forward; (void)up;
(void)local_player_index; (void)graph_index; (void)position; (void)forward; (void)up;
return 0;
}
int halo_vr_weapon_origin(short local_player_index, float origin[3])
+22 -1
View File
@@ -1823,6 +1823,22 @@ static void first_person_weapon_vr_arms(
vr_arm_nodes.barrel_logged_graph_index = graph_index;
}
}
if (ready && vr_arm_nodes.wrist[1] != NONE)
{
/* where this weapon's animations hold its hand, from the camera
(forward, right, up): the camera is put by it, so that the hand
lands on the real one (vr.c, halo_vr_weapon_camera) */
real_point3d const *wrist = &node_matrices[vr_arm_nodes.wrist[1]].position;
real_vector3d from_camera, camera_right;
float held[3];
vector_from_points3d(&weapon_camera->position, wrist, &from_camera);
cross_product3d(&weapon_camera->forward, &weapon_camera->up, &camera_right);
held[0] = dot_product3d(&from_camera, &weapon_camera->forward);
held[1] = dot_product3d(&from_camera, &camera_right);
held[2] = dot_product3d(&from_camera, &weapon_camera->up);
halo_vr_weapon_wrist(graph_index, held);
}
if (!ready && !melee)
vr_laser_follows_barrel(local_player_index, node_matrices, weapon_camera);
if (!halo_vr_body_frame(local_player_index, head, neck, body_forward) ||
@@ -2109,7 +2125,12 @@ static void first_person_weapon_build_node_matrices(
}
}
}
if (halo_vr_weapon_camera(local_player_index, weapon_position, weapon_forward, weapon_up))
if (halo_vr_weapon_camera(local_player_index,
first_person_weapon->weapon_index!=NONE ?
weapon_definition_get(weapon_get(first_person_weapon->weapon_index)->definition_index)->
weapon.interface_definition.first_person_animations.index :
NONE,
weapon_position, weapon_forward, weapon_up))
{
weapon_camera= render.camera;
weapon_camera.position.x= weapon_position[0];