VR: the whole body, vr.body "full": torso, hips and legs under the arms

The player's own biped is drawn in first person among the world's objects
(it sorts against the world by depth; the first-person weapon and arms,
drawn first into the stencil, stay over it), posed from a copy of its node
matrices once a frame (port/linux/game/vr_body.c): turned to the torso's
heading, the neck below and behind the eyes as the head turns about it, the
hips down with the physical crouch and part of the way with a lean, the
spine upright to the neck, the feet planted while standing and stepping
after the body as it turns, the knees bent by the arms' two-bone IK. The
head and the third-person arms are shrunk to nothing, the weapon and what
the biped carries left out; the first-person arms hang from the body's
shoulders (mirrored with the weapon left-handed). A passenger in an open
vehicle sits as the seat's animation has it; at a wheel or a gun the body
is drawn as before, in a closed seat none. Nothing of the game's own pose,
hit boxes or network messages changes.

The neck pivot, the planted feet and the leg's clamped reach follow
HaloCE-Quest-VR (CC0), rebuilt for this port's skeleton and caches.

vr.body gets "full" (VR options, Body: "Full body"); "arms" stays the
default. vr.log_performance reports the body's pose. debug.vr_body shows
the body flat (the emulator): from the eyes, looking down, or from the
front, side or back, the head turning, crouching or leaning on its own.

Checked in the emulator: the cyborg's 19 nodes found, the body posed
standing, crouched, leaning and turning (feet stepping), 0.006 ms a pose;
two-minute bot games on Blood Gulch with it on and off, no errors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Stefan MaierandClaude Opus 5.5 committed 2026-10-10 12:22:55 +02:00
1 parent 53e13fc69d
commit 378f8bf897
11 files changed
+1325 -18

No files matched your search

