Compare commits

...
Author SHA1 Message Date
Stefan MaierandClaude Opus 5.5 827eb0fc1d The keyboard gets the menu pointer's clicks; its BACK and ENTER hints click
The menus' mouse handling took the pointer's clicks each frame before the
keyboard asked for them, so the keyboard never saw one: with the keyboard
up the pointer is now the keyboard's alone. Clicking the hints under it
(B = BACK, A = ENTER, in its picture) cancels or finishes as Done does.
The pointer counts as moved past 3 pixels, not a hand's tremor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 12:47:51 +02:00
Stefan MaierandClaude Opus 5.5 a482a5f5bb The on-screen keyboard follows the menu pointer in VR
The key under the controller's pointer is picked and a click types it.
In the menus the triggers no longer reach the game as the gamepad's (they
click the pointer; on the keyboard they also moved its cursor).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 12:42:57 +02:00
Stefan MaierandClaude Opus 5.5 81f321f959 Of overlapping screen keys, the one whose icon is nearest on the left
A key's label is a text box often far wider than its text, reaching over
the next key (Back pressed Delete). Of the keys under the pointer, the one
whose icon begins nearest on its left is taken: each key from its icon to
the next one's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 12:30:20 +02:00
Stefan MaierandClaude Opus 5.5 f97ff9219f Menu pointer: narrower keys, the click where the trigger began to move
The screen keys' margin in VR was wide enough to reach the next key (Back
pressed Delete): now 2 pixels to the sides and 4 above and below, with a
label at most 25 pixels off. A click is taken where the controller
pointed as its trigger began to move (past a tenth), not a fixed time
before, which could still be on the key it just left.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 12:27:28 +02:00
Stefan MaierandClaude Opus 5.5 85e79a067a Steadier menu pointer clicks in VR
A click is taken where the controller pointed a few frames before the
trigger went down (pulling it tips the controller), and in VR a screen
key's button takes a label up to 40 pixels off and a margin of 6 about it,
so the keys under the menus (A, B, Back) are hit as readily as items.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 12:23:28 +02:00
Stefan MaierandClaude Opus 5.5 effeda1b7c Point at the menus with a controller in VR
The desktop builds' mouse in the menus (halo_ui_pointer.h) now comes, on
Android in VR, from a controller: where its aim's line meets the menus'
screen fixed in the room is the pointer, the trigger clicks (the hand
pressed last points; a click ticks its rumble), and a ringed dot shows
where it points. The item under it takes the focus and a click presses A,
as with the mouse; the online games and host options panels take it too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 11:56:05 +02:00
Stefan Maier 6299503191 Merge branch 'feature/bots-team' 2026-10-03 11:10:25 +02:00
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
11 changed files with 581 additions and 28 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
+15
View File
@@ -140,6 +140,21 @@ 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);
/* The menus' pointer (d3d8_gl.c's halo_ui_pointer_update): where a
controller points on the menus' screen, in their 640x480, whether that
moved since asked, and the trigger's clicks since (nonzero while it points
at the screen); and where it points this frame, to draw it (render.c) */
int halo_vr_menu_pointer(int *x, int *y, int *click_x, int *click_y, int *moved, int *clicks);
int halo_vr_menu_pointer_at(int *x, int *y);
/* 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);
+13 -3
View File
@@ -1365,11 +1365,21 @@ HRESULT WINAPI Direct3D_CreateDevice(UINT adapter, D3DDEVTYPE device_type, void
/* ---------- the menus' pointer */
#ifdef HALO_ANDROID
/* the controller pointing at the menus' screen in VR (halo_vr_menu_pointer) */
int halo_ui_pointer_update(int menus_active, struct halo_ui_pointer *pointer)
{
(void)menus_active;
(void)pointer;
return 0;
int x, y, click_x, click_y, moved, clicks;
if (!menus_active || !halo_vr_menu_pointer(&x, &y, &click_x, &click_y, &moved, &clicks))
return 0;
memset(pointer, 0, sizeof(*pointer));
pointer->x = (short)(x - (halo_screen_width() - 640) / 2);
pointer->y = (short)y;
pointer->click_x = (short)(click_x - (halo_screen_width() - 640) / 2);
pointer->click_y = (short)click_y;
pointer->moved = (unsigned char)(moved != 0);
pointer->left_clicks = (unsigned char)(clicks < 255 ? clicks : 255);
return 1;
}
#else
/* a point in the window, as SDL reports it, in the menus' coordinates: the
+235
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,209 @@ static int crosshair_pose(struct halo_xr_pose *pose, float *size)
return 1;
}
/* ---------- the menus' pointer
In the menus (the screen fixed in the room), a controller points at the
screen and its trigger clicks, as the desktop builds' mouse does
(halo_ui_pointer.h): where its aim's line meets the screen, in the menus'
640x480. The hand whose trigger was pressed last points; a click gives a
short tick of the hand's rumble. */
static struct
{
int valid;
float x, y;
float reported_x, reported_y;
int hand;
int trigger_down[HALO_XR_HAND_COUNT];
int clicks;
/* a click is taken where it pointed as the trigger began to move
(pulling it through tips the controller) */
int pulling[HALO_XR_HAND_COUNT];
float pull_x[HALO_XR_HAND_COUNT], pull_y[HALO_XR_HAND_COUNT];
float click_x, click_y;
} menu_pointer;
static void menu_pointer_update(void)
{
static const float forward[3] = { 0.0f, 0.0f, -1.0f };
const struct halo_xr_hand *hand;
float direction[3], t, hit_x, hit_y, width, height;
int index;
menu_pointer.valid = 0;
if (!vr.active || vr.last_overlay_mode != HALO_XR_OVERLAY_SCREEN || !vr.frame.focused)
{
menu_pointer.trigger_down[0] = menu_pointer.trigger_down[1] = 0;
menu_pointer.pulling[0] = menu_pointer.pulling[1] = 0;
return;
}
/* (the hand that clicked last points; a click counts as it goes down) */
for (index = 0; index < HALO_XR_HAND_COUNT; index++)
{
int down = vr.frame.hands[index].active && (vr.frame.hands[index].buttons & HALO_XR_BUTTON_TRIGGER);
if (down && !menu_pointer.trigger_down[index])
{
menu_pointer.hand = index;
menu_pointer.clicks++;
}
menu_pointer.trigger_down[index] = down;
}
hand = &vr.frame.hands[menu_pointer.hand];
if (!hand->active || !(hand->flags & HALO_XR_POSE_VALID))
{
hand = &vr.frame.hands[1 - menu_pointer.hand];
if (!hand->active || !(hand->flags & HALO_XR_POSE_VALID))
return;
}
quaternion_rotate(hand->aim.orientation, forward, direction);
if (direction[2] > -1e-3f)
return;
t = (-vr.screen_distance - hand->aim.position[2]) / direction[2];
if (t <= 0.0f)
return;
hit_x = hand->aim.position[0] + direction[0] * t;
hit_y = hand->aim.position[1] + direction[1] * t;
width = vr.screen_width;
height = vr.screen_width * 0.75f;
menu_pointer.x = (hit_x / width + 0.5f) * 640.0f;
menu_pointer.y = (0.5f - hit_y / height) * 480.0f;
menu_pointer.valid = menu_pointer.x >= 0.0f && menu_pointer.x < 640.0f && menu_pointer.y >= 0.0f &&
menu_pointer.y < 480.0f;
/* (the click where the trigger began to move, if it pointed then) */
{
int pulling = vr.frame.hands[menu_pointer.hand].trigger > 0.1f;
if (pulling && !menu_pointer.pulling[menu_pointer.hand] && menu_pointer.valid)
{
menu_pointer.pull_x[menu_pointer.hand] = menu_pointer.x;
menu_pointer.pull_y[menu_pointer.hand] = menu_pointer.y;
}
if (!pulling || !menu_pointer.pulling[menu_pointer.hand])
{
menu_pointer.click_x = menu_pointer.x;
menu_pointer.click_y = menu_pointer.y;
}
else
{
menu_pointer.click_x = menu_pointer.pull_x[menu_pointer.hand];
menu_pointer.click_y = menu_pointer.pull_y[menu_pointer.hand];
}
if (!pulling || menu_pointer.valid)
menu_pointer.pulling[menu_pointer.hand] = pulling;
}
}
int halo_vr_menu_pointer(int *x, int *y, int *click_x, int *click_y, int *moved, int *clicks)
{
menu_pointer_update();
*click_x = (int)menu_pointer.click_x;
*click_y = (int)menu_pointer.click_y;
*clicks = menu_pointer.clicks;
if (menu_pointer.clicks)
host_xr_haptic(menu_pointer.hand, 0.3f, 0.02f);
menu_pointer.clicks = 0;
if (!menu_pointer.valid)
return 0;
*x = (int)menu_pointer.x;
*y = (int)menu_pointer.y;
/* (moved past a hand's tremor: a few pixels) */
*moved = fabsf(menu_pointer.x - menu_pointer.reported_x) >= 3.0f ||
fabsf(menu_pointer.y - menu_pointer.reported_y) >= 3.0f;
if (*moved)
{
menu_pointer.reported_x = menu_pointer.x;
menu_pointer.reported_y = menu_pointer.y;
}
return 1;
}
int halo_vr_menu_pointer_at(int *x, int *y)
{
if (!menu_pointer.valid)
return 0;
*x = (int)menu_pointer.x;
*y = (int)menu_pointer.y;
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 +2400,16 @@ 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_menu_pointer(int *x, int *y, int *click_x, int *click_y, int *moved, int *clicks)
{
(void)x; (void)y; (void)click_x; (void)click_y; (void)moved; *clicks = 0;
return 0;
}
int halo_vr_menu_pointer_at(int *x, int *y) { (void)x; (void)y; 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)
{
+3 -2
View File
@@ -573,9 +573,10 @@ static void vr_gamepad_state(XINPUT_GAMEPAD *pad)
if (left->buttons & HALO_XR_BUTTON_MENU) pad->wButtons |= XINPUT_GAMEPAD_START;
if (left->buttons & HALO_XR_BUTTON_THUMBSTICK) pad->wButtons |= XINPUT_GAMEPAD_LEFT_THUMB;
if (right->buttons & HALO_XR_BUTTON_THUMBSTICK) pad->wButtons |= XINPUT_GAMEPAD_RIGHT_THUMB;
if ((BYTE)(right->trigger * 255.0f) > pad->bAnalogButtons[XINPUT_GAMEPAD_RIGHT_TRIGGER])
/* (in the menus the triggers click the pointer: halo_vr_menu_pointer) */
if (!menus && (BYTE)(right->trigger * 255.0f) > pad->bAnalogButtons[XINPUT_GAMEPAD_RIGHT_TRIGGER])
pad->bAnalogButtons[XINPUT_GAMEPAD_RIGHT_TRIGGER] = (BYTE)(right->trigger * 255.0f);
if ((BYTE)(left->trigger * 255.0f) > pad->bAnalogButtons[XINPUT_GAMEPAD_LEFT_TRIGGER])
if (!menus && (BYTE)(left->trigger * 255.0f) > pad->bAnalogButtons[XINPUT_GAMEPAD_LEFT_TRIGGER])
pad->bAnalogButtons[XINPUT_GAMEPAD_LEFT_TRIGGER] = (BYTE)(left->trigger * 255.0f);
if (menus)
{
+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
+101 -7
View File
@@ -677,6 +677,9 @@ struct widget_instance;
#include "text/text_group.h"
#include "text/unicode.h"
#include "ui_widget.h"
#ifdef HALO_ANDROID
#include "halo_vr.h"
#endif
/* ---------- constants */
@@ -5269,6 +5272,8 @@ struct ui_mouse_target
rectangle2d bounds;
short kind;
short button_index;
/* (a button's: where its icon begins) */
short icon_x0;
};
static struct ui_mouse_target ui_mouse_targets[UI_MOUSE_MAXIMUM_TARGETS];
@@ -5403,6 +5408,7 @@ static void ui_mouse_note_target(
rectangle2d bounds = definition->bounds;
short button_index;
short kind;
short icon_x0;
if (!ui_mouse_noting_targets ||
ui_mouse_target_count >= UI_MOUSE_MAXIMUM_TARGETS ||
@@ -5415,13 +5421,22 @@ static void ui_mouse_note_target(
bounds.y0 += offset.y;
bounds.y1 += offset.y;
button_index = ui_mouse_key_button(widget);
icon_x0 = bounds.x0;
if (button_index != NONE)
{
/* the icon and its label: the text beginning just right of it */
struct widget_instance *sibling;
rectangle2d label_bounds;
short best_distance = 17;
#ifdef HALO_ANDROID
/* (VR: a controller points less steadily than a mouse: the label a
little further off is the button's too) */
short most_distance = halo_vr_active() ? 25 : 17;
#else
short most_distance = 17;
#endif
best_distance = most_distance;
kind = _ui_mouse_target_button;
for (sibling = parent ? parent->child : NULL; sibling; sibling = sibling->next)
{
@@ -5445,12 +5460,23 @@ static void ui_mouse_note_target(
label_bounds = sibling_bounds;
}
}
if (best_distance < 17)
if (best_distance < most_distance)
{
bounds.x1 = MAX(bounds.x1, label_bounds.x1);
bounds.y0 = MIN(bounds.y0, label_bounds.y0);
bounds.y1 = MAX(bounds.y1, label_bounds.y1);
}
#ifdef HALO_ANDROID
/* (and a margin about it: above and below, little to the sides,
where the next key is) */
if (halo_vr_active())
{
bounds.x0 -= 2;
bounds.x1 += 2;
bounds.y0 -= 4;
bounds.y1 += 4;
}
#endif
}
else if (parent && ui_mouse_list_shows_several(parent))
{
@@ -5482,6 +5508,7 @@ static void ui_mouse_note_target(
target->bounds = bounds;
target->kind = kind;
target->button_index = button_index;
target->icon_x0 = icon_x0;
return;
}
@@ -5492,16 +5519,33 @@ static struct ui_mouse_target *ui_mouse_target_at(
short y)
{
long index;
struct ui_mouse_target *button = NULL;
for (index = ui_mouse_target_count - 1; index >= 0; index--)
{
rectangle2d const *bounds = &ui_mouse_targets[index].bounds;
struct ui_mouse_target *target = &ui_mouse_targets[index];
rectangle2d const *bounds = &target->bounds;
if (x >= bounds->x0 && x < bounds->x1 && y >= bounds->y0 && y < bounds->y1)
return &ui_mouse_targets[index];
if (!(x >= bounds->x0 && x < bounds->x1 && y >= bounds->y0 && y < bounds->y1))
continue;
/* port: a key's label's text box can be far wider than its text, over
the next key: of keys under the point, the one whose icon is nearest
on its left (each key from its icon to the next one's) */
if (target->kind == _ui_mouse_target_button)
{
if (!button ||
(target->icon_x0 <= x + 2 && (button->icon_x0 > x + 2 || target->icon_x0 > button->icon_x0)))
{
button = target;
}
continue;
}
if (!button)
return target;
break;
}
return NULL;
return button;
}
static boolean ui_mouse_widget_has_focus(
@@ -5692,6 +5736,8 @@ enum
MAXIMUM_SHOWN_PUBLIC_GAMES = 32,
/* the online games panel's rows: "enter a code" and the games */
ONLINE_PANEL_ROWS = 11,
/* (the rows before the games: "enter a code") */
ONLINE_PANEL_FIXED_ROWS = 1,
};
static struct
@@ -5932,6 +5978,48 @@ static boolean ui_join_code_take_event(
return FALSE;
}
/* the pointer over the VR port's panels: the row under it chosen, and a
click on it as A (TRUE: a panel is open and took it) */
static boolean ui_join_code_take_pointer(
struct halo_ui_pointer const *pointer)
{
struct event_record press;
short row;
/* (a click where it was clicked, which may be a moment before) */
short x = pointer->left_clicks ? pointer->click_x : pointer->x;
short y = pointer->left_clicks ? pointer->click_y : pointer->y;
csmemset(&press, 0, sizeof(press));
press.type = _event_type_button;
press.data.button.index = _gamepad_analog_button_a;
press.data.button.value = 1;
if (ui_join_code_globals.panel_open)
{
if (x >= 48 && x < 592 && y >= 128 && y < 128 + ONLINE_PANEL_ROWS * 22)
{
row = (short)(ui_join_code_globals.scroll + (y - 128) / 22);
if (row < ui_join_code_globals.game_count + ONLINE_PANEL_FIXED_ROWS)
{
ui_join_code_globals.selection = row;
if (pointer->left_clicks)
ui_join_code_panel_event(&press);
}
}
return TRUE;
}
if (ui_join_code_globals.host_panel_open)
{
if (x >= 148 && x < 492 && y >= 187 && y < 187 + HOST_PANEL_ROWS * 30)
{
ui_join_code_globals.host_selection = (short)((y - 187) / 30);
if (pointer->left_clicks)
ui_join_code_host_panel_event(&press);
}
return TRUE;
}
return FALSE;
}
/* the keyboard closed: the code typed, looked up */
static void ui_join_code_update(
void)
@@ -6186,8 +6274,9 @@ static void ui_widgets_process_mouse(
struct ui_mouse_target *target;
short controller_index = 0;
if (!halo_ui_pointer_update(ui_mouse_menus_active(), &pointer) ||
virtual_keyboard_active())
/* (the keyboard takes the pointer itself: virtual_keyboard.c) */
if (virtual_keyboard_active() ||
!halo_ui_pointer_update(ui_mouse_menus_active(), &pointer))
{
ui_mouse_press_count = 0;
ui_mouse_hover_pending = FALSE;
@@ -6195,6 +6284,11 @@ static void ui_widgets_process_mouse(
}
else
{
#ifdef HALO_ANDROID
/* (the VR port's panels over the menus take the pointer themselves) */
if (ui_join_code_take_pointer(&pointer))
return;
#endif
if (pointer.moved)
{
ui_mouse_hover_pending = TRUE;
+69
View File
@@ -1166,6 +1166,25 @@ static boolean virtual_keyboard_select(
return TRUE;
}
#ifdef HALO_ANDROID
/* the key drawn at a point of the menus' 640x480, or NONE */
static short virtual_keyboard_key_at(
short x,
short y)
{
short key;
for (key = 0; key < NUMBER_OF_VIRTUAL_KEYS; key++)
{
rectangle2d const *bounds = &keyboard_rect[key];
if (x >= bounds->x0 && x < bounds->x1 && y >= bounds->y0 && y < bounds->y1)
return key;
}
return NONE;
}
#endif
static void virtual_keyboard_process_internal(
void)
{
@@ -1304,6 +1323,56 @@ static void virtual_keyboard_process_internal(
}
}
#ifdef HALO_ANDROID
/* the VR port's: the controller's pointer (halo_ui_pointer.h) picks the
key under it, and its click types it */
{
struct halo_ui_pointer pointer;
if (halo_ui_pointer_update(TRUE, &pointer))
{
short x = pointer.left_clicks ? pointer.click_x : pointer.x;
short y = pointer.left_clicks ? pointer.click_y : pointer.y;
short key = virtual_keyboard_key_at(x, y);
if (key != NONE && (pointer.moved || pointer.left_clicks))
{
short row, column;
boolean found = FALSE;
for (row = 0; row < VIRTUAL_KEYBOARD_ROW_COUNT && !found; row++)
{
for (column = 0; column < VIRTUAL_KEYBOARD_COLUMN_COUNT && !found; column++)
{
if (virtual_keyboard_layout_table[row][column] == key)
{
virtual_keyboard_globals.row = row;
virtual_keyboard_globals.column = column;
found = TRUE;
}
}
}
}
if (pointer.left_clicks && key != NONE)
action = _event_key_select;
/* (the keys' hints under the keyboard, in its picture: B = BACK
cancels, A = ENTER finishes, as Done does) */
if (pointer.left_clicks && key == NONE && y >= 412 && y < 448)
{
if (x >= 380 && x < 480)
{
action = _event_cancel;
}
else if (x >= 488 && x < 600)
{
virtual_keyboard_globals.row = 0;
virtual_keyboard_globals.column = 0;
action = _event_key_select;
}
}
}
}
#endif
if (action != NONE)
{
virtual_keyboard_globals.last_key = virtual_keyboard_layout_table[
+30
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 */
@@ -866,6 +874,28 @@ static void render_vr_overlay(
interface_draw_screen();
halo_screen_ui_offset(TRUE);
render_ui_widgets(window->local_player_index, &window->rasterizer_camera.viewport_bounds);
if (menus_active)
{
/* the controller's pointer on the menus' screen: a dot, ringed */
int pointer_x, pointer_y;
if (halo_vr_menu_pointer_at(&pointer_x, &pointer_y))
{
rectangle2d dot;
pointer_x -= (int)(halo_screen_width() - 640) / 2;
dot.x0 = (short)(pointer_x - 5);
dot.x1 = (short)(pointer_x + 5);
dot.y0 = (short)(pointer_y - 5);
dot.y1 = (short)(pointer_y + 5);
draw_quad(&dot, 0xE0102030);
dot.x0 += 2;
dot.x1 -= 2;
dot.y0 += 2;
dot.y1 -= 2;
draw_quad(&dot, 0xFF8FD3FF);
}
}
halo_screen_ui_offset(FALSE);
rasterizer_window_end();