mirror of
https://github.com/Cesarus85/ringfall-mjolnir-vr.git
synced 2026-10-17 15:00:27 +02:00
2622 lines
89 KiB
C
2622 lines
89 KiB
C
/*
|
|
VR.C
|
|
|
|
The game's side of VR (port/linux/include/halo_vr.h has the outline). On
|
|
Android the host runs the OpenXR session (port/android/host/host_xr.c) and
|
|
this file turns its poses into the game's cameras and facing; elsewhere
|
|
these are stubs that report VR as off.
|
|
|
|
Frames of reference. The headset reports poses in its LOCAL space: metres,
|
|
+x right, +y up, -z forward, the origin and heading where the user last
|
|
recentred. The game's player stands in that space's place: its origin is
|
|
the game's camera position, and its forward is the "body" yaw kept here.
|
|
The right stick turns the body; the head turns on top of it, so the facing
|
|
the game uses (halo_vr_aim) is the body's yaw plus the head's.
|
|
*/
|
|
|
|
#include "halo_vr.h"
|
|
|
|
#ifdef HALO_ANDROID
|
|
|
|
#include "platform.h"
|
|
#include "port_config.h"
|
|
#include "guest_host.h"
|
|
#include "halo_xr_abi.h"
|
|
|
|
#include <SDL3/SDL.h>
|
|
#include <math.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#define METRES_PER_WORLD_UNIT 3.048f
|
|
#define PI 3.14159265358979f
|
|
#define GRENADE_SAMPLES 24
|
|
/* the facing the game changes itself by more than this (a respawn, a
|
|
teleport, a vehicle's seat) turns the body with it */
|
|
#define FACING_JUMP (10.0f * PI / 180.0f)
|
|
|
|
static struct
|
|
{
|
|
int active;
|
|
struct halo_xr_info info;
|
|
struct halo_xr_frame frame;
|
|
int frame_begun;
|
|
/* the hands as last seen with the app in focus (the system menu over
|
|
it reports none): held still meanwhile */
|
|
struct halo_xr_hand focused_hands[HALO_XR_HAND_COUNT];
|
|
int focused_hands_valid;
|
|
int eyes_captured; /* bit per eye, this frame */
|
|
int overlay_mode; /* HALO_XR_OVERLAY_*, this frame */
|
|
int overlay_blend;
|
|
int last_overlay_mode;
|
|
int last_frame_stereo; /* the last frame drew the eyes */
|
|
|
|
/* where in LOCAL space the game's camera is: the head's position when
|
|
the view last recentred (vr_recentre) */
|
|
float origin[3];
|
|
int origin_valid;
|
|
float game_camera[3]; /* the game's camera this frame (world units) */
|
|
int recentre_frames; /* recentre on this many more frames */
|
|
|
|
/* vr.log_performance: sums over the frames since the last report */
|
|
int log_performance;
|
|
unsigned long long report_start_us;
|
|
unsigned long long frame_start_us;
|
|
unsigned long long eyes_start_us;
|
|
unsigned long long eyes_us;
|
|
unsigned long long work_us;
|
|
unsigned long long wait_us;
|
|
int report_frames;
|
|
int report_stereo_frames;
|
|
|
|
/* the view's turning, gathered for halo_vr_look_delta */
|
|
float look_yaw;
|
|
float look_pitch;
|
|
float look_turned_yaw;
|
|
float look_turned_pitch;
|
|
int look_valid;
|
|
|
|
float body_yaw;
|
|
int aim_synced;
|
|
float last_yaw;
|
|
float last_pitch;
|
|
/* Seated (halo_vr_set_seat): the seat's kind, its vehicle's heading as
|
|
the game has it (each tick) and as drawn (each frame), and the body's
|
|
turn from that heading; the body turns with the vehicle. */
|
|
/* the test aid's ask (vehicle_spawn_update) */
|
|
int vehicle_spawn;
|
|
int vehicle_spawn_asked;
|
|
int vehicle_spawn_latched;
|
|
unsigned long long vehicle_spawn_held_us;
|
|
int seat_mode;
|
|
int seat_armed;
|
|
float seat_yaw;
|
|
float seat_frame_yaw;
|
|
int seat_frame_valid;
|
|
float seat_turn;
|
|
int multiview; /* vr.multiview: both eyes in one draw */
|
|
int multiview_logged;
|
|
int multiview_logged_views;
|
|
int vehicle_first_person;
|
|
int turret_handles; /* a gun's barrel rises as its handles go down */
|
|
float gunner_camera_back; /* metres */
|
|
float driver_camera_forward; /* metres */
|
|
float enclosed_seat_raise[5]; /* metres, by HALO_VR_HULL_* */
|
|
float glass_strength;
|
|
float vehicle_steer_angle; /* radians */
|
|
float vehicle_gun_steer_angle; /* radians: steering that aims guns too */
|
|
float vehicle_pitch_angle;
|
|
/* the wheel of an open vehicle (a Warthog's: wheel_update) */
|
|
int vehicle_wheel; /* vr.vehicle_wheel: the hands turn it */
|
|
float vehicle_wheel_angle; /* radians: turned this far, it steers fully */
|
|
int wheel_valid; /* where it is, as last drawn */
|
|
float wheel_hands_drawn[2][3]; /* the driver's drawn hands on it, from the camera (world units) */
|
|
int wheel_held[HALO_XR_HAND_COUNT];
|
|
int wheel_hands; /* the hands that held it last frame (bits) */
|
|
float wheel_reference; /* their turn about the column last frame */
|
|
float wheel_turn; /* the wheel's turn (radians, anticlockwise as the driver sees it) */
|
|
int wheel_logged;
|
|
float vehicle_steer_curve; /* the stick's deflection to this power */
|
|
float vehicle_steer_smoothing; /* seconds */
|
|
float steer[2]; /* the steering as smoothed: aside, up (-1..1) */
|
|
unsigned long long steer_last_us;
|
|
/* the comfort vignette: how strong at most, now, and the heading it
|
|
last saw */
|
|
float comfort_vignette;
|
|
float vignette;
|
|
float vignette_last_yaw;
|
|
int vignette_yaw_valid;
|
|
unsigned long long vignette_last_us;
|
|
int snap_latched;
|
|
unsigned long long last_frame_ms;
|
|
|
|
/* settings (config.toml, [vr]) */
|
|
unsigned long settings_read_at; /* config_changes() when the live ones were read */
|
|
int smooth_turn;
|
|
float snap_turn_degrees;
|
|
float smooth_turn_degrees_per_second;
|
|
int cinematics_3d;
|
|
float hud_distance;
|
|
float hud_width;
|
|
float screen_distance;
|
|
float screen_width;
|
|
int positional_tracking;
|
|
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];
|
|
unsigned long barrel_frame;
|
|
/* the barrel's direction in the weapon camera's frame (forward, left,
|
|
up): first-person models point their barrels in towards the screen's
|
|
centre, and the camera is turned so that they point where the
|
|
controller aims */
|
|
float barrel_direction[3];
|
|
int barrel_direction_valid;
|
|
float aim_distance; /* metres from the muzzle to what the weapon aims at */
|
|
unsigned long aim_distance_frame;
|
|
/* the scope this frame: drawn (halo_vr_scope to halo_vr_capture_scope),
|
|
captured, and where its disc is in LOCAL space */
|
|
int scope_pass;
|
|
int scope_captured;
|
|
struct halo_xr_pose scope_pose;
|
|
int motion_tracker_on_wrist;
|
|
float motion_tracker_size;
|
|
float motion_tracker_turn;
|
|
float motion_tracker_right;
|
|
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 muzzle_distance;
|
|
/* the left hand on the weapon (halo_vr_two_hand) */
|
|
int two_hand_gripping;
|
|
int two_hand_latched; /* the grip is held (on the weapon or not) */
|
|
int two_hand_valid;
|
|
float two_hand_reach; /* metres from the foregrip a closing grip takes the weapon */
|
|
/* the grenade in the left hand (grenade_update, vr.grenade_throw_speed):
|
|
the left trigger held, the hand's last positions (LOCAL), and the throw
|
|
as it let go */
|
|
float grenade_throw_speed;
|
|
float grenade_scale;
|
|
float grenade_offset[3]; /* in the grip's axes: right, up, back (metres) */
|
|
float grenade_down; /* down the knuckles (the aim's -y; metres) */
|
|
float grenade_back; /* back along the fingers (the aim's +z; metres) */
|
|
int grenade_held;
|
|
float grenade_sample[GRENADE_SAMPLES][3];
|
|
unsigned long long grenade_sample_us[GRENADE_SAMPLES];
|
|
int grenade_sample_count;
|
|
int grenade_thrown;
|
|
float grenade_hand[3];
|
|
float grenade_direction[3];
|
|
float grenade_strength;
|
|
unsigned long long grenade_released_us;
|
|
/* melee by swinging a hand (vr.melee_swing_speed) */
|
|
float melee_swing_speed;
|
|
float hand_last[HALO_XR_HAND_COUNT][3];
|
|
int hand_last_valid[HALO_XR_HAND_COUNT];
|
|
unsigned long long hand_last_us;
|
|
unsigned long long melee_until_us;
|
|
unsigned long long melee_ready_us;
|
|
/* until when a melee attack aims where the head looks: its damage is
|
|
dealt some ticks into the attack, along the unit's aim */
|
|
unsigned long long melee_aim_until_us;
|
|
int melee_grip_down;
|
|
float melee_strength; /* 0..1, the last attack's swing */
|
|
unsigned long long melee_jab_start_us; /* the right grip's attack: the weapon jabs */
|
|
/* the laser along the barrel, for the frame it was set in */
|
|
unsigned long frame_number;
|
|
unsigned long laser_frame;
|
|
float laser_start[3];
|
|
float laser_direction[3];
|
|
float two_hand_reference[3]; /* left wrist from right, in the weapon's forward, right, up */
|
|
float lean_limit;
|
|
float crouch_limit;
|
|
float rise_limit;
|
|
} vr;
|
|
|
|
/* d3d8_gl.c */
|
|
unsigned int d3d8_back_buffer_texture(void);
|
|
int d3d8_multiview_supported(void);
|
|
int d3d8_multiview_begin(const float rows[4][4], const float resolve_rect[2][4]);
|
|
void d3d8_multiview_rows(const float rows[4][4]);
|
|
void d3d8_multiview_depth(float a, float b);
|
|
unsigned int d3d8_multiview_end(void);
|
|
void d3d8_clear_back_buffer(float red, float green, float blue, float alpha);
|
|
void d3d8_clear_back_buffer_region(float left, float top, float right, float bottom);
|
|
|
|
/* The motion tracker, the HUD's one element at its bottom left, moves from
|
|
the HUD to the left wrist (host_xr.c draws it there): this part of the
|
|
640x480 HUD (left, top, right, bottom), square as the HUD shows it, around
|
|
its disc (x 49-131, y 362-444) and its range below. */
|
|
#define WRIST_REGION_LEFT (38.0f / 640.0f)
|
|
#define WRIST_REGION_TOP (352.0f / 480.0f)
|
|
#define WRIST_REGION_RIGHT (142.0f / 640.0f)
|
|
#define WRIST_REGION_BOTTOM (456.0f / 480.0f)
|
|
|
|
/* The weapon's crosshair, drawn at the HUD's centre, moves to where the
|
|
weapon aims, as far away as what it aims at (so the eyes focus on it with
|
|
the target), keeping the size it has in the HUD: this part of the HUD. */
|
|
#define CROSSHAIR_REGION_PIXELS 80.0f
|
|
#define CROSSHAIR_REGION_LEFT ((320.0f - CROSSHAIR_REGION_PIXELS * 0.5f) / 640.0f)
|
|
#define CROSSHAIR_REGION_TOP ((240.0f - CROSSHAIR_REGION_PIXELS * 0.5f) / 480.0f)
|
|
#define CROSSHAIR_REGION_RIGHT ((320.0f + CROSSHAIR_REGION_PIXELS * 0.5f) / 640.0f)
|
|
#define CROSSHAIR_REGION_BOTTOM ((240.0f + CROSSHAIR_REGION_PIXELS * 0.5f) / 480.0f)
|
|
/* how far it floats with nothing in the way, and its nearest, in metres */
|
|
#define CROSSHAIR_FARTHEST 60.0f
|
|
#define CROSSHAIR_NEAREST 0.5f
|
|
|
|
/* ---------- math */
|
|
|
|
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]);
|
|
|
|
static void quaternion_rotate(const float q[4], const float v[3], float out[3])
|
|
{
|
|
/* out = q v q^-1 */
|
|
float tx = 2.0f * (q[1] * v[2] - q[2] * v[1]);
|
|
float ty = 2.0f * (q[2] * v[0] - q[0] * v[2]);
|
|
float tz = 2.0f * (q[0] * v[1] - q[1] * v[0]);
|
|
|
|
out[0] = v[0] + q[3] * tx + (q[1] * tz - q[2] * ty);
|
|
out[1] = v[1] + q[3] * ty + (q[2] * tx - q[0] * tz);
|
|
out[2] = v[2] + q[3] * tz + (q[0] * ty - q[1] * tx);
|
|
}
|
|
|
|
/* a LOCAL-space vector as a game-space one, for the body's yaw */
|
|
static void local_to_game(const float v[3], float yaw, float out[3])
|
|
{
|
|
float forward = -v[2];
|
|
float left = -v[0];
|
|
float c = cosf(yaw), s = sinf(yaw);
|
|
|
|
out[0] = forward * c - left * s;
|
|
out[1] = forward * s + left * c;
|
|
out[2] = v[1];
|
|
}
|
|
|
|
static float wrap_angle(float angle)
|
|
{
|
|
while (angle > PI)
|
|
angle -= 2.0f * PI;
|
|
while (angle < -PI)
|
|
angle += 2.0f * PI;
|
|
return angle;
|
|
}
|
|
|
|
/* the head's yaw and pitch in LOCAL space (yaw counter-clockwise from
|
|
forward, seen from above, as the game's) */
|
|
static void head_angles(float *yaw, float *pitch)
|
|
{
|
|
static const float forward[3] = { 0.0f, 0.0f, -1.0f };
|
|
float direction[3];
|
|
|
|
quaternion_rotate(vr.frame.head.orientation, forward, direction);
|
|
*yaw = atan2f(-direction[0], -direction[2]);
|
|
*pitch = atan2f(direction[1], sqrtf(direction[0] * direction[0] + direction[2] * direction[2]));
|
|
}
|
|
|
|
/* seated, the vehicle's heading as last drawn (else as the game has it),
|
|
and the body turned with it */
|
|
static float seat_heading(void)
|
|
{
|
|
return vr.seat_frame_valid ? vr.seat_frame_yaw : vr.seat_yaw;
|
|
}
|
|
|
|
static void seat_body(void)
|
|
{
|
|
if (vr.seat_mode != HALO_VR_SEAT_NONE)
|
|
vr.body_yaw = wrap_angle(seat_heading() + vr.seat_turn);
|
|
}
|
|
|
|
/* seated, the way the head looks now is the vehicle's forward */
|
|
static void seat_face_forward(void)
|
|
{
|
|
float head_yaw, head_pitch;
|
|
|
|
head_angles(&head_yaw, &head_pitch);
|
|
vr.seat_turn = -head_yaw;
|
|
seat_body();
|
|
}
|
|
|
|
/* makes the head's position the game camera's, and the way it looks the
|
|
game's facing (halo_vr_aim syncs the body's yaw again), or seated, the
|
|
vehicle's forward */
|
|
static void vr_recentre(void)
|
|
{
|
|
if (!(vr.frame.head_flags & HALO_XR_POSE_VALID))
|
|
return;
|
|
memcpy(vr.origin, vr.frame.head.position, sizeof(vr.origin));
|
|
vr.origin_valid = 1;
|
|
vr.aim_synced = 0;
|
|
if (vr.seat_mode != HALO_VR_SEAT_NONE)
|
|
seat_face_forward();
|
|
}
|
|
|
|
/* the offset of a tracked point (an eye, a hand) from the game's camera, in
|
|
the game's axes (metres): the head's offset from the origin, kept within the
|
|
lean and crouch limits so the view stays near the player's body, plus the
|
|
point's own offset from the head */
|
|
/* the head's offset from the origin (LOCAL, metres), kept within the lean
|
|
and crouch limits */
|
|
static void clamped_head(float head[3])
|
|
{
|
|
int axis;
|
|
|
|
head[0] = head[1] = head[2] = 0.0f;
|
|
if (!vr.origin_valid)
|
|
vr_recentre();
|
|
if (vr.positional_tracking && vr.origin_valid)
|
|
{
|
|
float horizontal;
|
|
|
|
for (axis = 0; axis < 3; axis++)
|
|
head[axis] = vr.frame.head.position[axis] - vr.origin[axis];
|
|
horizontal = sqrtf(head[0] * head[0] + head[2] * head[2]);
|
|
if (horizontal > vr.lean_limit && horizontal > 0.0f)
|
|
{
|
|
head[0] *= vr.lean_limit / horizontal;
|
|
head[2] *= vr.lean_limit / horizontal;
|
|
}
|
|
if (head[1] < -vr.crouch_limit)
|
|
head[1] = -vr.crouch_limit;
|
|
if (head[1] > vr.rise_limit)
|
|
head[1] = vr.rise_limit;
|
|
}
|
|
}
|
|
|
|
static void tracked_offset(const float position[3], float out[3])
|
|
{
|
|
float head[3], local[3];
|
|
int axis;
|
|
|
|
clamped_head(head);
|
|
for (axis = 0; axis < 3; axis++)
|
|
local[axis] = head[axis] + position[axis] - vr.frame.head.position[axis];
|
|
local_to_game(local, vr.body_yaw, out);
|
|
}
|
|
|
|
/* a direction in the game's axes as one in LOCAL space */
|
|
static void game_to_local(const float v[3], float out[3])
|
|
{
|
|
float c = cosf(vr.body_yaw), s = sinf(vr.body_yaw);
|
|
float forward = v[0] * c + v[1] * s;
|
|
float left = -v[0] * s + v[1] * c;
|
|
|
|
out[0] = -left;
|
|
out[1] = v[2];
|
|
out[2] = -forward;
|
|
}
|
|
|
|
/* the inverse: a point in the game's world as one in LOCAL space */
|
|
static void world_to_local(const float world[3], float local[3])
|
|
{
|
|
float offset[3], head[3], forward, left;
|
|
float c = cosf(vr.body_yaw), s = sinf(vr.body_yaw);
|
|
int axis;
|
|
|
|
for (axis = 0; axis < 3; axis++)
|
|
offset[axis] = (world[axis] - vr.game_camera[axis]) * METRES_PER_WORLD_UNIT;
|
|
forward = offset[0] * c + offset[1] * s;
|
|
left = -offset[0] * s + offset[1] * c;
|
|
clamped_head(head);
|
|
local[0] = -left - head[0] + vr.frame.head.position[0];
|
|
local[1] = offset[2] - head[1] + vr.frame.head.position[1];
|
|
local[2] = -forward - head[2] + vr.frame.head.position[2];
|
|
}
|
|
|
|
/* ---------- platform side */
|
|
|
|
/* the settings the VR options (ui_widget.c) change while the game runs:
|
|
read at the start, and again whenever one changes */
|
|
static void vr_read_live_settings(void)
|
|
{
|
|
vr.settings_read_at = config_changes();
|
|
vr.smooth_turn = config_boolean("vr.smooth_turn");
|
|
vr.snap_turn_degrees = (float)config_real("vr.snap_turn_degrees");
|
|
vr.smooth_turn_degrees_per_second = (float)config_real("vr.smooth_turn_speed");
|
|
vr.motion_tracker_on_wrist = config_boolean("vr.motion_tracker_on_wrist");
|
|
vr.crosshair = config_boolean("vr.crosshair");
|
|
vr.laser = config_boolean("vr.laser");
|
|
vr.vehicle_wheel = config_boolean("vr.vehicle_wheel");
|
|
vr.comfort_vignette = (float)config_real("vr.comfort_vignette");
|
|
}
|
|
|
|
void halo_vr_initialize(void)
|
|
{
|
|
if (vr.active || !getenv("HALO_VR"))
|
|
return;
|
|
vr_read_live_settings();
|
|
vr.cinematics_3d = config_boolean("vr.cinematics_in_3d");
|
|
vr.hud_distance = (float)config_real("vr.hud_distance");
|
|
vr.hud_width = 2.0f * vr.hud_distance * tanf((float)config_real("vr.hud_field_of_view") * PI / 360.0f);
|
|
vr.screen_distance = (float)config_real("vr.screen_distance");
|
|
vr.screen_width = (float)config_real("vr.screen_width");
|
|
vr.positional_tracking = config_boolean("vr.positional_tracking");
|
|
vr.scope_size = (float)config_real("vr.scope_size");
|
|
vr.motion_tracker_size = (float)config_real("vr.motion_tracker_size");
|
|
vr.motion_tracker_turn = (float)config_real("vr.motion_tracker_turn");
|
|
vr.motion_tracker_right = (float)config_real("vr.motion_tracker_right");
|
|
vr.motion_tracker_down = (float)config_real("vr.motion_tracker_down");
|
|
vr.lean_limit = (float)config_real("vr.lean_limit");
|
|
vr.crouch_limit = (float)config_real("vr.crouch_limit");
|
|
vr.rise_limit = 0.25f;
|
|
vr.log_performance = config_boolean("vr.log_performance");
|
|
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.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");
|
|
vr.grenade_throw_speed = (float)config_real("vr.grenade_throw_speed");
|
|
vr.multiview = config_boolean("vr.multiview");
|
|
vr.vehicle_first_person = config_boolean("vr.vehicle_first_person");
|
|
vr.vehicle_spawn = config_boolean("debug.vehicle_spawn");
|
|
vr.turret_handles = config_boolean("vr.turret_handles");
|
|
vr.gunner_camera_back = (float)config_real("vr.gunner_camera_back");
|
|
vr.driver_camera_forward = (float)config_real("vr.driver_camera_forward");
|
|
vr.enclosed_seat_raise[HALO_VR_HULL_SCOUT] = (float)config_real("vr.ghost_eye_raise");
|
|
vr.enclosed_seat_raise[HALO_VR_HULL_FIGHTER] = (float)config_real("vr.banshee_eye_raise");
|
|
vr.enclosed_seat_raise[HALO_VR_HULL_TURRET] = (float)config_real("vr.turret_eye_raise");
|
|
vr.enclosed_seat_raise[HALO_VR_HULL_TANK] = (float)config_real("vr.tank_eye_raise");
|
|
vr.glass_strength = (float)config_real("vr.glass_strength");
|
|
vr.vehicle_steer_angle = (float)config_real("vr.vehicle_steer_degrees") * PI / 180.0f;
|
|
vr.vehicle_gun_steer_angle = (float)config_real("vr.vehicle_gun_steer_degrees") * PI / 180.0f;
|
|
vr.vehicle_pitch_angle = (float)config_real("vr.vehicle_pitch_degrees") * PI / 180.0f;
|
|
vr.vehicle_wheel_angle = (float)config_real("vr.vehicle_wheel_degrees") * PI / 180.0f;
|
|
if (vr.vehicle_wheel_angle < 10.0f * PI / 180.0f)
|
|
vr.vehicle_wheel_angle = 10.0f * PI / 180.0f;
|
|
vr.vehicle_steer_curve = (float)config_real("vr.vehicle_steer_curve");
|
|
vr.vehicle_steer_smoothing = (float)config_real("vr.vehicle_steer_smoothing");
|
|
vr.grenade_scale = (float)config_real("vr.grenade_scale");
|
|
vr.grenade_offset[0] = (float)config_real("vr.grenade_right");
|
|
vr.grenade_offset[1] = 0.0f;
|
|
vr.grenade_down = (float)config_real("vr.grenade_down");
|
|
vr.grenade_back = (float)config_real("vr.grenade_back");
|
|
vr.grenade_offset[2] = -(float)config_real("vr.grenade_forward");
|
|
if (!host_xr_initialize(&vr.info, (float)config_real("vr.refresh_rate")))
|
|
{
|
|
platform_log("VR: the headset's session could not be created; see logcat");
|
|
return;
|
|
}
|
|
vr.active = 1;
|
|
platform_log("VR: %s, eyes %dx%d at %.0f Hz", vr.info.system_name, vr.info.eye_width, vr.info.eye_height,
|
|
vr.info.refresh_rate);
|
|
}
|
|
|
|
int halo_vr_active(void)
|
|
{
|
|
return vr.active;
|
|
}
|
|
|
|
int halo_vr_eye_size(int *width, int *height)
|
|
{
|
|
const char *eye_width = getenv("HALO_VR_EYE_WIDTH");
|
|
const char *eye_height = getenv("HALO_VR_EYE_HEIGHT");
|
|
|
|
if (!getenv("HALO_VR") || !eye_width || !eye_height)
|
|
return 0;
|
|
*width = atoi(eye_width);
|
|
*height = atoi(eye_height);
|
|
return *width > 0 && *height > 0;
|
|
}
|
|
|
|
static unsigned long long now_us(void)
|
|
{
|
|
struct timespec now;
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
return (unsigned long long)now.tv_sec * 1000000ULL + (unsigned long long)now.tv_nsec / 1000ULL;
|
|
}
|
|
|
|
/* vr.log_performance: every five seconds, the frames the game gave the
|
|
headset, and where their time went: waiting for the headset (xrWaitFrame),
|
|
drawing the eyes, and the rest (the game's update, the overlay, the copies) */
|
|
static void performance_report(int stereo)
|
|
{
|
|
unsigned long long now = now_us();
|
|
|
|
vr.work_us += now - vr.frame_start_us;
|
|
vr.report_frames++;
|
|
vr.report_stereo_frames += stereo != 0;
|
|
if (!vr.report_start_us)
|
|
vr.report_start_us = now;
|
|
if (now - vr.report_start_us < 5000000)
|
|
return;
|
|
{
|
|
float frames = (float)vr.report_frames;
|
|
float seconds = (float)(now - vr.report_start_us) * 1e-6f;
|
|
|
|
platform_log("VR: %.1f fps (%d%% stereo); per frame %.1f ms working (eyes %.1f ms), %.1f ms waiting "
|
|
"for the headset", frames / seconds, (int)(100.0f * vr.report_stereo_frames / frames),
|
|
vr.work_us * 1e-3f / frames, vr.eyes_us * 1e-3f / frames, vr.wait_us * 1e-3f / frames);
|
|
}
|
|
vr.report_start_us = now;
|
|
vr.report_frames = vr.report_stereo_frames = 0;
|
|
vr.work_us = vr.eyes_us = vr.wait_us = 0;
|
|
}
|
|
|
|
/* gathers the view's turning (the head's, and the body's by the stick) for
|
|
halo_vr_look_delta, whether or not the game lets the player turn now */
|
|
static void look_update(void)
|
|
{
|
|
float yaw, pitch;
|
|
|
|
if (!(vr.frame.head_flags & HALO_XR_POSE_VALID))
|
|
{
|
|
vr.look_valid = 0;
|
|
return;
|
|
}
|
|
head_angles(&yaw, &pitch);
|
|
yaw = wrap_angle(yaw + vr.body_yaw);
|
|
if (vr.look_valid)
|
|
{
|
|
vr.look_turned_yaw += wrap_angle(yaw - vr.look_yaw);
|
|
vr.look_turned_pitch += pitch - vr.look_pitch;
|
|
}
|
|
vr.look_yaw = yaw;
|
|
vr.look_pitch = pitch;
|
|
vr.look_valid = 1;
|
|
}
|
|
|
|
/* The grenade in the left hand: while the left trigger is held the game
|
|
keeps it in the hand (halo_vr_grenade_held); as the trigger lets go, the
|
|
fastest the hand moved in its last moments is the throw (halo_vr_grenade_throw). */
|
|
static void grenade_update(void)
|
|
{
|
|
const struct halo_xr_hand *hand = &vr.frame.hands[HALO_XR_HAND_LEFT];
|
|
unsigned long long now = now_us();
|
|
int valid = hand->active && (hand->flags & HALO_XR_POSE_VALID);
|
|
/* (at a wheel the left trigger brakes: xinput_sdl.c) */
|
|
int held = valid && vr.last_overlay_mode != HALO_XR_OVERLAY_SCREEN && !vr.wheel_hands &&
|
|
hand->trigger > (vr.grenade_held ? 0.2f : 0.3f);
|
|
int count = vr.grenade_sample_count;
|
|
|
|
if (held && !vr.grenade_held)
|
|
count = 0;
|
|
if (valid && (held || vr.grenade_held))
|
|
{
|
|
if (count == GRENADE_SAMPLES)
|
|
{
|
|
memmove(vr.grenade_sample[0], vr.grenade_sample[1], sizeof(vr.grenade_sample[0]) * (GRENADE_SAMPLES - 1));
|
|
memmove(&vr.grenade_sample_us[0], &vr.grenade_sample_us[1],
|
|
sizeof(vr.grenade_sample_us[0]) * (GRENADE_SAMPLES - 1));
|
|
count--;
|
|
}
|
|
memcpy(vr.grenade_sample[count], hand->grip.position, sizeof(vr.grenade_sample[count]));
|
|
vr.grenade_sample_us[count] = now;
|
|
count++;
|
|
}
|
|
vr.grenade_sample_count = count;
|
|
if (vr.grenade_held && !held)
|
|
{
|
|
/* let go: the fastest the hand moved over its last 150 ms, each
|
|
speed over two frames or more (a single frame's is noisy) */
|
|
float best_speed = 0.0f, best_velocity[3] = { 0.0f, 0.0f, 0.0f };
|
|
int best = -1, i;
|
|
|
|
vr.grenade_released_us = now;
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
int j;
|
|
|
|
if (now - vr.grenade_sample_us[i] > 150000)
|
|
continue;
|
|
for (j = i - 1; j >= 0; j--)
|
|
{
|
|
float seconds = (float)(vr.grenade_sample_us[i] - vr.grenade_sample_us[j]) * 1e-6f;
|
|
|
|
if (seconds >= 0.02f)
|
|
{
|
|
float velocity[3], speed;
|
|
int axis;
|
|
|
|
if (seconds > 0.1f)
|
|
break;
|
|
for (axis = 0; axis < 3; axis++)
|
|
velocity[axis] = (vr.grenade_sample[i][axis] - vr.grenade_sample[j][axis]) / seconds;
|
|
speed = sqrtf(velocity[0] * velocity[0] + velocity[1] * velocity[1] + velocity[2] * velocity[2]);
|
|
if (speed > best_speed)
|
|
{
|
|
best_speed = speed;
|
|
memcpy(best_velocity, velocity, sizeof(best_velocity));
|
|
best = i;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
vr.grenade_thrown = 0;
|
|
if (vr.grenade_throw_speed > 0.0f && best >= 0 && best_speed >= vr.grenade_throw_speed)
|
|
{
|
|
int axis;
|
|
|
|
vr.grenade_thrown = 1;
|
|
memcpy(vr.grenade_hand, vr.grenade_sample[count - 1], sizeof(vr.grenade_hand));
|
|
for (axis = 0; axis < 3; axis++)
|
|
vr.grenade_direction[axis] = best_velocity[axis] / best_speed;
|
|
/* the throw's speed is its strength: full at 5 m/s */
|
|
vr.grenade_strength = best_speed / 5.0f;
|
|
if (vr.grenade_strength < 0.25f)
|
|
vr.grenade_strength = 0.25f;
|
|
if (vr.grenade_strength > 1.0f)
|
|
vr.grenade_strength = 1.0f;
|
|
host_xr_haptic(HALO_XR_HAND_LEFT, 0.6f, 0.06f);
|
|
platform_log("VR: grenade thrown at %.1f m/s", best_speed);
|
|
}
|
|
vr.grenade_sample_count = 0;
|
|
}
|
|
vr.grenade_held = held;
|
|
}
|
|
|
|
int halo_vr_grenade_held(short local_player_index)
|
|
{
|
|
return vr.active && vr.aim_with_controller && local_player_index == 0 && vr.grenade_held;
|
|
}
|
|
|
|
int halo_vr_grenade_in_hand(short local_player_index, float position[3], float forward[3], float up[3],
|
|
float *scale)
|
|
{
|
|
static const float local_forward[3] = { 0.0f, 0.0f, -1.0f };
|
|
static const float local_up[3] = { 0.0f, 1.0f, 0.0f };
|
|
const struct halo_xr_hand *hand = &vr.frame.hands[HALO_XR_HAND_LEFT];
|
|
float held[3], offset[3], direction[3];
|
|
int axis;
|
|
|
|
if (!vr.aim_with_controller || local_player_index != 0 || !halo_vr_stereo_frame() || !hand->active ||
|
|
!(hand->flags & HALO_XR_POSE_VALID))
|
|
{
|
|
return 0;
|
|
}
|
|
/* the grip's +x faces out of the left palm; the drawn hand's knuckles
|
|
run along the aim's y, and its fingers along the aim's -z
|
|
(first_person_weapons.c, vr_left_hand_free) */
|
|
{
|
|
float knuckles[3], fingers[3];
|
|
|
|
quaternion_rotate(hand->grip.orientation, vr.grenade_offset, held);
|
|
quaternion_rotate(hand->aim.orientation, local_up, knuckles);
|
|
quaternion_rotate(hand->aim.orientation, local_forward, fingers);
|
|
for (axis = 0; axis < 3; axis++)
|
|
held[axis] += hand->grip.position[axis] - knuckles[axis] * vr.grenade_down -
|
|
fingers[axis] * vr.grenade_back;
|
|
}
|
|
tracked_offset(held, offset);
|
|
for (axis = 0; axis < 3; axis++)
|
|
position[axis] = vr.game_camera[axis] + offset[axis] / METRES_PER_WORLD_UNIT;
|
|
quaternion_rotate(hand->grip.orientation, local_forward, direction);
|
|
local_to_game(direction, vr.body_yaw, forward);
|
|
quaternion_rotate(hand->grip.orientation, local_up, direction);
|
|
local_to_game(direction, vr.body_yaw, up);
|
|
*scale = vr.grenade_scale;
|
|
return 1;
|
|
}
|
|
|
|
int halo_vr_grenade_throw(short local_player_index, float origin[3], float direction[3], float *strength)
|
|
{
|
|
float offset[3];
|
|
int axis;
|
|
|
|
if (!vr.active || !vr.aim_with_controller || local_player_index != 0 || !vr.grenade_thrown ||
|
|
now_us() - vr.grenade_released_us > 1000000)
|
|
{
|
|
vr.grenade_thrown = 0;
|
|
return 0;
|
|
}
|
|
vr.grenade_thrown = 0;
|
|
tracked_offset(vr.grenade_hand, offset);
|
|
for (axis = 0; axis < 3; axis++)
|
|
origin[axis] = vr.game_camera[axis] + offset[axis] / METRES_PER_WORLD_UNIT;
|
|
local_to_game(vr.grenade_direction, vr.body_yaw, direction);
|
|
*strength = vr.grenade_strength;
|
|
return 1;
|
|
}
|
|
|
|
/* a hand thrust forward fast enough is a melee attack; the game sees its
|
|
button for a moment (halo_vr_melee) */
|
|
static void melee_update(void)
|
|
{
|
|
unsigned long long now = now_us();
|
|
float seconds = vr.hand_last_us ? (float)(now - vr.hand_last_us) * 1e-6f : 0.0f;
|
|
float look[3] = { 0.0f, 0.0f, -1.0f }, facing[3];
|
|
int hand;
|
|
|
|
vr.hand_last_us = now;
|
|
{
|
|
/* the right grip's melee attack (xinput_sdl.c) aims the same way */
|
|
int grip_down = vr.frame.hands[HALO_XR_HAND_RIGHT].active &&
|
|
(vr.frame.hands[HALO_XR_HAND_RIGHT].buttons & HALO_XR_BUTTON_SQUEEZE);
|
|
|
|
if (grip_down && !vr.melee_grip_down)
|
|
{
|
|
vr.melee_aim_until_us = now + 700000;
|
|
vr.melee_strength = 0.75f;
|
|
vr.melee_jab_start_us = now;
|
|
}
|
|
vr.melee_grip_down = grip_down;
|
|
}
|
|
quaternion_rotate(vr.frame.head.orientation, look, facing);
|
|
facing[1] = 0.0f;
|
|
normalize3(facing);
|
|
for (hand = 0; hand < HALO_XR_HAND_COUNT; hand++)
|
|
{
|
|
const struct halo_xr_hand *state = &vr.frame.hands[hand];
|
|
|
|
if (!state->active || !(state->flags & HALO_XR_POSE_VALID))
|
|
{
|
|
vr.hand_last_valid[hand] = 0;
|
|
continue;
|
|
}
|
|
/* (a grenade's throw is not a blow) */
|
|
/* (nor is turning the wheel) */
|
|
if (vr.hand_last_valid[hand] && seconds > 0.0f && seconds < 0.1f && vr.melee_swing_speed > 0.0f &&
|
|
!vr.wheel_held[hand] &&
|
|
vr.last_overlay_mode != HALO_XR_OVERLAY_SCREEN && now >= vr.melee_ready_us &&
|
|
!(hand == HALO_XR_HAND_LEFT && (vr.grenade_held || now - vr.grenade_released_us < 500000)))
|
|
{
|
|
float velocity[3], forward_speed;
|
|
int axis;
|
|
|
|
for (axis = 0; axis < 3; axis++)
|
|
velocity[axis] = (state->grip.position[axis] - vr.hand_last[hand][axis]) / seconds;
|
|
forward_speed = velocity[0] * facing[0] + velocity[2] * facing[2];
|
|
if (forward_speed > vr.melee_swing_speed)
|
|
{
|
|
vr.melee_until_us = now + 150000;
|
|
vr.melee_ready_us = now + 700000;
|
|
vr.melee_aim_until_us = now + 700000;
|
|
/* the swing's speed is the blow's weight: half at 2 m/s, full at 4 */
|
|
vr.melee_strength = forward_speed / 4.0f;
|
|
if (vr.melee_strength < 0.5f)
|
|
vr.melee_strength = 0.5f;
|
|
if (vr.melee_strength > 1.0f)
|
|
vr.melee_strength = 1.0f;
|
|
host_xr_haptic(hand, 0.8f, 0.08f);
|
|
platform_log("VR: melee, %s hand at %.1f m/s", hand ? "right" : "left", forward_speed);
|
|
}
|
|
}
|
|
memcpy(vr.hand_last[hand], state->grip.position, sizeof(vr.hand_last[hand]));
|
|
vr.hand_last_valid[hand] = 1;
|
|
}
|
|
}
|
|
|
|
int halo_vr_melee(void)
|
|
{
|
|
return vr.active && now_us() < vr.melee_until_us;
|
|
}
|
|
|
|
float halo_vr_melee_strength(void)
|
|
{
|
|
return vr.active && now_us() < vr.melee_aim_until_us ? vr.melee_strength : 0.0f;
|
|
}
|
|
|
|
static void turn_update(void)
|
|
{
|
|
const struct halo_xr_hand *hand = &vr.frame.hands[HALO_XR_HAND_RIGHT];
|
|
unsigned long long now = SDL_GetTicks();
|
|
float seconds = vr.last_frame_ms ? (float)(now - vr.last_frame_ms) * 0.001f : 0.0f;
|
|
float x = hand->active ? hand->thumbstick[0] : 0.0f;
|
|
|
|
vr.last_frame_ms = now;
|
|
if (seconds > 0.1f)
|
|
seconds = 0.1f;
|
|
/* no turning in the menus, where the stick moves the cursor */
|
|
if (vr.last_overlay_mode == HALO_XR_OVERLAY_SCREEN || !vr.frame.focused)
|
|
{
|
|
vr.snap_latched = 1;
|
|
return;
|
|
}
|
|
/* driving, the stick steers instead (halo_vr_aim) */
|
|
if (vr.seat_mode == HALO_VR_SEAT_STEER)
|
|
{
|
|
vr.snap_latched = 1;
|
|
seat_body();
|
|
return;
|
|
}
|
|
{
|
|
/* seated, the body turns in the seat */
|
|
float *turned = vr.seat_mode != HALO_VR_SEAT_NONE ? &vr.seat_turn : &vr.body_yaw;
|
|
|
|
if (vr.smooth_turn)
|
|
{
|
|
if (fabsf(x) > 0.15f)
|
|
*turned -= x * vr.smooth_turn_degrees_per_second * PI / 180.0f * seconds;
|
|
}
|
|
else if (!vr.snap_latched && fabsf(x) > 0.7f)
|
|
{
|
|
*turned -= (x > 0.0f ? 1.0f : -1.0f) * vr.snap_turn_degrees * PI / 180.0f;
|
|
vr.snap_latched = 1;
|
|
}
|
|
else if (fabsf(x) < 0.3f)
|
|
{
|
|
vr.snap_latched = 0;
|
|
}
|
|
*turned = wrap_angle(*turned);
|
|
}
|
|
seat_body();
|
|
}
|
|
|
|
/* At an open vehicle's wheel (a Warthog's, vr.vehicle_wheel), a hand that
|
|
grips near it takes hold of it, and the hands turn it about its column: both
|
|
by the line between them, one about the wheel's middle. Where it is, the
|
|
driver's hands as drawn hold it (halo_vr_set_wheel); its column runs from
|
|
there back towards the driver, tipped up WHEEL_COLUMN_TILT. Held, its turn
|
|
steers (halo_vr_aim); let go, the right stick does. */
|
|
#define WHEEL_REACH 0.35f /* metres from its middle */
|
|
#define WHEEL_COLUMN_TILT (45.0f * PI / 180.0f) /* from the horizontal */
|
|
|
|
static void wheel_release(void)
|
|
{
|
|
int hand;
|
|
|
|
for (hand = 0; hand < HALO_XR_HAND_COUNT; hand++)
|
|
vr.wheel_held[hand] = 0;
|
|
vr.wheel_hands = 0;
|
|
}
|
|
|
|
static void wheel_update(void)
|
|
{
|
|
static const float local_up[3] = { 0.0f, 1.0f, 0.0f };
|
|
float world[3], centre[3], forward_game[3], forward[3], column[3], up[3], side[3], line[3];
|
|
float heading = seat_heading(), along, angle;
|
|
int hand, axis, hands = 0;
|
|
|
|
if (vr.seat_mode != HALO_VR_SEAT_STEER || !vr.vehicle_wheel || !vr.wheel_valid || !vr.frame.focused ||
|
|
vr.last_overlay_mode == HALO_XR_OVERLAY_SCREEN)
|
|
{
|
|
wheel_release();
|
|
return;
|
|
}
|
|
for (axis = 0; axis < 3; axis++)
|
|
{
|
|
world[axis] = vr.game_camera[axis] +
|
|
(vr.wheel_hands_drawn[0][axis] + vr.wheel_hands_drawn[1][axis]) * 0.5f;
|
|
}
|
|
world_to_local(world, centre);
|
|
if (!vr.wheel_logged)
|
|
{
|
|
float apart[3];
|
|
|
|
for (axis = 0; axis < 3; axis++)
|
|
apart[axis] = (vr.wheel_hands_drawn[0][axis] - vr.wheel_hands_drawn[1][axis]) * METRES_PER_WORLD_UNIT;
|
|
platform_log("VR: wheel at %.2f %.2f %.2f (LOCAL), the hands %.2f m apart", centre[0], centre[1], centre[2],
|
|
sqrtf(apart[0] * apart[0] + apart[1] * apart[1] + apart[2] * apart[2]));
|
|
vr.wheel_logged = 1;
|
|
}
|
|
/* its column, and the wheel's up and left about it */
|
|
forward_game[0] = cosf(heading);
|
|
forward_game[1] = sinf(heading);
|
|
forward_game[2] = 0.0f;
|
|
game_to_local(forward_game, forward);
|
|
for (axis = 0; axis < 3; axis++)
|
|
column[axis] = -forward[axis] * cosf(WHEEL_COLUMN_TILT) + local_up[axis] * sinf(WHEEL_COLUMN_TILT);
|
|
along = local_up[0] * column[0] + local_up[1] * column[1] + local_up[2] * column[2];
|
|
for (axis = 0; axis < 3; axis++)
|
|
up[axis] = local_up[axis] - column[axis] * along;
|
|
normalize3(up);
|
|
cross3(column, up, side);
|
|
for (hand = 0; hand < HALO_XR_HAND_COUNT; hand++)
|
|
{
|
|
const struct halo_xr_hand *state = &vr.frame.hands[hand];
|
|
float apart[3];
|
|
|
|
if (!state->active || !(state->flags & HALO_XR_POSE_VALID) || state->squeeze < 0.35f)
|
|
{
|
|
vr.wheel_held[hand] = 0;
|
|
continue;
|
|
}
|
|
if (!vr.wheel_held[hand])
|
|
{
|
|
for (axis = 0; axis < 3; axis++)
|
|
apart[axis] = state->grip.position[axis] - centre[axis];
|
|
if (state->squeeze < 0.6f ||
|
|
sqrtf(apart[0] * apart[0] + apart[1] * apart[1] + apart[2] * apart[2]) > WHEEL_REACH)
|
|
{
|
|
continue;
|
|
}
|
|
vr.wheel_held[hand] = 1;
|
|
host_xr_haptic(hand, 0.3f, 0.05f);
|
|
}
|
|
hands |= 1 << hand;
|
|
}
|
|
if (!hands)
|
|
{
|
|
vr.wheel_hands = 0;
|
|
return;
|
|
}
|
|
/* the hands' turn about the column */
|
|
for (axis = 0; axis < 3; axis++)
|
|
{
|
|
line[axis] = hands == ((1 << HALO_XR_HAND_LEFT) | (1 << HALO_XR_HAND_RIGHT)) ?
|
|
vr.frame.hands[HALO_XR_HAND_LEFT].grip.position[axis] -
|
|
vr.frame.hands[HALO_XR_HAND_RIGHT].grip.position[axis] :
|
|
vr.frame.hands[hands & (1 << HALO_XR_HAND_LEFT) ? HALO_XR_HAND_LEFT : HALO_XR_HAND_RIGHT]
|
|
.grip.position[axis] - centre[axis];
|
|
}
|
|
angle = atan2f(line[0] * side[0] + line[1] * side[1] + line[2] * side[2],
|
|
line[0] * up[0] + line[1] * up[1] + line[2] * up[2]);
|
|
if (!vr.wheel_hands)
|
|
{
|
|
/* taken hold of: turned as far as it steers now */
|
|
vr.wheel_turn = -vr.steer[0] * vr.vehicle_wheel_angle;
|
|
}
|
|
else if (hands == vr.wheel_hands)
|
|
{
|
|
vr.wheel_turn += wrap_angle(angle - vr.wheel_reference);
|
|
}
|
|
if (vr.wheel_turn > vr.vehicle_wheel_angle)
|
|
vr.wheel_turn = vr.vehicle_wheel_angle;
|
|
if (vr.wheel_turn < -vr.vehicle_wheel_angle)
|
|
vr.wheel_turn = -vr.vehicle_wheel_angle;
|
|
vr.wheel_reference = angle;
|
|
vr.wheel_hands = hands;
|
|
}
|
|
|
|
void halo_vr_set_wheel(short local_player_index, const float left[3], const float right[3])
|
|
{
|
|
if (!vr.active || local_player_index != 0)
|
|
return;
|
|
vr.wheel_valid = left && right;
|
|
if (!vr.wheel_valid)
|
|
return;
|
|
memcpy(vr.wheel_hands_drawn[0], left, sizeof(vr.wheel_hands_drawn[0]));
|
|
memcpy(vr.wheel_hands_drawn[1], right, sizeof(vr.wheel_hands_drawn[1]));
|
|
}
|
|
|
|
int halo_vr_wheel_held(void)
|
|
{
|
|
return vr.active && vr.wheel_hands != 0;
|
|
}
|
|
|
|
/* The comfort vignette: while a vehicle turns the player (from 15 degrees a
|
|
second, fully at 60), the eye images darken from their edges; it comes in
|
|
quickly and goes slowly. */
|
|
static void vignette_update(void)
|
|
{
|
|
unsigned long long now = now_us();
|
|
float seconds = vr.vignette_last_us ? (float)(now - vr.vignette_last_us) * 1e-6f : 0.0f;
|
|
float target = 0.0f, rate;
|
|
|
|
vr.vignette_last_us = now;
|
|
if (seconds > 0.1f)
|
|
seconds = 0.1f;
|
|
if (vr.seat_mode != HALO_VR_SEAT_NONE && vr.comfort_vignette > 0.0f)
|
|
{
|
|
float yaw = seat_heading();
|
|
|
|
if (vr.vignette_yaw_valid && seconds > 0.0f)
|
|
{
|
|
float turning = fabsf(wrap_angle(yaw - vr.vignette_last_yaw)) / seconds;
|
|
|
|
target = (turning - 15.0f * PI / 180.0f) / (45.0f * PI / 180.0f);
|
|
if (target < 0.0f)
|
|
target = 0.0f;
|
|
if (target > 1.0f)
|
|
target = 1.0f;
|
|
target *= vr.comfort_vignette;
|
|
}
|
|
vr.vignette_last_yaw = yaw;
|
|
vr.vignette_yaw_valid = 1;
|
|
}
|
|
else
|
|
{
|
|
vr.vignette_yaw_valid = 0;
|
|
}
|
|
rate = (target > vr.vignette ? 6.0f : 1.5f) * seconds;
|
|
if (vr.vignette < target)
|
|
vr.vignette = vr.vignette + rate < target ? vr.vignette + rate : target;
|
|
else
|
|
vr.vignette = vr.vignette - rate > target ? vr.vignette - rate : target;
|
|
host_xr_set_vignette(vr.vignette);
|
|
}
|
|
|
|
void halo_vr_set_seat(short local_player_index, int seat, int armed, float vehicle_yaw)
|
|
{
|
|
if (!vr.active || local_player_index != 0)
|
|
return;
|
|
if (!vr.vehicle_first_person)
|
|
seat = HALO_VR_SEAT_NONE;
|
|
vr.seat_yaw = vehicle_yaw;
|
|
vr.seat_armed = armed;
|
|
if (seat == vr.seat_mode)
|
|
return;
|
|
platform_log("VR: seat %d (was %d)", seat, vr.seat_mode);
|
|
if (vr.seat_mode == HALO_VR_SEAT_NONE)
|
|
{
|
|
/* sat down: facing the vehicle's forward where the head looks */
|
|
vr.seat_mode = seat;
|
|
vr.seat_frame_valid = 0;
|
|
seat_face_forward();
|
|
return;
|
|
}
|
|
vr.seat_mode = seat;
|
|
seat_body();
|
|
}
|
|
|
|
void halo_vr_set_seat_frame(short local_player_index, float vehicle_yaw)
|
|
{
|
|
if (!vr.active || local_player_index != 0 || vr.seat_mode == HALO_VR_SEAT_NONE)
|
|
return;
|
|
vr.seat_frame_yaw = vehicle_yaw;
|
|
vr.seat_frame_valid = 1;
|
|
seat_body();
|
|
}
|
|
|
|
/* both sticks held in for a second and a half: a vehicle, once */
|
|
static void vehicle_spawn_update(void)
|
|
{
|
|
int held = vr.vehicle_spawn && vr.frame.focused && vr.last_overlay_mode != HALO_XR_OVERLAY_SCREEN &&
|
|
(vr.frame.hands[HALO_XR_HAND_LEFT].buttons & HALO_XR_BUTTON_THUMBSTICK) &&
|
|
(vr.frame.hands[HALO_XR_HAND_RIGHT].buttons & HALO_XR_BUTTON_THUMBSTICK);
|
|
unsigned long long now = now_us();
|
|
|
|
if (!held)
|
|
{
|
|
vr.vehicle_spawn_held_us = 0;
|
|
vr.vehicle_spawn_latched = 0;
|
|
return;
|
|
}
|
|
if (!vr.vehicle_spawn_held_us)
|
|
vr.vehicle_spawn_held_us = now;
|
|
if (!vr.vehicle_spawn_latched && now - vr.vehicle_spawn_held_us > 1500000)
|
|
{
|
|
vr.vehicle_spawn_asked = 1;
|
|
vr.vehicle_spawn_latched = 1;
|
|
host_xr_haptic(HALO_XR_HAND_LEFT, 0.5f, 0.1f);
|
|
host_xr_haptic(HALO_XR_HAND_RIGHT, 0.5f, 0.1f);
|
|
}
|
|
}
|
|
|
|
int halo_vr_take_vehicle_spawn(short local_player_index)
|
|
{
|
|
if (!vr.active || local_player_index != 0 || !vr.vehicle_spawn_asked)
|
|
return 0;
|
|
vr.vehicle_spawn_asked = 0;
|
|
return 1;
|
|
}
|
|
|
|
int halo_vr_vehicle_first_person(void)
|
|
{
|
|
return vr.active && vr.vehicle_first_person;
|
|
}
|
|
|
|
float halo_vr_glass_strength(void)
|
|
{
|
|
return vr.active ? vr.glass_strength : 1.0f;
|
|
}
|
|
|
|
float halo_vr_enclosed_seat_raise(int hull)
|
|
{
|
|
return hull > HALO_VR_HULL_OPEN && hull <= HALO_VR_HULL_TANK ?
|
|
vr.enclosed_seat_raise[hull] / METRES_PER_WORLD_UNIT : 0.0f;
|
|
}
|
|
|
|
float halo_vr_driver_camera_forward(void)
|
|
{
|
|
return vr.driver_camera_forward / METRES_PER_WORLD_UNIT;
|
|
}
|
|
|
|
float halo_vr_gunner_camera_back(void)
|
|
{
|
|
return vr.gunner_camera_back / METRES_PER_WORLD_UNIT;
|
|
}
|
|
|
|
/* seated at a gun or a window, where the right controller aims it: at a
|
|
gun's handles (vr.turret_handles), the barrel rises as the hand tips down */
|
|
static void seat_aim_direction(float direction[3])
|
|
{
|
|
float up[3], right[3];
|
|
|
|
controller_axes(direction, up, right);
|
|
if ((vr.seat_armed & HALO_VR_SEAT_TURRET) && vr.turret_handles)
|
|
direction[2] = -direction[2];
|
|
}
|
|
|
|
/* seated, where the aim runs from (world units) and along: from the eyes,
|
|
where the right controller points, or driving, the way the stick steers */
|
|
static int seat_aim(float start[3], float direction[3])
|
|
{
|
|
int axis;
|
|
|
|
float offset[3];
|
|
|
|
if (vr.seat_mode == HALO_VR_SEAT_NONE)
|
|
return 0;
|
|
/* from the eyes, as the game aims a vehicle's gun (aim_assist.c) */
|
|
tracked_offset(vr.frame.head.position, offset);
|
|
for (axis = 0; axis < 3; axis++)
|
|
start[axis] = vr.game_camera[axis] + offset[axis] / METRES_PER_WORLD_UNIT;
|
|
if (vr.seat_mode == HALO_VR_SEAT_AIM && weapon_hand_tracked(0))
|
|
{
|
|
seat_aim_direction(direction);
|
|
return 1;
|
|
}
|
|
direction[0] = cosf(vr.last_pitch) * cosf(vr.last_yaw);
|
|
direction[1] = cosf(vr.last_pitch) * sinf(vr.last_yaw);
|
|
direction[2] = sinf(vr.last_pitch);
|
|
return 1;
|
|
}
|
|
|
|
void halo_vr_frame_begin(void)
|
|
{
|
|
unsigned long long waited;
|
|
|
|
if (!vr.active || vr.frame_begun)
|
|
return;
|
|
waited = now_us();
|
|
if (!host_xr_begin_frame(&vr.frame))
|
|
memset(&vr.frame, 0, sizeof(vr.frame));
|
|
if (vr.frame.focused)
|
|
{
|
|
memcpy(vr.focused_hands, vr.frame.hands, sizeof(vr.focused_hands));
|
|
vr.focused_hands_valid = 1;
|
|
}
|
|
else if (vr.focused_hands_valid && vr.frame.session_running)
|
|
{
|
|
/* the system menu is up: the hands stay where they were, their
|
|
buttons let go (the weapon does not fall back to the head) */
|
|
int hand;
|
|
|
|
memcpy(vr.frame.hands, vr.focused_hands, sizeof(vr.frame.hands));
|
|
for (hand = 0; hand < HALO_XR_HAND_COUNT; hand++)
|
|
{
|
|
vr.frame.hands[hand].trigger = vr.frame.hands[hand].squeeze = 0.0f;
|
|
vr.frame.hands[hand].thumbstick[0] = vr.frame.hands[hand].thumbstick[1] = 0.0f;
|
|
vr.frame.hands[hand].buttons = 0;
|
|
}
|
|
}
|
|
vr.frame_start_us = now_us();
|
|
vr.wait_us += vr.frame_start_us - waited;
|
|
vr.frame_begun = 1;
|
|
vr.frame_number++;
|
|
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)
|
|
{
|
|
/* the headset recentred (the Meta button held): its LOCAL space
|
|
moves over the next few frames */
|
|
vr.recentre_frames = 10;
|
|
}
|
|
if (vr.recentre_frames > 0)
|
|
{
|
|
vr.recentre_frames--;
|
|
vr_recentre();
|
|
}
|
|
if (vr.settings_read_at != config_changes())
|
|
vr_read_live_settings();
|
|
turn_update();
|
|
wheel_update();
|
|
vignette_update();
|
|
vehicle_spawn_update();
|
|
look_update();
|
|
grenade_update();
|
|
melee_update();
|
|
}
|
|
|
|
void halo_vr_present(unsigned int back_buffer_texture)
|
|
{
|
|
struct halo_xr_submit submit;
|
|
|
|
if (!vr.active)
|
|
return;
|
|
halo_vr_frame_begin();
|
|
memset(&submit, 0, sizeof(submit));
|
|
submit.eyes = vr.eyes_captured == 3;
|
|
if (!submit.eyes)
|
|
{
|
|
/* nothing drawn per eye: the whole picture goes on the screen */
|
|
vr.overlay_mode = HALO_XR_OVERLAY_SCREEN;
|
|
vr.overlay_blend = 0;
|
|
}
|
|
submit.overlay_mode = vr.overlay_mode;
|
|
submit.overlay_blend = vr.overlay_blend;
|
|
if (vr.overlay_mode == HALO_XR_OVERLAY_HEAD_LOCKED)
|
|
{
|
|
submit.overlay_distance = vr.hud_distance;
|
|
submit.overlay_width = vr.hud_width;
|
|
}
|
|
else
|
|
{
|
|
submit.overlay_distance = vr.screen_distance;
|
|
submit.overlay_width = vr.screen_width;
|
|
submit.screen.orientation[3] = 1.0f;
|
|
submit.screen.position[2] = -vr.screen_distance;
|
|
}
|
|
if (vr.overlay_mode == HALO_XR_OVERLAY_HEAD_LOCKED && vr.motion_tracker_on_wrist && vr.aim_with_controller)
|
|
{
|
|
/* the motion tracker to the wrist, and out of the HUD */
|
|
submit.wrist = host_xr_copy_layer_region(HALO_XR_LAYER_WRIST, back_buffer_texture, HALO_XR_COPY_ALPHA,
|
|
WRIST_REGION_LEFT,
|
|
WRIST_REGION_TOP, WRIST_REGION_RIGHT, WRIST_REGION_BOTTOM);
|
|
submit.wrist_size = vr.motion_tracker_size;
|
|
submit.wrist_turn = vr.motion_tracker_turn;
|
|
submit.wrist_right = vr.motion_tracker_right;
|
|
submit.wrist_down = vr.motion_tracker_down;
|
|
d3d8_clear_back_buffer_region(WRIST_REGION_LEFT, WRIST_REGION_TOP, WRIST_REGION_RIGHT, WRIST_REGION_BOTTOM);
|
|
}
|
|
/* the crosshair, drawn apart (halo_vr_capture_crosshair), to where the
|
|
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;
|
|
submit.scope_size = vr.scope_size;
|
|
submit.scope_pose = vr.scope_pose;
|
|
}
|
|
host_xr_copy_layer(HALO_XR_LAYER_OVERLAY, back_buffer_texture,
|
|
vr.overlay_blend ? HALO_XR_COPY_ALPHA : HALO_XR_COPY_OPAQUE);
|
|
host_xr_end_frame(&submit);
|
|
vr.last_overlay_mode = vr.overlay_mode;
|
|
vr.last_frame_stereo = submit.eyes;
|
|
vr.frame_begun = 0;
|
|
if (vr.log_performance)
|
|
performance_report(submit.eyes);
|
|
}
|
|
|
|
/* ---------- the game's side */
|
|
|
|
int halo_vr_stereo_frame(void)
|
|
{
|
|
return vr.active && vr.frame_begun && vr.frame.session_running && vr.frame.should_render &&
|
|
(vr.frame.views[0].flags & HALO_XR_POSE_VALID) && (vr.frame.views[1].flags & HALO_XR_POSE_VALID);
|
|
}
|
|
|
|
int halo_vr_stereo_player(short local_player_index, int cinematic)
|
|
{
|
|
int stereo = halo_vr_stereo_frame() && local_player_index == 0 && (!cinematic || vr.cinematics_3d);
|
|
|
|
/* from the menus, a loading screen or a cinematic into play: the game's
|
|
camera is where the head is now, and the player faces where it looks */
|
|
if (stereo && !vr.last_frame_stereo)
|
|
vr_recentre();
|
|
return stereo;
|
|
}
|
|
|
|
void halo_vr_eye_camera(int eye, const float game_position[3],
|
|
float position[3], float forward[3], float up[3],
|
|
float *vertical_field_of_view, float frustum_bounds[4])
|
|
{
|
|
static const float local_forward[3] = { 0.0f, 0.0f, -1.0f };
|
|
static const float local_up[3] = { 0.0f, 1.0f, 0.0f };
|
|
const struct halo_xr_view *view = &vr.frame.views[eye];
|
|
float offset[3], direction[3];
|
|
|
|
if (eye == 0)
|
|
vr.eyes_start_us = now_us();
|
|
float half_tangent;
|
|
int axis;
|
|
|
|
tracked_offset(view->pose.position, offset);
|
|
memcpy(vr.game_camera, game_position, sizeof(vr.game_camera));
|
|
for (axis = 0; axis < 3; axis++)
|
|
position[axis] = game_position[axis] + offset[axis] / METRES_PER_WORLD_UNIT;
|
|
quaternion_rotate(view->pose.orientation, local_forward, direction);
|
|
local_to_game(direction, vr.body_yaw, forward);
|
|
quaternion_rotate(view->pose.orientation, local_up, direction);
|
|
local_to_game(direction, vr.body_yaw, up);
|
|
|
|
/* The game's viewport is 640x480, stretched to the eye image: its
|
|
projection maps the frustum bounds' [-1, 1] to the vertical field of
|
|
view's tangent, times 4:3 across. The bounds put the eye's own
|
|
(asymmetric) edges there. */
|
|
half_tangent = (view->tangent_up - view->tangent_down) * 0.5f;
|
|
*vertical_field_of_view = 2.0f * atanf(half_tangent);
|
|
frustum_bounds[0] = view->tangent_left / (half_tangent * (4.0f / 3.0f));
|
|
frustum_bounds[1] = view->tangent_right / (half_tangent * (4.0f / 3.0f));
|
|
frustum_bounds[2] = view->tangent_down / half_tangent;
|
|
frustum_bounds[3] = view->tangent_up / half_tangent;
|
|
}
|
|
|
|
/* ---------- both eyes at once (vr.multiview)
|
|
|
|
The scene is drawn once, from a camera between the eyes whose view takes in
|
|
both eyes' (with a margin, for what is near: the eyes stand apart), into two
|
|
layers; each layer's positions are moved from that camera's picture to its
|
|
eye's (d3d8_multiview_begin, nv2a_vsh.c). With d the depth along the view,
|
|
a point's picture across, x, is (2 tx - (L + R)) / (R - L) times d for the
|
|
tangent tx = X / d of the view's edges L, R; up, y, likewise, but for rows
|
|
from the top. An eye that stands t aside sees X - t: x' = a x + b d + c. */
|
|
|
|
#define MULTIVIEW_MARGIN 0.1f
|
|
|
|
static float multiview_bounds[4]; /* the shared view's L, R, D, U */
|
|
static int multiview_drawing;
|
|
/* the rows for what is drawn in the world, and for the sky, which is drawn
|
|
about the camera, as though infinitely far: no eye stands aside of it */
|
|
static float multiview_rows[2][4][4];
|
|
|
|
int halo_vr_multiview(void)
|
|
{
|
|
int available = vr.active && vr.multiview && d3d8_multiview_supported();
|
|
|
|
if (vr.active && vr.multiview && !vr.multiview_logged)
|
|
{
|
|
platform_log("VR: both eyes %s", available ? "in one draw (multiview)" : "drawn one by one (no multiview)");
|
|
vr.multiview_logged = 1;
|
|
}
|
|
return available && halo_vr_stereo_frame();
|
|
}
|
|
|
|
void halo_vr_center_camera(const float game_position[3], float position[3], float forward[3], float up[3],
|
|
float *vertical_field_of_view, float frustum_bounds[4])
|
|
{
|
|
static const float local_forward[3] = { 0.0f, 0.0f, -1.0f };
|
|
static const float local_up[3] = { 0.0f, 1.0f, 0.0f };
|
|
static const float local_right[3] = { 1.0f, 0.0f, 0.0f };
|
|
const struct halo_xr_view *views = vr.frame.views;
|
|
float centre[3], offset[3], direction[3], right_local[3], up_local[3], rows[4][4];
|
|
float left, right, down, top, half_tangent;
|
|
int axis, eye;
|
|
|
|
vr.eyes_start_us = now_us();
|
|
for (axis = 0; axis < 3; axis++)
|
|
centre[axis] = (views[0].pose.position[axis] + views[1].pose.position[axis]) * 0.5f;
|
|
tracked_offset(centre, offset);
|
|
memcpy(vr.game_camera, game_position, sizeof(vr.game_camera));
|
|
for (axis = 0; axis < 3; axis++)
|
|
position[axis] = game_position[axis] + offset[axis] / METRES_PER_WORLD_UNIT;
|
|
/* the eyes look alike (parallel views): the left one's way */
|
|
quaternion_rotate(views[0].pose.orientation, local_forward, direction);
|
|
local_to_game(direction, vr.body_yaw, forward);
|
|
quaternion_rotate(views[0].pose.orientation, local_up, direction);
|
|
local_to_game(direction, vr.body_yaw, up);
|
|
quaternion_rotate(views[0].pose.orientation, local_right, right_local);
|
|
quaternion_rotate(views[0].pose.orientation, local_up, up_local);
|
|
|
|
left = fminf(views[0].tangent_left, views[1].tangent_left) - MULTIVIEW_MARGIN;
|
|
right = fmaxf(views[0].tangent_right, views[1].tangent_right) + MULTIVIEW_MARGIN;
|
|
down = fminf(views[0].tangent_down, views[1].tangent_down) - MULTIVIEW_MARGIN;
|
|
top = fmaxf(views[0].tangent_up, views[1].tangent_up) + MULTIVIEW_MARGIN;
|
|
multiview_bounds[0] = left;
|
|
multiview_bounds[1] = right;
|
|
multiview_bounds[2] = down;
|
|
multiview_bounds[3] = top;
|
|
half_tangent = (top - down) * 0.5f;
|
|
*vertical_field_of_view = 2.0f * atanf(half_tangent);
|
|
frustum_bounds[0] = left / (half_tangent * (4.0f / 3.0f));
|
|
frustum_bounds[1] = right / (half_tangent * (4.0f / 3.0f));
|
|
frustum_bounds[2] = down / half_tangent;
|
|
frustum_bounds[3] = top / half_tangent;
|
|
|
|
for (eye = 0; eye < HALO_XR_EYE_COUNT; eye++)
|
|
{
|
|
const struct halo_xr_view *view = &views[eye];
|
|
float aside[3], tx, ty;
|
|
float eye_width = view->tangent_right - view->tangent_left;
|
|
float eye_height = view->tangent_up - view->tangent_down;
|
|
|
|
/* the eye's place from the shared camera, in its view's axes (world
|
|
units) */
|
|
for (axis = 0; axis < 3; axis++)
|
|
aside[axis] = view->pose.position[axis] - centre[axis];
|
|
tx = (aside[0] * right_local[0] + aside[1] * right_local[1] + aside[2] * right_local[2]) /
|
|
METRES_PER_WORLD_UNIT;
|
|
ty = (aside[0] * up_local[0] + aside[1] * up_local[1] + aside[2] * up_local[2]) / METRES_PER_WORLD_UNIT;
|
|
rows[eye * 2][0] = (right - left) / eye_width;
|
|
rows[eye * 2][1] = (left + right - view->tangent_left - view->tangent_right) / eye_width;
|
|
rows[eye * 2][2] = -2.0f * tx / eye_width;
|
|
rows[eye * 2][3] = 0.0f;
|
|
/* (rows from the top: y runs against the tangents) */
|
|
rows[eye * 2 + 1][0] = (top - down) / eye_height;
|
|
rows[eye * 2 + 1][1] = -(down + top - view->tangent_down - view->tangent_up) / eye_height;
|
|
rows[eye * 2 + 1][2] = 2.0f * ty / eye_height;
|
|
rows[eye * 2 + 1][3] = 0.0f;
|
|
}
|
|
memcpy(multiview_rows[0], rows, sizeof(rows));
|
|
memcpy(multiview_rows[1], rows, sizeof(rows));
|
|
for (eye = 0; eye < HALO_XR_EYE_COUNT; eye++)
|
|
multiview_rows[1][eye * 2][2] = multiview_rows[1][eye * 2 + 1][2] = 0.0f;
|
|
if (!vr.multiview_logged_views)
|
|
{
|
|
/* (the correction assumes parallel views: say if they are not) */
|
|
float other[3], apart[3];
|
|
|
|
quaternion_rotate(views[1].pose.orientation, local_forward, other);
|
|
quaternion_rotate(views[0].pose.orientation, local_forward, direction);
|
|
for (axis = 0; axis < 3; axis++)
|
|
apart[axis] = views[1].pose.position[axis] - views[0].pose.position[axis];
|
|
platform_log("VR multiview: views %.2f degrees apart, eyes %.1f mm apart; shared view %.2f..%.2f, %.2f..%.2f",
|
|
acosf(fminf(1.0f, direction[0] * other[0] + direction[1] * other[1] + direction[2] * other[2])) * 180.0f / PI,
|
|
sqrtf(apart[0] * apart[0] + apart[1] * apart[1] + apart[2] * apart[2]) * 1000.0f, left, right, down, top);
|
|
vr.multiview_logged_views = 1;
|
|
}
|
|
{
|
|
/* each eye's picture's place in the shared one (rows from the top) */
|
|
float resolve_rect[2][4];
|
|
|
|
for (eye = 0; eye < HALO_XR_EYE_COUNT; eye++)
|
|
{
|
|
resolve_rect[eye][0] = (views[eye].tangent_left - left) / (right - left);
|
|
resolve_rect[eye][1] = (top - views[eye].tangent_up) / (top - down);
|
|
resolve_rect[eye][2] = (views[eye].tangent_right - left) / (right - left);
|
|
resolve_rect[eye][3] = (top - views[eye].tangent_down) / (top - down);
|
|
}
|
|
multiview_drawing = d3d8_multiview_begin(rows, resolve_rect);
|
|
}
|
|
}
|
|
|
|
/* the shared camera's projection's depth (render.c: its [2][2] and [3][2]):
|
|
what the game projects itself carries z = -[2][2] + [3][2] / depth */
|
|
void halo_vr_multiview_projection(float projection_22, float projection_32)
|
|
{
|
|
d3d8_multiview_depth(-projection_22, projection_32);
|
|
}
|
|
|
|
/* the sky is being drawn (render.c): as though infinitely far */
|
|
void halo_vr_multiview_sky(int sky)
|
|
{
|
|
if (multiview_drawing)
|
|
d3d8_multiview_rows(multiview_rows[sky ? 1 : 0]);
|
|
}
|
|
|
|
/* after the shared draw: its layers become the eyes' images */
|
|
void halo_vr_capture_multiview(void)
|
|
{
|
|
unsigned int layers = d3d8_multiview_end();
|
|
|
|
if (!multiview_drawing)
|
|
return;
|
|
multiview_drawing = 0;
|
|
if (host_xr_copy_layer_array(HALO_XR_LAYER_EYE_LEFT, layers, 0))
|
|
vr.eyes_captured |= 1;
|
|
if (host_xr_copy_layer_array(HALO_XR_LAYER_EYE_RIGHT, layers, 1))
|
|
vr.eyes_captured |= 2;
|
|
if (vr.eyes_start_us)
|
|
vr.eyes_us += now_us() - vr.eyes_start_us;
|
|
}
|
|
|
|
void halo_vr_capture_eye(int eye)
|
|
{
|
|
if (!vr.active || eye < 0 || eye >= HALO_XR_EYE_COUNT)
|
|
return;
|
|
if (host_xr_copy_layer(HALO_XR_LAYER_EYE_LEFT + eye, d3d8_back_buffer_texture(), 1))
|
|
vr.eyes_captured |= 1 << eye;
|
|
if (eye == HALO_XR_EYE_COUNT - 1 && vr.eyes_start_us)
|
|
vr.eyes_us += now_us() - vr.eyes_start_us;
|
|
}
|
|
|
|
void halo_vr_begin_overlay(int menus_active)
|
|
{
|
|
if (!vr.active)
|
|
return;
|
|
d3d8_clear_back_buffer(0.0f, 0.0f, 0.0f, 0.0f);
|
|
vr.overlay_mode = menus_active ? HALO_XR_OVERLAY_SCREEN : HALO_XR_OVERLAY_HEAD_LOCKED;
|
|
vr.overlay_blend = 1;
|
|
}
|
|
|
|
int halo_vr_aim(short local_player_index, float current_yaw, float current_pitch,
|
|
float *yaw, float *pitch)
|
|
{
|
|
float head_yaw, head_pitch;
|
|
|
|
(void)current_pitch;
|
|
if (!halo_vr_stereo_frame() || local_player_index != 0 || !(vr.frame.head_flags & HALO_XR_POSE_VALID))
|
|
{
|
|
vr.aim_synced = 0;
|
|
return 0;
|
|
}
|
|
head_angles(&head_yaw, &head_pitch);
|
|
if (vr.seat_mode == HALO_VR_SEAT_STEER)
|
|
{
|
|
/* driving: the right stick steers off the vehicle's heading, and
|
|
aims above or below the horizon (the body turns with the vehicle) */
|
|
const struct halo_xr_hand *hand = &vr.frame.hands[HALO_XR_HAND_RIGHT];
|
|
float stick[2];
|
|
unsigned long long now = now_us();
|
|
float seconds = vr.steer_last_us ? (float)(now - vr.steer_last_us) * 1e-6f : 0.0f;
|
|
float blend;
|
|
int axis;
|
|
|
|
vr.steer_last_us = now;
|
|
stick[0] = hand->active && vr.frame.focused ? hand->thumbstick[0] : 0.0f;
|
|
stick[1] = hand->active && vr.frame.focused ? hand->thumbstick[1] : 0.0f;
|
|
/* past the dead zone, along a curve (fine aim near the middle, all
|
|
of it at the end), eased in (vr.vehicle_steer_curve, _smoothing) */
|
|
blend = vr.vehicle_steer_smoothing > 0.0f && seconds < 0.2f ?
|
|
1.0f - expf(-seconds / vr.vehicle_steer_smoothing) :
|
|
1.0f;
|
|
for (axis = 0; axis < 2; axis++)
|
|
{
|
|
float amount = (fabsf(stick[axis]) - 0.15f) / 0.85f;
|
|
|
|
amount = amount > 0.0f ? powf(amount > 1.0f ? 1.0f : amount, vr.vehicle_steer_curve) : 0.0f;
|
|
if (stick[axis] < 0.0f)
|
|
amount = -amount;
|
|
if (axis == 0 && vr.wheel_hands)
|
|
{
|
|
/* (the hands on the wheel steer, as they turn it: clockwise
|
|
to the right) */
|
|
vr.steer[0] = -vr.wheel_turn / vr.vehicle_wheel_angle;
|
|
continue;
|
|
}
|
|
vr.steer[axis] += (amount - vr.steer[axis]) * blend;
|
|
}
|
|
/* (a Ghost or a Banshee aims its guns as it steers: less, finer) */
|
|
*yaw = wrap_angle(vr.seat_yaw - vr.steer[0] *
|
|
(vr.seat_armed ? vr.vehicle_gun_steer_angle : vr.vehicle_steer_angle));
|
|
*pitch = vr.steer[1] * vr.vehicle_pitch_angle;
|
|
vr.aim_synced = 1;
|
|
}
|
|
else if (vr.seat_mode == HALO_VR_SEAT_AIM && weapon_hand_tracked(local_player_index))
|
|
{
|
|
/* a gunner or passenger: where the right controller points */
|
|
float forward[3];
|
|
|
|
seat_aim_direction(forward);
|
|
*yaw = atan2f(forward[1], forward[0]);
|
|
*pitch = asinf(forward[2] > 1.0f ? 1.0f : forward[2] < -1.0f ? -1.0f : forward[2]);
|
|
vr.aim_synced = 1;
|
|
}
|
|
else
|
|
{
|
|
if (vr.seat_mode != HALO_VR_SEAT_NONE)
|
|
{
|
|
/* seated: the body is the vehicle's */
|
|
vr.aim_synced = 1;
|
|
}
|
|
else if (!vr.aim_synced)
|
|
{
|
|
/* keep the way the game faces: the body turns to match the head */
|
|
vr.body_yaw = wrap_angle(current_yaw - head_yaw);
|
|
vr.aim_synced = 1;
|
|
}
|
|
else if (fabsf(wrap_angle(current_yaw - vr.last_yaw)) > FACING_JUMP)
|
|
{
|
|
vr.body_yaw = wrap_angle(vr.body_yaw + wrap_angle(current_yaw - vr.last_yaw));
|
|
}
|
|
*yaw = wrap_angle(vr.body_yaw + head_yaw);
|
|
*pitch = head_pitch;
|
|
}
|
|
if (*yaw < 0.0f)
|
|
*yaw += 2.0f * PI;
|
|
vr.last_yaw = *yaw;
|
|
vr.last_pitch = *pitch;
|
|
return 1;
|
|
}
|
|
|
|
/* ---------- vector helpers */
|
|
|
|
static void cross3(const float a[3], const float b[3], float out[3])
|
|
{
|
|
float result[3];
|
|
|
|
result[0] = a[1] * b[2] - a[2] * b[1];
|
|
result[1] = a[2] * b[0] - a[0] * b[2];
|
|
result[2] = a[0] * b[1] - a[1] * b[0];
|
|
memcpy(out, result, sizeof(result));
|
|
}
|
|
|
|
static float normalize3(float v[3])
|
|
{
|
|
float length = sqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
|
|
|
|
if (length > 1e-6f)
|
|
{
|
|
v[0] /= length;
|
|
v[1] /= length;
|
|
v[2] /= length;
|
|
}
|
|
return length;
|
|
}
|
|
|
|
/* turns v by the least rotation that takes the unit vector from to to */
|
|
static void rotate_between(const float from[3], const float to[3], float v[3])
|
|
{
|
|
float axis[3], turned[3], cosine, sine, along;
|
|
int index;
|
|
|
|
cross3(from, to, axis);
|
|
sine = normalize3(axis);
|
|
cosine = from[0] * to[0] + from[1] * to[1] + from[2] * to[2];
|
|
if (sine < 1e-6f)
|
|
return;
|
|
cross3(axis, v, turned);
|
|
along = axis[0] * v[0] + axis[1] * v[1] + axis[2] * v[2];
|
|
for (index = 0; index < 3; index++)
|
|
v[index] = v[index] * cosine + turned[index] * sine + axis[index] * along * (1.0f - cosine);
|
|
}
|
|
|
|
/* ---------- the weapon in the right hand (vr.aim_with = "controller") */
|
|
|
|
static int weapon_hand_tracked(short local_player_index)
|
|
{
|
|
const struct halo_xr_hand *hand = &vr.frame.hands[HALO_XR_HAND_RIGHT];
|
|
|
|
return vr.aim_with_controller && local_player_index == 0 && halo_vr_stereo_frame() &&
|
|
hand->active && (hand->flags & HALO_XR_POSE_VALID);
|
|
}
|
|
|
|
/* the weapon's axes in the game's: where the right controller points */
|
|
/* The weapon's frames, all in the game's axes:
|
|
- the controller's: where it points;
|
|
- the model's: the controller's turned so that the first-person model's
|
|
barrel, pointed in towards the screen's centre, points where the
|
|
controller does (barrel_direction);
|
|
- with both hands on it, both turned alike so that the line from the
|
|
model's right hand to its left, in the model's frame, runs from the right
|
|
controller to the left one.
|
|
The weapon aims along the controller's frame (so turned); the model hangs
|
|
from the model's frame. */
|
|
static void controller_axes(float forward[3], float up[3], float right[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 };
|
|
const float *orientation = vr.frame.hands[HALO_XR_HAND_RIGHT].aim.orientation;
|
|
float direction[3];
|
|
|
|
quaternion_rotate(orientation, local_forward, direction);
|
|
local_to_game(direction, vr.body_yaw, forward);
|
|
quaternion_rotate(orientation, local_up, direction);
|
|
local_to_game(direction, vr.body_yaw, up);
|
|
/* the game's axes are right-handed with +z up: forward x up is right */
|
|
cross3(forward, up, right);
|
|
}
|
|
|
|
static void model_axes(const float forward[3], const float up[3], const float right[3],
|
|
float model_forward[3], float model_up[3], float model_right[3])
|
|
{
|
|
float barrel[3];
|
|
int axis;
|
|
|
|
memcpy(model_forward, forward, 3 * sizeof(float));
|
|
memcpy(model_up, up, 3 * sizeof(float));
|
|
memcpy(model_right, right, 3 * sizeof(float));
|
|
if (!vr.barrel_direction_valid)
|
|
return;
|
|
for (axis = 0; axis < 3; axis++)
|
|
{
|
|
barrel[axis] = forward[axis] * vr.barrel_direction[0] - right[axis] * vr.barrel_direction[1] +
|
|
up[axis] * vr.barrel_direction[2];
|
|
}
|
|
if (normalize3(barrel) > 0.0f)
|
|
{
|
|
rotate_between(barrel, forward, model_forward);
|
|
rotate_between(barrel, forward, model_up);
|
|
cross3(model_forward, model_up, model_right);
|
|
}
|
|
}
|
|
|
|
/* with both hands on the weapon, the turn that brings the model's grips in
|
|
line with the hands (from, to); 0 without */
|
|
static int two_hand_turn(const float model_forward[3], const float model_up[3], const float model_right[3],
|
|
float from[3], float to[3])
|
|
{
|
|
float left[3], right_hand[3];
|
|
int axis;
|
|
|
|
if (!vr.two_hand_gripping || !vr.two_hand_valid)
|
|
return 0;
|
|
for (axis = 0; axis < 3; axis++)
|
|
{
|
|
from[axis] = model_forward[axis] * vr.two_hand_reference[0] + model_right[axis] * vr.two_hand_reference[1] +
|
|
model_up[axis] * vr.two_hand_reference[2];
|
|
}
|
|
tracked_offset(vr.frame.hands[HALO_XR_HAND_LEFT].grip.position, left);
|
|
tracked_offset(vr.frame.hands[HALO_XR_HAND_RIGHT].grip.position, right_hand);
|
|
for (axis = 0; axis < 3; axis++)
|
|
to[axis] = left[axis] - right_hand[axis];
|
|
return normalize3(from) > 0.0f && normalize3(to) > 0.0f;
|
|
}
|
|
|
|
static void turn_axes(const float from[3], const float to[3], float forward[3], float up[3], float right[3])
|
|
{
|
|
rotate_between(from, to, forward);
|
|
rotate_between(from, to, up);
|
|
cross3(forward, up, right);
|
|
}
|
|
|
|
/* where the weapon aims */
|
|
static void weapon_axes(float forward[3], float up[3], float right[3])
|
|
{
|
|
float model_forward[3], model_up[3], model_right[3], from[3], to[3];
|
|
|
|
controller_axes(forward, up, right);
|
|
model_axes(forward, up, right, model_forward, model_up, model_right);
|
|
if (two_hand_turn(model_forward, model_up, model_right, from, to))
|
|
turn_axes(from, to, forward, up, right);
|
|
}
|
|
|
|
/* how the weapon's model hangs */
|
|
static void weapon_model_axes(float forward[3], float up[3], float right[3])
|
|
{
|
|
float controller_forward[3], controller_up[3], controller_right[3], from[3], to[3];
|
|
|
|
controller_axes(controller_forward, controller_up, controller_right);
|
|
model_axes(controller_forward, controller_up, controller_right, forward, up, right);
|
|
if (two_hand_turn(forward, up, right, from, to))
|
|
turn_axes(from, to, forward, up, right);
|
|
}
|
|
|
|
/* the hand's offset from the game's camera, in world units */
|
|
static void hand_offset(float out[3])
|
|
{
|
|
int axis;
|
|
|
|
tracked_offset(vr.frame.hands[HALO_XR_HAND_RIGHT].grip.position, out);
|
|
for (axis = 0; axis < 3; axis++)
|
|
out[axis] /= METRES_PER_WORLD_UNIT;
|
|
}
|
|
|
|
int halo_vr_aim_vector(short local_player_index, float vector[3])
|
|
{
|
|
float up[3], right[3];
|
|
|
|
/* a punch lands where the player looks, not where the weapon points */
|
|
if (vr.active && now_us() < vr.melee_aim_until_us && halo_vr_look_vector(local_player_index, vector))
|
|
return 1;
|
|
if (!weapon_hand_tracked(local_player_index))
|
|
return 0;
|
|
weapon_axes(vector, up, right);
|
|
return 1;
|
|
}
|
|
|
|
void halo_vr_set_barrel(short local_player_index, const float world[3], const float direction[3])
|
|
{
|
|
if (local_player_index != 0)
|
|
return;
|
|
memcpy(vr.barrel, world, sizeof(vr.barrel));
|
|
vr.barrel_frame = vr.frame_number;
|
|
if (!vr.barrel_direction_valid || memcmp(vr.barrel_direction, direction, sizeof(vr.barrel_direction)))
|
|
{
|
|
float degrees = acosf(direction[0] > 1.0f ? 1.0f : direction[0]) * 180.0f / PI;
|
|
|
|
if (!vr.barrel_direction_valid || fabsf(direction[0] - vr.barrel_direction[0]) > 1e-3f)
|
|
platform_log("VR: this weapon's barrel points %.1f degrees off its camera; turned to aim", degrees);
|
|
memcpy(vr.barrel_direction, direction, sizeof(vr.barrel_direction));
|
|
vr.barrel_direction_valid = 1;
|
|
}
|
|
}
|
|
|
|
/* the barrel was drawn this frame or the last */
|
|
static int barrel_known(void)
|
|
{
|
|
return vr.barrel_frame && vr.frame_number - vr.barrel_frame <= 1;
|
|
}
|
|
|
|
int halo_vr_weapon_origin_offset(short local_player_index, float offset[3])
|
|
{
|
|
float forward[3], up[3], right[3];
|
|
int axis;
|
|
|
|
if (!weapon_hand_tracked(local_player_index))
|
|
return 0;
|
|
weapon_axes(forward, up, right);
|
|
/* along the weapon as drawn (each weapon's model sits differently in
|
|
the hand), else along the controller */
|
|
if (barrel_known())
|
|
{
|
|
for (axis = 0; axis < 3; axis++)
|
|
offset[axis] = vr.barrel[axis] - vr.game_camera[axis];
|
|
}
|
|
else
|
|
{
|
|
hand_offset(offset);
|
|
}
|
|
for (axis = 0; axis < 3; axis++)
|
|
offset[axis] += forward[axis] * vr.muzzle_distance / METRES_PER_WORLD_UNIT;
|
|
return 1;
|
|
}
|
|
|
|
int halo_vr_weapon_origin(short local_player_index, float origin[3])
|
|
{
|
|
float offset[3];
|
|
int axis;
|
|
|
|
if (!halo_vr_weapon_origin_offset(local_player_index, offset))
|
|
return 0;
|
|
for (axis = 0; axis < 3; axis++)
|
|
origin[axis] = vr.game_camera[axis] + offset[axis];
|
|
return 1;
|
|
}
|
|
|
|
int halo_vr_weapon_camera(short local_player_index, float position[3], float forward[3], float up[3])
|
|
{
|
|
float right[3], hand[3];
|
|
int axis;
|
|
|
|
if (!weapon_hand_tracked(local_player_index))
|
|
return 0;
|
|
weapon_model_axes(forward, up, right);
|
|
hand_offset(hand);
|
|
{
|
|
/* the right grip's melee attack, which the hand does not swing: the
|
|
weapon jabs forward and back (the game's own animation is held
|
|
off, first_person_weapons.c) */
|
|
float seconds = vr.melee_jab_start_us ? (float)(now_us() - vr.melee_jab_start_us) * 1e-6f : 1.0f;
|
|
|
|
if (seconds < 0.33f)
|
|
{
|
|
float jab = sinf(seconds / 0.33f * PI) * 0.25f / METRES_PER_WORLD_UNIT;
|
|
|
|
for (axis = 0; axis < 3; axis++)
|
|
hand[axis] += forward[axis] * jab;
|
|
}
|
|
}
|
|
/* the first-person model hangs from a camera: put that camera where the
|
|
model's grip lands in the hand */
|
|
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;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int halo_vr_laser(short local_player_index, float start[3], float direction[3])
|
|
{
|
|
if (!vr.laser)
|
|
return 0;
|
|
if (local_player_index == 0 && vr.laser_frame == vr.frame_number)
|
|
{
|
|
memcpy(start, vr.laser_start, sizeof(vr.laser_start));
|
|
memcpy(direction, vr.laser_direction, sizeof(vr.laser_direction));
|
|
return 1;
|
|
}
|
|
return halo_vr_ready_laser(local_player_index, start, direction);
|
|
}
|
|
|
|
int halo_vr_ready_laser(short local_player_index, float start[3], float direction[3])
|
|
{
|
|
float up[3], right[3], offset[3];
|
|
int axis;
|
|
|
|
/* seated, the vehicle's aim (and the crosshair's distance along it) */
|
|
if (local_player_index == 0 && vr.active && seat_aim(start, direction))
|
|
return 1;
|
|
|
|
if (!halo_vr_weapon_origin_offset(local_player_index, offset))
|
|
return 0;
|
|
weapon_axes(direction, up, right);
|
|
for (axis = 0; axis < 3; axis++)
|
|
start[axis] = vr.game_camera[axis] + offset[axis];
|
|
return 1;
|
|
}
|
|
|
|
int halo_vr_look_vector(short local_player_index, float vector[3])
|
|
{
|
|
static const float forward[3] = { 0.0f, 0.0f, -1.0f };
|
|
float direction[3];
|
|
|
|
if (local_player_index != 0 || !halo_vr_stereo_frame() || !(vr.frame.head_flags & HALO_XR_POSE_VALID))
|
|
return 0;
|
|
quaternion_rotate(vr.frame.head.orientation, forward, direction);
|
|
local_to_game(direction, vr.body_yaw, vector);
|
|
return 1;
|
|
}
|
|
|
|
void halo_vr_debug_look(short local_player_index, const float unit_looking[3], int in_vehicle)
|
|
{
|
|
static unsigned long long last_us;
|
|
float head[3] = { 0.0f, 0.0f, 0.0f };
|
|
int valid;
|
|
unsigned long long now = now_us();
|
|
|
|
if (!vr.log_performance || local_player_index != 0 || now - last_us < 1000000)
|
|
return;
|
|
last_us = now;
|
|
valid = halo_vr_look_vector(local_player_index, head);
|
|
platform_log("VR look: head %s (%.2f %.2f %.2f), unit looking (%.2f %.2f %.2f)%s", valid ? "valid" : "INVALID",
|
|
head[0], head[1], head[2], unit_looking[0], unit_looking[1], unit_looking[2], in_vehicle ? ", in a vehicle" : "");
|
|
}
|
|
|
|
/* the body: where the head is (world units) and its heading, flat */
|
|
int halo_vr_body_frame(short local_player_index, float head[3], float forward[3])
|
|
{
|
|
float offset[3], look[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++)
|
|
head[axis] = vr.game_camera[axis] + offset[axis] / METRES_PER_WORLD_UNIT;
|
|
if (!halo_vr_look_vector(local_player_index, look))
|
|
return 0;
|
|
forward[0] = look[0];
|
|
forward[1] = look[1];
|
|
forward[2] = 0.0f;
|
|
if (normalize3(forward) < 1e-3f)
|
|
{
|
|
forward[0] = cosf(vr.body_yaw);
|
|
forward[1] = sinf(vr.body_yaw);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int halo_vr_left_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 };
|
|
static const float local_up[3] = { 0.0f, 1.0f, 0.0f };
|
|
const struct halo_xr_hand *hand = &vr.frame.hands[HALO_XR_HAND_LEFT];
|
|
float offset[3], direction[3];
|
|
int axis;
|
|
|
|
if (!vr.aim_with_controller || local_player_index != 0 || !halo_vr_stereo_frame() || !hand->active ||
|
|
!(hand->flags & HALO_XR_POSE_VALID))
|
|
{
|
|
return 0;
|
|
}
|
|
tracked_offset(hand->grip.position, offset);
|
|
for (axis = 0; axis < 3; axis++)
|
|
position[axis] = vr.game_camera[axis] + offset[axis] / METRES_PER_WORLD_UNIT;
|
|
quaternion_rotate(hand->aim.orientation, local_forward, direction);
|
|
local_to_game(direction, vr.body_yaw, forward);
|
|
quaternion_rotate(hand->aim.orientation, local_up, direction);
|
|
local_to_game(direction, vr.body_yaw, up);
|
|
return 1;
|
|
}
|
|
|
|
int halo_vr_two_hand(short local_player_index, const float foregrip[3], const float reference[3])
|
|
{
|
|
const struct halo_xr_hand *hand = &vr.frame.hands[HALO_XR_HAND_LEFT];
|
|
float position[3], forward[3], up[3];
|
|
int squeezed = hand->squeeze > (vr.two_hand_latched ? 0.35f : 0.6f);
|
|
|
|
memcpy(vr.two_hand_reference, reference, sizeof(vr.two_hand_reference));
|
|
vr.two_hand_valid = 1;
|
|
if (!squeezed || !halo_vr_left_hand(local_player_index, position, forward, up))
|
|
{
|
|
vr.two_hand_latched = vr.two_hand_gripping = 0;
|
|
return 0;
|
|
}
|
|
if (!vr.two_hand_latched)
|
|
{
|
|
/* the grip closed: on the weapon if the hand is at its foregrip */
|
|
float distance = sqrtf((position[0] - foregrip[0]) * (position[0] - foregrip[0]) +
|
|
(position[1] - foregrip[1]) * (position[1] - foregrip[1]) +
|
|
(position[2] - foregrip[2]) * (position[2] - foregrip[2])) * METRES_PER_WORLD_UNIT;
|
|
|
|
vr.two_hand_latched = 1;
|
|
vr.two_hand_gripping = distance < vr.two_hand_reach;
|
|
if (vr.two_hand_gripping)
|
|
host_xr_haptic(HALO_XR_HAND_LEFT, 0.4f, 0.04f);
|
|
}
|
|
return vr.two_hand_gripping;
|
|
}
|
|
|
|
int halo_vr_look_delta(float *yaw, float *pitch)
|
|
{
|
|
/* past the head's tremble */
|
|
const float threshold = 2.0f * PI / 180.0f;
|
|
|
|
*yaw = *pitch = 0.0f;
|
|
if (fabsf(vr.look_turned_yaw) > threshold)
|
|
{
|
|
*yaw = vr.look_turned_yaw;
|
|
vr.look_turned_yaw = 0.0f;
|
|
}
|
|
if (fabsf(vr.look_turned_pitch) > threshold)
|
|
{
|
|
*pitch = vr.look_turned_pitch;
|
|
vr.look_turned_pitch = 0.0f;
|
|
}
|
|
return *yaw != 0.0f || *pitch != 0.0f;
|
|
}
|
|
|
|
void halo_vr_set_laser(short local_player_index, const float start[3], const float direction[3])
|
|
{
|
|
if (local_player_index != 0)
|
|
return;
|
|
memcpy(vr.laser_start, start, sizeof(vr.laser_start));
|
|
memcpy(vr.laser_direction, direction, sizeof(vr.laser_direction));
|
|
vr.laser_frame = vr.frame_number;
|
|
}
|
|
|
|
/* ---------- the crosshair
|
|
|
|
render.c tells, each frame, how far the weapon's aim runs before it hits
|
|
something; the crosshair floats there, along the controller's aim from
|
|
the muzzle, facing the eyes, as large as the HUD showed it. */
|
|
|
|
void halo_vr_set_aim_distance(float world_units)
|
|
{
|
|
vr.aim_distance = world_units * METRES_PER_WORLD_UNIT;
|
|
vr.aim_distance_frame = vr.frame_number;
|
|
}
|
|
|
|
/* a rotation from its axes (unit, right-handed) */
|
|
static void quaternion_from_axes(const float x[3], const float y[3], const float z[3], float q[4])
|
|
{
|
|
float trace = x[0] + y[1] + z[2];
|
|
|
|
if (trace > 0.0f)
|
|
{
|
|
float s = sqrtf(trace + 1.0f) * 2.0f;
|
|
|
|
q[3] = 0.25f * s;
|
|
q[0] = (y[2] - z[1]) / s;
|
|
q[1] = (z[0] - x[2]) / s;
|
|
q[2] = (x[1] - y[0]) / s;
|
|
}
|
|
else if (x[0] > y[1] && x[0] > z[2])
|
|
{
|
|
float s = sqrtf(1.0f + x[0] - y[1] - z[2]) * 2.0f;
|
|
|
|
q[3] = (y[2] - z[1]) / s;
|
|
q[0] = 0.25f * s;
|
|
q[1] = (y[0] + x[1]) / s;
|
|
q[2] = (z[0] + x[2]) / s;
|
|
}
|
|
else if (y[1] > z[2])
|
|
{
|
|
float s = sqrtf(1.0f + y[1] - x[0] - z[2]) * 2.0f;
|
|
|
|
q[3] = (z[0] - x[2]) / s;
|
|
q[0] = (y[0] + x[1]) / s;
|
|
q[1] = 0.25f * s;
|
|
q[2] = (z[1] + y[2]) / s;
|
|
}
|
|
else
|
|
{
|
|
float s = sqrtf(1.0f + z[2] - x[0] - y[1]) * 2.0f;
|
|
|
|
q[3] = (x[1] - y[0]) / s;
|
|
q[0] = (z[0] + x[2]) / s;
|
|
q[1] = (z[1] + y[2]) / s;
|
|
q[2] = 0.25f * s;
|
|
}
|
|
}
|
|
|
|
static int crosshair_pose(struct halo_xr_pose *pose, float *size)
|
|
{
|
|
static const float world_up[3] = { 0.0f, 1.0f, 0.0f };
|
|
float forward[3], up[3], right[3], offset[3], start_world[3], start[3], direction[3], x[3], y[3], z[3];
|
|
float distance = vr.aim_distance_frame == vr.frame_number ? vr.aim_distance : CROSSHAIR_FARTHEST;
|
|
float angle;
|
|
int axis;
|
|
|
|
if (distance > CROSSHAIR_FARTHEST)
|
|
distance = CROSSHAIR_FARTHEST;
|
|
if (distance < CROSSHAIR_NEAREST)
|
|
distance = CROSSHAIR_NEAREST;
|
|
/* driving with no gun to aim, none */
|
|
if (vr.seat_mode == HALO_VR_SEAT_STEER && !vr.seat_armed)
|
|
return 0;
|
|
/* seated, along the vehicle's aim; else along the weapon's aim from its
|
|
muzzle, as its shots go */
|
|
if (!seat_aim(start_world, forward))
|
|
{
|
|
if (!halo_vr_weapon_origin_offset(0, offset))
|
|
return 0;
|
|
weapon_axes(forward, up, right);
|
|
for (axis = 0; axis < 3; axis++)
|
|
start_world[axis] = vr.game_camera[axis] + offset[axis];
|
|
}
|
|
world_to_local(start_world, start);
|
|
game_to_local(forward, direction);
|
|
for (axis = 0; axis < 3; axis++)
|
|
{
|
|
pose->position[axis] = start[axis] + direction[axis] * distance;
|
|
z[axis] = vr.frame.head.position[axis] - pose->position[axis];
|
|
}
|
|
/* facing the eyes, upright */
|
|
if (normalize3(z) < 1e-4f)
|
|
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);
|
|
/* the angle the region spans in the HUD, at this distance */
|
|
angle = CROSSHAIR_REGION_PIXELS / 640.0f * vr.hud_width / vr.hud_distance;
|
|
*size = 2.0f * distance * tanf(angle * 0.5f);
|
|
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;
|
|
/* (a stick moved the menu's choice: the pointer, not moved far from
|
|
here since, leaves it be) */
|
|
int stick_quiet;
|
|
float stick_x, stick_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;
|
|
/* (a stick pushed: what it chooses stays chosen until the pointer
|
|
moves well away) */
|
|
{
|
|
int index;
|
|
|
|
for (index = 0; index < HALO_XR_HAND_COUNT; index++)
|
|
{
|
|
const struct halo_xr_hand *hand = &vr.frame.hands[index];
|
|
|
|
if (hand->active && (fabsf(hand->thumbstick[0]) > 0.4f || fabsf(hand->thumbstick[1]) > 0.4f))
|
|
{
|
|
menu_pointer.stick_quiet = 1;
|
|
menu_pointer.stick_x = menu_pointer.x;
|
|
menu_pointer.stick_y = menu_pointer.y;
|
|
}
|
|
}
|
|
if (menu_pointer.stick_quiet && (fabsf(menu_pointer.x - menu_pointer.stick_x) > 25.0f ||
|
|
fabsf(menu_pointer.y - menu_pointer.stick_y) > 25.0f))
|
|
{
|
|
menu_pointer.stick_quiet = 0;
|
|
}
|
|
}
|
|
/* (moved past a hand's tremor: a few pixels) */
|
|
*moved = !menu_pointer.stick_quiet && (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
|
|
scope's eyepiece, from a camera there looking along the weapon: the disc
|
|
magnifies what it covers of the player's view by the zoom's magnification.
|
|
The world around it stays as it is (zooming the whole view sickens). */
|
|
|
|
#define SCOPE_ABOVE_HAND 0.07f
|
|
#define SCOPE_AHEAD_OF_HAND 0.22f
|
|
|
|
/* (the weapon in hand's: halo_vr_set_scope_raise) */
|
|
static float scope_raise;
|
|
|
|
void halo_vr_set_scope_raise(float metres)
|
|
{
|
|
scope_raise = metres;
|
|
}
|
|
|
|
int halo_vr_scope(short local_player_index, float magnification, float position[3], float forward[3], float up[3],
|
|
float *vertical_field_of_view)
|
|
{
|
|
const struct halo_xr_hand *hand = &vr.frame.hands[HALO_XR_HAND_RIGHT];
|
|
float right[3], game_offset[3], local_forward[3], local_up[3], local_right[3], back[3];
|
|
float eye_distance = 0.0f;
|
|
int axis;
|
|
|
|
if (magnification <= 1.0f || vr.scope_size <= 0.0f || !weapon_hand_tracked(local_player_index))
|
|
return 0;
|
|
/* the disc: above and ahead of the hand, moved sideways over the barrel
|
|
as drawn, facing back along the weapon's aim */
|
|
weapon_axes(forward, up, right);
|
|
game_to_local(forward, local_forward);
|
|
game_to_local(up, local_up);
|
|
game_to_local(right, local_right);
|
|
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;
|
|
}
|
|
if (barrel_known())
|
|
{
|
|
float barrel[3], sideways = 0.0f;
|
|
|
|
world_to_local(vr.barrel, barrel);
|
|
for (axis = 0; axis < 3; axis++)
|
|
sideways += (barrel[axis] - hand->aim.position[axis]) * local_right[axis];
|
|
for (axis = 0; axis < 3; axis++)
|
|
vr.scope_pose.position[axis] += local_right[axis] * sideways;
|
|
}
|
|
for (axis = 0; axis < 3; axis++)
|
|
{
|
|
back[axis] = -local_forward[axis];
|
|
eye_distance += (vr.scope_pose.position[axis] - vr.frame.head.position[axis]) *
|
|
(vr.scope_pose.position[axis] - vr.frame.head.position[axis]);
|
|
}
|
|
quaternion_from_axes(local_right, local_up, back, vr.scope_pose.orientation);
|
|
eye_distance = sqrtf(eye_distance);
|
|
if (eye_distance < 0.05f)
|
|
eye_distance = 0.05f;
|
|
|
|
/* the camera: at the disc, along the weapon */
|
|
tracked_offset(vr.scope_pose.position, game_offset);
|
|
for (axis = 0; axis < 3; axis++)
|
|
position[axis] = vr.game_camera[axis] + game_offset[axis] / METRES_PER_WORLD_UNIT;
|
|
*vertical_field_of_view = 2.0f * atanf(vr.scope_size * 0.5f / eye_distance) / magnification;
|
|
vr.scope_pass = 1;
|
|
return 1;
|
|
}
|
|
|
|
int halo_vr_scope_pass(void)
|
|
{
|
|
return vr.scope_pass;
|
|
}
|
|
|
|
void halo_vr_capture_scope(float left, float top, float right, float bottom)
|
|
{
|
|
vr.scope_pass = 0;
|
|
vr.scope_captured = host_xr_copy_layer_region(HALO_XR_LAYER_SCOPE, d3d8_back_buffer_texture(), HALO_XR_COPY_DISC,
|
|
left, top, right, bottom);
|
|
}
|
|
|
|
int halo_vr_hide_crosshair(void)
|
|
{
|
|
/* the HUD's crosshair marks where the head looks: left out of it, and
|
|
drawn on its own to be taken to where the weapon aims (vr.crosshair) */
|
|
return vr.active && vr.aim_with_controller;
|
|
}
|
|
|
|
int halo_vr_crosshair_apart(void)
|
|
{
|
|
return vr.active && vr.aim_with_controller && vr.crosshair && halo_vr_stereo_frame();
|
|
}
|
|
|
|
void halo_vr_capture_crosshair(void)
|
|
{
|
|
vr.crosshair_captured = host_xr_copy_layer_region(HALO_XR_LAYER_CROSSHAIR, d3d8_back_buffer_texture(),
|
|
HALO_XR_COPY_ALPHA, CROSSHAIR_REGION_LEFT, CROSSHAIR_REGION_TOP, CROSSHAIR_REGION_RIGHT,
|
|
CROSSHAIR_REGION_BOTTOM);
|
|
d3d8_clear_back_buffer(0.0f, 0.0f, 0.0f, 0.0f);
|
|
}
|
|
|
|
/* the controllers, for the gamepad the game reads (xinput_sdl.c) */
|
|
int halo_vr_controllers(struct halo_xr_hand hands[HALO_XR_HAND_COUNT], int *menus)
|
|
{
|
|
if (!vr.active || !vr.frame.focused)
|
|
return 0;
|
|
memcpy(hands, vr.frame.hands, sizeof(vr.frame.hands));
|
|
*menus = vr.last_overlay_mode == HALO_XR_OVERLAY_SCREEN;
|
|
return 1;
|
|
}
|
|
|
|
#else
|
|
|
|
int halo_vr_active(void) { return 0; }
|
|
int halo_vr_stereo_frame(void) { return 0; }
|
|
int halo_vr_stereo_player(short local_player_index, int cinematic) { (void)local_player_index; (void)cinematic; return 0; }
|
|
void halo_vr_eye_camera(int eye, const float game_position[3], float position[3], float forward[3], float up[3],
|
|
float *vertical_field_of_view, float frustum_bounds[4])
|
|
{
|
|
(void)eye; (void)game_position; (void)position; (void)forward; (void)up;
|
|
(void)vertical_field_of_view; (void)frustum_bounds;
|
|
}
|
|
void halo_vr_capture_eye(int eye) { (void)eye; }
|
|
int halo_vr_multiview(void) { return 0; }
|
|
void halo_vr_center_camera(const float game_position[3], float position[3], float forward[3], float up[3],
|
|
float *vertical_field_of_view, float frustum_bounds[4])
|
|
{
|
|
(void)game_position; (void)position; (void)forward; (void)up; (void)vertical_field_of_view; (void)frustum_bounds;
|
|
}
|
|
void halo_vr_capture_multiview(void) {}
|
|
void halo_vr_multiview_sky(int sky) { (void)sky; }
|
|
void halo_vr_multiview_projection(float projection_22, float projection_32)
|
|
{
|
|
(void)projection_22; (void)projection_32;
|
|
}
|
|
void halo_vr_begin_overlay(int menus_active) { (void)menus_active; }
|
|
int halo_vr_aim(short local_player_index, float current_yaw, float current_pitch, float *yaw, float *pitch)
|
|
{
|
|
(void)local_player_index; (void)current_yaw; (void)current_pitch; (void)yaw; (void)pitch;
|
|
return 0;
|
|
}
|
|
void halo_vr_initialize(void) {}
|
|
void halo_vr_frame_begin(void) {}
|
|
void halo_vr_present(unsigned int back_buffer_texture) { (void)back_buffer_texture; }
|
|
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)local_player_index; (void)position; (void)forward; (void)up;
|
|
return 0;
|
|
}
|
|
int halo_vr_weapon_origin(short local_player_index, float origin[3])
|
|
{
|
|
(void)local_player_index; (void)origin;
|
|
return 0;
|
|
}
|
|
int halo_vr_laser(short local_player_index, float start[3], float direction[3])
|
|
{
|
|
(void)local_player_index; (void)start; (void)direction;
|
|
return 0;
|
|
}
|
|
int halo_vr_look_vector(short local_player_index, float vector[3]) { (void)local_player_index; (void)vector; return 0; }
|
|
void halo_vr_debug_look(short local_player_index, const float unit_looking[3], int in_vehicle)
|
|
{
|
|
(void)local_player_index; (void)unit_looking; (void)in_vehicle;
|
|
}
|
|
int halo_vr_melee(void) { return 0; }
|
|
void halo_vr_set_seat(short local_player_index, int seat, int armed, float vehicle_yaw)
|
|
{
|
|
(void)local_player_index; (void)seat; (void)armed; (void)vehicle_yaw;
|
|
}
|
|
void halo_vr_set_seat_frame(short local_player_index, float vehicle_yaw)
|
|
{
|
|
(void)local_player_index; (void)vehicle_yaw;
|
|
}
|
|
int halo_vr_vehicle_first_person(void) { return 0; }
|
|
int halo_vr_take_vehicle_spawn(short local_player_index) { (void)local_player_index; return 0; }
|
|
float halo_vr_gunner_camera_back(void) { return 0.0f; }
|
|
float halo_vr_driver_camera_forward(void) { return 0.0f; }
|
|
float halo_vr_enclosed_seat_raise(int hull) { (void)hull; return 0.0f; }
|
|
float halo_vr_glass_strength(void) { return 1.0f; }
|
|
int halo_vr_grenade_held(short local_player_index) { (void)local_player_index; return 0; }
|
|
int halo_vr_grenade_in_hand(short local_player_index, float position[3], float forward[3], float up[3],
|
|
float *scale)
|
|
{
|
|
(void)local_player_index; (void)position; (void)forward; (void)up; (void)scale;
|
|
return 0;
|
|
}
|
|
int halo_vr_grenade_throw(short local_player_index, float origin[3], float direction[3], float *strength)
|
|
{
|
|
(void)local_player_index; (void)origin; (void)direction; (void)strength;
|
|
return 0;
|
|
}
|
|
float halo_vr_melee_strength(void) { return 0.0f; }
|
|
int halo_vr_look_delta(float *yaw, float *pitch) { *yaw = *pitch = 0.0f; return 0; }
|
|
int halo_vr_body_frame(short local_player_index, float head[3], float forward[3])
|
|
{
|
|
(void)local_player_index; (void)head; (void)forward;
|
|
return 0;
|
|
}
|
|
int halo_vr_left_hand(short local_player_index, float position[3], float forward[3], float up[3])
|
|
{
|
|
(void)local_player_index; (void)position; (void)forward; (void)up;
|
|
return 0;
|
|
}
|
|
int halo_vr_two_hand(short local_player_index, const float foregrip[3], const float reference[3])
|
|
{
|
|
(void)local_player_index; (void)foregrip; (void)reference;
|
|
return 0;
|
|
}
|
|
void halo_vr_set_laser(short local_player_index, const float start[3], const float direction[3])
|
|
{
|
|
(void)local_player_index; (void)start; (void)direction;
|
|
}
|
|
int halo_vr_ready_laser(short local_player_index, float start[3], float direction[3])
|
|
{
|
|
(void)local_player_index; (void)start; (void)direction;
|
|
return 0;
|
|
}
|
|
int halo_vr_scope(short local_player_index, float magnification, float position[3], float forward[3], float up[3],
|
|
float *vertical_field_of_view)
|
|
{
|
|
(void)local_player_index; (void)magnification; (void)position; (void)forward; (void)up;
|
|
(void)vertical_field_of_view;
|
|
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)
|
|
{
|
|
(void)left; (void)top; (void)right; (void)bottom;
|
|
}
|
|
void halo_vr_set_aim_distance(float world_units) { (void)world_units; }
|
|
void halo_vr_set_barrel(short local_player_index, const float world[3], const float direction[3])
|
|
{
|
|
(void)local_player_index; (void)world; (void)direction;
|
|
}
|
|
int halo_vr_hide_crosshair(void) { return 0; }
|
|
int halo_vr_crosshair_apart(void) { return 0; }
|
|
void halo_vr_capture_crosshair(void) {}
|
|
|
|
#endif
|