+2 -2
View File
@@ -115,7 +115,7 @@ static boolean vr_unit_vector(
}
/* the rotation taking direction a to direction b, the shortest way */
static void vr_rotation_between(
void vr_rotation_between(
real_vector3d const *a,
real_vector3d const *b,
real rotation[3][3])
@@ -648,7 +648,7 @@ void vr_finger_nodes(
}
/* a node and all under it turned about the node */
static void vr_turn_subtree(
void vr_turn_subtree(
struct vr_skeleton const *skeleton,
real_matrix4x3 *matrices,
short root,
+6
View File
@@ -44,6 +44,12 @@ struct vr_limb_history
boolean vr_skeleton_build(struct vr_skeleton *skeleton, long graph_index, short node_count);
boolean vr_skeleton_under(struct vr_skeleton const *skeleton, short node_index, short root);
/* the rotation (column vectors) taking direction a to direction b, the
shortest way; a node and all under it turned by one about the node (the
whole body, vr_body.c) */
void vr_rotation_between(real_vector3d const *a, real_vector3d const *b, real rotation[3][3]);
void vr_turn_subtree(struct vr_skeleton const *skeleton, real_matrix4x3 *matrices, short root,
real rotation[3][3]);
/* one arm (bones: upper arm, forearm, hand) bent from the shoulder for the
hand to reach target, its elbow towards the pole; the hand (and what it
File diff suppressed because it is too large. Load diff
+36
View File
@@ -0,0 +1,36 @@
/*
VR_BODY.H
The whole body in VR (vr.body "full", vr_body.c): the player's own biped
drawn under the first-person arms and weapon. Include after the game's
math, object and render headers.
*/
#ifndef __VR_BODY_H
#define __VR_BODY_H
#ifdef HALO_ANDROID
struct render_camera;
/* render_objects.c: whether the object is the local player's own unit, drawn
whole in first person this frame (its weapon and what it carries left
out), and its node matrices as posed */
boolean vr_body_drawn(short local_player_index, long object_index);
real_matrix4x3 *vr_body_node_matrices(long object_index);
/* first_person_weapons.c: the drawn body's shoulders this frame (world
units; the model's left, then right), from which the first-person arms hang;
FALSE when no body is drawn */
boolean vr_body_shoulders(short local_player_index, real_point3d shoulders[2]);
/* debug.vr_body, flat (the emulator): the camera of a player's view moved to
look at the body (render.c), and whether the first-person weapon is left out
of it (first_person_weapons.c) */
boolean vr_body_debug_camera(short local_player_index, struct render_camera *render_camera,
struct render_camera *rasterizer_camera);
boolean vr_body_debug_hides_first_person(void);
#endif
#endif
+12 -3
View File
@@ -131,6 +131,10 @@ int halo_vr_ready_laser(short local_player_index, float start[3], float directio
weapon aims along both hands; */
int halo_vr_body_frame(short local_player_index, float head[3], float neck[3], float forward[3]);
int halo_vr_free_hand(short local_player_index, float position[3], float forward[3], float up[3]);
/* - for the whole body: the eyes (world units), the head's forward and up,
and the game's camera: where the eyes are standing straight (lower
crouched by the button), from which the physical crouch is measured; */
int halo_vr_head_pose(short local_player_index, float eye[3], float forward[3], float up[3], float standing[3]);
int halo_vr_two_hand(short local_player_index, const float foregrip[3], const float reference[3]);
/* - the fingers of a hand of the model (0 the left, the free hand; 1 the
right, the weapon's), from the controller's trigger, grip and touch: the thumb, index, middle finger and the last two, each
@@ -152,12 +156,15 @@ int halo_vr_finger_curls(short local_player_index, int hand, float curls[4]);
int halo_vr_free_arm_bends(void);
double halo_vr_pose_time(void);
/* - what of the body is drawn (vr.body): the arms and hands, the hands
alone, or neither (the weapon alone) */
alone, or neither (the weapon alone); or the whole body, its torso, hips
and legs the player's own biped's (port/linux/game/vr_body.c), the arms
and hands as with "arms" */
enum
{
HALO_VR_BODY_ARMS = 0,
HALO_VR_BODY_HANDS,
HALO_VR_BODY_OFF,
HALO_VR_BODY_FULL,
};
int halo_vr_body_mode(void);
/* a hand has just been thrust forward: a melee attack (xinput_sdl.c) */
@@ -361,13 +368,15 @@ enum
void halo_vr_phase(int phase, int ticks);
/* vr.log_performance: time taken by a part of the drawing, for the report
(first_person_weapons.c): its start (0 when not logged), and its end. The
first-person pose (its animation and the arms), the arms alone, and the
pose taken as built already this frame (counted) */
first-person pose (its animation and the arms), the arms alone, the
pose taken as built already this frame (counted), and the whole body's pose
(vr_body.c; flat with debug.vr_body too, reported alone) */
enum
{
HALO_VR_TIMING_FIRST_PERSON,
HALO_VR_TIMING_ARMS,
HALO_VR_TIMING_FIRST_PERSON_KEPT,
HALO_VR_TIMING_BODY,
HALO_VR_TIMING_COUNT
};
unsigned long long halo_vr_timing_start(void);
+13 -2
View File
@@ -255,8 +255,10 @@ static const struct config_setting config_settings[] =
"its grip button to take hold of it with both hands." },
{ "vr.body", _config_string, "\"arms\"", "HALO_VR_BODY", _environment_value, _platform_android,
"What of your body is drawn with the weapon: \"arms\", the arms and hands\n"
"reaching from the shoulders to the controllers; \"hands\", the hands\n"
"alone; \"off\", neither (the weapon alone)." },
"reaching from the shoulders to the controllers; \"full\", the whole\n"
"body, its torso, hips and legs too, under the head and following the\n"
"physical crouch and lean, the feet stepping after it; \"hands\", the\n"
"hands alone; \"off\", neither (the weapon alone)." },
{ "vr.fingers", _config_boolean, "true", "HALO_VR_FINGERS", _environment_value, _platform_android,
"The free hand's fingers follow the controller: the index finger the\n"
"trigger, the others the grip, the thumb where it rests (on the Quest's\n"
@@ -715,6 +717,15 @@ static const struct config_setting config_settings[] =
_platform_android,
"Off the headset (flat), the first-person weapon drawn mirrored, as the\n"
"left hand's is in VR: a test aid for what reads mirrored there. Read once." },
{ "debug.vr_body", _config_string, "\"off\"", "HALO_DEBUG_VR_BODY", _environment_value, _platform_android,
"Off the headset (flat), the player's own body drawn as vr.body \"full\"\n"
"draws it, posed from the flat camera, a test aid: \"eye\" (seen from the\n"
"eyes), \"down\" (looking down, as in the headset), \"front\", \"side\" or\n"
"\"back\" (the camera moved to look at it); after a colon how the head\n"
"moves: \"cycle\" (turning, crouching, leaning, five seconds each; the\n"
"default), \"still\", \"turn\", \"crouch\", \"lean\", or \"raw\" (the game's\n"
"own pose, only the head and arms shrunk). \"off\" (default): none. Read\n"
"once." },
{ "debug.network_corrupt", _config_real, "0.0", "HALO_NETWORK_CORRUPT", _environment_value, _platform_all,
"Percent of the datagrams received that are damaged at random, to test\n"
"that nothing a machine sends can crash the game; 0 none." },
+68 -4
View File
@@ -545,7 +545,8 @@ static void vr_read_live_settings(void)
const char *body = config_string("vr.body");
vr.body_mode = !strcmp(body, "hands") ? HALO_VR_BODY_HANDS :
!strcmp(body, "off") ? HALO_VR_BODY_OFF : HALO_VR_BODY_ARMS;
!strcmp(body, "off") ? HALO_VR_BODY_OFF :
!strcmp(body, "full") ? HALO_VR_BODY_FULL : HALO_VR_BODY_ARMS;
}
{
int left_handed = !strcmp(config_string("vr.handedness"), "left");
@@ -750,9 +751,24 @@ void halo_vr_phase(int phase, int ticks)
vr.frame_ticks = ticks;
}
/* debug.vr_body (flat, port/linux/game/vr_body.c): the body's pose timed
off the headset too */
static int debug_body_timing(void)
{
static int timing = -1;
if (timing < 0)
{
const char *view = config_string("debug.vr_body");
timing = view && *view && strcmp(view, "off") != 0;
}
return timing;
}
unsigned long long halo_vr_timing_start(void)
{
return vr.active && vr.log_performance ? now_us() : 0;
return (vr.active && vr.log_performance) || (!vr.active && debug_body_timing()) ? now_us() : 0;
}
unsigned long halo_vr_frame_serial(void)
@@ -766,6 +782,23 @@ void halo_vr_timing_end(int part, unsigned long long start)
return;
vr.timing_us[part] += now_us() - start;
vr.timing_count[part]++;
if (!vr.active)
{
/* (flat, debug.vr_body: the body alone, every five seconds) */
unsigned long long now = now_us();
if (!vr.report_start_us)
vr.report_start_us = now;
if (now - vr.report_start_us >= 5000000 && vr.timing_count[HALO_VR_TIMING_BODY])
{
platform_log("VR body (flat): %.3f ms a pose, %d poses in %.1f s",
vr.timing_us[HALO_VR_TIMING_BODY] * 1e-3f / vr.timing_count[HALO_VR_TIMING_BODY],
vr.timing_count[HALO_VR_TIMING_BODY], (now - vr.report_start_us) * 1e-6f);
memset(vr.timing_us, 0, sizeof(vr.timing_us));
memset(vr.timing_count, 0, sizeof(vr.timing_count));
vr.report_start_us = now;
}
}
}
/* the frame's parts, from the marks halo_vr_phase left (a part not marked
@@ -843,11 +876,14 @@ static void performance_report(int stereo)
memset(vr.phase_late, 0, sizeof(vr.phase_late));
/* (the first-person pose: built how often a frame and in how long,
the arms' part of it, and how often taken as built) */
platform_log("VR: first person: %.2f ms a frame in %.2f builds (arms %.3f ms), %.2f kept",
platform_log("VR: first person: %.2f ms a frame in %.2f builds (arms %.3f ms), %.2f kept; "
"body %.3f ms a frame in %.2f poses",
vr.timing_us[HALO_VR_TIMING_FIRST_PERSON] * 1e-3f / frames,
vr.timing_count[HALO_VR_TIMING_FIRST_PERSON] / frames,
vr.timing_us[HALO_VR_TIMING_ARMS] * 1e-3f / frames,
vr.timing_count[HALO_VR_TIMING_FIRST_PERSON_KEPT] / frames);
vr.timing_count[HALO_VR_TIMING_FIRST_PERSON_KEPT] / frames,
vr.timing_us[HALO_VR_TIMING_BODY] * 1e-3f / frames,
vr.timing_count[HALO_VR_TIMING_BODY] / frames);
memset(vr.timing_us, 0, sizeof(vr.timing_us));
memset(vr.timing_count, 0, sizeof(vr.timing_count));
}
@@ -2587,6 +2623,29 @@ int halo_vr_body_frame(short local_player_index, float head[3], float neck[3], f
return 1;
}
/* the whole body (vr.body "full", port/linux/game/vr_body.c): the eyes
(world units), the head's forward and up, and the game's camera, where the
eyes are standing straight (as recentred; lower crouched by the button) */
int halo_vr_head_pose(short local_player_index, float eye[3], float forward[3], float up[3], float standing[3])
{
static const float local_forward[3] = { 0.0f, 0.0f, -1.0f };
static const float local_up[3] = { 0.0f, 1.0f, 0.0f };
float offset[3], direction[3];
int axis;
if (local_player_index != 0 || !halo_vr_stereo_frame() || !(vr.frame.head_flags & HALO_XR_POSE_VALID))
return 0;
tracked_offset(vr.frame.head.position, offset);
for (axis = 0; axis < 3; axis++)
eye[axis] = vr.game_camera[axis] + offset[axis] / METRES_PER_WORLD_UNIT;
quaternion_rotate(vr.frame.head.orientation, local_forward, direction);
local_to_game(direction, vr.body_yaw, forward);
quaternion_rotate(vr.frame.head.orientation, local_up, direction);
local_to_game(direction, vr.body_yaw, up);
memcpy(standing, vr.game_camera, sizeof(vr.game_camera));
return 1;
}
int halo_vr_free_hand(short local_player_index, float position[3], float forward[3], float up[3])
{
static const float local_forward[3] = { 0.0f, 0.0f, -1.0f };
@@ -3334,6 +3393,11 @@ int halo_vr_body_frame(short local_player_index, float head[3], float neck[3], f
(void)local_player_index; (void)head; (void)neck; (void)forward;
return 0;
}
int halo_vr_head_pose(short local_player_index, float eye[3], float forward[3], float up[3], float standing[3])
{
(void)local_player_index; (void)eye; (void)forward; (void)up; (void)standing;
return 0;
}
int halo_vr_free_hand(short local_player_index, float position[3], float forward[3], float up[3])
{
(void)local_player_index; (void)position; (void)forward; (void)up;
+37
View File
@@ -127,6 +127,7 @@ symbols in this file:
#ifdef HALO_ANDROID
#include "halo_vr.h"
#include "vr_arms.h"
#include "vr_body.h"
#endif
#include "view_fov.h" /* port: port/linux/game/view_fov.c */
@@ -584,6 +585,11 @@ void first_person_weapon_draw(
/* port: not drawn with display.viewmodel_visible off (view_fov.c) */
if (!viewmodel_is_visible())
return;
#ifdef HALO_ANDROID
/* (debug.vr_body: the body seen from aside, flat, vr_body.c) */
if (vr_body_debug_hides_first_person())
return;
#endif
if (render.local_player_index!=NONE)
{
struct first_person_weapon *first_person_weapon= first_person_weapon_get(render.local_player_index);
@@ -1705,6 +1711,9 @@ static void first_person_weapon_vr_arms(
static char const *const fore_names[2] = { "l forearm", "r forearm" };
static char const *const wrist_names[2] = { "l wriste", "r wriste" };
float head[3], neck[3], body_forward[3];
/* the whole body's shoulders (vr.body "full", vr_body.c) */
real_point3d body_shoulders[2];
boolean body_drawn = FALSE;
real_vector3d forward, left, weapon_left, weapon_right;
real_point3d eye;
real_matrix4x3 animated_upper[2];
@@ -1805,6 +1814,31 @@ static void first_person_weapon_vr_arms(
vr_mirror_point(on, normal, neck);
vr_mirror_vector(normal, body_forward);
}
/* (the drawn body's shoulders: the room's left one is the model's
right in its mirror image) */
if (body == HALO_VR_BODY_FULL && vr_body_shoulders(local_player_index, body_shoulders))
{
body_drawn = TRUE;
if (halo_vr_first_person_mirror(local_player_index, on, normal))
{
real_point3d room_left = body_shoulders[0];
body_shoulders[0] = body_shoulders[1];
body_shoulders[1] = room_left;
for (side = 0; side < 2; side++)
{
float point[3];
point[0] = body_shoulders[side].x;
point[1] = body_shoulders[side].y;
point[2] = body_shoulders[side].z;
vr_mirror_point(on, normal, point);
body_shoulders[side].x = point[0];
body_shoulders[side].y = point[1];
body_shoulders[side].z = point[2];
}
}
}
}
/* (the shoulders hang from the neck, which the head turns about) */
eye.x = neck[0];
@@ -1884,6 +1918,9 @@ static void first_person_weapon_vr_arms(
point_from_line3d(&shoulder, global_up3d, -0.20f / VR_METRES_PER_WORLD_UNIT, &shoulder);
point_from_line3d(&shoulder, &forward, -0.04f / VR_METRES_PER_WORLD_UNIT, &shoulder);
point_from_line3d(&shoulder, &left, outward * 0.19f / VR_METRES_PER_WORLD_UNIT, &shoulder);
/* (the whole body drawn: from its shoulders) */
if (body_drawn)
shoulder = body_shoulders[side];
/* the elbow bends down, out and back */
pole.i = -global_up3d->i;
pole.j = -global_up3d->j;
+4 -4
View File
@@ -7716,8 +7716,8 @@ static void ui_vr_options_vignette_text(
snprintf(text, size, "%.0f%%", strength * 100.0f);
}
/* vr.body's values, and as the panel shows them */
static char const *const vr_bodies[] = { "arms", "hands", "off" };
static char const *const vr_body_names[] = { "Arms and hands", "Hands only", "Weapon only" };
static char const *const vr_bodies[] = { "full", "arms", "hands", "off" };
static char const *const vr_body_names[] = { "Full body", "Arms and hands", "Hands only", "Weapon only" };
/* audio.voice_chat's (network_voice.c); push to talk is the free hand's grip */
static char const *const vr_voice_chats[] = { "push_to_talk", "open_mic", "off" };
static char const *const vr_voice_chat_names[] = { "Push to talk (grip)", "Open mic", "Off" };
@@ -7803,7 +7803,7 @@ static short ui_vr_options_body(
if (body && strcmp(body, vr_bodies[index]) == 0)
return index;
}
return 0;
return 1; /* (vr.c: anything else is "arms") */
}
static boolean ui_vr_options_screen(
@@ -8582,7 +8582,7 @@ static void ui_vr_options_render(
case _vr_option_fingers:
label = "Fingers";
snprintf(value, sizeof(value), "%s", config_boolean("vr.fingers") ? "Tracked" : "Off");
unused = ui_vr_options_body() == 2;
unused = strcmp(vr_bodies[ui_vr_options_body()], "off") == 0;
break;
case _vr_option_voice_chat:
label = "Voice chat";
+28
View File
@@ -95,6 +95,9 @@ symbols in this file:
/* port/linux/game/vr_graphics.c */
void vr_graphics_update(void);
/* port/linux/game/vr_body.c: debug.vr_body, flat */
boolean vr_body_debug_camera(short local_player_index, struct render_camera *render_camera,
struct render_camera *rasterizer_camera);
#endif
#include "main/main.h"
#include "structures/structures.h"
@@ -155,6 +158,8 @@ static void render_vr_laser(
short local_player_index);
static void render_vr_scope(
struct render_window *window);
static boolean render_vr_body_debug_frame(
struct render_window *window);
#endif
void render_sky(
@@ -645,6 +650,22 @@ static void render_player_frame(
}
#ifdef HALO_ANDROID
/* debug.vr_body (flat, vr_body.c): the player's own body as VR draws it,
the camera moved to look at it; FALSE when the view is the player's own */
static boolean render_vr_body_debug_frame(
struct render_window *window)
{
struct render_window debug_window = *window;
if (!vr_body_debug_camera(window->local_player_index, &debug_window.render_camera,
&debug_window.rasterizer_camera))
{
return FALSE;
}
render_player_frame(&debug_window, NULL);
return TRUE;
}
static void render_vr_camera_set(
struct render_camera *camera,
const float position[3],
@@ -983,6 +1004,13 @@ void render_frame(
render_vr_player_frame(window);
continue;
}
#endif
#ifdef HALO_ANDROID
else if (window->local_player_index != NONE && screenshot_index == NULL &&
render_vr_body_debug_frame(window))
{
continue;
}
#endif
else if (window->local_player_index != NONE)
{
+18 -3
View File
@@ -115,6 +115,7 @@ symbols in this file:
#include "units/unit_definitions.h"
#include "units/vehicles.h"
#include "halo_vr.h"
#include "vr_body.h" /* port: port/linux/game/vr_body.c */
#endif
#include "rasterizer/rasterizer_console_vars.h"
#include "rasterizer/rasterizer_model_types.h"
@@ -584,10 +585,16 @@ static void render_object_list(
while (object_index != NONE)
{
struct object_datum *object = object_get(object_index);
#ifdef HALO_ANDROID
/* port: VR, the player's own body under the first-person arms and
weapon (vr.body "full", vr_body.c), without what it carries */
boolean vr_body = !data->shadow && !render.camera.mirrored && object_is_first_person_camera(object_index) &&
vr_body_drawn(render.local_player_index, object_index);
#endif
if (!object_is_first_person_camera(object_index) || render.camera.mirrored
#ifdef HALO_ANDROID
|| (!data->shadow && render_object_vr_seated_body(object_index))
|| (!data->shadow && render_object_vr_seated_body(object_index)) || vr_body
#endif
)
{
@@ -627,7 +634,11 @@ static void render_object_list(
#ifdef HALO_ANDROID
if (object_is_first_person_camera(object_index) && !render.camera.mirrored)
node_matrices = object_vr_headless_node_matrices(object_index);
{
node_matrices = vr_body ?
vr_body_node_matrices(object_index) :
object_vr_headless_node_matrices(object_index);
}
#endif
match_assert(
@@ -771,7 +782,11 @@ static void render_object_list(
widgets_render(object_index, data->lighting, &animation);
}
if (object->object.first_child_object_index != NONE)
if (object->object.first_child_object_index != NONE
#ifdef HALO_ANDROID
&& !vr_body
#endif
)
{
render_object_list(
data,