Compare commits

...
Author SHA1 Message Date
Stefan MaierandClaude Opus 5.5 7242151017 Names over the heads at 60% of their size on the HUD
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 10:48:01 +02:00
Stefan MaierandClaude Opus 5.5 4b8f8dde8b Players' names over their heads in VR, each on a quad of its own
The HUD in VR is a screen at one distance before the eyes, where the names
upstream draws on it hung. Each name is now drawn into a slot of a region
of the back buffer before the HUD (up to eight, one under another), taken
into a names layer, and shown on a quad of its own over its player's head,
facing the eyes, as wide as it would look on the HUD: in depth, for both
eyes. Who is named, and when, is as upstream has it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 10:31:36 +02:00
7 changed files with 234 additions and 16 deletions

No files matched your search

+44 -2
View File
@@ -794,6 +794,12 @@ the eyes reads it. Shown only while it is. */
#define WRIST_IMAGE_SIZE 512
#define CROSSHAIR_IMAGE_SIZE 256
/* the names layer: HALO_XR_MAXIMUM_NAMES slots of 4:1, one over another */
#define NAMES_IMAGE_WIDTH 512
#define NAMES_IMAGE_HEIGHT (NAMES_IMAGE_WIDTH / 4 * HALO_XR_MAXIMUM_NAMES)
/* a name's slot, as the game draws it: wider than its image's 4:1 (the
game's 640x480 drawn square): the height a slot shows at, to its width */
#define NAME_SLOT_ASPECT (30.0f / 160.0f)
#define WRIST_BEHIND_PALM 0.05f
#define WRIST_DOWN_HANDLE 0.02f
#define WRIST_ABOVE_HAND 0.04f
@@ -1032,8 +1038,15 @@ int host_xr_initialize(struct halo_xr_info *info, float wanted_refresh_rate)
{
int size = layer == HALO_XR_LAYER_WRIST || layer == HALO_XR_LAYER_SCOPE ? WRIST_IMAGE_SIZE :
layer == HALO_XR_LAYER_CROSSHAIR ? CROSSHAIR_IMAGE_SIZE : 0;
int width = size ? size : xr.eye_width;
int height = size ? size : xr.eye_height;
if (!swapchain_create(&xr.swapchains[layer], size ? size : xr.eye_width, size ? size : xr.eye_height))
if (layer == HALO_XR_LAYER_NAMES)
{
width = NAMES_IMAGE_WIDTH;
height = NAMES_IMAGE_HEIGHT;
}
if (!swapchain_create(&xr.swapchains[layer], width, height))
return 0;
}
if (!copy_initialize() || !actions_initialize())
@@ -1354,7 +1367,8 @@ int host_xr_end_frame(const struct halo_xr_submit *submit)
XrCompositionLayerQuad wrist = { XR_TYPE_COMPOSITION_LAYER_QUAD };
XrCompositionLayerQuad scope = { XR_TYPE_COMPOSITION_LAYER_QUAD };
XrCompositionLayerQuad crosshair = { XR_TYPE_COMPOSITION_LAYER_QUAD };
const XrCompositionLayerBaseHeader *layers[5];
XrCompositionLayerQuad names[HALO_XR_MAXIMUM_NAMES];
const XrCompositionLayerBaseHeader *layers[5 + HALO_XR_MAXIMUM_NAMES];
uint32_t layer_count = 0;
int eye;
@@ -1458,6 +1472,34 @@ int host_xr_end_frame(const struct halo_xr_submit *submit)
layers[layer_count++] = (const XrCompositionLayerBaseHeader *)&crosshair;
}
if (xr.frame_state.shouldRender && submit->name_count > 0 && xr.swapchains[HALO_XR_LAYER_NAMES].filled)
{
struct xr_swapchain *swapchain = &xr.swapchains[HALO_XR_LAYER_NAMES];
int slot_height = swapchain->height / HALO_XR_MAXIMUM_NAMES;
int name;
for (name = 0; name < submit->name_count && name < HALO_XR_MAXIMUM_NAMES; name++)
{
XrCompositionLayerQuad *quad_name = &names[name];
memset(quad_name, 0, sizeof(*quad_name));
quad_name->type = XR_TYPE_COMPOSITION_LAYER_QUAD;
quad_name->layerFlags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
quad_name->space = xr.local_space;
quad_name->eyeVisibility = XR_EYE_VISIBILITY_BOTH;
quad_name->subImage.swapchain = swapchain->handle;
/* (slot 0 at the image's top: GL's rows run bottom up) */
quad_name->subImage.imageRect.offset.x = 0;
quad_name->subImage.imageRect.offset.y = swapchain->height - (name + 1) * slot_height;
quad_name->subImage.imageRect.extent.width = swapchain->width;
quad_name->subImage.imageRect.extent.height = slot_height;
quad_name->size.width = submit->name_widths[name];
quad_name->size.height = submit->name_widths[name] * NAME_SLOT_ASPECT;
pose_to_xr(&quad_name->pose, &submit->name_poses[name]);
layers[layer_count++] = (const XrCompositionLayerBaseHeader *)quad_name;
}
}
end.displayTime = xr.frame_state.predictedDisplayTime;
end.environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
end.layerCount = layer_count;
+10 -1
View File
@@ -27,7 +27,10 @@ enum
HALO_XR_LAYER_WRIST = 3, /* the motion tracker, on the left wrist */
HALO_XR_LAYER_SCOPE = 4, /* the zoomed view, a disc over the weapon */
HALO_XR_LAYER_CROSSHAIR = 5, /* the weapon's crosshair, where it aims */
HALO_XR_LAYER_COUNT = 6,
HALO_XR_LAYER_NAMES = 6, /* players' names, each a quad over a head */
HALO_XR_LAYER_COUNT = 7,
/* the names layer's slots, one a name, stacked from its top */
HALO_XR_MAXIMUM_NAMES = 8,
/* host_xr_copy_layer's clear_alpha: the source's alpha kept, made
opaque, or cut to a disc (premultiplied) */
@@ -147,6 +150,12 @@ struct halo_xr_submit
int crosshair;
float crosshair_size;
struct halo_xr_pose crosshair_pose;
/* the names layer was filled: its first name_count slots, each shown
this wide (metres, a slot's height following from its shape), here in
LOCAL space, facing the eyes */
int name_count;
float name_widths[HALO_XR_MAXIMUM_NAMES];
struct halo_xr_pose name_poses[HALO_XR_MAXIMUM_NAMES];
};
#endif
+9
View File
@@ -140,6 +140,15 @@ out (halo_vr_scope_pass). */
int halo_vr_scope(short local_player_index, float magnification, float position[3], float forward[3], float up[3],
float *vertical_field_of_view);
int halo_vr_scope_pass(void);
/* Players' names over their heads (hud.c, render.c): whether they are drawn
apart this frame; the slot a name is drawn into (left, top, right, bottom
of the game's 640x480); a name's place (world units, over the head),
giving its slot (-1: no more); and the slots, drawn on the cleared back
buffer, taken (it is cleared again) */
int halo_vr_names_apart(void);
void halo_vr_name_slot(int slot, short bounds[4]);
int halo_vr_add_name(const float point[3]);
void halo_vr_capture_names(void);
/* how much higher than a rifle's the weapon in hand has its scope's disc
(metres; render.c, before halo_vr_scope) */
void halo_vr_set_scope_raise(float metres);
+102
View File
@@ -134,6 +134,11 @@ static struct
float scope_size;
int crosshair;
int crosshair_captured; /* this frame (halo_vr_capture_crosshair) */
/* players' names this frame (halo_vr_add_name), and whether their
slots were taken (halo_vr_capture_names) */
int name_count;
float name_points[HALO_XR_MAXIMUM_NAMES][3];
int names_captured;
/* the barrel of the weapon as drawn (halo_vr_set_barrel): where shots,
the crosshair and the laser start */
float barrel[3];
@@ -242,6 +247,7 @@ the target), keeping the size it has in the HUD: this part of the HUD. */
static void cross3(const float a[3], const float b[3], float out[3]);
static float normalize3(float v[3]);
static int crosshair_pose(struct halo_xr_pose *pose, float *size);
static int name_pose(const float point[3], struct halo_xr_pose *pose, float *width);
static int weapon_hand_tracked(short local_player_index);
static void controller_axes(float forward[3], float up[3], float right[3]);
static void hand_offset(float out[3]);
@@ -1021,6 +1027,8 @@ void halo_vr_frame_begin(void)
vr.eyes_captured = 0;
vr.scope_captured = 0;
vr.crosshair_captured = 0;
vr.name_count = 0;
vr.names_captured = 0;
vr.overlay_mode = HALO_XR_OVERLAY_SCREEN;
vr.overlay_blend = 0;
if (vr.frame.recentred)
@@ -1087,6 +1095,20 @@ void halo_vr_present(unsigned int back_buffer_texture)
weapon aims */
if (vr.crosshair_captured && vr.overlay_mode == HALO_XR_OVERLAY_HEAD_LOCKED)
submit.crosshair = crosshair_pose(&submit.crosshair_pose, &submit.crosshair_size);
/* the names, each over its player's head */
if (vr.names_captured && submit.eyes && vr.overlay_mode == HALO_XR_OVERLAY_HEAD_LOCKED)
{
int name;
for (name = 0; name < vr.name_count; name++)
{
if (name_pose(vr.name_points[name], &submit.name_poses[submit.name_count],
&submit.name_widths[submit.name_count]))
{
submit.name_count++;
}
}
}
if (vr.scope_captured && submit.eyes)
{
submit.scope = 1;
@@ -1947,6 +1969,82 @@ static int crosshair_pose(struct halo_xr_pose *pose, float *size)
return 1;
}
/* ---------- players' names
Names over the heads (hud.c) are drawn into slots of a region of the back
buffer, one under another, before the HUD, and each slot is shown on a quad
of its own over its player's head, facing the eyes, as wide as the name
looks at the HUD's distance: in VR the HUD is a screen at one distance,
where names drawn on it hung before the eyes. */
#define NAME_SLOT_LEFT 240
#define NAME_SLOT_WIDTH 160
#define NAME_SLOT_HEIGHT 30
/* how large a name looks against the HUD's text (smaller: over a head, not
on the screen before the eyes) */
#define NAME_SCALE 0.6f
int halo_vr_names_apart(void)
{
return vr.active && halo_vr_stereo_frame();
}
void halo_vr_name_slot(int slot, short bounds[4])
{
bounds[0] = NAME_SLOT_LEFT;
bounds[1] = (short)(slot * NAME_SLOT_HEIGHT);
bounds[2] = NAME_SLOT_LEFT + NAME_SLOT_WIDTH;
bounds[3] = (short)((slot + 1) * NAME_SLOT_HEIGHT);
}
int halo_vr_add_name(const float point[3])
{
int slot = vr.name_count;
if (slot >= HALO_XR_MAXIMUM_NAMES)
return -1;
memcpy(vr.name_points[slot], point, sizeof(vr.name_points[slot]));
vr.name_count++;
return slot;
}
void halo_vr_capture_names(void)
{
if (vr.name_count > 0)
{
vr.names_captured = host_xr_copy_layer_region(HALO_XR_LAYER_NAMES, d3d8_back_buffer_texture(),
HALO_XR_COPY_ALPHA, NAME_SLOT_LEFT / 640.0f, 0.0f, (NAME_SLOT_LEFT + NAME_SLOT_WIDTH) / 640.0f,
HALO_XR_MAXIMUM_NAMES * NAME_SLOT_HEIGHT / 480.0f);
}
d3d8_clear_back_buffer(0.0f, 0.0f, 0.0f, 0.0f);
}
static int name_pose(const float point[3], struct halo_xr_pose *pose, float *width)
{
static const float world_up[3] = { 0.0f, 1.0f, 0.0f };
float x[3], y[3], z[3];
float distance;
float angle;
int axis;
world_to_local(point, pose->position);
for (axis = 0; axis < 3; axis++)
z[axis] = vr.frame.head.position[axis] - pose->position[axis];
distance = normalize3(z);
/* (facing the eyes, upright) */
if (distance < 0.05f)
return 0;
cross3(world_up, z, x);
if (normalize3(x) < 1e-4f)
return 0;
cross3(z, x, y);
quaternion_from_axes(x, y, z, pose->orientation);
/* (as wide as the slot looks on the HUD, smaller) */
angle = (float)NAME_SLOT_WIDTH / 640.0f * vr.hud_width / vr.hud_distance * NAME_SCALE;
*width = 2.0f * distance * tanf(angle * 0.5f);
return 1;
}
/* ---------- the scope
Zoomed in, the weapon shows its view on a disc over the barrel, like a
@@ -2175,6 +2273,10 @@ int halo_vr_scope(short local_player_index, float magnification, float position[
return 0;
}
int halo_vr_scope_pass(void) { return 0; }
int halo_vr_names_apart(void) { return 0; }
void halo_vr_name_slot(int slot, short bounds[4]) { (void)slot; (void)bounds; }
int halo_vr_add_name(const float point[3]) { (void)point; return -1; }
void halo_vr_capture_names(void) {}
void halo_vr_set_scope_raise(float metres) { (void)metres; }
void halo_vr_capture_scope(float left, float top, float right, float bottom)
{
+56 -13
View File
@@ -1033,13 +1033,6 @@ static short hud_player_names_setting(
}
}
#ifdef HALO_ANDROID
/* (the VR port's: in VR the HUD is a screen at a fixed distance before
the eyes, so names drawn on it hang there, not over the heads; none
until they are drawn into each eye's picture) */
if (halo_vr_active())
return _player_names_none;
#endif
return setting;
}
@@ -1134,7 +1127,8 @@ static void hud_draw_player_name(
long player_index,
boolean ally,
boolean indicator,
real enemy_range)
real enemy_range,
boolean vr_apart)
{
struct player_datum const *player = player_get(player_index);
long font_index = hud_get_font_index();
@@ -1164,10 +1158,35 @@ static void hud_draw_player_name(
/* (where the game draws a teammate's triangle) */
head_position.z += 0.30000001f;
matrix4x3_transform_point(&render.frustum.world_to_view, &head_position, &view_position);
if (!render_camera_view_to_screen(&render.camera, &render.frustum, &view_position, &screen_position))
return;
x = (short)fast_ftol(screen_position.x) - render.camera.viewport_bounds.x0;
y = (short)fast_ftol(screen_position.y) - render.camera.viewport_bounds.y0;
#ifdef HALO_ANDROID
if (vr_apart)
{
/* (VR: in a slot of its own, shown over the head: halo_vr.h) */
float point[3];
short slot_bounds[4];
int slot;
point[0] = head_position.x;
point[1] = head_position.y;
point[2] = head_position.z + (indicator ? 0.15f : 0.0f);
slot = halo_vr_add_name(point);
if (slot < 0)
return;
halo_vr_name_slot(slot, slot_bounds);
x = (short)((slot_bounds[0] + slot_bounds[2]) / 2);
y = (short)(slot_bounds[3] - 4);
/* (an ally's whole at any distance it is drawn at) */
view_position.z = -1.0f;
indicator = FALSE;
}
else
#endif
{
if (!render_camera_view_to_screen(&render.camera, &render.frustum, &view_position, &screen_position))
return;
x = (short)fast_ftol(screen_position.x) - render.camera.viewport_bounds.x0;
y = (short)fast_ftol(screen_position.y) - render.camera.viewport_bounds.y0;
}
if (indicator)
{
struct bitmap_data *bitmap = bitmap_group_get_bitmap_from_sequence(
@@ -1209,8 +1228,32 @@ static void hud_draw_player_name(
return;
}
static void hud_draw_player_names_now(
boolean vr_apart);
static void hud_draw_player_names(
void)
{
#ifdef HALO_ANDROID
/* (VR: apart, before the HUD: hud_draw_player_names_vr) */
if (halo_vr_active())
return;
#endif
hud_draw_player_names_now(FALSE);
}
#ifdef HALO_ANDROID
/* VR: the names drawn into their slots, each then shown over its player's
head (render.c, before the HUD; halo_vr_names_apart) */
void hud_draw_player_names_vr(
void)
{
hud_draw_player_names_now(TRUE);
}
#endif
static void hud_draw_player_names_now(
boolean vr_apart)
{
short setting = hud_player_names_setting();
long player_index = local_player_get_player_index(render.local_player_index);
@@ -1235,7 +1278,7 @@ static void hud_draw_player_names(
continue;
if ((ally && setting == _player_names_enemies) || (!ally && setting == _player_names_allies))
continue;
hud_draw_player_name(iterator.datum_index, ally, ally && indicators, enemy_range);
hud_draw_player_name(iterator.datum_index, ally, ally && indicators, enemy_range, vr_apart);
}
return;
+5
View File
@@ -29,6 +29,11 @@ struct hud_scripted_globals_definition
/* ---------- prototypes/HUD.C */
#ifdef HALO_ANDROID
#ifdef HALO_ANDROID
/* VR: players' names drawn into their slots (halo_vr_names_apart) */
void hud_draw_player_names_vr(
void);
#endif
void hud_draw_crosshair(
void);
#endif
+8
View File
@@ -853,6 +853,14 @@ static void render_vr_overlay(
parameters.suppress_clear = TRUE;
parameters.window_index = render.window_index;
if (!menus_active && halo_vr_names_apart())
{
/* the players' names, each to be shown over its head */
rasterizer_window_begin(&parameters);
hud_draw_player_names_vr();
rasterizer_window_end();
halo_vr_capture_names();
}
if (!menus_active && halo_vr_crosshair_apart())
{
/* the weapon's crosshair alone, taken to where the weapon aims */