mirror of
https://github.com/Cesarus85/ringfall-mjolnir-vr.git
synced 2026-10-17 16:00:26 +02:00
Compare commits
38
Commits
No files matched your search
+184
-6
@@ -25,19 +25,170 @@ named where code is taken over.
|
||||
| Menus | The PC version's menus in VR (`vr.menus`), clicked with a laser; text typed on the on-screen keyboard |
|
||||
| Multiplayer | Up to date with OpenCE (network version 20): cross-play, its server browser, co-op campaign, password lobbies; join codes and the VR online list |
|
||||
| Bots | Bots in multiplayer: on foot (paths, items, objectives), in vehicles and at turrets; they make room for a joining person |
|
||||
| Controls | Left-handed mode (`vr.handedness`): the weapon in the left hand, the arms and grips mirrored, sticks and buttons mirrored on both controllers (`vr.mirror_controls`), the assault rifle's counter read the right way round (2026-10-06) |
|
||||
| Graphics | Presets low / medium / high / max (`vr.graphics`), refresh rate and resolution in the VR options, all kept over a restart (2026-10-06) |
|
||||
| Arms and hands | Both arms by IK from shoulders that follow the torso, the elbow led by the hand's roll; fingers from trigger, grip and touch; `vr.body` arms / hands / off, `vr.fingers` (2026-10-06) |
|
||||
|
||||
## Next, in order
|
||||
|
||||
| # | Step | State |
|
||||
| --- | --- | --- |
|
||||
| 1 | Steam Frame CPU time: Mesa's trace markers off (after astromaddie/HaloCE-VR), no `glGet` stalls in the visibility counters' copies; then measured in the headset | built (branch `frame/performance-2`) |
|
||||
| 3a | Visible arms and hands: the arms by IK to the controllers, fingers by trigger and grip | |
|
||||
| 2 | Controls and comfort: left-handed mode, buttons remapped, flashlight by tapping the head, finer turning and vignette choices, graphics presets and refresh rate in the VR options | |
|
||||
| 1 | Steam Frame CPU time: Mesa's trace markers off (after astromaddie/HaloCE-VR), no `glGet` stalls in the visibility counters' copies | **done** (in `main` 2026-10-06): markers quieted, no problems; the frame's work about unchanged (11-12.5 ms of 13.9 in a bot game) |
|
||||
| 3a | Visible arms and hands: the arms by IK to the controllers, fingers by trigger and grip | **done** (in `main` 2026-10-06; Quest 3 and Steam Frame good; 0.02 ms a frame) |
|
||||
| 2 | Controls and comfort: left-handed mode, buttons remapped, flashlight by tapping the head, finer turning and vignette choices, graphics presets and refresh rate in the VR options | first part **done** (in `main` 2026-10-06; Quest 3 and Steam Frame good): left-handed mode, graphics presets, refresh rate and resolution in the VR options. Left: buttons remapped, flashlight by tapping the head, finer turning and vignette choices |
|
||||
| 3b | The whole body: torso from head and hands, legs and feet following in the room; full / arms only / hands only | |
|
||||
| 3c | Weapons held physically, holsters at shoulder and hip (the buttons stay) | |
|
||||
| 3d | Other VR players shown with their heads, hands and bodies (today their bodies move as flat players' do: only their aim and throws come from their hands) | |
|
||||
| 4 | Later: disc image import in the launcher, sprint by arm swing (offline) | |
|
||||
|
||||
## Step 2, first part (done)
|
||||
|
||||
Left-handed mode and graphics presets, after HaloCE-Quest-VR (CC0): its
|
||||
mirrored first-person model, its graphics switches.
|
||||
|
||||
Left-handed (`vr.handedness = "left"`):
|
||||
- The weapon in the left hand: aim, shots, muzzle, laser, crosshair,
|
||||
scope disc, turret aim. The first-person model is posed as the right
|
||||
hand's in a mirror image of the room ("model space", `halo_vr.h`) and
|
||||
mirrored back; a model whose node matrices mirror has its triangles'
|
||||
winding turned over (`rasterizer_set_model_skinning`, `d3d8_gl.c`). The
|
||||
arms reach to the other shoulders, the foregrip is gripped with the
|
||||
right hand, the right hand's fingers follow its controller.
|
||||
- Grenades held and thrown with the right trigger and hand; melee by the
|
||||
left grip (a swing of either hand, as before); the motion tracker on the
|
||||
right wrist (`halo_xr_submit.wrist_hand`); the rumble's light motor in
|
||||
the weapon hand.
|
||||
- The left trigger fires, the right one throws; at the Warthog's wheel the
|
||||
left trigger accelerates, the right one brakes.
|
||||
- With `vr.mirror_controls` (default): the right stick moves, the left
|
||||
turns and steers; their clicks, the bumpers and the face buttons change
|
||||
sides (Quest: X jumps, Y light / held grenades, A action and reload, B
|
||||
switch weapon; Steam Frame: the left D-pad does A, B, X, Y as their
|
||||
mirror image, down jump, left light, right action, up switch weapon,
|
||||
and A, B, X, Y do the D-pad's). The menus keep their buttons; the
|
||||
laser pointer works from either hand as before.
|
||||
- Other machines see the shots leave the left hand (the network message
|
||||
is unchanged: aim and muzzle from the camera).
|
||||
- The assault rifle's ammo counter and compass read the right way round
|
||||
on the mirrored model (2026-10-06): their geometry stays mirrored, each
|
||||
digit's bitmap is drawn turned over in u and the digits' order across
|
||||
the display swapped (numeric shaders drawn mirrored,
|
||||
`rasterizer_xbox_transparent_geometry.c`). Checked flat in the emulator
|
||||
with `debug.mirror_first_person` (the first-person weapon mirrored off
|
||||
the headset): 60, 48, 36 ... 06 read correctly. Steam Frame good
|
||||
(2026-10-06).
|
||||
- Kept as they are (accepted 2026-10-06): the small static icons under the
|
||||
counter (round and "x") stay mirror images (a texture atlas, not
|
||||
numeric). The game's prompts still name the Xbox's buttons.
|
||||
- Settings kept: on Android `config.toml` is written beside itself and
|
||||
renamed over (`port_config.c`), so one pushed by adb (the shell's, which
|
||||
the app could not open for writing) no longer loses the VR options'
|
||||
changes at a restart.
|
||||
- Headset tests 2026-10-06: Quest 3 and Steam Frame good (left hand,
|
||||
controls mirrored and not, counter, presets, refresh rates, resolution,
|
||||
settings kept over a restart).
|
||||
|
||||
Graphics (`vr.graphics`, `port/linux/game/vr_graphics.c`), set each frame
|
||||
on the game's own switches:
|
||||
|
||||
| Preset | Leaves out |
|
||||
| --- | --- |
|
||||
| `"high"` (default) | nothing: as before |
|
||||
| `"medium"` | object shadows, environment reflections, lens flares |
|
||||
| `"low"` | also specular light, bump maps, grass and other detail objects, the screen's fog layer, contrails, weather, camouflage's second pass |
|
||||
| `"max"` | nothing; models lit for each pixel, shadows at 512x512 |
|
||||
|
||||
A client in another's game keeps grass, lens flares and the fog layer as
|
||||
the host's rules have them (`cheats.c`). Shadows, specular passes,
|
||||
reflections and detail objects are draws (CPU, which the Frame's GL
|
||||
driver is short of); bump maps, fog, camouflage and per-pixel lighting
|
||||
the GPU's fill.
|
||||
|
||||
VR options (Y): two pages, PLAY (the comfort rows, "Weapon hand",
|
||||
"Mirrored controls") and MORE ("Graphics", "Refresh rate", "Resolution",
|
||||
the controller buttons page, Quit). The refresh rate steps through what
|
||||
the runtime offers and changes at once; on the Steam Frame the row shows
|
||||
SteamOS's rate. The resolution (`vr.resolution_scale`, 70-120%) takes a
|
||||
restart. The controller buttons page shows the layout as set.
|
||||
|
||||
| Setting | Values |
|
||||
| --- | --- |
|
||||
| `vr.handedness` | `"right"` (default), `"left"` |
|
||||
| `vr.mirror_controls` | `true` (default): sticks and buttons mirrored when left-handed; `false`: as right-handed (triggers and grips still follow the hands) |
|
||||
| `vr.graphics` | `"low"`, `"medium"`, `"high"` (default), `"max"` |
|
||||
| `vr.refresh_rate` | Hz, as offered (Quest 3: 72, 80, 90, 120) |
|
||||
| `vr.resolution_scale` | 0.7 to 1.2, on restart |
|
||||
|
||||
To try in the headset (Quest 3 and Steam Frame):
|
||||
1. Left-handed: aim, laser, crosshair, scope of sniper and rocket
|
||||
launcher; weapon held right in the left hand, arms from the right
|
||||
shoulders, nothing inside out (faces culled the wrong way would show
|
||||
the inside of the gun or the glove).
|
||||
2. Two-handed grip with the right hand; grenades in the right hand (held
|
||||
and thrown); melee by grip and by swing; motion tracker on the right
|
||||
wrist.
|
||||
3. Buttons and sticks mirrored on the Touch and the Frame controllers
|
||||
(every function reachable), then `Mirrored controls: Off`.
|
||||
4. Vehicles: wheel, throttle and brake, steering stick, a gunner's aim;
|
||||
menus and the laser pointer; a network game (shots from the left hand
|
||||
on the other machine).
|
||||
5. Graphics: each preset's look and smoothness (`vr.log_performance`),
|
||||
in a bot game; Max on the Quest at 90 Hz; refresh rates 72/80/90/120
|
||||
on the Quest; the Frame's row.
|
||||
|
||||
## Step 3a (done)
|
||||
|
||||
Tried on the Quest 3 and the Steam Frame, in `main`. The arm IK,
|
||||
the forearm's twist and the fingers' poses follow HaloCE-Quest-VR (CC0),
|
||||
in `port/linux/game/vr_arms.c`.
|
||||
|
||||
- The first-person pose (weapon, arms) is built once a frame instead of
|
||||
per eye and scope view (SteamVR: two to three times before).
|
||||
- `vr.log_performance` reports its cost (`VR: first person: ... ms a
|
||||
frame in ... builds (arms ... ms), ... kept`) and, once, how the hands'
|
||||
skin hangs from its bones (none from the root: no triangle filter
|
||||
needed).
|
||||
- Both arms bend from shoulders below the neck, square to the torso. The
|
||||
torso follows the head beyond 15 degrees and turns towards the hands;
|
||||
snap and smooth turns carry it at once.
|
||||
- The free left arm bends at the elbow to the controller (before: carried
|
||||
rigidly). The hand's roll is shared along forearm and upper arm.
|
||||
- The free hand's fingers follow the controller: index from the trigger,
|
||||
the others from the grip, the thumb from where it rests (touch on the
|
||||
Quest's, Index and Steam Frame controllers; half curled where unknown),
|
||||
closed round a held grenade. The weapon hand keeps its grip; its index
|
||||
finger lifts off the trigger when the controller says it is off.
|
||||
- host_xr: touch actions for thumb and index; `suggest()` takes 64
|
||||
bindings (was 32, silently cut) and retries a profile without the
|
||||
touch paths should the runtime refuse them.
|
||||
|
||||
Settings (`[vr]` in config.toml; the first two also in the VR options,
|
||||
Y on the main or pause menu):
|
||||
|
||||
| Setting | Values |
|
||||
| --- | --- |
|
||||
| `vr.body` | `"arms"` (default), `"hands"` (hands alone, the arms shrunk behind the wrists), `"off"` (the weapon alone) |
|
||||
| `vr.fingers` | `true` (default): fingers follow the controller; `false`: as the weapon's animation has them |
|
||||
| `vr.arm_ik` | `true` (default): the free arm bends at the elbow; `false`: rigid as before, should its skin tear |
|
||||
|
||||
After the first Quest tries (2026-10-06):
|
||||
- The shoulders 13 cm behind the eyes and 20 cm below the neck: the open
|
||||
end of the sleeve was in view. Looking hard down to the side one still
|
||||
sees into the arm (the model ends at the shoulder): left as it is.
|
||||
- The elbow bends in the arm's plane (`vr_align_elbow`): it had turned in
|
||||
with the elbow out to the side; the upper arm takes none of the hand's
|
||||
roll.
|
||||
- The free arm is solved first and its hand turned after (as
|
||||
HaloCE-Quest-VR does): turned first, its skin stretched like a rubber
|
||||
band.
|
||||
- The elbow follows the hand's roll (lifted out, the controller rolls in):
|
||||
before, it bent only towards a fixed direction.
|
||||
- Arm length, shoulder width, fingers, grenade grip, body modes, scope,
|
||||
vehicles: fine.
|
||||
|
||||
Not in it (3a.7): the glove fitted to each weapon's grip (gun anchor) and
|
||||
the left hand animated through reloads.
|
||||
|
||||
## Open points outside the order
|
||||
|
||||
- Multiview on the Steam Frame: why the eyes do not fuse there (suspected
|
||||
@@ -47,6 +198,33 @@ named where code is taken over.
|
||||
of 30 and more players.
|
||||
- The VR on-screen keyboard shows a password in the clear while it is
|
||||
typed.
|
||||
- Headset tests of the latest merge (`merge/upstream-2026-10-06`): menus,
|
||||
bots, co-op, a password game between Frame and Quest, a bot making room
|
||||
for a joining person.
|
||||
|
||||
## Headset tests (2026-10-06, all three in `main`)
|
||||
|
||||
Quest 3, 2026-10-06: menus, laser, VR options, head turning, arms and
|
||||
hands, fingers, body modes, scope, vehicles, a bot game (bots spread and
|
||||
leave their bases) and a large online game: all good.
|
||||
Steam Frame, 2026-10-06: buttons, picture (no doubled image), arms, hands,
|
||||
fingers, wheel: all good; SteamVR reprojected 18% of the frames, unseen.
|
||||
What follows is left for the two headsets together.
|
||||
|
||||
Upstream merge (`merge/upstream-2026-10-06`):
|
||||
- Menus, a bot game, head turning (no doubled image), co-op briefly.
|
||||
- A password game hosted on the Frame, joined from the Quest.
|
||||
- A bot making room for a person joining a full lobby.
|
||||
|
||||
Performance (`frame/performance-2`, and the merge's upstream speed-ups),
|
||||
with `vr.log_performance = true`:
|
||||
- Frame: the log says `trace markers: N descriptors to /dev/null`; frame
|
||||
time ("working") against before (about 12.5 ms in a bot game on Blood
|
||||
Gulch); SteamVR's reprojected share (vrcompositor.txt) against before
|
||||
(2 to 17 %); no doubled image.
|
||||
- Lens flares still appear and hide as before (their visibility counters'
|
||||
copies changed).
|
||||
- The pose cache (3a.1): `first person: ... builds` about one a frame;
|
||||
scope, crosshair, laser and the weapon in the hand as before.
|
||||
- Quest: 90 Hz held as before in a bot game and in a large online game;
|
||||
nothing slower than the last build.
|
||||
|
||||
Arms and hands (3a): on the Steam Frame, as on the Quest (fingers on its
|
||||
controllers, every button still working).
|
||||
@@ -135,6 +135,11 @@ into a layer's image, opaque */
|
||||
int host_xr_copy_layer_array(int layer, unsigned int texture, int array_layer);
|
||||
/* darkens the eye images from their edges, strength 0..1 (0: off) */
|
||||
void host_xr_set_vignette(float strength);
|
||||
/* the refresh rates the headset offers (Hz, at most maximum of them; 0 when
|
||||
it lets none be chosen), and the one it runs at (0: unknown); and asking
|
||||
for the highest offered at or below wanted (0: the highest) */
|
||||
int host_xr_refresh_rates(float *rates, int maximum, float *current);
|
||||
void host_xr_set_refresh_rate(float wanted);
|
||||
|
||||
/* ---------- Android */
|
||||
|
||||
|
||||
+181
-38
@@ -105,6 +105,10 @@ static struct
|
||||
XrAction button_b; /* B on the right controller, Y on the left */
|
||||
XrAction menu;
|
||||
XrAction thumbrest;
|
||||
/* the fingers' touch, for the hands drawn (where the runtime has it):
|
||||
the thumb on the stick, a button or the rest, the index on the trigger */
|
||||
XrAction thumb_touch;
|
||||
XrAction index_touch;
|
||||
/* the Steam Frame's controllers' own (XR_VALVE_frame_controller_interaction) */
|
||||
int has_frame_controller;
|
||||
int frame_layout;
|
||||
@@ -116,10 +120,10 @@ static struct
|
||||
XrAction haptic;
|
||||
struct xr_hand_actions hands[HALO_XR_HAND_COUNT];
|
||||
|
||||
/* where the head and the left hand are this frame (the wrist layer) */
|
||||
/* where the head and the hands are this frame (the wrist layer) */
|
||||
XrPosef head;
|
||||
XrPosef left_grip;
|
||||
int left_grip_valid;
|
||||
XrPosef grips[HALO_XR_HAND_COUNT];
|
||||
int grips_valid[HALO_XR_HAND_COUNT];
|
||||
|
||||
int has_performance_settings;
|
||||
/* (XR_META_performance_metrics: the compositor's counts, in the log,
|
||||
@@ -585,22 +589,51 @@ struct binding
|
||||
const char *path;
|
||||
};
|
||||
|
||||
static void suggest(const char *profile, const struct binding *bindings, int count)
|
||||
{
|
||||
XrActionSuggestedBinding suggested[32];
|
||||
XrInteractionProfileSuggestedBinding information = { XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING };
|
||||
int index;
|
||||
/* a profile's bindings: those it has to have, and those it may (a path the
|
||||
runtime does not know refuses the whole suggestion, which leaves the
|
||||
profile with none: then the first alone, as before). Nonzero if taken. */
|
||||
#define MAXIMUM_SUGGESTED_BINDINGS 64
|
||||
|
||||
for (index = 0; index < count && index < 32; index++)
|
||||
static int suggest_bindings(const char *profile, const struct binding *bindings, int count,
|
||||
const struct binding *optional, int optional_count)
|
||||
{
|
||||
XrActionSuggestedBinding suggested[MAXIMUM_SUGGESTED_BINDINGS];
|
||||
XrInteractionProfileSuggestedBinding information = { XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING };
|
||||
XrResult result;
|
||||
int index, total = 0;
|
||||
|
||||
if (count + optional_count > MAXIMUM_SUGGESTED_BINDINGS)
|
||||
{
|
||||
suggested[index].action = *bindings[index].action;
|
||||
suggested[index].binding = path_from_string(bindings[index].path);
|
||||
host_logf(HOST_LOG_ERROR, "xr: %d bindings for %s, room for %d: the rest left out", count + optional_count,
|
||||
profile, MAXIMUM_SUGGESTED_BINDINGS);
|
||||
}
|
||||
for (index = 0; index < count + optional_count && total < MAXIMUM_SUGGESTED_BINDINGS; index++)
|
||||
{
|
||||
const struct binding *binding = index < count ? &bindings[index] : &optional[index - count];
|
||||
|
||||
suggested[total].action = *binding->action;
|
||||
suggested[total].binding = path_from_string(binding->path);
|
||||
total++;
|
||||
}
|
||||
information.interactionProfile = path_from_string(profile);
|
||||
information.suggestedBindings = suggested;
|
||||
information.countSuggestedBindings = (uint32_t)index;
|
||||
if (!XR_SUCCEEDED(xrSuggestInteractionProfileBindings(xr.instance, &information)))
|
||||
host_logf(HOST_LOG_WARN, "xr: no bindings for %s", profile);
|
||||
information.countSuggestedBindings = (uint32_t)total;
|
||||
result = xrSuggestInteractionProfileBindings(xr.instance, &information);
|
||||
if (XR_SUCCEEDED(result))
|
||||
return 1;
|
||||
if (optional_count > 0)
|
||||
{
|
||||
host_logf(HOST_LOG_WARN, "xr: %s refused with its optional bindings (%d); suggested without them", profile,
|
||||
(int)result);
|
||||
return suggest_bindings(profile, bindings, count, NULL, 0);
|
||||
}
|
||||
host_logf(HOST_LOG_WARN, "xr: no bindings for %s (%d)", profile, (int)result);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void suggest(const char *profile, const struct binding *bindings, int count)
|
||||
{
|
||||
suggest_bindings(profile, bindings, count, NULL, 0);
|
||||
}
|
||||
|
||||
static int actions_initialize(void)
|
||||
@@ -627,6 +660,8 @@ static int actions_initialize(void)
|
||||
xr.button_b = action_create("button_upper", "B / Y", XR_ACTION_TYPE_BOOLEAN_INPUT);
|
||||
xr.menu = action_create("menu", "Menu", XR_ACTION_TYPE_BOOLEAN_INPUT);
|
||||
xr.thumbrest = action_create("thumbrest", "Thumb rest", XR_ACTION_TYPE_BOOLEAN_INPUT);
|
||||
xr.thumb_touch = action_create("thumb_touch", "Thumb resting", XR_ACTION_TYPE_BOOLEAN_INPUT);
|
||||
xr.index_touch = action_create("index_touch", "Finger on the trigger", XR_ACTION_TYPE_BOOLEAN_INPUT);
|
||||
xr.button_x = action_create("button_x", "X", XR_ACTION_TYPE_BOOLEAN_INPUT);
|
||||
xr.button_y = action_create("button_y", "Y", XR_ACTION_TYPE_BOOLEAN_INPUT);
|
||||
xr.view = action_create("view", "View", XR_ACTION_TYPE_BOOLEAN_INPUT);
|
||||
@@ -732,13 +767,62 @@ static int actions_initialize(void)
|
||||
{ &xr.haptic, "/user/hand/right/output/haptic" },
|
||||
};
|
||||
|
||||
/* the fingers' touch, as each profile has it (after HaloCE-Quest-VR,
|
||||
CC0; the Frame's from Valve's "Steam Frame Input" page): optional, a
|
||||
profile refusing them keeps its buttons */
|
||||
static const struct binding touch_fingers[] =
|
||||
{
|
||||
{ &xr.thumb_touch, "/user/hand/left/input/thumbstick/touch" },
|
||||
{ &xr.thumb_touch, "/user/hand/right/input/thumbstick/touch" },
|
||||
{ &xr.thumb_touch, "/user/hand/left/input/thumbrest/touch" },
|
||||
{ &xr.thumb_touch, "/user/hand/right/input/thumbrest/touch" },
|
||||
{ &xr.thumb_touch, "/user/hand/left/input/x/touch" },
|
||||
{ &xr.thumb_touch, "/user/hand/left/input/y/touch" },
|
||||
{ &xr.thumb_touch, "/user/hand/right/input/a/touch" },
|
||||
{ &xr.thumb_touch, "/user/hand/right/input/b/touch" },
|
||||
{ &xr.index_touch, "/user/hand/left/input/trigger/touch" },
|
||||
{ &xr.index_touch, "/user/hand/right/input/trigger/touch" },
|
||||
};
|
||||
static const struct binding index_fingers[] =
|
||||
{
|
||||
{ &xr.thumb_touch, "/user/hand/left/input/thumbstick/touch" },
|
||||
{ &xr.thumb_touch, "/user/hand/right/input/thumbstick/touch" },
|
||||
{ &xr.thumb_touch, "/user/hand/left/input/trackpad/touch" },
|
||||
{ &xr.thumb_touch, "/user/hand/right/input/trackpad/touch" },
|
||||
{ &xr.thumb_touch, "/user/hand/left/input/a/touch" },
|
||||
{ &xr.thumb_touch, "/user/hand/left/input/b/touch" },
|
||||
{ &xr.thumb_touch, "/user/hand/right/input/a/touch" },
|
||||
{ &xr.thumb_touch, "/user/hand/right/input/b/touch" },
|
||||
{ &xr.index_touch, "/user/hand/left/input/trigger/touch" },
|
||||
{ &xr.index_touch, "/user/hand/right/input/trigger/touch" },
|
||||
};
|
||||
static const struct binding frame_fingers[] =
|
||||
{
|
||||
{ &xr.thumb_touch, "/user/hand/left/input/thumbstick/touch" },
|
||||
{ &xr.thumb_touch, "/user/hand/right/input/thumbstick/touch" },
|
||||
{ &xr.thumb_touch, "/user/hand/right/input/a/touch" },
|
||||
{ &xr.thumb_touch, "/user/hand/right/input/b/touch" },
|
||||
{ &xr.thumb_touch, "/user/hand/right/input/x/touch" },
|
||||
{ &xr.thumb_touch, "/user/hand/right/input/y/touch" },
|
||||
{ &xr.thumb_touch, "/user/hand/left/input/dpad_up/touch" },
|
||||
{ &xr.thumb_touch, "/user/hand/left/input/dpad_down/touch" },
|
||||
{ &xr.thumb_touch, "/user/hand/left/input/dpad_left/touch" },
|
||||
{ &xr.thumb_touch, "/user/hand/left/input/dpad_right/touch" },
|
||||
{ &xr.thumb_touch, "/user/hand/left/input/view/touch" },
|
||||
{ &xr.index_touch, "/user/hand/left/input/trigger/touch" },
|
||||
{ &xr.index_touch, "/user/hand/right/input/trigger/touch" },
|
||||
};
|
||||
|
||||
if (xr.has_frame_controller)
|
||||
{
|
||||
suggest("/interaction_profiles/valve/frame_controller_valve", frame_controller,
|
||||
sizeof(frame_controller) / sizeof(frame_controller[0]));
|
||||
suggest_bindings("/interaction_profiles/valve/frame_controller_valve", frame_controller,
|
||||
sizeof(frame_controller) / sizeof(frame_controller[0]), frame_fingers,
|
||||
sizeof(frame_fingers) / sizeof(frame_fingers[0]));
|
||||
}
|
||||
suggest("/interaction_profiles/oculus/touch_controller", touch, sizeof(touch) / sizeof(touch[0]));
|
||||
suggest("/interaction_profiles/valve/index_controller", index, sizeof(index) / sizeof(index[0]));
|
||||
suggest_bindings("/interaction_profiles/oculus/touch_controller", touch, sizeof(touch) / sizeof(touch[0]),
|
||||
touch_fingers, sizeof(touch_fingers) / sizeof(touch_fingers[0]));
|
||||
suggest_bindings("/interaction_profiles/valve/index_controller", index, sizeof(index) / sizeof(index[0]),
|
||||
index_fingers, sizeof(index_fingers) / sizeof(index_fingers[0]));
|
||||
suggest("/interaction_profiles/khr/simple_controller", simple, sizeof(simple) / sizeof(simple[0]));
|
||||
}
|
||||
|
||||
@@ -781,6 +865,21 @@ static int action_boolean(XrAction action, int hand)
|
||||
return state.currentState != XR_FALSE;
|
||||
}
|
||||
|
||||
/* (and whether the runtime gives the action for the hand at all) */
|
||||
static int action_boolean_active(XrAction action, int hand, int *active)
|
||||
{
|
||||
XrActionStateGetInfo information = { XR_TYPE_ACTION_STATE_GET_INFO };
|
||||
XrActionStateBoolean state = { XR_TYPE_ACTION_STATE_BOOLEAN };
|
||||
|
||||
*active = 0;
|
||||
information.action = action;
|
||||
information.subactionPath = xr.hands[hand].path;
|
||||
if (!XR_SUCCEEDED(xrGetActionStateBoolean(xr.session, &information, &state)) || !state.isActive)
|
||||
return 0;
|
||||
*active = 1;
|
||||
return state.currentState != XR_FALSE;
|
||||
}
|
||||
|
||||
static int action_vector(XrAction action, int hand, float vector[2])
|
||||
{
|
||||
XrActionStateGetInfo information = { XR_TYPE_ACTION_STATE_GET_INFO };
|
||||
@@ -847,11 +946,8 @@ static void hands_read(struct halo_xr_frame *frame)
|
||||
{
|
||||
out->flags = flags_from_location(location.locationFlags);
|
||||
pose_from_xr(&out->grip, &location.pose);
|
||||
if (hand == HALO_XR_HAND_LEFT)
|
||||
{
|
||||
xr.left_grip = location.pose;
|
||||
xr.left_grip_valid = (out->flags & HALO_XR_POSE_VALID) != 0;
|
||||
}
|
||||
xr.grips[hand] = location.pose;
|
||||
xr.grips_valid[hand] = (out->flags & HALO_XR_POSE_VALID) != 0;
|
||||
}
|
||||
location.locationFlags = 0;
|
||||
if (XR_SUCCEEDED(xrLocateSpace(xr.hands[hand].aim_space, xr.local_space,
|
||||
@@ -881,6 +977,17 @@ static void hands_read(struct halo_xr_frame *frame)
|
||||
(action_boolean(xr.dpad[1], hand) ? HALO_XR_BUTTON_DPAD_DOWN : 0) |
|
||||
(action_boolean(xr.dpad[2], hand) ? HALO_XR_BUTTON_DPAD_LEFT : 0) |
|
||||
(action_boolean(xr.dpad[3], hand) ? HALO_XR_BUTTON_DPAD_RIGHT : 0);
|
||||
{
|
||||
/* the fingers' touch, and whether there is any */
|
||||
int thumb_active = 0, index_active = 0;
|
||||
|
||||
if (action_boolean_active(xr.thumb_touch, hand, &thumb_active))
|
||||
out->buttons |= HALO_XR_BUTTON_THUMB_TOUCH;
|
||||
if (action_boolean_active(xr.index_touch, hand, &index_active))
|
||||
out->buttons |= HALO_XR_BUTTON_INDEX_TOUCH;
|
||||
if (thumb_active)
|
||||
out->buttons |= HALO_XR_BUTTON_TOUCH_KNOWN;
|
||||
}
|
||||
}
|
||||
frame->controller_layout = xr.frame_layout ? HALO_XR_LAYOUT_FRAME : HALO_XR_LAYOUT_TOUCH;
|
||||
if (xr.frame_layout)
|
||||
@@ -938,7 +1045,9 @@ On the back of the left wrist, like a watch: behind the palm, where the
|
||||
grip pose is, and out of the back of the hand. In the left grip pose -Z
|
||||
runs to the fingers, +Y up the handle and -X out of the back of the hand;
|
||||
the layer faces -X with its top towards the elbow, as the wrist turned to
|
||||
the eyes reads it. Shown only while it is. */
|
||||
the eyes reads it. Shown only while it is. Left-handed (wrist_hand), on the
|
||||
right wrist, as the mirror image: there +X runs out of the back of the
|
||||
hand. */
|
||||
|
||||
#define WRIST_IMAGE_SIZE 512
|
||||
#define CROSSHAIR_IMAGE_SIZE 256
|
||||
@@ -978,26 +1087,32 @@ static void quaternion_rotate_vector(const XrQuaternionf *q, const XrVector3f *v
|
||||
|
||||
static int wrist_pose(const struct halo_xr_submit *submit, XrPosef *pose)
|
||||
{
|
||||
/* the layer's axes in the grip's: x = -Y, y = +Z, z = -X, so the
|
||||
layer's right and down, as it is read, are the grip's -Y and -Z */
|
||||
static const XrQuaternionf layer_in_grip = { 0.5f, -0.5f, -0.5f, 0.5f };
|
||||
const XrVector3f offset = { -WRIST_ABOVE_HAND, -WRIST_DOWN_HANDLE - submit->wrist_right,
|
||||
WRIST_BEHIND_PALM - submit->wrist_down };
|
||||
/* the layer's axes in the left grip's: x = -Y, y = +Z, z = -X, so the
|
||||
layer's right and down, as it is read, are the grip's -Y and -Z; in the
|
||||
right grip's x = +Y, y = +Z, z = +X (the mirror image, read the right
|
||||
way round: its x turned back) */
|
||||
static const XrQuaternionf layer_in_left_grip = { 0.5f, -0.5f, -0.5f, 0.5f };
|
||||
static const XrQuaternionf layer_in_right_grip = { 0.5f, 0.5f, 0.5f, 0.5f };
|
||||
int right = submit->wrist_hand == HALO_XR_HAND_RIGHT;
|
||||
const XrPosef *grip = &xr.grips[right ? HALO_XR_HAND_RIGHT : HALO_XR_HAND_LEFT];
|
||||
const XrVector3f offset = { right ? WRIST_ABOVE_HAND : -WRIST_ABOVE_HAND,
|
||||
-WRIST_DOWN_HANDLE - submit->wrist_right, WRIST_BEHIND_PALM - submit->wrist_down };
|
||||
const XrVector3f layer_normal = { 0.0f, 0.0f, 1.0f };
|
||||
/* clockwise as read: about the layer's normal, negatively */
|
||||
float half_turn = -submit->wrist_turn * WRIST_PI / 360.0f;
|
||||
/* clockwise as read: about the layer's normal, negatively (the mirror
|
||||
image's the other way) */
|
||||
float half_turn = (right ? 1.0f : -1.0f) * submit->wrist_turn * WRIST_PI / 360.0f;
|
||||
XrQuaternionf turn = { 0.0f, 0.0f, sinf(half_turn), cosf(half_turn) };
|
||||
XrQuaternionf unturned;
|
||||
XrVector3f moved, normal, to_eyes;
|
||||
float distance;
|
||||
|
||||
if (!xr.left_grip_valid)
|
||||
if (!xr.grips_valid[right ? HALO_XR_HAND_RIGHT : HALO_XR_HAND_LEFT])
|
||||
return 0;
|
||||
quaternion_rotate_vector(&xr.left_grip.orientation, &offset, &moved);
|
||||
pose->position.x = xr.left_grip.position.x + moved.x;
|
||||
pose->position.y = xr.left_grip.position.y + moved.y;
|
||||
pose->position.z = xr.left_grip.position.z + moved.z;
|
||||
quaternion_multiply(&xr.left_grip.orientation, &layer_in_grip, &unturned);
|
||||
quaternion_rotate_vector(&grip->orientation, &offset, &moved);
|
||||
pose->position.x = grip->position.x + moved.x;
|
||||
pose->position.y = grip->position.y + moved.y;
|
||||
pose->position.z = grip->position.z + moved.z;
|
||||
quaternion_multiply(&grip->orientation, right ? &layer_in_right_grip : &layer_in_left_grip, &unturned);
|
||||
quaternion_multiply(&unturned, &turn, &pose->orientation);
|
||||
|
||||
quaternion_rotate_vector(&pose->orientation, &layer_normal, &normal);
|
||||
@@ -1445,7 +1560,7 @@ int host_xr_begin_frame(struct halo_xr_frame *frame)
|
||||
pose_from_xr(&frame->head, &head.pose);
|
||||
xr.head = head.pose;
|
||||
}
|
||||
xr.left_grip_valid = 0;
|
||||
xr.grips_valid[HALO_XR_HAND_LEFT] = xr.grips_valid[HALO_XR_HAND_RIGHT] = 0;
|
||||
if (xr.focused)
|
||||
hands_read(frame);
|
||||
return 1;
|
||||
@@ -1937,6 +2052,34 @@ void host_xr_set_vignette(float strength)
|
||||
xr.vignette = strength < 0.0f ? 0.0f : strength > 1.0f ? 1.0f : strength;
|
||||
}
|
||||
|
||||
/* the VR options' refresh rates: those the runtime offers (none where it
|
||||
lets none be chosen, as SteamVR, whose rate is SteamOS's), and the one it
|
||||
runs at, by XR_FB_display_refresh_rate or else by its frames' period */
|
||||
int host_xr_refresh_rates(float *rates, int maximum, float *current)
|
||||
{
|
||||
float offered[16];
|
||||
uint32_t count = 0, index;
|
||||
|
||||
*current = 0.0f;
|
||||
if (!xr.initialized)
|
||||
return 0;
|
||||
if (xr.has_refresh_rate)
|
||||
xr.get_refresh_rate(xr.session, current);
|
||||
if (*current <= 0.0f && xr.frame_state.predictedDisplayPeriod > 0)
|
||||
*current = 1e9f / (float)xr.frame_state.predictedDisplayPeriod;
|
||||
if (!xr.has_refresh_rate || !XR_SUCCEEDED(xr.enumerate_refresh_rates(xr.session, 16, &count, offered)))
|
||||
return 0;
|
||||
for (index = 0; index < count && (int)index < maximum; index++)
|
||||
rates[index] = offered[index];
|
||||
return (int)index;
|
||||
}
|
||||
|
||||
void host_xr_set_refresh_rate(float wanted)
|
||||
{
|
||||
if (xr.initialized)
|
||||
refresh_rate_choose(wanted);
|
||||
}
|
||||
|
||||
void host_xr_haptic(int hand, float amplitude, float seconds)
|
||||
{
|
||||
XrHapticActionInfo information = { XR_TYPE_HAPTIC_ACTION_INFO };
|
||||
|
||||
@@ -61,6 +61,8 @@ host_xr_end_frame
|
||||
host_xr_haptic
|
||||
host_xr_set_vignette
|
||||
host_xr_copy_layer_array
|
||||
host_xr_refresh_rates
|
||||
host_xr_set_refresh_rate
|
||||
host_android_path
|
||||
|
||||
host_errno
|
||||
@@ -24,7 +24,7 @@ enum
|
||||
HALO_XR_LAYER_EYE_LEFT = 0,
|
||||
HALO_XR_LAYER_EYE_RIGHT = 1,
|
||||
HALO_XR_LAYER_OVERLAY = 2, /* HUD, menus, cinematics: a flat quad */
|
||||
HALO_XR_LAYER_WRIST = 3, /* the motion tracker, on the left wrist */
|
||||
HALO_XR_LAYER_WRIST = 3, /* the motion tracker, on the free hand's wrist */
|
||||
HALO_XR_LAYER_SCOPE = 4, /* the zoomed view, a disc over the weapon */
|
||||
HALO_XR_LAYER_CROSSHAIR = 5, /* the weapon's crosshair, where it aims */
|
||||
HALO_XR_LAYER_NAMES = 6, /* players' names, each a quad over a head */
|
||||
@@ -63,6 +63,12 @@ enum
|
||||
HALO_XR_BUTTON_DPAD_DOWN = 1 << 10,
|
||||
HALO_XR_BUTTON_DPAD_LEFT = 1 << 11,
|
||||
HALO_XR_BUTTON_DPAD_RIGHT = 1 << 12,
|
||||
/* the fingers' touch (the hands drawn: vr.c): the thumb on the stick, a
|
||||
button or the rest; the index finger on the trigger; and whether the
|
||||
controller tells the thumb's at all */
|
||||
HALO_XR_BUTTON_THUMB_TOUCH = 1 << 13,
|
||||
HALO_XR_BUTTON_INDEX_TOUCH = 1 << 14,
|
||||
HALO_XR_BUTTON_TOUCH_KNOWN = 1 << 15,
|
||||
};
|
||||
|
||||
/* the controllers' layout, for what the game tells the player */
|
||||
@@ -149,15 +155,18 @@ struct halo_xr_submit
|
||||
float overlay_width; /* metres; the height follows from 4:3 */
|
||||
/* the screen's placement in LOCAL space (HALO_XR_OVERLAY_SCREEN) */
|
||||
struct halo_xr_pose screen;
|
||||
/* the wrist layer was filled: shown on the back of the left wrist,
|
||||
this wide (metres), while the wrist is turned to the eyes */
|
||||
/* the wrist layer was filled: shown on the back of the left wrist (of
|
||||
the right one, left-handed: wrist_hand, a HALO_XR_HAND_*), this wide
|
||||
(metres), while the wrist is turned to the eyes */
|
||||
int wrist;
|
||||
float wrist_size;
|
||||
/* its placement as the wrist turned to the eyes reads it: turned this
|
||||
many degrees clockwise, and moved this far right and down (metres) */
|
||||
/* its placement as the left wrist turned to the eyes reads it: turned
|
||||
this many degrees clockwise, and moved this far right and down (metres;
|
||||
on the right wrist, the mirror image) */
|
||||
float wrist_turn;
|
||||
float wrist_right;
|
||||
float wrist_down;
|
||||
int wrist_hand;
|
||||
/* the scope layer was filled: a disc this wide (metres), here in LOCAL
|
||||
space, facing back along the weapon */
|
||||
int scope;
|
||||
|
||||
@@ -3,19 +3,22 @@ NETWORK_VR.C
|
||||
|
||||
VR players' hands for the other machines: an extension of the distributed
|
||||
netcode (port/linux/NETCODE.md, network_distributed.c). A VR player's
|
||||
weapon aims where the right controller points and fires from the hand, and
|
||||
the left hand holds a grenade and throws it its own way (port/linux/src/
|
||||
vr.c); the input the netcode relays has only where the head looks, so the
|
||||
other machines would show that player aiming and throwing where they look.
|
||||
weapon aims where the weapon hand's controller points and fires from the
|
||||
hand, and the free hand holds a grenade and throws it its own way
|
||||
(port/linux/src/vr.c; the right hand and the left, or left-handed the other
|
||||
way round); the input the netcode relays has only where the head looks, so
|
||||
the other machines would show that player aiming and throwing where they
|
||||
look.
|
||||
|
||||
Every tick, a machine with a VR player sends the host that player's hands
|
||||
(_distributed_message_vr_poses, unreliable): where the weapon aims, where
|
||||
its muzzle is from the unit's camera, whether the left hand holds a
|
||||
grenade, and the last throw (from where, which way, how hard) for a second
|
||||
after it. The host keeps them, its own VR players' too, and sends each
|
||||
client those of the players of other machines. A machine drives a VR
|
||||
player of another (players.c, weapons.c, units.c) as long as their hands
|
||||
keep coming, else as before.
|
||||
its muzzle is from the unit's camera (whichever hand holds it: the others
|
||||
see shots leave the left hand of a left-handed player), whether the free
|
||||
hand holds a grenade, and the last throw (from where, which way, how hard)
|
||||
for a second after it. The host keeps them, its own VR players' too, and
|
||||
sends each client those of the players of other machines. A machine drives
|
||||
a VR player of another (players.c, weapons.c, units.c) as long as their
|
||||
hands keep coming, else as before.
|
||||
|
||||
What the hands decide is only how the game looks: damage is the shooter's
|
||||
report, which the host checks as any other (network_damage.c).
|
||||
|
||||
@@ -0,0 +1,875 @@
|
||||
/*
|
||||
VR_ARMS.C
|
||||
|
||||
The VR arms' bones (vr_arms.h): the first-person hands' arms bent from the
|
||||
shoulders to the hands (interface/first_person_weapons.c poses them).
|
||||
|
||||
The solver, the forearm's twist and their helpers are after
|
||||
moistman42069/HaloCE-Quest-VR (CC0), port/linux/game/vr_render.c, nearly
|
||||
as they are there: the skeleton is read through its animation graph's
|
||||
index (the nodes under each node found once a frame, not per node), and
|
||||
the time is the headset's frame's (halo_vr_pose_time).
|
||||
*/
|
||||
|
||||
#include "cseries.h"
|
||||
#include "math/real_math.h"
|
||||
#include "models/model_animation_definitions.h"
|
||||
#include "halo_vr.h"
|
||||
#include "vr_arms.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef HALO_ANDROID
|
||||
|
||||
#define VR_UNITS_PER_METRE (1.0f / 3.048f)
|
||||
|
||||
/* ---------- the skeleton */
|
||||
|
||||
boolean vr_skeleton_build(
|
||||
struct vr_skeleton *skeleton,
|
||||
long graph_index,
|
||||
short node_count)
|
||||
{
|
||||
short node_index;
|
||||
|
||||
skeleton->graph_index = NONE;
|
||||
if (graph_index == NONE || node_count <= 0 || node_count > MAXIMUM_NODES_PER_ANIMATION ||
|
||||
node_count != animation_graph_node_count(graph_index))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
for (node_index = 0; node_index < node_count; node_index++)
|
||||
{
|
||||
short parent = animation_graph_node_parent(graph_index, node_index);
|
||||
|
||||
/* (parents before their children: else no parent) */
|
||||
skeleton->parents[node_index] = parent >= 0 && parent < node_index ? parent : NONE;
|
||||
skeleton->under[node_index] = 0;
|
||||
}
|
||||
/* each node under itself and all its ancestors */
|
||||
for (node_index = 0; node_index < node_count; node_index++)
|
||||
{
|
||||
short ancestor = node_index;
|
||||
|
||||
while (ancestor != NONE)
|
||||
{
|
||||
skeleton->under[ancestor] |= 1ULL << node_index;
|
||||
ancestor = skeleton->parents[ancestor];
|
||||
}
|
||||
}
|
||||
skeleton->graph_index = graph_index;
|
||||
skeleton->node_count = node_count;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
boolean vr_skeleton_under(
|
||||
struct vr_skeleton const *skeleton,
|
||||
short node_index,
|
||||
short root)
|
||||
{
|
||||
return root >= 0 && root < skeleton->node_count && node_index >= 0 && node_index < skeleton->node_count &&
|
||||
(skeleton->under[root] & (1ULL << node_index)) != 0;
|
||||
}
|
||||
|
||||
/* ---------- rotations (column vectors) */
|
||||
|
||||
/* v += a * scale */
|
||||
static void vr_vector_scale_add_local(
|
||||
real_vector3d *v,
|
||||
real_vector3d const *a,
|
||||
real scale)
|
||||
{
|
||||
v->i += a->i * scale;
|
||||
v->j += a->j * scale;
|
||||
v->k += a->k * scale;
|
||||
}
|
||||
|
||||
static real vr_length(
|
||||
real_vector3d const *v)
|
||||
{
|
||||
return (real)sqrt(v->i * v->i + v->j * v->j + v->k * v->k);
|
||||
}
|
||||
|
||||
static void vr_point_minus(
|
||||
real_point3d const *a,
|
||||
real_point3d const *b,
|
||||
real_vector3d *out)
|
||||
{
|
||||
out->i = a->x - b->x;
|
||||
out->j = a->y - b->y;
|
||||
out->k = a->z - b->z;
|
||||
}
|
||||
|
||||
static boolean vr_unit_vector(
|
||||
real_vector3d *v)
|
||||
{
|
||||
real length = vr_length(v);
|
||||
|
||||
if (!(length > 1e-5f && length < 1e8f))
|
||||
return FALSE;
|
||||
v->i /= length;
|
||||
v->j /= length;
|
||||
v->k /= length;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* the rotation taking direction a to direction b, the shortest way */
|
||||
static void vr_rotation_between(
|
||||
real_vector3d const *a,
|
||||
real_vector3d const *b,
|
||||
real rotation[3][3])
|
||||
{
|
||||
real_vector3d u = *a, v = *b, axis;
|
||||
real c, s, t;
|
||||
|
||||
normalize3d(&u);
|
||||
normalize3d(&v);
|
||||
cross_product3d(&u, &v, &axis);
|
||||
s = vr_length(&axis);
|
||||
c = u.i * v.i + u.j * v.j + u.k * v.k;
|
||||
if (s < 1e-6f)
|
||||
{
|
||||
memset(rotation, 0, sizeof(real) * 9);
|
||||
if (c >= 0.0f)
|
||||
{
|
||||
rotation[0][0] = rotation[1][1] = rotation[2][2] = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* half a turn about an axis across */
|
||||
real_vector3d basis;
|
||||
int row, column;
|
||||
|
||||
basis.i = 1.0f;
|
||||
basis.j = basis.k = 0.0f;
|
||||
if (fabs(u.i) > 0.8f)
|
||||
{
|
||||
basis.i = 0.0f;
|
||||
basis.j = 1.0f;
|
||||
}
|
||||
cross_product3d(&u, &basis, &axis);
|
||||
normalize3d(&axis);
|
||||
for (row = 0; row < 3; row++)
|
||||
{
|
||||
for (column = 0; column < 3; column++)
|
||||
rotation[row][column] = 2.0f * axis.n[row] * axis.n[column] - (row == column ? 1.0f : 0.0f);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
axis.i /= s;
|
||||
axis.j /= s;
|
||||
axis.k /= s;
|
||||
t = 1.0f - c;
|
||||
rotation[0][0] = c + axis.i * axis.i * t;
|
||||
rotation[0][1] = axis.i * axis.j * t - axis.k * s;
|
||||
rotation[0][2] = axis.i * axis.k * t + axis.j * s;
|
||||
rotation[1][0] = axis.j * axis.i * t + axis.k * s;
|
||||
rotation[1][1] = c + axis.j * axis.j * t;
|
||||
rotation[1][2] = axis.j * axis.k * t - axis.i * s;
|
||||
rotation[2][0] = axis.k * axis.i * t - axis.j * s;
|
||||
rotation[2][1] = axis.k * axis.j * t + axis.i * s;
|
||||
rotation[2][2] = c + axis.k * axis.k * t;
|
||||
}
|
||||
|
||||
/* a rotation of angle radians about the unit axis */
|
||||
static void vr_axis_rotation(
|
||||
real_vector3d const *axis,
|
||||
real angle,
|
||||
real rotation[3][3])
|
||||
{
|
||||
real c = (real)cos(angle), s = (real)sin(angle), t = 1.0f - c;
|
||||
real x = axis->i, y = axis->j, z = axis->k;
|
||||
|
||||
rotation[0][0] = c + x * x * t;
|
||||
rotation[0][1] = x * y * t - z * s;
|
||||
rotation[0][2] = x * z * t + y * s;
|
||||
rotation[1][0] = y * x * t + z * s;
|
||||
rotation[1][1] = c + y * y * t;
|
||||
rotation[1][2] = y * z * t - x * s;
|
||||
rotation[2][0] = z * x * t - y * s;
|
||||
rotation[2][1] = z * y * t + x * s;
|
||||
rotation[2][2] = c + z * z * t;
|
||||
}
|
||||
|
||||
static void vr_rotate_vector(
|
||||
real rotation[3][3],
|
||||
real_vector3d const *in,
|
||||
real_vector3d *out)
|
||||
{
|
||||
real_vector3d v = *in;
|
||||
|
||||
out->i = rotation[0][0] * v.i + rotation[0][1] * v.j + rotation[0][2] * v.k;
|
||||
out->j = rotation[1][0] * v.i + rotation[1][1] * v.j + rotation[1][2] * v.k;
|
||||
out->k = rotation[2][0] * v.i + rotation[2][1] * v.j + rotation[2][2] * v.k;
|
||||
}
|
||||
|
||||
static void vr_multiply_rotations(
|
||||
real a[3][3],
|
||||
real b[3][3],
|
||||
real out[3][3])
|
||||
{
|
||||
real r[3][3];
|
||||
int row, column;
|
||||
|
||||
for (row = 0; row < 3; row++)
|
||||
{
|
||||
for (column = 0; column < 3; column++)
|
||||
r[row][column] = a[row][0] * b[0][column] + a[row][1] * b[1][column] + a[row][2] * b[2][column];
|
||||
}
|
||||
memcpy(out, r, sizeof(r));
|
||||
}
|
||||
|
||||
/* a node moved from `from` to `to` and turned by rotation about itself: its
|
||||
matrix, or that of a node it carries (whose old matrix is node) */
|
||||
static void vr_carry(
|
||||
real_matrix4x3 *node,
|
||||
real_point3d const *from,
|
||||
real_point3d const *to,
|
||||
real rotation[3][3])
|
||||
{
|
||||
real_vector3d offset;
|
||||
|
||||
vr_point_minus(&node->position, from, &offset);
|
||||
vr_rotate_vector(rotation, &offset, &offset);
|
||||
node->position.x = to->x + offset.i;
|
||||
node->position.y = to->y + offset.j;
|
||||
node->position.z = to->z + offset.k;
|
||||
vr_rotate_vector(rotation, &node->forward, &node->forward);
|
||||
vr_rotate_vector(rotation, &node->left, &node->left);
|
||||
vr_rotate_vector(rotation, &node->up, &node->up);
|
||||
}
|
||||
|
||||
/* ---------- the arm */
|
||||
|
||||
void vr_solve_arm(
|
||||
struct vr_skeleton const *skeleton,
|
||||
real_matrix4x3 *matrices,
|
||||
short const bones[NUMBER_OF_VR_ARM_BONES],
|
||||
real_point3d shoulder,
|
||||
real_point3d const *target,
|
||||
real_vector3d const *pole,
|
||||
boolean hand_stays,
|
||||
struct vr_limb_history *history)
|
||||
{
|
||||
real_matrix4x3 old[NUMBER_OF_VR_ARM_BONES];
|
||||
real rotation[NUMBER_OF_VR_ARM_BONES][3][3];
|
||||
real_point3d moved_to[NUMBER_OF_VR_ARM_BONES];
|
||||
real_vector3d upper, fore, reach, direction, bend, old_direction;
|
||||
real upper_length, fore_length, distance, along, across;
|
||||
real_point3d solved_target = *target;
|
||||
short moved[MAXIMUM_NODES_PER_ANIMATION];
|
||||
short index, bone;
|
||||
|
||||
for (bone = 0; bone < NUMBER_OF_VR_ARM_BONES; bone++)
|
||||
{
|
||||
if (bones[bone] < 0 || bones[bone] >= skeleton->node_count)
|
||||
return;
|
||||
old[bone] = matrices[bones[bone]];
|
||||
}
|
||||
vr_point_minus(&old[_vr_arm_fore].position, &old[_vr_arm_upper].position, &upper);
|
||||
vr_point_minus(&old[_vr_arm_hand].position, &old[_vr_arm_fore].position, &fore);
|
||||
upper_length = vr_length(&upper);
|
||||
fore_length = vr_length(&fore);
|
||||
if (!isfinite(upper_length) || !isfinite(fore_length) || upper_length < 1e-4f || fore_length < 1e-4f)
|
||||
return;
|
||||
vr_point_minus(target, &shoulder, &reach);
|
||||
distance = vr_length(&reach);
|
||||
if (!isfinite(distance) || distance < 1e-4f)
|
||||
return;
|
||||
direction = reach;
|
||||
normalize3d(&direction);
|
||||
/* out of reach: the shoulder reaches a little (8 cm), then the arm
|
||||
stretches a little (18%); the hand stays where it is held */
|
||||
if (distance > (upper_length + fore_length) * (history ? 0.996f : 0.999f))
|
||||
{
|
||||
real maximum = (upper_length + fore_length) * (history ? 0.996f : 0.999f);
|
||||
real travel = distance - maximum;
|
||||
|
||||
travel = MIN(travel, 0.08f * VR_UNITS_PER_METRE);
|
||||
shoulder.x += direction.i * travel;
|
||||
shoulder.y += direction.j * travel;
|
||||
shoulder.z += direction.k * travel;
|
||||
distance -= travel;
|
||||
if (history && distance > maximum)
|
||||
{
|
||||
real stretch = MIN(distance / maximum, 1.18f);
|
||||
|
||||
upper_length *= stretch;
|
||||
fore_length *= stretch;
|
||||
maximum *= stretch;
|
||||
}
|
||||
if (hand_stays && distance > maximum)
|
||||
{
|
||||
/* (further still, as this port had it: the shoulder follows the
|
||||
hand, and the arm stays whole) */
|
||||
shoulder.x += direction.i * (distance - maximum);
|
||||
shoulder.y += direction.j * (distance - maximum);
|
||||
shoulder.z += direction.k * (distance - maximum);
|
||||
}
|
||||
distance = MIN(distance, maximum);
|
||||
}
|
||||
if (history)
|
||||
{
|
||||
/* bent no further than 145 degrees: nearer, the bones shorten (to
|
||||
65% at most) */
|
||||
real minimum = (real)sqrt(upper_length * upper_length + fore_length * fore_length -
|
||||
1.638304f * upper_length * fore_length);
|
||||
|
||||
if (distance < minimum)
|
||||
{
|
||||
real compress = MAX(0.65f, distance / minimum);
|
||||
|
||||
upper_length *= compress;
|
||||
fore_length *= compress;
|
||||
}
|
||||
}
|
||||
if (distance < (real)fabs(upper_length - fore_length) + 1e-3f)
|
||||
distance = (real)fabs(upper_length - fore_length) + 1e-3f;
|
||||
solved_target.x = shoulder.x + direction.i * distance;
|
||||
solved_target.y = shoulder.y + direction.j * distance;
|
||||
solved_target.z = shoulder.z + direction.k * distance;
|
||||
/* the elbow: in the plane of the reach and the pole */
|
||||
{
|
||||
real dot = pole->i * direction.i + pole->j * direction.j + pole->k * direction.k;
|
||||
|
||||
bend.i = pole->i - direction.i * dot;
|
||||
bend.j = pole->j - direction.j * dot;
|
||||
bend.k = pole->k - direction.k * dot;
|
||||
if (vr_length(&bend) < 1e-4f)
|
||||
{
|
||||
/* reaching along the pole: the old bend, else a steady side */
|
||||
dot = dot_product3d(&upper, &direction);
|
||||
bend = upper;
|
||||
bend.i -= dot * direction.i;
|
||||
bend.j -= dot * direction.j;
|
||||
bend.k -= dot * direction.k;
|
||||
if (vr_length(&bend) < 1e-4f)
|
||||
{
|
||||
real_vector3d basis;
|
||||
|
||||
basis.i = basis.j = 0.0f;
|
||||
basis.k = 1.0f;
|
||||
if (fabs(direction.k) > 0.9f)
|
||||
{
|
||||
basis.j = 1.0f;
|
||||
basis.k = 0.0f;
|
||||
}
|
||||
cross_product3d(&direction, &basis, &bend);
|
||||
}
|
||||
}
|
||||
if (history)
|
||||
{
|
||||
/* the elbow's swing about the reach, eased (18 a second, at most 8
|
||||
radians a second) unless the arm was away or jumped */
|
||||
double now = halo_vr_pose_time();
|
||||
double dt = now - history->time;
|
||||
real_vector3d previous = history->bend, travel;
|
||||
real confidence = PIN(vr_length(&bend) / 0.35f, 0.0f, 1.0f);
|
||||
|
||||
vr_point_minus(&shoulder, &history->shoulder, &travel);
|
||||
dot = dot_product3d(&previous, &direction);
|
||||
previous.i -= dot * direction.i;
|
||||
previous.j -= dot * direction.j;
|
||||
previous.k -= dot * direction.k;
|
||||
if (history->valid && history->graph_index == skeleton->graph_index && dt >= 0.0 && dt < 0.25 &&
|
||||
vr_length(&travel) < 0.5f * VR_UNITS_PER_METRE && vr_unit_vector(&previous) && vr_unit_vector(&bend))
|
||||
{
|
||||
real_vector3d tangent;
|
||||
real angle, change;
|
||||
|
||||
cross_product3d(&previous, &bend, &tangent);
|
||||
angle = (real)atan2(dot_product3d(&tangent, &direction), dot_product3d(&previous, &bend));
|
||||
change = angle * (1.0f - (real)exp(-18.0 * MIN(dt, 0.05))) * confidence;
|
||||
change = PIN(change, (real)(-8.0 * dt), (real)(8.0 * dt));
|
||||
cross_product3d(&direction, &previous, &tangent);
|
||||
bend.i = previous.i * (real)cos(change) + tangent.i * (real)sin(change);
|
||||
bend.j = previous.j * (real)cos(change) + tangent.j * (real)sin(change);
|
||||
bend.k = previous.k * (real)cos(change) + tangent.k * (real)sin(change);
|
||||
}
|
||||
else
|
||||
{
|
||||
history->roll_valid = FALSE;
|
||||
}
|
||||
normalize3d(&bend);
|
||||
history->valid = TRUE;
|
||||
history->graph_index = skeleton->graph_index;
|
||||
history->time = now;
|
||||
history->shoulder = shoulder;
|
||||
history->bend = bend;
|
||||
}
|
||||
else
|
||||
{
|
||||
normalize3d(&bend);
|
||||
}
|
||||
}
|
||||
along = (upper_length * upper_length + distance * distance - fore_length * fore_length) / (2.0f * distance);
|
||||
across = upper_length * upper_length - along * along;
|
||||
across = across > 0.0f ? (real)sqrt(across) : 0.0f;
|
||||
moved_to[_vr_arm_upper] = shoulder;
|
||||
moved_to[_vr_arm_fore].x = shoulder.x + direction.i * along + bend.i * across;
|
||||
moved_to[_vr_arm_fore].y = shoulder.y + direction.j * along + bend.j * across;
|
||||
moved_to[_vr_arm_fore].z = shoulder.z + direction.k * along + bend.k * across;
|
||||
moved_to[_vr_arm_hand] = solved_target;
|
||||
|
||||
/* each bone turned to point at the next joint, and the hand carried by
|
||||
the forearm's turn */
|
||||
vr_point_minus(&moved_to[_vr_arm_fore], &moved_to[_vr_arm_upper], &reach);
|
||||
vr_rotation_between(&upper, &reach, rotation[_vr_arm_upper]);
|
||||
vr_rotate_vector(rotation[_vr_arm_upper], &fore, &old_direction);
|
||||
vr_point_minus(&moved_to[_vr_arm_hand], &moved_to[_vr_arm_fore], &reach);
|
||||
vr_rotation_between(&old_direction, &reach, rotation[_vr_arm_fore]);
|
||||
vr_multiply_rotations(rotation[_vr_arm_fore], rotation[_vr_arm_upper], rotation[_vr_arm_fore]);
|
||||
if (hand_stays)
|
||||
{
|
||||
/* the hand stays as it was put, and what it holds */
|
||||
memset(rotation[_vr_arm_hand], 0, sizeof(rotation[_vr_arm_hand]));
|
||||
rotation[_vr_arm_hand][0][0] = rotation[_vr_arm_hand][1][1] = rotation[_vr_arm_hand][2][2] = 1.0f;
|
||||
moved_to[_vr_arm_hand] = old[_vr_arm_hand].position;
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy(rotation[_vr_arm_hand], rotation[_vr_arm_fore], sizeof(rotation[_vr_arm_hand]));
|
||||
}
|
||||
|
||||
/* every node under a moved bone goes with the nearest one above it
|
||||
(parents come first) */
|
||||
for (index = 0; index < skeleton->node_count; index++)
|
||||
{
|
||||
short parent = skeleton->parents[index];
|
||||
|
||||
moved[index] = NONE;
|
||||
for (bone = 0; bone < NUMBER_OF_VR_ARM_BONES; bone++)
|
||||
{
|
||||
if (bones[bone] == index)
|
||||
moved[index] = bone;
|
||||
}
|
||||
if (moved[index] == NONE && parent != NONE)
|
||||
moved[index] = moved[parent];
|
||||
if (moved[index] != NONE)
|
||||
{
|
||||
bone = moved[index];
|
||||
vr_carry(&matrices[index], &old[bone].position, &moved_to[bone], rotation[bone]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* The controller's roll shared along the arm rather than all of it at the
|
||||
wrist's skin: the hand's turn about the forearm from where the animation
|
||||
has it against its forearm (old_fore, old_hand), 85% of it on the forearm
|
||||
(none on the upper arm: vr_align_elbow rolls it), within 110 degrees, running on across half a
|
||||
turn (history). The bones' places and the hand stay. */
|
||||
void vr_distribute_arm_twist(
|
||||
real_matrix4x3 *matrices,
|
||||
short const bones[NUMBER_OF_VR_ARM_BONES],
|
||||
real_matrix4x3 const *old_fore,
|
||||
real_matrix4x3 const *old_hand,
|
||||
struct vr_limb_history *history)
|
||||
{
|
||||
real_matrix4x3 *fore = &matrices[bones[_vr_arm_fore]];
|
||||
real_matrix4x3 *hand = &matrices[bones[_vr_arm_hand]];
|
||||
real_matrix3x3 delta;
|
||||
real_vector3d expected[3], desired[3], axis;
|
||||
real_quaternion q;
|
||||
real angle, rotation[3][3];
|
||||
int a, b, row, column;
|
||||
|
||||
vr_point_minus(&hand->position, &fore->position, &axis);
|
||||
if (!vr_unit_vector(&axis))
|
||||
return;
|
||||
for (a = 0; a < 3; a++)
|
||||
{
|
||||
expected[a].i = expected[a].j = expected[a].k = 0.0f;
|
||||
desired[a].i = hand->n[a][0];
|
||||
desired[a].j = hand->n[a][1];
|
||||
desired[a].k = hand->n[a][2];
|
||||
for (b = 0; b < 3; b++)
|
||||
{
|
||||
real amount = old_fore->n[b][0] * old_hand->n[a][0] + old_fore->n[b][1] * old_hand->n[a][1] +
|
||||
old_fore->n[b][2] * old_hand->n[a][2];
|
||||
|
||||
for (row = 0; row < 3; row++)
|
||||
expected[a].n[row] += fore->n[b][row] * amount;
|
||||
}
|
||||
if (!vr_unit_vector(&expected[a]) || !vr_unit_vector(&desired[a]))
|
||||
return;
|
||||
}
|
||||
for (row = 0; row < 3; row++)
|
||||
{
|
||||
for (column = 0; column < 3; column++)
|
||||
{
|
||||
delta.n[row][column] = 0.0f;
|
||||
for (a = 0; a < 3; a++)
|
||||
delta.n[row][column] += desired[a].n[row] * expected[a].n[column];
|
||||
}
|
||||
}
|
||||
matrix3x3_rotation_to_quaternion(&delta, &q);
|
||||
angle = 2.0f * (real)atan2(dot_product3d(&q.v, &axis), q.w);
|
||||
if (!isfinite(angle))
|
||||
return;
|
||||
angle = (real)atan2(sin(angle), cos(angle));
|
||||
/* (continuous across half a turn before the limit) */
|
||||
if (history && history->roll_valid)
|
||||
angle = history->roll + (real)atan2(sin(angle - history->roll), cos(angle - history->roll));
|
||||
angle = PIN(angle, -1.92f, 1.92f);
|
||||
if (history)
|
||||
{
|
||||
history->roll = angle;
|
||||
history->roll_valid = TRUE;
|
||||
}
|
||||
vr_axis_rotation(&axis, angle * 0.85f, rotation);
|
||||
vr_rotate_vector(rotation, &fore->forward, &fore->forward);
|
||||
vr_rotate_vector(rotation, &fore->left, &fore->left);
|
||||
vr_rotate_vector(rotation, &fore->up, &fore->up);
|
||||
}
|
||||
|
||||
/* The upper arm rolled about its length so that the elbow bends in the
|
||||
plane the arm now bends in, as the animation has it bend: the shortest
|
||||
turn that took the upper arm to its new direction leaves its hinge where
|
||||
it was, and with the elbow out to the side the elbow's skin turned in
|
||||
(Quest, 2026-10-06). The hinge is the animation's bend (upper arm by
|
||||
forearm, old_upper, old_fore, old_hand) in the upper arm's own axes. A
|
||||
straight arm, then or now, has no bend: left as it is. */
|
||||
void vr_align_elbow(
|
||||
real_matrix4x3 *matrices,
|
||||
short const bones[NUMBER_OF_VR_ARM_BONES],
|
||||
real_matrix4x3 const *old_upper,
|
||||
real_matrix4x3 const *old_fore,
|
||||
real_matrix4x3 const *old_hand)
|
||||
{
|
||||
real_matrix4x3 *upper = &matrices[bones[_vr_arm_upper]];
|
||||
real_matrix4x3 const *fore = &matrices[bones[_vr_arm_fore]];
|
||||
real_matrix4x3 const *hand = &matrices[bones[_vr_arm_hand]];
|
||||
real_vector3d old_along, old_below, old_bend, along, below, bend, hinge;
|
||||
real c_forward, c_left, c_up, angle, rotation[3][3];
|
||||
|
||||
vr_point_minus(&old_fore->position, &old_upper->position, &old_along);
|
||||
vr_point_minus(&old_hand->position, &old_fore->position, &old_below);
|
||||
cross_product3d(&old_along, &old_below, &old_bend);
|
||||
vr_point_minus(&fore->position, &upper->position, &along);
|
||||
vr_point_minus(&hand->position, &fore->position, &below);
|
||||
cross_product3d(&along, &below, &bend);
|
||||
if (vr_length(&old_bend) < 1e-4f * vr_length(&old_along) * vr_length(&old_below) ||
|
||||
vr_length(&bend) < 0.05f * vr_length(&along) * vr_length(&below) ||
|
||||
!vr_unit_vector(&old_bend) || !vr_unit_vector(&bend) || !vr_unit_vector(&along))
|
||||
{
|
||||
return;
|
||||
}
|
||||
/* (the hinge in the upper arm's axes, then where those axes are now) */
|
||||
c_forward = dot_product3d(&old_bend, &old_upper->forward);
|
||||
c_left = dot_product3d(&old_bend, &old_upper->left);
|
||||
c_up = dot_product3d(&old_bend, &old_upper->up);
|
||||
hinge.i = upper->forward.i * c_forward + upper->left.i * c_left + upper->up.i * c_up;
|
||||
hinge.j = upper->forward.j * c_forward + upper->left.j * c_left + upper->up.j * c_up;
|
||||
hinge.k = upper->forward.k * c_forward + upper->left.k * c_left + upper->up.k * c_up;
|
||||
/* (both across the arm: the turn about it between them) */
|
||||
vr_vector_scale_add_local(&hinge, &along, -dot_product3d(&hinge, &along));
|
||||
vr_vector_scale_add_local(&bend, &along, -dot_product3d(&bend, &along));
|
||||
if (!vr_unit_vector(&hinge) || !vr_unit_vector(&bend))
|
||||
return;
|
||||
{
|
||||
real_vector3d cross;
|
||||
|
||||
cross_product3d(&hinge, &bend, &cross);
|
||||
angle = (real)atan2(dot_product3d(&cross, &along), dot_product3d(&hinge, &bend));
|
||||
}
|
||||
if (!isfinite(angle))
|
||||
return;
|
||||
vr_axis_rotation(&along, angle, rotation);
|
||||
vr_rotate_vector(rotation, &upper->forward, &upper->forward);
|
||||
vr_rotate_vector(rotation, &upper->left, &upper->left);
|
||||
vr_rotate_vector(rotation, &upper->up, &upper->up);
|
||||
}
|
||||
|
||||
/* ---------- the fingers
|
||||
|
||||
Each finger's joints are found by name: "finger" and its number (0 the
|
||||
thumb to 4 the little finger), or "thumb", "index", "middle", "ring",
|
||||
"pinky", on the hand's side ("l ", "r "). They are posed by absolute
|
||||
directions in the controller's palm frame, each segment's subtree turned,
|
||||
its length kept (after HaloCE-Quest-VR, CC0, nearly verbatim; its touch of
|
||||
the world, a few rays a finger, is left out: it costs the Steam Frame's
|
||||
processor too much). */
|
||||
|
||||
/* the finger (0 the thumb .. 4 the little finger) a node's name says it is
|
||||
a joint of, on that side; NONE when not a finger's */
|
||||
static short vr_finger_of(
|
||||
char const *name,
|
||||
char const *side)
|
||||
{
|
||||
static char const *const names[] = { "thumb", "index", "middle", "ring", "pinky" };
|
||||
char const *finger;
|
||||
short index;
|
||||
|
||||
if (!name || !strstr(name, side))
|
||||
return NONE;
|
||||
finger = strstr(name, "finger");
|
||||
if (finger && finger[6] >= '0' && finger[6] <= '4')
|
||||
return (short)(finger[6] - '0');
|
||||
for (index = 0; index < 5; index++)
|
||||
{
|
||||
if (strstr(name, names[index]))
|
||||
return index;
|
||||
}
|
||||
return NONE;
|
||||
}
|
||||
|
||||
void vr_finger_nodes(
|
||||
struct vr_skeleton const *skeleton,
|
||||
short hand,
|
||||
char const *side,
|
||||
short joints[5][3])
|
||||
{
|
||||
short node_index, finger, depth;
|
||||
|
||||
for (finger = 0; finger < 5; finger++)
|
||||
{
|
||||
for (depth = 0; depth < 3; depth++)
|
||||
joints[finger][depth] = NONE;
|
||||
}
|
||||
for (node_index = 0; node_index < skeleton->node_count; node_index++)
|
||||
{
|
||||
short at;
|
||||
|
||||
finger = vr_finger_of(animation_graph_node_name(skeleton->graph_index, node_index), side);
|
||||
if (finger == NONE || !vr_skeleton_under(skeleton, node_index, hand))
|
||||
continue;
|
||||
/* its depth: how many of the same finger's joints above it */
|
||||
depth = 0;
|
||||
for (at = skeleton->parents[node_index]; at != NONE; at = skeleton->parents[at])
|
||||
{
|
||||
if (vr_finger_of(animation_graph_node_name(skeleton->graph_index, at), side) == finger)
|
||||
depth++;
|
||||
}
|
||||
if (depth < 3)
|
||||
joints[finger][depth] = node_index;
|
||||
}
|
||||
}
|
||||
|
||||
/* a node and all under it turned about the node */
|
||||
static void vr_turn_subtree(
|
||||
struct vr_skeleton const *skeleton,
|
||||
real_matrix4x3 *matrices,
|
||||
short root,
|
||||
real rotation[3][3])
|
||||
{
|
||||
real_point3d pivot = matrices[root].position;
|
||||
unsigned long long under = skeleton->under[root];
|
||||
short node_index;
|
||||
|
||||
for (node_index = 0; node_index < skeleton->node_count; node_index++)
|
||||
{
|
||||
if (under & (1ULL << node_index))
|
||||
vr_carry(&matrices[node_index], &pivot, &pivot, rotation);
|
||||
}
|
||||
}
|
||||
|
||||
/* a finger's joints (low, middle, tip) curled 0 (straight, a little bent
|
||||
at rest) to 1 (a fist), in the palm's frame: forward along the fingers,
|
||||
up the knuckles towards the index, palm out of the palm */
|
||||
static boolean vr_pose_finger(
|
||||
struct vr_skeleton const *skeleton,
|
||||
real_matrix4x3 *matrices,
|
||||
short hand,
|
||||
short const joints[3],
|
||||
int finger,
|
||||
real curl,
|
||||
real_vector3d const *palm,
|
||||
real_vector3d const *forward,
|
||||
real_vector3d const *up)
|
||||
{
|
||||
static real const rest[3] = { 0.12f, 0.15f, 0.10f };
|
||||
static real const bend[3] = { 1.25f, 1.6f, 1.1f };
|
||||
real_vector3d base, axis, direction, wanted;
|
||||
real angle = 0.0f, dot, rotation[3][3];
|
||||
int depth;
|
||||
|
||||
if (joints[0] == NONE || joints[1] == NONE || joints[2] == NONE)
|
||||
return FALSE;
|
||||
vr_point_minus(&matrices[joints[0]].position, &matrices[hand].position, &base);
|
||||
dot = dot_product3d(&base, palm);
|
||||
base.i -= dot * palm->i;
|
||||
base.j -= dot * palm->j;
|
||||
base.k -= dot * palm->k;
|
||||
if (finger == 0)
|
||||
{
|
||||
/* the thumb: up the handle, or across the palm */
|
||||
base.i = (up->i * 0.9f + forward->i * 0.3f) * (1.0f - curl) +
|
||||
(forward->i * 0.75f + palm->i * 0.55f + up->i * 0.1f) * curl;
|
||||
base.j = (up->j * 0.9f + forward->j * 0.3f) * (1.0f - curl) +
|
||||
(forward->j * 0.75f + palm->j * 0.55f + up->j * 0.1f) * curl;
|
||||
base.k = (up->k * 0.9f + forward->k * 0.3f) * (1.0f - curl) +
|
||||
(forward->k * 0.75f + palm->k * 0.55f + up->k * 0.1f) * curl;
|
||||
}
|
||||
if (!vr_unit_vector(&base))
|
||||
return FALSE;
|
||||
cross_product3d(&base, palm, &axis);
|
||||
if (!vr_unit_vector(&axis))
|
||||
return FALSE;
|
||||
for (depth = 0; depth < 3; depth++)
|
||||
{
|
||||
short joint = joints[depth];
|
||||
|
||||
if (depth < 2)
|
||||
{
|
||||
vr_point_minus(&matrices[joints[depth + 1]].position, &matrices[joint].position, &direction);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* the tip: its own axis that the middle joint's bone runs along */
|
||||
real_vector3d previous;
|
||||
real_vector3d *axes[3];
|
||||
real_vector3d *last[3];
|
||||
real best = 0.8f, sign = 1.0f;
|
||||
int a, found = -1;
|
||||
|
||||
axes[0] = &matrices[joints[1]].forward;
|
||||
axes[1] = &matrices[joints[1]].left;
|
||||
axes[2] = &matrices[joints[1]].up;
|
||||
last[0] = &matrices[joint].forward;
|
||||
last[1] = &matrices[joint].left;
|
||||
last[2] = &matrices[joint].up;
|
||||
vr_point_minus(&matrices[joint].position, &matrices[joints[1]].position, &previous);
|
||||
if (!vr_unit_vector(&previous))
|
||||
return FALSE;
|
||||
for (a = 0; a < 3; a++)
|
||||
{
|
||||
real_vector3d unit = *axes[a];
|
||||
|
||||
if (!vr_unit_vector(&unit))
|
||||
continue;
|
||||
dot = dot_product3d(&unit, &previous);
|
||||
if (fabs(dot) > best)
|
||||
{
|
||||
best = (real)fabs(dot);
|
||||
found = a;
|
||||
sign = dot < 0.0f ? -1.0f : 1.0f;
|
||||
}
|
||||
}
|
||||
if (found < 0)
|
||||
return TRUE;
|
||||
direction.i = last[found]->i * sign;
|
||||
direction.j = last[found]->j * sign;
|
||||
direction.k = last[found]->k * sign;
|
||||
}
|
||||
if (!vr_unit_vector(&direction))
|
||||
return FALSE;
|
||||
angle += finger == 0 ? (depth == 0 ? 0.0f : (depth == 1 ? 0.35f : 0.3f) * curl) :
|
||||
rest[depth] + bend[depth] * curl;
|
||||
vr_axis_rotation(&axis, angle, rotation);
|
||||
vr_rotate_vector(rotation, &base, &wanted);
|
||||
vr_rotation_between(&direction, &wanted, rotation);
|
||||
vr_turn_subtree(skeleton, matrices, joint, rotation);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void vr_pose_fingers(
|
||||
struct vr_skeleton const *skeleton,
|
||||
real_matrix4x3 *matrices,
|
||||
short hand,
|
||||
short const joints[5][3],
|
||||
float const curls[4],
|
||||
real_vector3d const *palm,
|
||||
real_vector3d const *forward,
|
||||
real_vector3d const *up)
|
||||
{
|
||||
int finger;
|
||||
|
||||
for (finger = 0; finger < 5; finger++)
|
||||
vr_pose_finger(skeleton, matrices, hand, joints[finger], finger, curls[finger < 3 ? finger : 3], palm, forward, up);
|
||||
}
|
||||
|
||||
/* the finger's middle and tip turned this much (0..1) towards the line of
|
||||
its low joint: a finger lifted off the trigger (the weapon hand's index,
|
||||
whose low joint, and the rest of the hand, keep the weapon's grip) */
|
||||
void vr_straighten_finger(
|
||||
struct vr_skeleton const *skeleton,
|
||||
real_matrix4x3 *matrices,
|
||||
short const joints[3],
|
||||
real amount)
|
||||
{
|
||||
real_vector3d low, middle, axis;
|
||||
real rotation[3][3];
|
||||
real angle;
|
||||
|
||||
if (amount <= 0.0f || joints[0] == NONE || joints[1] == NONE || joints[2] == NONE)
|
||||
return;
|
||||
vr_point_minus(&matrices[joints[1]].position, &matrices[joints[0]].position, &low);
|
||||
vr_point_minus(&matrices[joints[2]].position, &matrices[joints[1]].position, &middle);
|
||||
if (!vr_unit_vector(&low) || !vr_unit_vector(&middle))
|
||||
return;
|
||||
cross_product3d(&middle, &low, &axis);
|
||||
if (!vr_unit_vector(&axis))
|
||||
return;
|
||||
angle = (real)acos(PIN(dot_product3d(&middle, &low), -1.0f, 1.0f)) * PIN(amount, 0.0f, 1.0f);
|
||||
vr_axis_rotation(&axis, angle, rotation);
|
||||
vr_turn_subtree(skeleton, matrices, joints[1], rotation);
|
||||
}
|
||||
|
||||
/* ---------- hands alone (vr.body "hands")
|
||||
|
||||
The arm's bones (not the hand's, its fingers' or the weapon's) shrunk to
|
||||
nothing at a point 3.5 cm back from the wrist along the hand's line,
|
||||
knuckles to wrist: the cuff closes just behind the hand. Shrunk onto their
|
||||
own places, the forearm drew the wrist's skin to the elbow and the hand
|
||||
looked cut off (after HaloCE-Quest-VR, CC0, its vr_hide_forearms and
|
||||
vr_hand_back_axis). */
|
||||
void vr_hide_arm(
|
||||
struct vr_skeleton const *skeleton,
|
||||
real_matrix4x3 *matrices,
|
||||
short const bones[NUMBER_OF_VR_ARM_BONES],
|
||||
short const fingers[5][3],
|
||||
short gun)
|
||||
{
|
||||
real_point3d centre = matrices[bones[_vr_arm_hand]].position;
|
||||
real_point3d knuckles;
|
||||
real_vector3d back;
|
||||
boolean along = FALSE;
|
||||
short finger, node_index, found = 0;
|
||||
|
||||
knuckles.x = knuckles.y = knuckles.z = 0.0f;
|
||||
/* (the four fingers' bases: the thumb's sits off the line) */
|
||||
for (finger = 1; finger < 5; finger++)
|
||||
{
|
||||
if (fingers[finger][0] == NONE)
|
||||
continue;
|
||||
knuckles.x += matrices[fingers[finger][0]].position.x;
|
||||
knuckles.y += matrices[fingers[finger][0]].position.y;
|
||||
knuckles.z += matrices[fingers[finger][0]].position.z;
|
||||
found++;
|
||||
}
|
||||
if (found)
|
||||
{
|
||||
knuckles.x /= found;
|
||||
knuckles.y /= found;
|
||||
knuckles.z /= found;
|
||||
vr_point_minus(¢re, &knuckles, &back);
|
||||
along = vr_unit_vector(&back);
|
||||
}
|
||||
if (!along)
|
||||
{
|
||||
vr_point_minus(&matrices[bones[_vr_arm_fore]].position, ¢re, &back);
|
||||
along = vr_unit_vector(&back);
|
||||
}
|
||||
if (along)
|
||||
{
|
||||
centre.x += back.i * 0.035f * VR_UNITS_PER_METRE;
|
||||
centre.y += back.j * 0.035f * VR_UNITS_PER_METRE;
|
||||
centre.z += back.k * 0.035f * VR_UNITS_PER_METRE;
|
||||
}
|
||||
for (node_index = 0; node_index < skeleton->node_count; node_index++)
|
||||
{
|
||||
if (!vr_skeleton_under(skeleton, node_index, bones[_vr_arm_upper]) ||
|
||||
vr_skeleton_under(skeleton, node_index, bones[_vr_arm_hand]) ||
|
||||
(gun != NONE && vr_skeleton_under(skeleton, node_index, gun)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
matrices[node_index].scale = 0.0f;
|
||||
matrices[node_index].position = centre;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
VR_ARMS.H
|
||||
|
||||
The VR arms' bones (vr_arms.c), for the first-person hands
|
||||
(interface/first_person_weapons.c): the arms bent from the shoulders to the
|
||||
hands. Include after the game's math and animation headers.
|
||||
*/
|
||||
|
||||
#ifndef __VR_ARMS_H
|
||||
#define __VR_ARMS_H
|
||||
|
||||
#ifdef HALO_ANDROID
|
||||
|
||||
enum
|
||||
{
|
||||
_vr_arm_upper,
|
||||
_vr_arm_fore,
|
||||
_vr_arm_hand,
|
||||
NUMBER_OF_VR_ARM_BONES
|
||||
};
|
||||
|
||||
/* a skeleton's parents, and the nodes under each node (it among them), from
|
||||
its animation graph: parents come before their children */
|
||||
struct vr_skeleton
|
||||
{
|
||||
long graph_index;
|
||||
short node_count;
|
||||
short parents[MAXIMUM_NODES_PER_ANIMATION];
|
||||
unsigned long long under[MAXIMUM_NODES_PER_ANIMATION];
|
||||
};
|
||||
|
||||
/* an arm's last frame, for its elbow's swing and its forearm's twist to run
|
||||
on smoothly (one each arm) */
|
||||
struct vr_limb_history
|
||||
{
|
||||
long graph_index;
|
||||
double time;
|
||||
real_point3d shoulder;
|
||||
real_vector3d bend;
|
||||
boolean valid;
|
||||
boolean roll_valid;
|
||||
real roll;
|
||||
};
|
||||
|
||||
boolean vr_skeleton_build(struct vr_skeleton *skeleton, long graph_index, short node_count);
|
||||
boolean vr_skeleton_under(struct vr_skeleton const *skeleton, short node_index, short root);
|
||||
|
||||
/* one arm (bones: upper arm, forearm, hand) bent from the shoulder for the
|
||||
hand to reach target, its elbow towards the pole; the hand (and what it
|
||||
holds) stays where it is, or turns with the forearm */
|
||||
void vr_solve_arm(struct vr_skeleton const *skeleton, real_matrix4x3 *matrices,
|
||||
short const bones[NUMBER_OF_VR_ARM_BONES], real_point3d shoulder, real_point3d const *target,
|
||||
real_vector3d const *pole, boolean hand_stays, struct vr_limb_history *history);
|
||||
/* the hand's roll from the animation (old_fore, old_hand: the arm as
|
||||
animated) shared by the forearm and the upper arm */
|
||||
void vr_align_elbow(real_matrix4x3 *matrices, short const bones[NUMBER_OF_VR_ARM_BONES],
|
||||
real_matrix4x3 const *old_upper, real_matrix4x3 const *old_fore, real_matrix4x3 const *old_hand);
|
||||
void vr_distribute_arm_twist(real_matrix4x3 *matrices, short const bones[NUMBER_OF_VR_ARM_BONES],
|
||||
real_matrix4x3 const *old_fore, real_matrix4x3 const *old_hand, struct vr_limb_history *history);
|
||||
|
||||
/* a hand's fingers' joints (low, middle, tip; NONE where not found), by
|
||||
their names on that side ("l ", "r "): the thumb, index, middle, ring and
|
||||
little finger */
|
||||
void vr_finger_nodes(struct vr_skeleton const *skeleton, short hand, char const *side, short joints[5][3]);
|
||||
/* the fingers curled (halo_vr_finger_curls: the thumb, index, middle, the
|
||||
last two) in the palm's frame: forward along the fingers, up the knuckles
|
||||
towards the index, palm out of the palm */
|
||||
void vr_pose_fingers(struct vr_skeleton const *skeleton, real_matrix4x3 *matrices, short hand,
|
||||
short const joints[5][3], float const curls[4], real_vector3d const *palm, real_vector3d const *forward,
|
||||
real_vector3d const *up);
|
||||
/* a finger's middle and tip turned this much (0..1) into line with its low
|
||||
joint */
|
||||
void vr_straighten_finger(struct vr_skeleton const *skeleton, real_matrix4x3 *matrices, short const joints[3],
|
||||
real amount);
|
||||
|
||||
/* the arm's bones shrunk to nothing just behind the wrist, the hand (with
|
||||
its fingers) and the weapon left: the hand alone */
|
||||
void vr_hide_arm(struct vr_skeleton const *skeleton, real_matrix4x3 *matrices,
|
||||
short const bones[NUMBER_OF_VR_ARM_BONES], short const fingers[5][3], short gun);
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
VR_GRAPHICS.C
|
||||
|
||||
The VR graphics presets (vr.graphics, the VR options' "Graphics" row): which
|
||||
of the game's costlier effects are drawn, for headsets with little time to
|
||||
spare (the Steam Frame spends most of its frame in its GL driver: fewer
|
||||
draws help it most; the Quest 3 holds 90 Hz with all of them).
|
||||
|
||||
- "high", the default: everything, as before;
|
||||
- "medium": no object shadows, environment reflections or lens flares;
|
||||
- "low": nor specular light, bump maps, grass and other detail objects,
|
||||
the screen's fog layer, contrails, weather or camouflage's second pass;
|
||||
- "max": "high", the models lit for each pixel and the shadows drawn at
|
||||
512x512 (d3d8_gl.c asks halo_vr_graphics).
|
||||
|
||||
Each effect is a switch the game has already: the rasterizer's console
|
||||
variables (rasterizer_debug_options) and render.c's effect switches, after
|
||||
HaloCE-Quest-VR's vr_graphics.c (CC0). Scripts and the console set some of
|
||||
them (render_effects turns particles off in some cutscenes): a switch is
|
||||
drawn only while both the game and the preset want it. A change the game
|
||||
made since the last frame is taken as its wish, kept and combined with the
|
||||
preset's. Playing in another's game, a client draws grass, lens flares and
|
||||
the screen's fog as the host's rules have it (cheats.c), whatever the
|
||||
preset; the reductions it does keep there, cheats.c leaves alone
|
||||
(vr_graphics_reduces).
|
||||
|
||||
What each costs: shadows (each object's map, blur and projection: draws),
|
||||
specular light and reflections (passes of the level's geometry: draws and
|
||||
fill), detail objects (many small draws), lens flares (their visibility
|
||||
tests), contrails and weather (particles), bump maps, the fog layer and
|
||||
camouflage's pass (the GPU's fill).
|
||||
*/
|
||||
|
||||
#ifdef HALO_ANDROID
|
||||
|
||||
#include "cseries.h"
|
||||
#include "rasterizer/rasterizer_console_vars.h"
|
||||
#include "networking/network_game_globals.h"
|
||||
#include "halo_vr.h"
|
||||
|
||||
/* source/render/render.c, render_objects.c; source/effects/
|
||||
weather_particle_systems.c */
|
||||
extern boolean render_shadows;
|
||||
extern boolean render_contrails_enabled;
|
||||
extern boolean render_weather_particle_systems_enabled;
|
||||
extern boolean weather;
|
||||
|
||||
void platform_log(char const *format, ...);
|
||||
|
||||
/* an effect: whether each preset draws it (low, medium; high and max draw
|
||||
everything), whether a client in another's game draws it anyway, and the
|
||||
game's switches it governs */
|
||||
struct vr_graphics_effect
|
||||
{
|
||||
char const *name;
|
||||
boolean preset[2];
|
||||
boolean host_rules;
|
||||
boolean *switches[2];
|
||||
};
|
||||
|
||||
static struct vr_graphics_effect vr_graphics_effects[] =
|
||||
{
|
||||
{ "shadows", { FALSE, FALSE }, FALSE,
|
||||
{ &render_shadows, &rasterizer_debug_options.draw_environment_shadows } },
|
||||
{ "specular", { FALSE, TRUE }, FALSE,
|
||||
{ &rasterizer_debug_options.draw_environment_specular_lights,
|
||||
&rasterizer_debug_options.draw_environment_specular_lightmaps } },
|
||||
{ "reflections", { FALSE, FALSE }, FALSE,
|
||||
{ &rasterizer_debug_options.draw_environment_reflections, NULL } },
|
||||
{ "bump maps", { FALSE, TRUE }, FALSE,
|
||||
{ &rasterizer_debug_options.bump_mapping_enabled, NULL } },
|
||||
{ "detail objects", { FALSE, TRUE }, TRUE,
|
||||
{ &rasterizer_debug_options.draw_detail_objects, NULL } },
|
||||
{ "lens flares", { FALSE, FALSE }, TRUE,
|
||||
{ &rasterizer_debug_options.draw_lens_flares, NULL } },
|
||||
{ "screen fog", { FALSE, TRUE }, TRUE,
|
||||
{ &rasterizer_debug_options.draw_environment_fog_screen, NULL } },
|
||||
{ "contrails", { FALSE, TRUE }, FALSE,
|
||||
{ &render_contrails_enabled, NULL } },
|
||||
{ "weather", { FALSE, TRUE }, FALSE,
|
||||
{ &weather, &render_weather_particle_systems_enabled } },
|
||||
{ "camouflage pass", { FALSE, TRUE }, FALSE,
|
||||
{ &rasterizer_debug_options.active_camouflage_multipass_enabled, NULL } },
|
||||
};
|
||||
|
||||
#define NUMBER_OF_VR_GRAPHICS_EFFECTS (sizeof(vr_graphics_effects) / sizeof(vr_graphics_effects[0]))
|
||||
#define VR_GRAPHICS_SWITCHES (sizeof(vr_graphics_effects[0].switches) / sizeof(vr_graphics_effects[0].switches[0]))
|
||||
|
||||
static struct
|
||||
{
|
||||
boolean started;
|
||||
int preset;
|
||||
boolean client;
|
||||
/* whether the preset allows each effect; what the game wants of each
|
||||
switch, and what was written last frame */
|
||||
boolean allowed[NUMBER_OF_VR_GRAPHICS_EFFECTS];
|
||||
boolean wanted[NUMBER_OF_VR_GRAPHICS_EFFECTS][VR_GRAPHICS_SWITCHES];
|
||||
boolean written[NUMBER_OF_VR_GRAPHICS_EFFECTS][VR_GRAPHICS_SWITCHES];
|
||||
} vr_graphics;
|
||||
|
||||
static void vr_graphics_choose(
|
||||
int preset,
|
||||
boolean client)
|
||||
{
|
||||
static char const *const names[] = { "low", "medium", "high", "max" };
|
||||
unsigned int index;
|
||||
|
||||
vr_graphics.preset = preset;
|
||||
vr_graphics.client = client;
|
||||
for (index = 0; index < NUMBER_OF_VR_GRAPHICS_EFFECTS; index++)
|
||||
{
|
||||
struct vr_graphics_effect const *effect = &vr_graphics_effects[index];
|
||||
|
||||
vr_graphics.allowed[index] = preset >= HALO_VR_GRAPHICS_HIGH || effect->preset[preset] ||
|
||||
(client && effect->host_rules);
|
||||
}
|
||||
platform_log("VR: graphics %s%s", names[preset], client ? " (in another's game)" : "");
|
||||
}
|
||||
|
||||
/* each frame, before it is drawn (render.c) */
|
||||
void vr_graphics_update(
|
||||
void)
|
||||
{
|
||||
int preset = halo_vr_graphics();
|
||||
boolean client = network_game_distributed_client();
|
||||
unsigned int index, which;
|
||||
|
||||
if (!vr_graphics.started)
|
||||
{
|
||||
/* the game's switches as it starts are its wishes */
|
||||
for (index = 0; index < NUMBER_OF_VR_GRAPHICS_EFFECTS; index++)
|
||||
{
|
||||
for (which = 0; which < VR_GRAPHICS_SWITCHES && vr_graphics_effects[index].switches[which]; which++)
|
||||
{
|
||||
vr_graphics.wanted[index][which] = *vr_graphics_effects[index].switches[which];
|
||||
vr_graphics.written[index][which] = *vr_graphics_effects[index].switches[which];
|
||||
}
|
||||
}
|
||||
vr_graphics.started = TRUE;
|
||||
vr_graphics_choose(preset, client);
|
||||
}
|
||||
else if (preset != vr_graphics.preset || client != vr_graphics.client)
|
||||
{
|
||||
vr_graphics_choose(preset, client);
|
||||
}
|
||||
for (index = 0; index < NUMBER_OF_VR_GRAPHICS_EFFECTS; index++)
|
||||
{
|
||||
for (which = 0; which < VR_GRAPHICS_SWITCHES && vr_graphics_effects[index].switches[which]; which++)
|
||||
{
|
||||
boolean *game_switch = vr_graphics_effects[index].switches[which];
|
||||
boolean value;
|
||||
|
||||
/* (the game, a script, the console changed it since: its wish) */
|
||||
if (*game_switch != vr_graphics.written[index][which])
|
||||
vr_graphics.wanted[index][which] = *game_switch;
|
||||
value = vr_graphics.wanted[index][which] && vr_graphics.allowed[index];
|
||||
*game_switch = value;
|
||||
vr_graphics.written[index][which] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* whether the preset keeps this switch of the game's off now (cheats.c, a
|
||||
client's rules: those it may keep off) */
|
||||
boolean vr_graphics_reduces(
|
||||
boolean const *game_switch)
|
||||
{
|
||||
unsigned int index, which;
|
||||
|
||||
if (!vr_graphics.started)
|
||||
return FALSE;
|
||||
for (index = 0; index < NUMBER_OF_VR_GRAPHICS_EFFECTS; index++)
|
||||
{
|
||||
for (which = 0; which < VR_GRAPHICS_SWITCHES && vr_graphics_effects[index].switches[which]; which++)
|
||||
{
|
||||
if (vr_graphics_effects[index].switches[which] == game_switch)
|
||||
return !vr_graphics.allowed[index];
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -88,13 +88,14 @@ degrees on an axis (the other axis 0); nonzero if it has. The game counts
|
||||
it as looking around (player_control.c's action tests). */
|
||||
int halo_vr_look_delta(float *yaw, float *pitch);
|
||||
|
||||
/* The weapon in the right hand (vr.aim_with = "controller"; each returns 0,
|
||||
and the game does as before, when the hand is not tracked):
|
||||
/* The weapon in the right hand, or the left one (vr.handedness; vr.aim_with
|
||||
= "controller"; each returns 0, and the game does as before, when the hand
|
||||
is not tracked):
|
||||
- the direction the player's weapon aims, where the controller points (the
|
||||
body and the view keep facing where the head looks);
|
||||
- where its shots start, as an offset from the unit's camera (world units);
|
||||
- the camera the first-person weapon model is posed from, so that it lies
|
||||
in the hand;
|
||||
in the hand (in model space, below);
|
||||
- the aiming laser's start and direction; */
|
||||
int halo_vr_aim_vector(short local_player_index, float vector[3]);
|
||||
int halo_vr_weapon_origin_offset(short local_player_index, float offset[3]);
|
||||
@@ -104,35 +105,75 @@ int halo_vr_weapon_origin_offset(short local_player_index, float offset[3]);
|
||||
int halo_vr_weapon_origin(short local_player_index, float origin[3]);
|
||||
int halo_vr_weapon_camera(short local_player_index, float position[3], float forward[3], float up[3]);
|
||||
int halo_vr_laser(short local_player_index, float start[3], float direction[3]);
|
||||
/* Model space: the first-person model (first_person_weapons.c) is always
|
||||
posed as the right hand's. Left-handed, it is posed in a mirror image of
|
||||
the room and drawn mirrored back (its triangles' winding turned over); the
|
||||
mirror: a point on it (world units) and its normal, or 0 when it is not
|
||||
mirrored. What first_person_weapons.c and vr.c exchange about the model
|
||||
is in model space: the weapon camera, the free hand, the foregrip, the
|
||||
fingers (0 the model's left hand, the free one), the barrel and the laser
|
||||
along it. */
|
||||
int halo_vr_first_person_mirror(short local_player_index, float point[3], float normal[3]);
|
||||
/* the laser for this frame, along the barrel (first_person_weapons.c: while
|
||||
an animation turns the weapon, as in a reload), and the laser at the ready,
|
||||
where the controller aims */
|
||||
an animation turns the weapon, as in a reload; model space), and the laser
|
||||
at the ready, where the controller aims (in the room) */
|
||||
void halo_vr_set_laser(short local_player_index, const float start[3], const float direction[3]);
|
||||
int halo_vr_ready_laser(short local_player_index, float start[3], float direction[3]);
|
||||
/* The arms (interface/first_person_weapons.c):
|
||||
- the body: the head's position (world units) and flat heading, from
|
||||
which the shoulders hang;
|
||||
- the left hand's pose;
|
||||
- the left hand on the weapon: given where the model's left hand holds it
|
||||
- the body: the eyes' position (world units), the neck they turn about,
|
||||
and the torso's flat heading (which follows the head beyond a few
|
||||
degrees, and the hands; the stick's turns turn it at once), from which
|
||||
the shoulders hang;
|
||||
- the free hand's pose (the left one, left-handed the right; model space);
|
||||
- the free hand on the weapon: given where the model's left hand holds it
|
||||
(world units) and the line from its right hand to its left in the
|
||||
weapon's forward, right and up, whether the left hand grips it; then the
|
||||
weapon's forward, right and up, whether the free hand grips it; then the
|
||||
weapon aims along both hands; */
|
||||
int halo_vr_body_frame(short local_player_index, float head[3], float forward[3]);
|
||||
int halo_vr_left_hand(short local_player_index, float position[3], float forward[3], float up[3]);
|
||||
int halo_vr_body_frame(short local_player_index, float head[3], float neck[3], float forward[3]);
|
||||
int halo_vr_free_hand(short local_player_index, float position[3], float forward[3], float up[3]);
|
||||
int halo_vr_two_hand(short local_player_index, const float foregrip[3], const float reference[3]);
|
||||
/* - the fingers of a hand of the model (0 the left, the free hand; 1 the
|
||||
right, the weapon's), from the controller's trigger, grip and touch: the thumb, index, middle finger and the last two, each
|
||||
curled 0 (straight) to 1; 0 when the hand is not tracked or vr.fingers
|
||||
is off, else HALO_VR_FINGERS_CURLED, and HALO_VR_FINGERS_TOUCH too when
|
||||
the controller tells where the fingers rest (else a finger off the
|
||||
trigger reads as one on it, unpressed); */
|
||||
enum
|
||||
{
|
||||
HALO_VR_FINGERS_CURLED = 1,
|
||||
HALO_VR_FINGERS_TOUCH = 2,
|
||||
};
|
||||
int halo_vr_finger_curls(short local_player_index, int hand, float curls[4]);
|
||||
/* - whether the free hand's arm bends at the elbow to reach it from the
|
||||
shoulder (vr.arm_ik); else it keeps the shape it has on the foregrip,
|
||||
pointed at the shoulder;
|
||||
- the time of the headset's frame (seconds, steady through the frame), by
|
||||
which the arms' bending is eased */
|
||||
int halo_vr_free_arm_bends(void);
|
||||
double halo_vr_pose_time(void);
|
||||
/* - what of the body is drawn (vr.body): the arms and hands, the hands
|
||||
alone, or neither (the weapon alone) */
|
||||
enum
|
||||
{
|
||||
HALO_VR_BODY_ARMS = 0,
|
||||
HALO_VR_BODY_HANDS,
|
||||
HALO_VR_BODY_OFF,
|
||||
};
|
||||
int halo_vr_body_mode(void);
|
||||
/* a hand has just been thrust forward: a melee attack (xinput_sdl.c) */
|
||||
int halo_vr_melee(void);
|
||||
/* the weight of the melee attack under way, from the swing's speed (0..1;
|
||||
0 with none): the game scales melee damage by the player's run instead,
|
||||
and in VR a player punches standing (units.c) */
|
||||
float halo_vr_melee_strength(void);
|
||||
/* The grenade (units.c): whether the left trigger holds it in the hand; and
|
||||
/* The grenade (units.c): whether the free hand's trigger holds it in the
|
||||
hand; and
|
||||
as it let go, whether the hand threw it: from where (world units), which
|
||||
way (the game's axes) and how hard (0..1 of the game's throw) */
|
||||
int halo_vr_grenade_held(short local_player_index);
|
||||
int halo_vr_grenade_throw(short local_player_index, float origin[3], float direction[3], float *strength);
|
||||
/* where the held grenade is drawn (render_objects.c): in the left palm, and
|
||||
at what size */
|
||||
/* where the held grenade is drawn (first_person_weapons.c): in the free
|
||||
hand's palm, and at what size */
|
||||
int halo_vr_grenade_in_hand(short local_player_index, float position[3], float forward[3], float up[3],
|
||||
float *scale);
|
||||
/* The scope (render.c): zoomed in by magnification, the camera for the
|
||||
@@ -163,9 +204,9 @@ void halo_vr_capture_names(void);
|
||||
void halo_vr_set_scope_raise(float metres);
|
||||
void halo_vr_capture_scope(float left, float top, float right, float bottom);
|
||||
/* where the weapon's gun node is as drawn, at the ready (world units;
|
||||
first_person_weapons.c, each frame): shots, the crosshair and the laser
|
||||
start there; and the direction its barrel points in the weapon camera's
|
||||
frame (forward, left, up), which the camera is turned to aim */
|
||||
first_person_weapons.c, each frame; model space): shots, the crosshair and
|
||||
the laser start there; and the direction its barrel points in the weapon
|
||||
camera's frame (forward, left, up), which the camera is turned to aim */
|
||||
void halo_vr_set_barrel(short local_player_index, const float world[3], const float direction[3]);
|
||||
/* how far, in world units, the weapon's aim runs before it hits something
|
||||
(render.c, each frame); the crosshair floats there */
|
||||
@@ -181,7 +222,8 @@ void halo_vr_capture_crosshair(void);
|
||||
/* Vehicles (player_control.c, each tick): the seat the player sits in and
|
||||
its vehicle's heading (yaw, radians). Seated, the body turns with the
|
||||
vehicle; a driver who steers by facing (HALO_VR_SEAT_STEER) steers with the
|
||||
right stick, else the right controller aims (HALO_VR_SEAT_AIM). And each
|
||||
turning stick, else the weapon hand's controller aims (HALO_VR_SEAT_AIM).
|
||||
And each
|
||||
frame (render.c) the heading as drawn, between ticks. */
|
||||
enum
|
||||
{
|
||||
@@ -207,6 +249,26 @@ int halo_vr_wheel_held(void);
|
||||
/* the controllers' layout (HALO_XR_LAYOUT_*: 0 the Quest's, 1 the Steam
|
||||
Frame's), for what the game tells the player */
|
||||
int halo_vr_controller_layout(void);
|
||||
/* the hand that holds the weapon (HALO_XR_HAND_*: 1 the right, 0 the left,
|
||||
vr.handedness) */
|
||||
int halo_vr_weapon_hand(void);
|
||||
/* the graphics preset (vr.graphics; HALO_VR_GRAPHICS_HIGH outside VR): which
|
||||
effects are drawn (port/linux/game/vr_graphics.c), and with "max" the
|
||||
models lit for each pixel and larger shadows (d3d8_gl.c) */
|
||||
enum
|
||||
{
|
||||
HALO_VR_GRAPHICS_LOW = 0,
|
||||
HALO_VR_GRAPHICS_MEDIUM,
|
||||
HALO_VR_GRAPHICS_HIGH,
|
||||
HALO_VR_GRAPHICS_MAX,
|
||||
};
|
||||
int halo_vr_graphics(void);
|
||||
/* the VR options: the refresh rates the headset offers (Hz, at most maximum;
|
||||
0 when it lets none be chosen, as SteamVR), and the one it runs at (0:
|
||||
unknown; vr.refresh_rate asks for one); and vr.resolution_scale as the app
|
||||
started (it takes a restart) */
|
||||
int halo_vr_refresh_rates(float *rates, int maximum, float *current);
|
||||
float halo_vr_started_resolution_scale(void);
|
||||
/* whether a seated player's view stays in first person (director.c) */
|
||||
int halo_vr_vehicle_first_person(void);
|
||||
/* VR test aid (debug.vehicle_spawn): both sticks held in for a moment asks
|
||||
@@ -236,7 +298,7 @@ coming (each returns 0 otherwise, and for this machine's own players, and
|
||||
the game does as before):
|
||||
- where the weapon aims, on foot;
|
||||
- where its shots start (world units), from the unit's camera;
|
||||
- whether the left hand holds the grenade;
|
||||
- whether the free hand holds the grenade;
|
||||
- the hand's throw, once, as the grenade goes (as halo_vr_grenade_throw);
|
||||
and this machine's own VR player's throw, to send (units.c). */
|
||||
int halo_vr_network_aim(long player_index, float vector[3]);
|
||||
@@ -262,6 +324,23 @@ enum
|
||||
HALO_VR_PHASE_COUNT
|
||||
};
|
||||
void halo_vr_phase(int phase, int ticks);
|
||||
/* vr.log_performance: time taken by a part of the drawing, for the report
|
||||
(first_person_weapons.c): its start (0 when not logged), and its end. The
|
||||
first-person pose (its animation and the arms), the arms alone, and the
|
||||
pose taken as built already this frame (counted) */
|
||||
enum
|
||||
{
|
||||
HALO_VR_TIMING_FIRST_PERSON,
|
||||
HALO_VR_TIMING_ARMS,
|
||||
HALO_VR_TIMING_FIRST_PERSON_KEPT,
|
||||
HALO_VR_TIMING_COUNT
|
||||
};
|
||||
unsigned long long halo_vr_timing_start(void);
|
||||
void halo_vr_timing_end(int part, unsigned long long start);
|
||||
/* the headset's frames, counted (0 before the first): what is drawn the same
|
||||
for every eye, as the first-person pose posed from the controller, is built
|
||||
once a frame */
|
||||
unsigned long halo_vr_frame_serial(void);
|
||||
void halo_vr_present(unsigned int back_buffer_texture);
|
||||
/* the eye image size: the back buffer's size in pixels in VR */
|
||||
int halo_vr_eye_size(int *width, int *height);
|
||||
|
||||
@@ -281,6 +281,10 @@ static long shadow_scale_choose(void)
|
||||
long resolution = config_integer("display.shadow_resolution");
|
||||
long scale = 1;
|
||||
|
||||
/* (VR's "max" graphics: 512 at least) */
|
||||
if (halo_vr_graphics() == HALO_VR_GRAPHICS_MAX && resolution < 512)
|
||||
resolution = 512;
|
||||
|
||||
while (scale < SHADOW_MAP_MAXIMUM_SCALE && SHADOW_MAP_SIZE * scale * 2 <= resolution)
|
||||
scale *= 2;
|
||||
return scale;
|
||||
@@ -3170,6 +3174,22 @@ static GLenum blend_equation(DWORD operation)
|
||||
}
|
||||
}
|
||||
|
||||
/* the model being drawn mirrors: its node matrices are left-handed (the
|
||||
left hand's first-person weapon in VR, first_person_weapons.c), and its
|
||||
triangles' winding is turned over (set by rasterizer_xbox.c as it sets them,
|
||||
after HaloCE-Quest-VR, CC0) */
|
||||
static int mirrored_skinning;
|
||||
|
||||
void d3d8_set_mirrored_skinning(int mirrored)
|
||||
{
|
||||
mirrored_skinning = mirrored != 0;
|
||||
}
|
||||
|
||||
int d3d8_mirrored_skinning(void)
|
||||
{
|
||||
return mirrored_skinning;
|
||||
}
|
||||
|
||||
static void apply_raster_state(BOOL has_depth)
|
||||
{
|
||||
DWORD *rs = D3D__RenderState;
|
||||
@@ -3305,6 +3325,8 @@ static void apply_raster_state(BOOL has_depth)
|
||||
#endif
|
||||
GLenum cull_mode = rs[D3DRS_CULLMODE] == rs[D3DRS_FRONTFACE] ? GL_FRONT : GL_BACK;
|
||||
|
||||
if (mirrored_skinning)
|
||||
front_face = front_face == GL_CW ? GL_CCW : GL_CW;
|
||||
if (gl_state.front_face != front_face)
|
||||
{
|
||||
gl_state.front_face = front_face;
|
||||
@@ -3416,9 +3438,10 @@ static void draw_flush(void)
|
||||
#endif
|
||||
}
|
||||
|
||||
/* display.per_pixel_lighting: the model lighting programs' draws are lit for
|
||||
each pixel (nv2a_psh.c model_lighting), from shaders of their own; read
|
||||
again when a setting changes */
|
||||
/* display.per_pixel_lighting (or VR's "max" graphics, vr.graphics): the
|
||||
model lighting programs' draws are lit for each pixel (nv2a_psh.c
|
||||
model_lighting), from shaders of their own; read again when a setting
|
||||
changes */
|
||||
static BOOL per_pixel_lighting(void)
|
||||
{
|
||||
static unsigned long read_at = (unsigned long)-1;
|
||||
@@ -3427,7 +3450,7 @@ static BOOL per_pixel_lighting(void)
|
||||
if (read_at != config_changes())
|
||||
{
|
||||
read_at = config_changes();
|
||||
enabled = config_boolean("display.per_pixel_lighting") != 0;
|
||||
enabled = config_boolean("display.per_pixel_lighting") != 0 || halo_vr_graphics() == HALO_VR_GRAPHICS_MAX;
|
||||
}
|
||||
return enabled;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ it exists, so that the player's edits and comments stay.
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -164,6 +165,13 @@ static const struct config_setting config_settings[] =
|
||||
{ "vr.refresh_rate", _config_real, "90.0", "HALO_VR_REFRESH_RATE", _environment_value, _platform_android,
|
||||
"The display refresh rate to ask the headset for, in Hz (72, 90, 120);\n"
|
||||
"it picks the nearest it has at or below. 0 for the highest." },
|
||||
{ "vr.graphics", _config_string, "\"high\"", "HALO_VR_GRAPHICS", _environment_value, _platform_android,
|
||||
"Which of the costlier effects are drawn: \"high\" all of them; \"medium\"\n"
|
||||
"no object shadows, environment reflections or lens flares; \"low\" nor\n"
|
||||
"specular light, bump maps, grass, the screen's fog, contrails, weather\n"
|
||||
"or camouflage's second pass (a client in another's game keeps grass,\n"
|
||||
"lens flares and fog); \"max\" all of them, the models lit for each\n"
|
||||
"pixel and shadows at 512x512." },
|
||||
{ "vr.positional_tracking", _config_boolean, "true", "HALO_VR_POSITIONAL", _environment_value, _platform_android,
|
||||
"Moving your head moves the view (leaning, ducking); false only turns it.\n"
|
||||
"The view recentres when play starts or resumes after a cinematic, and\n"
|
||||
@@ -180,8 +188,19 @@ static const struct config_setting config_settings[] =
|
||||
"Log the frame rate and where the frame time goes every five seconds\n"
|
||||
"(adb logcat -s halo)." },
|
||||
{ "vr.aim_with", _config_string, "\"controller\"", "HALO_VR_AIM_WITH", _environment_value, _platform_android,
|
||||
"\"controller\": the weapon is in your right hand and aims where it points,\n"
|
||||
"while you move and look with your head; \"head\": you aim where you look." },
|
||||
"\"controller\": the weapon is in your hand (vr.handedness) and aims where\n"
|
||||
"it points, while you move and look with your head; \"head\": you aim\n"
|
||||
"where you look." },
|
||||
{ "vr.handedness", _config_string, "\"right\"", "HALO_VR_HANDEDNESS", _environment_value, _platform_android,
|
||||
"The hand that holds the weapon: \"right\", or \"left\", which mirrors the\n"
|
||||
"weapon and the arms, throws grenades and grips the foregrip with the\n"
|
||||
"right hand, and fires with the left trigger." },
|
||||
{ "vr.mirror_controls", _config_boolean, "true", "HALO_VR_MIRROR_CONTROLS", _environment_value,
|
||||
_platform_android,
|
||||
"Left-handed, the sticks and buttons swap sides too: the right stick moves\n"
|
||||
"and the left one turns, X jumps and A reloads (on the Steam Frame the\n"
|
||||
"D-pad takes A, B, X and Y as their mirror image); false keeps them as\n"
|
||||
"right-handed. The menus keep their buttons either way." },
|
||||
{ "vr.laser", _config_boolean, "false", "HALO_VR_LASER", _environment_value, _platform_android,
|
||||
"A laser from the weapon to what it aims at (with aim_with = \"controller\")." },
|
||||
{ "vr.crosshair", _config_boolean, "true", "HALO_VR_CROSSHAIR", _environment_value, _platform_android,
|
||||
@@ -196,21 +215,33 @@ static const struct config_setting config_settings[] =
|
||||
{ "vr.weapon_down", _config_real, "0.14", "HALO_VR_WEAPON_DOWN", _environment_value, _platform_android,
|
||||
"See weapon_forward." },
|
||||
{ "vr.two_hand_reach", _config_real, "0.2", "HALO_VR_TWO_HAND_REACH", _environment_value, _platform_android,
|
||||
"How near, in metres, the left hand must be to the weapon's foregrip for\n"
|
||||
"How near, in metres, the free hand must be to the weapon's foregrip for\n"
|
||||
"its grip button to take hold of it with both hands." },
|
||||
{ "vr.body", _config_string, "\"arms\"", "HALO_VR_BODY", _environment_value, _platform_android,
|
||||
"What of your body is drawn with the weapon: \"arms\", the arms and hands\n"
|
||||
"reaching from the shoulders to the controllers; \"hands\", the hands\n"
|
||||
"alone; \"off\", neither (the weapon alone)." },
|
||||
{ "vr.fingers", _config_boolean, "true", "HALO_VR_FINGERS", _environment_value, _platform_android,
|
||||
"The free hand's fingers follow the controller: the index finger the\n"
|
||||
"trigger, the others the grip, the thumb where it rests (on the Quest's\n"
|
||||
"and the Steam Frame's controllers); false leaves the hand as the\n"
|
||||
"weapon's animation has it." },
|
||||
{ "vr.arm_ik", _config_boolean, "true", "HALO_VR_ARM_IK", _environment_value, _platform_android,
|
||||
"The free hand's arm bends at the elbow to reach it from the shoulder;\n"
|
||||
"false keeps the arm's shape as on the weapon, pointed at the shoulder." },
|
||||
{ "vr.melee_swing_speed", _config_real, "2.0", "HALO_VR_MELEE_SWING", _environment_value, _platform_android,
|
||||
"Thrusting a hand forward faster than this, in metres per second, is a\n"
|
||||
"melee attack (the right grip is too); 0 turns it off." },
|
||||
"melee attack (the weapon hand's grip is too); 0 turns it off." },
|
||||
{ "vr.grenade_throw_speed", _config_real, "1.5", "HALO_VR_GRENADE_THROW", _environment_value, _platform_android,
|
||||
"A grenade is held while the left trigger is, and thrown as it lets go:\n"
|
||||
"along the hand's throw, when the hand moved faster than this (metres per\n"
|
||||
"second; full strength at 4), else as the game throws it. 0 turns the\n"
|
||||
"hand's throw off." },
|
||||
"A grenade is held while the free hand's trigger is, and thrown as it\n"
|
||||
"lets go: along the hand's throw, when the hand moved faster than this\n"
|
||||
"(metres per second; full strength at 4), else as the game throws it. 0\n"
|
||||
"turns the hand's throw off." },
|
||||
{ "vr.grenade_scale", _config_real, "0.55", "HALO_VR_GRENADE_SCALE", _environment_value, _platform_android,
|
||||
"The size the grenade held in the left hand is drawn at (1 as in the game)." },
|
||||
"The size the grenade held in the free hand is drawn at (1 as in the game)." },
|
||||
{ "vr.grenade_right", _config_real, "0.055", "HALO_VR_GRENADE_RIGHT", _environment_value, _platform_android,
|
||||
"Where the held grenade is drawn, in metres from the left controller's\n"
|
||||
"grip: towards the palm (right),", },
|
||||
"Where the held grenade is drawn, in metres from the free controller's\n"
|
||||
"grip: towards the palm (the left hand's right),", },
|
||||
{ "vr.grenade_forward", _config_real, "0.01", "HALO_VR_GRENADE_FORWARD", _environment_value, _platform_android,
|
||||
"the grip's forward (through the fist, towards the index finger),", },
|
||||
{ "vr.grenade_down", _config_real, "0.01", "HALO_VR_GRENADE_DOWN", _environment_value, _platform_android,
|
||||
@@ -542,6 +573,10 @@ static const struct config_setting config_settings[] =
|
||||
"The headset's layer copies: 1 deferred (no waits for the GL thread or the\n"
|
||||
"GPU in the middle of the frame), 0 each one restored and released at once,\n"
|
||||
"-1 as the runtime needs (deferred on SteamVR). Read when the app starts." },
|
||||
{ "debug.mirror_first_person", _config_boolean, "false", "HALO_MIRROR_FIRST_PERSON", _environment_value,
|
||||
_platform_android,
|
||||
"Off the headset (flat), the first-person weapon drawn mirrored, as the\n"
|
||||
"left hand's is in VR: a test aid for what reads mirrored there. Read once." },
|
||||
{ "debug.test_input", _config_string, "\"\"", "HALO_TEST_INPUT", _environment_value, _platform_all,
|
||||
"\"bot:<seed>\" plays controller 1 with a scripted pattern (automated\n"
|
||||
"network tests); \"look:<seed>\" stands still, only turning and looking\n"
|
||||
@@ -683,13 +718,24 @@ static char *config_read_file(const char *path, size_t *size)
|
||||
static int config_write_file(const char *path, const char *text)
|
||||
{
|
||||
#ifdef HALO_ANDROID
|
||||
FILE *file = fopen(path, "wb");
|
||||
/* written beside it and renamed over it: a file the app may not write
|
||||
(one pushed by adb belongs to the shell) is replaced all the same, as the
|
||||
folder is the app's, and a write cut short leaves the old file whole */
|
||||
char temporary[1100];
|
||||
FILE *file;
|
||||
int written;
|
||||
|
||||
snprintf(temporary, sizeof(temporary), "%s.tmp", path);
|
||||
file = fopen(temporary, "wb");
|
||||
if (!file)
|
||||
return 0;
|
||||
written = fwrite(text, 1, strlen(text), file) == strlen(text);
|
||||
return fclose(file) == 0 && written;
|
||||
written = fclose(file) == 0 && written;
|
||||
if (written && rename(temporary, path) == 0)
|
||||
return 1;
|
||||
platform_log("settings: cannot replace %s (%s)", path, strerror(errno));
|
||||
remove(temporary);
|
||||
return 0;
|
||||
#else
|
||||
return SDL_SaveFile(path, text, strlen(text));
|
||||
#endif
|
||||
|
||||
+583
-76
File diff suppressed because it is too large.
Load diff
@@ -851,6 +851,14 @@ Holding a Warthog's wheel (vr.c), the right trigger is the throttle and the
|
||||
left one the brake (backwards, once stopped): the left stick pushed up or
|
||||
down as far, unless it is pushed further.
|
||||
|
||||
Left-handed (vr.handedness), vr.c hands the controllers over with the sides
|
||||
swapped where the hands' jobs are (halo_vr_controllers): the left trigger
|
||||
fires and the left grip strikes, the right ones hold and throw the grenade
|
||||
and grip the foregrip; and with vr.mirror_controls the sticks, bumpers and
|
||||
face buttons too (the right stick moves, X jumps, A reloads; on the Steam
|
||||
Frame the left D-pad takes A, B, X and Y). Here "right" and "left" are the
|
||||
sides so handed over.
|
||||
|
||||
In the menus the buttons are the Xbox's by position: A accepts, B goes back. */
|
||||
|
||||
#include "guest_host.h"
|
||||
@@ -1038,12 +1046,15 @@ DWORD WINAPI XInputSetState(HANDLE device, PXINPUT_FEEDBACK feedback)
|
||||
if (port < 0)
|
||||
return ERROR_DEVICE_NOT_CONNECTED;
|
||||
#ifdef HALO_ANDROID
|
||||
/* VR: the heavy (left) motor in the left hand, the light one in the
|
||||
right, as the game refreshes them every frame */
|
||||
/* VR: the heavy (left) motor in the free hand, the light one in the
|
||||
weapon's (left-handed, the other way round), as the game refreshes them
|
||||
every frame */
|
||||
if (port == 0 && halo_vr_active())
|
||||
{
|
||||
host_xr_haptic(HALO_XR_HAND_LEFT, feedback->Rumble.wLeftMotorSpeed / 65535.0f, 0.1f);
|
||||
host_xr_haptic(HALO_XR_HAND_RIGHT, feedback->Rumble.wRightMotorSpeed / 65535.0f, 0.1f);
|
||||
int weapon = halo_vr_weapon_hand();
|
||||
|
||||
host_xr_haptic(HALO_XR_HAND_COUNT - 1 - weapon, feedback->Rumble.wLeftMotorSpeed / 65535.0f, 0.1f);
|
||||
host_xr_haptic(weapon, feedback->Rumble.wRightMotorSpeed / 65535.0f, 0.1f);
|
||||
}
|
||||
#endif
|
||||
count = sdl_gamepads(gamepads);
|
||||
|
||||
@@ -577,10 +577,10 @@ long player_aim_projectile(
|
||||
if (halo_vr_active() && player->local_player_index==0)
|
||||
{
|
||||
struct unit_datum *vr_unit= unit_get(player->unit_index);
|
||||
float vr_eyes[3], vr_heading[3];
|
||||
float vr_eyes[3], vr_neck[3], vr_heading[3];
|
||||
|
||||
if (vr_unit->object.parent_object_index!=NONE &&
|
||||
halo_vr_body_frame(player->local_player_index, vr_eyes, vr_heading))
|
||||
halo_vr_body_frame(player->local_player_index, vr_eyes, vr_neck, vr_heading))
|
||||
{
|
||||
/* VR, seated: as the game aims a vehicle's gun, at what lies
|
||||
along the player's aim, from the eyes (vr.c draws the crosshair
|
||||
|
||||
@@ -118,6 +118,10 @@ void cheats_dispose_from_old_map(
|
||||
|
||||
/* network_game_globals.c's */
|
||||
boolean network_game_distributed_client(void);
|
||||
#ifdef HALO_ANDROID
|
||||
/* port/linux/game/vr_graphics.c */
|
||||
boolean vr_graphics_reduces(boolean const *game_switch);
|
||||
#endif
|
||||
/* bipeds.c's, player_control.c's */
|
||||
extern boolean rider_ejection;
|
||||
extern boolean player_autoaim_flag;
|
||||
@@ -155,6 +159,12 @@ static boolean cheats_network_client_rasterizer_enforce(
|
||||
environment_part <= &rasterizer_debug_options.draw_environment_fog_screen;
|
||||
environment_part++)
|
||||
{
|
||||
#ifdef HALO_ANDROID
|
||||
/* (VR: a part the graphics preset keeps off, as it may in another's
|
||||
game: shadows, specular light, reflections; vr_graphics.c) */
|
||||
if (vr_graphics_reduces(environment_part))
|
||||
continue;
|
||||
#endif
|
||||
if (!*environment_part)
|
||||
changed = TRUE;
|
||||
*environment_part = TRUE;
|
||||
|
||||
@@ -126,6 +126,7 @@ symbols in this file:
|
||||
#include "units/units.h"
|
||||
#ifdef HALO_ANDROID
|
||||
#include "halo_vr.h"
|
||||
#include "vr_arms.h"
|
||||
#endif
|
||||
|
||||
/* ---------- constants */
|
||||
@@ -467,10 +468,31 @@ long first_person_weapon_get_local_index(
|
||||
}
|
||||
|
||||
#ifdef HALO_ANDROID
|
||||
/* VR: the grenade the player holds (the left trigger, units.c) hangs from
|
||||
the body, which the first-person view leaves out, and with it the grenade:
|
||||
drawn instead in the left hand, as it is this frame, with the hands (which
|
||||
are drawn over the world, and would hide it wherever they overlap) */
|
||||
int config_boolean(char const *name);
|
||||
void model_vr_log_skin(long model_index);
|
||||
|
||||
/* VR: the headset's frame each local player's pose was last built for
|
||||
from the controller (halo_vr_frame_serial; 0 when not), and its weapon */
|
||||
static unsigned long first_person_weapon_vr_pose_serial[MAXIMUM_NUMBER_OF_LOCAL_PLAYERS];
|
||||
static long first_person_weapon_vr_pose_weapon[MAXIMUM_NUMBER_OF_LOCAL_PLAYERS];
|
||||
|
||||
/* the pose was built for this frame already, from the controller, which
|
||||
the eyes do not move (from the eye's own camera, each eye builds its own) */
|
||||
static boolean first_person_weapon_vr_pose_kept(
|
||||
short local_player_index)
|
||||
{
|
||||
unsigned long serial= halo_vr_frame_serial();
|
||||
|
||||
return serial!=0 &&
|
||||
first_person_weapon_vr_pose_serial[local_player_index]==serial &&
|
||||
first_person_weapon_vr_pose_weapon[local_player_index]==first_person_weapon_get(local_player_index)->weapon_index;
|
||||
}
|
||||
|
||||
/* VR: the grenade the player holds (the free hand's trigger, units.c)
|
||||
hangs from the body, which the first-person view leaves out, and with it
|
||||
the grenade: drawn instead in the free hand, as it is this frame, with the
|
||||
hands (which are drawn over the world, and would hide it wherever they
|
||||
overlap; in the room, not mirrored) */
|
||||
static void first_person_weapon_draw_held_grenade(
|
||||
long unit_index,
|
||||
struct render_lighting *lighting,
|
||||
@@ -636,6 +658,22 @@ void first_person_weapon_draw(
|
||||
weapon_definition->weapon.interface_definition.first_person_animations.index,
|
||||
first_person_weapon->node_matrices,
|
||||
first_person_weapon->hands_node_remapping_table);
|
||||
#ifdef HALO_ANDROID
|
||||
/* (vr.log_performance: once, how its skin hangs from its
|
||||
bones, for the VR arms) */
|
||||
{
|
||||
static int log_skin= -1;
|
||||
|
||||
if (log_skin<0)
|
||||
log_skin= config_boolean("vr.log_performance")!=0;
|
||||
if (log_skin)
|
||||
model_vr_log_skin(first_person_interface->hands.index);
|
||||
}
|
||||
#endif
|
||||
#ifdef HALO_ANDROID
|
||||
/* VR: the weapon alone (vr.body "off") */
|
||||
if (halo_vr_body_mode()!=HALO_VR_BODY_OFF)
|
||||
#endif
|
||||
render_model(
|
||||
first_person_interface->hands.index,
|
||||
0.0f,
|
||||
@@ -725,7 +763,23 @@ void first_person_weapon_render_update(
|
||||
first_person_weapon_set_visibility(render.local_player_index, visible);
|
||||
if (first_person_weapon->visible)
|
||||
{
|
||||
#ifdef HALO_ANDROID
|
||||
unsigned long long timing= halo_vr_timing_start();
|
||||
|
||||
/* VR: posed from the controller, the pose is the same for
|
||||
every eye (and the scope's view): built once a frame */
|
||||
if (first_person_weapon_vr_pose_kept(render.local_player_index))
|
||||
{
|
||||
halo_vr_timing_end(HALO_VR_TIMING_FIRST_PERSON_KEPT, timing);
|
||||
}
|
||||
else
|
||||
{
|
||||
first_person_weapon_build_node_matrices(render.local_player_index);
|
||||
halo_vr_timing_end(HALO_VR_TIMING_FIRST_PERSON, timing);
|
||||
}
|
||||
#else
|
||||
first_person_weapon_build_node_matrices(render.local_player_index);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1164,16 +1218,132 @@ static void first_person_weapon_set_state(
|
||||
#ifdef HALO_ANDROID
|
||||
/* ---------- VR arms
|
||||
|
||||
The first-person skeleton hangs from the camera at the right controller
|
||||
(halo_vr_weapon_camera), so the weapon and the right hand lie where they
|
||||
should; its arms, though, reach up to shoulders that float with the weapon.
|
||||
Here each arm's upper arm and forearm are turned (two-bone inverse
|
||||
kinematics) to run from the player's shoulders, below the head, to its
|
||||
wrist. The left wrist, unless the hand holds the weapon's foregrip, goes to
|
||||
the left controller, its fingers with it. */
|
||||
The first-person skeleton hangs from the camera at the weapon hand's
|
||||
controller (halo_vr_weapon_camera), so the weapon and the right hand lie
|
||||
where they should; its arms, though, reach up to shoulders that float with
|
||||
the weapon. Here each arm's upper arm and forearm are turned (two-bone
|
||||
inverse kinematics, port/linux/game/vr_arms.c) to run from the player's
|
||||
shoulders, below the neck and square to the torso, to its wrist, the
|
||||
controller's roll shared along the forearm. The left wrist, unless the hand
|
||||
holds the weapon's foregrip, goes to the free controller, its fingers with
|
||||
it.
|
||||
|
||||
Left-handed (vr.handedness) all this is done in model space (halo_vr.h): a
|
||||
mirror image of the room, where the left controller holds the weapon as a
|
||||
right hand would; the body is mirrored into it, and the posed skeleton
|
||||
mirrored back (vr_mirror_pose), so that the weapon lies in the left hand,
|
||||
the model's left arm reaches to the right controller. */
|
||||
|
||||
#define VR_METRES_PER_WORLD_UNIT 3.048f
|
||||
|
||||
/* a point or a vector across the mirror (a point on it, its normal) */
|
||||
static void vr_mirror_point(
|
||||
float const on[3],
|
||||
float const normal[3],
|
||||
float point[3])
|
||||
{
|
||||
float along = (point[0] - on[0]) * normal[0] + (point[1] - on[1]) * normal[1] + (point[2] - on[2]) * normal[2];
|
||||
|
||||
point[0] -= 2.f * along * normal[0];
|
||||
point[1] -= 2.f * along * normal[1];
|
||||
point[2] -= 2.f * along * normal[2];
|
||||
}
|
||||
|
||||
static void vr_mirror_vector(
|
||||
float const normal[3],
|
||||
float vector[3])
|
||||
{
|
||||
float along = vector[0] * normal[0] + vector[1] * normal[1] + vector[2] * normal[2];
|
||||
|
||||
vector[0] -= 2.f * along * normal[0];
|
||||
vector[1] -= 2.f * along * normal[1];
|
||||
vector[2] -= 2.f * along * normal[2];
|
||||
}
|
||||
|
||||
/* the posed skeleton across a mirror: every node mirrored, its axes too
|
||||
(a left-handed frame: rasterizer_xbox.c turns its triangles' winding over).
|
||||
Numeric displays drawn so, the assault rifle's ammo counter and compass,
|
||||
are read the right way round by rasterizer_xbox_transparent_geometry.c. */
|
||||
static void vr_mirror_nodes(
|
||||
float const on[3],
|
||||
float const normal[3],
|
||||
real_matrix4x3 *node_matrices,
|
||||
short node_count)
|
||||
{
|
||||
short node_index;
|
||||
|
||||
for (node_index = 0; node_index < node_count; node_index++)
|
||||
{
|
||||
real_matrix4x3 *node = &node_matrices[node_index];
|
||||
real_vector3d *axes[3];
|
||||
float vector[3];
|
||||
short axis;
|
||||
|
||||
vector[0] = node->position.x;
|
||||
vector[1] = node->position.y;
|
||||
vector[2] = node->position.z;
|
||||
vr_mirror_point(on, normal, vector);
|
||||
node->position.x = vector[0];
|
||||
node->position.y = vector[1];
|
||||
node->position.z = vector[2];
|
||||
axes[0] = &node->forward;
|
||||
axes[1] = &node->left;
|
||||
axes[2] = &node->up;
|
||||
for (axis = 0; axis < 3; axis++)
|
||||
{
|
||||
vector[0] = axes[axis]->i;
|
||||
vector[1] = axes[axis]->j;
|
||||
vector[2] = axes[axis]->k;
|
||||
vr_mirror_vector(normal, vector);
|
||||
axes[axis]->i = vector[0];
|
||||
axes[axis]->j = vector[1];
|
||||
axes[axis]->k = vector[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* the posed skeleton out of model space (left-handed) */
|
||||
static void vr_mirror_pose(
|
||||
short local_player_index,
|
||||
real_matrix4x3 *node_matrices,
|
||||
short node_count)
|
||||
{
|
||||
float on[3], normal[3];
|
||||
|
||||
if (halo_vr_first_person_mirror(local_player_index, on, normal))
|
||||
vr_mirror_nodes(on, normal, node_matrices, node_count);
|
||||
}
|
||||
|
||||
/* debug.mirror_first_person (a test aid, flat): the first-person weapon
|
||||
mirrored across the camera's own forward and up, as the left hand's is in
|
||||
VR; its numeric displays can so be checked off the headset */
|
||||
int config_boolean(char const *name);
|
||||
|
||||
static void debug_mirror_first_person(
|
||||
struct render_camera const *camera,
|
||||
real_matrix4x3 *node_matrices,
|
||||
short node_count)
|
||||
{
|
||||
static int wanted = -1;
|
||||
real_vector3d left;
|
||||
float on[3], normal[3];
|
||||
|
||||
if (wanted < 0)
|
||||
wanted = config_boolean("debug.mirror_first_person") ? 1 : 0;
|
||||
if (!wanted)
|
||||
return;
|
||||
cross_product3d(&camera->up, &camera->forward, &left);
|
||||
if (normalize3d(&left) == 0.0f)
|
||||
return;
|
||||
on[0] = camera->position.x;
|
||||
on[1] = camera->position.y;
|
||||
on[2] = camera->position.z;
|
||||
normal[0] = left.i;
|
||||
normal[1] = left.j;
|
||||
normal[2] = left.k;
|
||||
vr_mirror_nodes(on, normal, node_matrices, node_count);
|
||||
}
|
||||
|
||||
struct vr_arm_nodes
|
||||
{
|
||||
long graph_index;
|
||||
@@ -1186,6 +1356,10 @@ struct vr_arm_nodes
|
||||
short left_middle;
|
||||
short left_pinky;
|
||||
long barrel_logged_graph_index;
|
||||
/* the fingers' joints (vr_finger_nodes) of the skeleton they were
|
||||
found in: each hand's thumb, index, middle, ring, little finger */
|
||||
long fingers_graph_index;
|
||||
short fingers[2][5][3];
|
||||
/* the whole pose in the weapon camera's frame, outside a melee attack */
|
||||
boolean idle_pose_valid;
|
||||
short idle_pose_count;
|
||||
@@ -1193,7 +1367,11 @@ struct vr_arm_nodes
|
||||
};
|
||||
|
||||
static struct vr_arm_nodes vr_arm_nodes = { NONE, { NONE, NONE }, { NONE, NONE }, { NONE, NONE }, NONE, NONE, NONE, NONE,
|
||||
NONE };
|
||||
NONE, NONE };
|
||||
/* the skeleton this frame, and each arm's last (its elbow's swing and its
|
||||
forearm's twist run on from them) */
|
||||
static struct vr_skeleton vr_arm_skeleton;
|
||||
static struct vr_limb_history vr_arm_history[2];
|
||||
|
||||
static void vr_vector_scale_add(
|
||||
real_vector3d *out,
|
||||
@@ -1227,69 +1405,6 @@ static void vr_rotate_between(
|
||||
v->k = v->k * cosine + turned.k * sine + axis.k * along * (1.f - cosine);
|
||||
}
|
||||
|
||||
/* turns v about the unit axis by angle (radians) */
|
||||
static void vr_rotate_about(
|
||||
real_vector3d const *axis,
|
||||
real angle,
|
||||
real_vector3d *v)
|
||||
{
|
||||
real_vector3d turned;
|
||||
real sin_angle = sine(angle);
|
||||
real cos_angle = cosine(angle);
|
||||
real along = dot_product3d(axis, v);
|
||||
|
||||
cross_product3d(axis, v, &turned);
|
||||
v->i = v->i * cos_angle + turned.i * sin_angle + axis->i * along * (1.f - cos_angle);
|
||||
v->j = v->j * cos_angle + turned.j * sin_angle + axis->j * along * (1.f - cos_angle);
|
||||
v->k = v->k * cos_angle + turned.k * sin_angle + axis->k * along * (1.f - cos_angle);
|
||||
}
|
||||
|
||||
/* A forearm turned only to point at the wrist keeps the animation's twist,
|
||||
while the hand may have rolled half a turn from it: the skin between them
|
||||
collapses. Twists the forearm about its bone by most of the hand's roll
|
||||
from the animation (the hand's turn from old_wrist to new_wrist, applied to
|
||||
the animated forearm old_forearm), as a real forearm twists along its
|
||||
length. */
|
||||
static void vr_twist_forearm(
|
||||
real_matrix4x3 *forearm,
|
||||
real_matrix4x3 const *old_forearm,
|
||||
real_matrix4x3 const *old_wrist,
|
||||
real_matrix4x3 const *new_wrist,
|
||||
real_point3d const *wrist_position)
|
||||
{
|
||||
real_vector3d const *old_axes[3];
|
||||
real_vector3d const *new_axes[3];
|
||||
real_vector3d bone, turned_up, up, across;
|
||||
real along, angle;
|
||||
short axis;
|
||||
|
||||
vector_from_points3d(&forearm->position, wrist_position, &bone);
|
||||
if (normalize3d(&bone) < 1e-6f)
|
||||
return;
|
||||
/* the animated forearm's up, turned as the hand turned */
|
||||
old_axes[0] = &old_wrist->forward;
|
||||
old_axes[1] = &old_wrist->left;
|
||||
old_axes[2] = &old_wrist->up;
|
||||
new_axes[0] = &new_wrist->forward;
|
||||
new_axes[1] = &new_wrist->left;
|
||||
new_axes[2] = &new_wrist->up;
|
||||
turned_up.i = turned_up.j = turned_up.k = 0.f;
|
||||
for (axis = 0; axis < 3; axis++)
|
||||
vr_vector_scale_add(&turned_up, new_axes[axis], dot_product3d(&old_forearm->up, old_axes[axis]));
|
||||
/* both ups across the bone, and the angle between them about it */
|
||||
up = forearm->up;
|
||||
vr_vector_scale_add(&up, &bone, -dot_product3d(&up, &bone));
|
||||
vr_vector_scale_add(&turned_up, &bone, -dot_product3d(&turned_up, &bone));
|
||||
if (normalize3d(&up) < 1e-4f || normalize3d(&turned_up) < 1e-4f)
|
||||
return;
|
||||
cross_product3d(&up, &turned_up, &across);
|
||||
along = dot_product3d(&up, &turned_up);
|
||||
angle = arctangent(dot_product3d(&across, &bone), along) * 0.6f;
|
||||
vr_rotate_about(&bone, angle, &forearm->forward);
|
||||
vr_rotate_about(&bone, angle, &forearm->left);
|
||||
vr_rotate_about(&bone, angle, &forearm->up);
|
||||
}
|
||||
|
||||
/* a node turned so that the bone to its child, from old to new direction */
|
||||
static void vr_turn_bone(
|
||||
real_matrix4x3 *node,
|
||||
@@ -1308,39 +1423,6 @@ static void vr_turn_bone(
|
||||
node->position = *position;
|
||||
}
|
||||
|
||||
/* moves the descendants of a node that moved from old to new with it */
|
||||
static void vr_carry_descendants(
|
||||
long graph_index,
|
||||
real_matrix4x3 *node_matrices,
|
||||
short node_count,
|
||||
short root,
|
||||
real_matrix4x3 const *old_root,
|
||||
real_matrix4x3 const *new_root)
|
||||
{
|
||||
real_matrix4x3 inverse;
|
||||
real_matrix4x3 delta;
|
||||
short node_index;
|
||||
|
||||
matrix4x3_inverse(old_root, &inverse);
|
||||
matrix4x3_multiply(new_root, &inverse, &delta);
|
||||
for (node_index = 0; node_index < node_count; node_index++)
|
||||
{
|
||||
short ancestor = animation_graph_node_parent(graph_index, node_index);
|
||||
short steps;
|
||||
|
||||
/* parents come first; bounded in case of a malformed graph */
|
||||
for (steps = 0; ancestor > 0 && ancestor != root && steps < node_count; steps++)
|
||||
ancestor = animation_graph_node_parent(graph_index, ancestor);
|
||||
if (ancestor == root && node_index != root)
|
||||
{
|
||||
real_matrix4x3 moved;
|
||||
|
||||
matrix4x3_multiply(&delta, &node_matrices[node_index], &moved);
|
||||
node_matrices[node_index] = moved;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* the upper arm turned about the elbow to point at the shoulder, keeping
|
||||
its length (the free left arm: vr_left_hand_free) */
|
||||
static void vr_point_upper_arm(
|
||||
@@ -1362,50 +1444,6 @@ static void vr_point_upper_arm(
|
||||
vr_turn_bone(&node_matrices[upper], &old_direction, &new_direction, &position);
|
||||
}
|
||||
|
||||
/* one arm: upper arm and forearm from the shoulder to the wrist at target */
|
||||
static void vr_arm(
|
||||
real_matrix4x3 *node_matrices,
|
||||
short upper,
|
||||
short fore,
|
||||
short wrist,
|
||||
real_point3d shoulder,
|
||||
real_point3d const *target,
|
||||
real_vector3d const *pole)
|
||||
{
|
||||
real_vector3d old_upper, old_fore, reach, bend, new_upper, new_fore;
|
||||
real upper_length, fore_length, distance, along, height;
|
||||
real_point3d elbow;
|
||||
|
||||
vector_from_points3d(&node_matrices[upper].position, &node_matrices[fore].position, &old_upper);
|
||||
vector_from_points3d(&node_matrices[fore].position, &node_matrices[wrist].position, &old_fore);
|
||||
upper_length = magnitude3d(&old_upper);
|
||||
fore_length = magnitude3d(&old_fore);
|
||||
vector_from_points3d(&shoulder, target, &reach);
|
||||
distance = normalize3d(&reach);
|
||||
if (upper_length < 1e-4f || fore_length < 1e-4f || distance < 1e-4f)
|
||||
return;
|
||||
/* out of reach: the shoulder follows the hand (the arm stays whole) */
|
||||
if (distance > (upper_length + fore_length) * 0.999f)
|
||||
{
|
||||
distance = (upper_length + fore_length) * 0.999f;
|
||||
shoulder = *target;
|
||||
point_from_line3d(target, &reach, -distance, &shoulder);
|
||||
}
|
||||
along = (upper_length * upper_length - fore_length * fore_length + distance * distance) / (2.f * distance);
|
||||
height = upper_length * upper_length - along * along;
|
||||
height = height > 0.f ? square_root(height) : 0.f;
|
||||
bend = *pole;
|
||||
vr_vector_scale_add(&bend, &reach, -dot_product3d(pole, &reach));
|
||||
normalize3d(&bend);
|
||||
point_from_line3d(&shoulder, &reach, along, &elbow);
|
||||
point_from_line3d(&elbow, &bend, height, &elbow);
|
||||
|
||||
vector_from_points3d(&shoulder, &elbow, &new_upper);
|
||||
vector_from_points3d(&elbow, target, &new_fore);
|
||||
vr_turn_bone(&node_matrices[upper], &old_upper, &new_upper, &shoulder);
|
||||
vr_turn_bone(&node_matrices[fore], &old_fore, &new_fore, &elbow);
|
||||
}
|
||||
|
||||
/* an orthonormal frame from a direction and a second one across it */
|
||||
static void vr_frame_from(
|
||||
real_vector3d const *direction,
|
||||
@@ -1422,14 +1460,14 @@ static void vr_frame_from(
|
||||
|
||||
/* the free left hand: the wrist behind the controller's grip, the fingers
|
||||
along the controller and the knuckles, little finger to index, up its
|
||||
handle, as a hand holds it. The whole arm follows the hand as it is shaped
|
||||
on the foregrip: bent to the shoulder instead (vr_arm), the arm's skin,
|
||||
bound partly to the skeleton's root at the other hand, tears into ribbons
|
||||
wherever the hand is far from where the animation has it. */
|
||||
handle, as a hand holds it; its fingers with it. The arm is bent to it
|
||||
from the shoulder afterwards (vr_solve_arm), or, rigid (vr.arm_ik off),
|
||||
follows the hand as it is shaped on the foregrip. (The arm's skin hangs
|
||||
from the arm's bones alone, none from the skeleton's root: 3a.0's log.) */
|
||||
static void vr_left_hand_free(
|
||||
long graph_index,
|
||||
struct vr_skeleton const *skeleton,
|
||||
real_matrix4x3 *node_matrices,
|
||||
short node_count,
|
||||
boolean rigid,
|
||||
float const hand_position[3],
|
||||
float const hand_forward[3],
|
||||
float const hand_up[3])
|
||||
@@ -1473,23 +1511,24 @@ static void vr_left_hand_free(
|
||||
wrist->position.y = hand_position[1];
|
||||
wrist->position.z = hand_position[2];
|
||||
point_from_line3d(&wrist->position, &to[0], -0.07f / VR_METRES_PER_WORLD_UNIT, &wrist->position);
|
||||
vr_carry_descendants(graph_index, node_matrices, node_count, wrist_index, &old_wrist, wrist);
|
||||
/* the forearm and the upper arm with it, as they are on the foregrip */
|
||||
/* what the wrist carries (its fingers) with it, and rigid, the forearm
|
||||
and the upper arm too, as they are on the foregrip */
|
||||
{
|
||||
real_matrix4x3 inverse, delta, moved;
|
||||
short chain[2];
|
||||
short link;
|
||||
short node_index;
|
||||
|
||||
chain[0] = vr_arm_nodes.fore[0];
|
||||
chain[1] = vr_arm_nodes.upper[0];
|
||||
matrix4x3_inverse(&old_wrist, &inverse);
|
||||
matrix4x3_multiply(wrist, &inverse, &delta);
|
||||
for (link = 0; link < 2; link++)
|
||||
for (node_index = 0; node_index < skeleton->node_count; node_index++)
|
||||
{
|
||||
if (chain[link] == NONE)
|
||||
if (node_index == wrist_index ||
|
||||
!(vr_skeleton_under(skeleton, node_index, wrist_index) ||
|
||||
(rigid && (node_index == vr_arm_nodes.fore[0] || node_index == vr_arm_nodes.upper[0]))))
|
||||
{
|
||||
continue;
|
||||
matrix4x3_multiply(&delta, &node_matrices[chain[link]], &moved);
|
||||
node_matrices[chain[link]] = moved;
|
||||
}
|
||||
matrix4x3_multiply(&delta, &node_matrices[node_index], &moved);
|
||||
node_matrices[node_index] = moved;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1547,6 +1586,16 @@ static void vr_laser_follows_barrel(
|
||||
{
|
||||
return;
|
||||
}
|
||||
{
|
||||
/* (the laser at the ready is the room's: into model space) */
|
||||
float on[3], normal[3];
|
||||
|
||||
if (halo_vr_first_person_mirror(local_player_index, on, normal))
|
||||
{
|
||||
vr_mirror_point(on, normal, laser_start);
|
||||
vr_mirror_vector(normal, laser_direction);
|
||||
}
|
||||
}
|
||||
matrix4x3_from_point_and_vectors(&camera_matrix, &weapon_camera->position, &weapon_camera->forward,
|
||||
&weapon_camera->up);
|
||||
matrix4x3_multiply(&camera_matrix, &vr_arm_nodes.idle_pose[vr_arm_nodes.gun], &ready_gun);
|
||||
@@ -1570,6 +1619,58 @@ static void vr_laser_follows_barrel(
|
||||
halo_vr_set_laser(local_player_index, laser_start, laser_direction);
|
||||
}
|
||||
|
||||
/* The fingers (vr.fingers): the free left hand's from its controller's
|
||||
trigger, grip and touch (vr_pose_fingers); of the hand on the weapon, only
|
||||
the index finger, lifted a little off the trigger when the controller
|
||||
says it is (the rest keeps the weapon's grip). */
|
||||
static void first_person_weapon_vr_fingers(
|
||||
short local_player_index,
|
||||
real_matrix4x3 *node_matrices,
|
||||
boolean left_free,
|
||||
float const hand_forward[3],
|
||||
float const hand_up[3])
|
||||
{
|
||||
float curls[4];
|
||||
int fingers;
|
||||
|
||||
if (vr_arm_nodes.fingers_graph_index != vr_arm_skeleton.graph_index)
|
||||
{
|
||||
vr_arm_nodes.fingers_graph_index = vr_arm_skeleton.graph_index;
|
||||
vr_finger_nodes(&vr_arm_skeleton, vr_arm_nodes.wrist[0], "l ", vr_arm_nodes.fingers[0]);
|
||||
vr_finger_nodes(&vr_arm_skeleton, vr_arm_nodes.wrist[1], "r ", vr_arm_nodes.fingers[1]);
|
||||
}
|
||||
if (left_free && vr_arm_nodes.wrist[0] != NONE &&
|
||||
halo_vr_finger_curls(local_player_index, 0, curls))
|
||||
{
|
||||
/* the palm's frame: along the fingers, up the knuckles, and out of
|
||||
the left palm (inwards, to the right) */
|
||||
real_vector3d forward, up, palm;
|
||||
real along;
|
||||
|
||||
forward.i = hand_forward[0];
|
||||
forward.j = hand_forward[1];
|
||||
forward.k = hand_forward[2];
|
||||
up.i = hand_up[0];
|
||||
up.j = hand_up[1];
|
||||
up.k = hand_up[2];
|
||||
along = dot_product3d(&forward, &up);
|
||||
vr_vector_scale_add(&up, &forward, -along);
|
||||
if (normalize3d(&forward) > 1e-4f && normalize3d(&up) > 1e-4f)
|
||||
{
|
||||
cross_product3d(&forward, &up, &palm);
|
||||
vr_pose_fingers(&vr_arm_skeleton, node_matrices, vr_arm_nodes.wrist[0], vr_arm_nodes.fingers[0], curls,
|
||||
&palm, &forward, &up);
|
||||
}
|
||||
}
|
||||
fingers = halo_vr_finger_curls(local_player_index, 1, curls);
|
||||
if (vr_arm_nodes.wrist[1] != NONE && (fingers & HALO_VR_FINGERS_TOUCH))
|
||||
{
|
||||
/* (resting on the trigger the index curls 0.22 or more) */
|
||||
vr_straighten_finger(&vr_arm_skeleton, node_matrices, vr_arm_nodes.fingers[1][1],
|
||||
0.6f * PIN(1.f - curls[1] / 0.22f, 0.f, 1.f));
|
||||
}
|
||||
}
|
||||
|
||||
static void first_person_weapon_vr_arms(
|
||||
short local_player_index,
|
||||
long graph_index,
|
||||
@@ -1591,14 +1692,18 @@ static void first_person_weapon_vr_arms(
|
||||
static char const *const upper_names[2] = { "l upperarm", "r upperarm" };
|
||||
static char const *const fore_names[2] = { "l forearm", "r forearm" };
|
||||
static char const *const wrist_names[2] = { "l wriste", "r wriste" };
|
||||
float head[3], body_forward[3];
|
||||
float head[3], neck[3], body_forward[3];
|
||||
real_vector3d forward, left, weapon_left, weapon_right;
|
||||
real_point3d eye;
|
||||
real_matrix4x3 animated_upper[2];
|
||||
real_matrix4x3 animated_forearm[2];
|
||||
real_matrix4x3 animated_wrist[2];
|
||||
short side;
|
||||
boolean gripping;
|
||||
boolean left_free;
|
||||
boolean rigid = !halo_vr_free_arm_bends();
|
||||
int body = halo_vr_body_mode();
|
||||
float hand_position[3], hand_forward[3], hand_up[3];
|
||||
|
||||
if (vr_arm_nodes.graph_index != graph_index)
|
||||
{
|
||||
@@ -1672,11 +1777,27 @@ static void first_person_weapon_vr_arms(
|
||||
}
|
||||
if (!ready && !melee)
|
||||
vr_laser_follows_barrel(local_player_index, node_matrices, weapon_camera);
|
||||
if (!halo_vr_body_frame(local_player_index, head, body_forward))
|
||||
if (!halo_vr_body_frame(local_player_index, head, neck, body_forward) ||
|
||||
!vr_skeleton_build(&vr_arm_skeleton, graph_index, node_count))
|
||||
{
|
||||
return;
|
||||
eye.x = head[0];
|
||||
eye.y = head[1];
|
||||
eye.z = head[2];
|
||||
}
|
||||
{
|
||||
/* (the body is the room's: into model space, where the shoulders
|
||||
change sides) */
|
||||
float on[3], normal[3];
|
||||
|
||||
if (halo_vr_first_person_mirror(local_player_index, on, normal))
|
||||
{
|
||||
vr_mirror_point(on, normal, head);
|
||||
vr_mirror_point(on, normal, neck);
|
||||
vr_mirror_vector(normal, body_forward);
|
||||
}
|
||||
}
|
||||
/* (the shoulders hang from the neck, which the head turns about) */
|
||||
eye.x = neck[0];
|
||||
eye.y = neck[1];
|
||||
eye.z = neck[2];
|
||||
forward.i = body_forward[0];
|
||||
forward.j = body_forward[1];
|
||||
forward.k = 0.f;
|
||||
@@ -1693,6 +1814,8 @@ static void first_person_weapon_vr_arms(
|
||||
{
|
||||
animated_forearm[side] = node_matrices[vr_arm_nodes.fore[side]];
|
||||
animated_wrist[side] = node_matrices[vr_arm_nodes.wrist[side]];
|
||||
if (vr_arm_nodes.upper[side] != NONE)
|
||||
animated_upper[side] = node_matrices[vr_arm_nodes.upper[side]];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1715,13 +1838,19 @@ static void first_person_weapon_vr_arms(
|
||||
reference[2] = dot_product3d(&between, &weapon_camera->up);
|
||||
gripping = halo_vr_two_hand(local_player_index, foregrip, reference);
|
||||
}
|
||||
/* (the weapon alone: no hands to pose) */
|
||||
if (body == HALO_VR_BODY_OFF)
|
||||
return;
|
||||
if (!gripping && vr_arm_nodes.wrist[0] != NONE)
|
||||
{
|
||||
float hand_position[3], hand_forward[3], hand_up[3];
|
||||
|
||||
if (halo_vr_left_hand(local_player_index, hand_position, hand_forward, hand_up))
|
||||
if (halo_vr_free_hand(local_player_index, hand_position, hand_forward, hand_up))
|
||||
{
|
||||
vr_left_hand_free(graph_index, node_matrices, node_count, hand_position, hand_forward, hand_up);
|
||||
/* (rigid: the hand and its arm carried to the controller now;
|
||||
bent, the arm is solved to it first, below, and the hand turned
|
||||
after, as HaloCE-Quest-VR does: turned first, the solve's swing
|
||||
turned it on and the arm's skin stretched) */
|
||||
if (rigid)
|
||||
vr_left_hand_free(&vr_arm_skeleton, node_matrices, rigid, hand_position, hand_forward, hand_up);
|
||||
left_free = TRUE;
|
||||
}
|
||||
}
|
||||
@@ -1730,15 +1859,18 @@ static void first_person_weapon_vr_arms(
|
||||
{
|
||||
real_point3d shoulder;
|
||||
real_vector3d pole;
|
||||
short bones[NUMBER_OF_VR_ARM_BONES];
|
||||
real outward = side ? -1.f : 1.f; /* left is +left */
|
||||
|
||||
if (vr_arm_nodes.upper[side] == NONE || vr_arm_nodes.fore[side] == NONE || vr_arm_nodes.wrist[side] == NONE)
|
||||
continue;
|
||||
|
||||
/* the shoulder: below the eyes, a little behind them, to the side */
|
||||
/* the shoulder: below the eyes, behind them (a little behind the
|
||||
neck: in front of it, the open end of the sleeve came into view
|
||||
looking down to the side), to the side, square to the torso */
|
||||
shoulder = eye;
|
||||
point_from_line3d(&shoulder, global_up3d, -0.25f / VR_METRES_PER_WORLD_UNIT, &shoulder);
|
||||
point_from_line3d(&shoulder, &forward, -0.06f / VR_METRES_PER_WORLD_UNIT, &shoulder);
|
||||
point_from_line3d(&shoulder, global_up3d, -0.20f / VR_METRES_PER_WORLD_UNIT, &shoulder);
|
||||
point_from_line3d(&shoulder, &forward, -0.04f / VR_METRES_PER_WORLD_UNIT, &shoulder);
|
||||
point_from_line3d(&shoulder, &left, outward * 0.19f / VR_METRES_PER_WORLD_UNIT, &shoulder);
|
||||
/* the elbow bends down, out and back */
|
||||
pole.i = -global_up3d->i;
|
||||
@@ -1746,17 +1878,90 @@ static void first_person_weapon_vr_arms(
|
||||
pole.k = -global_up3d->k;
|
||||
vr_vector_scale_add(&pole, &left, outward * 0.6f);
|
||||
vr_vector_scale_add(&pole, &forward, -0.3f);
|
||||
if (!side && left_free)
|
||||
/* and as the hand rolls: an elbow lifted out to the side rolls the
|
||||
controller's top in, an elbow tucked in rolls it out. Its underside
|
||||
(away from the thumb) leads the elbow: the right hand's the weapon's,
|
||||
the free left hand's its controller's (the hand on the foregrip
|
||||
keeps the default) */
|
||||
{
|
||||
real_vector3d below;
|
||||
boolean rolls = FALSE;
|
||||
|
||||
if (side)
|
||||
{
|
||||
below.i = -weapon_camera->up.i;
|
||||
below.j = -weapon_camera->up.j;
|
||||
below.k = -weapon_camera->up.k;
|
||||
rolls = TRUE;
|
||||
}
|
||||
else if (left_free)
|
||||
{
|
||||
below.i = -hand_up[0];
|
||||
below.j = -hand_up[1];
|
||||
below.k = -hand_up[2];
|
||||
rolls = TRUE;
|
||||
}
|
||||
if (rolls && normalize3d(&pole) > 0.f)
|
||||
{
|
||||
vr_vector_scale_add(&pole, &below, 1.5f);
|
||||
if (normalize3d(&pole) <= 0.f)
|
||||
{
|
||||
pole.i = -global_up3d->i;
|
||||
pole.j = -global_up3d->j;
|
||||
pole.k = -global_up3d->k;
|
||||
}
|
||||
}
|
||||
}
|
||||
bones[_vr_arm_upper] = vr_arm_nodes.upper[side];
|
||||
bones[_vr_arm_fore] = vr_arm_nodes.fore[side];
|
||||
bones[_vr_arm_hand] = vr_arm_nodes.wrist[side];
|
||||
/* (the hands alone: the arms are shrunk away below) */
|
||||
if (body == HALO_VR_BODY_HANDS)
|
||||
continue;
|
||||
if (!side && left_free && rigid)
|
||||
{
|
||||
/* the free left arm keeps its shape (vr_left_hand_free), but
|
||||
for the upper arm, which points back at the shoulder */
|
||||
vr_point_upper_arm(node_matrices, vr_arm_nodes.upper[side], vr_arm_nodes.fore[side], &shoulder);
|
||||
continue;
|
||||
}
|
||||
vr_arm(node_matrices, vr_arm_nodes.upper[side], vr_arm_nodes.fore[side], vr_arm_nodes.wrist[side],
|
||||
shoulder, &node_matrices[vr_arm_nodes.wrist[side]].position, &pole);
|
||||
vr_twist_forearm(&node_matrices[vr_arm_nodes.fore[side]], &animated_forearm[side], &animated_wrist[side],
|
||||
&node_matrices[vr_arm_nodes.wrist[side]], &node_matrices[vr_arm_nodes.wrist[side]].position);
|
||||
if (!side && left_free)
|
||||
{
|
||||
/* the free left arm bent to the controller's grip (the wrist
|
||||
7 cm behind it), the hand turned to the controller after */
|
||||
real_point3d target;
|
||||
|
||||
target.x = hand_position[0] - hand_forward[0] * 0.07f / VR_METRES_PER_WORLD_UNIT;
|
||||
target.y = hand_position[1] - hand_forward[1] * 0.07f / VR_METRES_PER_WORLD_UNIT;
|
||||
target.z = hand_position[2] - hand_forward[2] * 0.07f / VR_METRES_PER_WORLD_UNIT;
|
||||
vr_solve_arm(&vr_arm_skeleton, node_matrices, bones, shoulder, &target, &pole, FALSE,
|
||||
&vr_arm_history[side]);
|
||||
vr_left_hand_free(&vr_arm_skeleton, node_matrices, FALSE, hand_position, hand_forward, hand_up);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* the hand stays where it is (on the weapon): the arm bends to
|
||||
it */
|
||||
vr_solve_arm(&vr_arm_skeleton, node_matrices, bones, shoulder,
|
||||
&node_matrices[bones[_vr_arm_hand]].position, &pole, TRUE, &vr_arm_history[side]);
|
||||
}
|
||||
vr_align_elbow(node_matrices, bones, &animated_upper[side], &animated_forearm[side], &animated_wrist[side]);
|
||||
vr_distribute_arm_twist(node_matrices, bones, &animated_forearm[side], &animated_wrist[side],
|
||||
&vr_arm_history[side]);
|
||||
}
|
||||
first_person_weapon_vr_fingers(local_player_index, node_matrices, left_free, hand_forward, hand_up);
|
||||
if (body == HALO_VR_BODY_HANDS)
|
||||
{
|
||||
for (side = 0; side < 2; side++)
|
||||
{
|
||||
short bones[NUMBER_OF_VR_ARM_BONES];
|
||||
|
||||
bones[_vr_arm_upper] = vr_arm_nodes.upper[side];
|
||||
bones[_vr_arm_fore] = vr_arm_nodes.fore[side];
|
||||
bones[_vr_arm_hand] = vr_arm_nodes.wrist[side];
|
||||
if (bones[_vr_arm_upper] != NONE && bones[_vr_arm_fore] != NONE && bones[_vr_arm_hand] != NONE)
|
||||
vr_hide_arm(&vr_arm_skeleton, node_matrices, bones, vr_arm_nodes.fingers[side], vr_arm_nodes.gun);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1767,8 +1972,8 @@ static void first_person_weapon_build_node_matrices(
|
||||
struct first_person_weapon *first_person_weapon= first_person_weapon_get(local_player_index);
|
||||
struct render_camera const *camera= &render.camera;
|
||||
#ifdef HALO_ANDROID
|
||||
/* VR: posed from the right controller, so the weapon lies in the hand
|
||||
(port/linux/src/vr.c) */
|
||||
/* VR: posed from the weapon hand's controller, so the weapon lies in the
|
||||
hand (port/linux/src/vr.c) */
|
||||
struct render_camera weapon_camera;
|
||||
float weapon_position[3], weapon_forward[3], weapon_up[3];
|
||||
|
||||
@@ -2122,8 +2327,12 @@ static void first_person_weapon_build_node_matrices(
|
||||
(short)animation_graph->nodes.count,
|
||||
camera);
|
||||
#ifdef HALO_ANDROID
|
||||
first_person_weapon_vr_pose_serial[local_player_index]= camera!=&render.camera ? halo_vr_frame_serial() : 0;
|
||||
first_person_weapon_vr_pose_weapon[local_player_index]= first_person_weapon->weapon_index;
|
||||
if (camera!=&render.camera)
|
||||
{
|
||||
unsigned long long timing= halo_vr_timing_start();
|
||||
|
||||
first_person_weapon_vr_arms(
|
||||
local_player_index,
|
||||
weapon_definition->weapon.interface_definition.first_person_animations.index,
|
||||
@@ -2131,6 +2340,16 @@ static void first_person_weapon_build_node_matrices(
|
||||
(short)animation_graph->nodes.count,
|
||||
camera,
|
||||
first_person_weapon->state);
|
||||
/* (left-handed: out of model space; what the frame takes from
|
||||
the pose after, markers, effects, is then the room's) */
|
||||
vr_mirror_pose(local_player_index, first_person_weapon->node_matrices,
|
||||
(short)MIN(animation_graph->nodes.count, MAXIMUM_NODES_PER_ANIMATION));
|
||||
halo_vr_timing_end(HALO_VR_TIMING_ARMS, timing);
|
||||
}
|
||||
else
|
||||
{
|
||||
debug_mirror_first_person(camera, first_person_weapon->node_matrices,
|
||||
(short)MIN(animation_graph->nodes.count, MAXIMUM_NODES_PER_ANIMATION));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
+477
-105
@@ -6645,6 +6645,7 @@ they change), and from it a page of what the controllers' buttons do. */
|
||||
|
||||
int config_boolean(char const *name);
|
||||
double config_real(char const *name);
|
||||
char const *config_string(char const *name);
|
||||
int config_write(char const *name, char const *value);
|
||||
int config_write_boolean(char const *name, int value);
|
||||
|
||||
@@ -6655,16 +6656,26 @@ enum
|
||||
_vr_option_smooth_speed,
|
||||
_vr_option_vignette,
|
||||
_vr_option_wheel,
|
||||
_vr_option_handedness,
|
||||
_vr_option_mirror_controls,
|
||||
_vr_option_laser,
|
||||
_vr_option_crosshair,
|
||||
_vr_option_motion_tracker,
|
||||
_vr_option_body,
|
||||
_vr_option_fingers,
|
||||
_vr_option_graphics,
|
||||
_vr_option_refresh_rate,
|
||||
_vr_option_resolution,
|
||||
_vr_option_controls,
|
||||
_vr_option_quit,
|
||||
NUMBER_OF_VR_OPTIONS,
|
||||
|
||||
VR_OPTIONS_TOP = 98,
|
||||
VR_OPTIONS_ROW_HEIGHT = 26,
|
||||
/* (twelve rows a page between the title and the footer, in the box) */
|
||||
VR_OPTIONS_TOP = 94,
|
||||
VR_OPTIONS_ROW_HEIGHT = 22,
|
||||
VR_OPTIONS_FOOTER = 362,
|
||||
VR_OPTIONS_PER_PAGE = 12,
|
||||
NUMBER_OF_VR_OPTION_PAGES = 2,
|
||||
|
||||
_vr_controls_on_foot = 0,
|
||||
_vr_controls_vehicles,
|
||||
@@ -6682,12 +6693,145 @@ static struct
|
||||
boolean quit_armed;
|
||||
} ui_vr_options_globals;
|
||||
|
||||
/* the panel's pages (its tabs), their rows in order (NONE past the last) */
|
||||
static char const *const vr_option_page_names[NUMBER_OF_VR_OPTION_PAGES] = { "PLAY", "MORE" };
|
||||
static short const vr_option_pages[NUMBER_OF_VR_OPTION_PAGES][VR_OPTIONS_PER_PAGE] =
|
||||
{
|
||||
{ _vr_option_turning, _vr_option_snap_angle, _vr_option_smooth_speed, _vr_option_vignette, _vr_option_wheel,
|
||||
_vr_option_handedness, _vr_option_mirror_controls, _vr_option_laser, _vr_option_crosshair,
|
||||
_vr_option_motion_tracker, _vr_option_body, _vr_option_fingers },
|
||||
{ _vr_option_graphics, _vr_option_refresh_rate, _vr_option_resolution, _vr_option_controls, _vr_option_quit,
|
||||
NONE, NONE, NONE, NONE, NONE, NONE, NONE },
|
||||
};
|
||||
|
||||
/* the page a row is on, and its place there */
|
||||
static short ui_vr_options_page_of(
|
||||
short option,
|
||||
short *index)
|
||||
{
|
||||
short page, row;
|
||||
|
||||
for (page = 0; page < NUMBER_OF_VR_OPTION_PAGES; page++)
|
||||
{
|
||||
for (row = 0; row < VR_OPTIONS_PER_PAGE; row++)
|
||||
{
|
||||
if (vr_option_pages[page][row] == option)
|
||||
{
|
||||
if (index)
|
||||
*index = row;
|
||||
return page;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (index)
|
||||
*index = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static short ui_vr_options_page_rows(
|
||||
short page)
|
||||
{
|
||||
short rows = 0;
|
||||
|
||||
while (rows < VR_OPTIONS_PER_PAGE && vr_option_pages[page][rows] != NONE)
|
||||
rows++;
|
||||
return rows;
|
||||
}
|
||||
|
||||
/* the settings' hands: the weapon in the left one, and the sticks and
|
||||
buttons mirrored with it (vr.c) */
|
||||
static boolean ui_vr_left_handed(
|
||||
void)
|
||||
{
|
||||
char const *handedness = config_string("vr.handedness");
|
||||
|
||||
return handedness && strcmp(handedness, "left") == 0;
|
||||
}
|
||||
|
||||
static boolean ui_vr_controls_mirrored(
|
||||
void)
|
||||
{
|
||||
return ui_vr_left_handed() && config_boolean("vr.mirror_controls");
|
||||
}
|
||||
|
||||
void platform_request_quit(void);
|
||||
|
||||
static real const vr_snap_angles[] = { 30.0f, 45.0f, 60.0f, 90.0f };
|
||||
static real const vr_smooth_speeds[] = { 60.0f, 90.0f, 120.0f, 150.0f, 180.0f };
|
||||
static real const vr_vignettes[] = { 0.0f, 0.35f, 0.7f, 1.0f };
|
||||
static char const *const vr_vignette_names[] = { "Off", "Light", "Medium", "Strong" };
|
||||
/* vr.body's values, and as the panel shows them */
|
||||
static char const *const vr_bodies[] = { "arms", "hands", "off" };
|
||||
static char const *const vr_body_names[] = { "Arms and hands", "Hands only", "Weapon only" };
|
||||
/* vr.graphics's (port/linux/game/vr_graphics.c), and vr.resolution_scale's
|
||||
(read as the app starts) */
|
||||
static char const *const vr_graphics_presets[] = { "low", "medium", "high", "max" };
|
||||
static char const *const vr_graphics_names[] = { "Low", "Medium", "High", "Max" };
|
||||
static real const vr_resolution_scales[] = { 0.7f, 0.8f, 0.9f, 1.0f, 1.1f, 1.2f };
|
||||
|
||||
static short ui_vr_options_graphics(
|
||||
void)
|
||||
{
|
||||
char const *graphics = config_string("vr.graphics");
|
||||
short index;
|
||||
|
||||
for (index = 0; index < (short)NUMBEROF(vr_graphics_presets); index++)
|
||||
{
|
||||
if (graphics && strcmp(graphics, vr_graphics_presets[index]) == 0)
|
||||
return index;
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
|
||||
/* the refresh rates the headset offers, in order, and which of them
|
||||
vr.refresh_rate chooses (the highest at or below it; 0 asks for the
|
||||
highest); 0 rates where it lets none be chosen (SteamVR: SteamOS's) */
|
||||
static short ui_vr_options_refresh_rates(
|
||||
real rates[16],
|
||||
short *chosen,
|
||||
real *current)
|
||||
{
|
||||
float offered[16];
|
||||
float now = 0.0f;
|
||||
short count = (short)halo_vr_refresh_rates(offered, 16, &now);
|
||||
real wanted = (real)config_real("vr.refresh_rate");
|
||||
short index;
|
||||
|
||||
*current = now;
|
||||
*chosen = 0;
|
||||
for (index = 0; index < count; index++)
|
||||
{
|
||||
short place = index;
|
||||
|
||||
/* (in order, lowest first) */
|
||||
while (place > 0 && rates[place - 1] > offered[index])
|
||||
{
|
||||
rates[place] = rates[place - 1];
|
||||
place--;
|
||||
}
|
||||
rates[place] = offered[index];
|
||||
}
|
||||
for (index = 0; index < count; index++)
|
||||
{
|
||||
if (wanted <= 0.0f || rates[index] <= wanted + 0.5f)
|
||||
*chosen = index;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static short ui_vr_options_body(
|
||||
void)
|
||||
{
|
||||
char const *body = config_string("vr.body");
|
||||
short index;
|
||||
|
||||
for (index = 0; index < (short)NUMBEROF(vr_bodies); index++)
|
||||
{
|
||||
if (body && strcmp(body, vr_bodies[index]) == 0)
|
||||
return index;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static boolean ui_vr_options_screen(
|
||||
void)
|
||||
@@ -6748,11 +6892,42 @@ static void ui_vr_options_change(
|
||||
ui_vr_options_step("vr.comfort_vignette", vr_vignettes, NUMBEROF(vr_vignettes), change);
|
||||
break;
|
||||
case _vr_option_wheel: config_write_boolean("vr.vehicle_wheel", !config_boolean("vr.vehicle_wheel")); break;
|
||||
case _vr_option_handedness: config_write("vr.handedness", ui_vr_left_handed() ? "right" : "left"); break;
|
||||
case _vr_option_mirror_controls:
|
||||
config_write_boolean("vr.mirror_controls", !config_boolean("vr.mirror_controls"));
|
||||
break;
|
||||
case _vr_option_laser: config_write_boolean("vr.laser", !config_boolean("vr.laser")); break;
|
||||
case _vr_option_crosshair: config_write_boolean("vr.crosshair", !config_boolean("vr.crosshair")); break;
|
||||
case _vr_option_motion_tracker:
|
||||
config_write_boolean("vr.motion_tracker_on_wrist", !config_boolean("vr.motion_tracker_on_wrist"));
|
||||
break;
|
||||
case _vr_option_body:
|
||||
config_write("vr.body", vr_bodies[(ui_vr_options_body() + change + (short)NUMBEROF(vr_bodies)) %
|
||||
(short)NUMBEROF(vr_bodies)]);
|
||||
break;
|
||||
case _vr_option_fingers: config_write_boolean("vr.fingers", !config_boolean("vr.fingers")); break;
|
||||
case _vr_option_graphics:
|
||||
config_write("vr.graphics", vr_graphics_presets[(ui_vr_options_graphics() + change +
|
||||
(short)NUMBEROF(vr_graphics_presets)) % (short)NUMBEROF(vr_graphics_presets)]);
|
||||
break;
|
||||
case _vr_option_refresh_rate:
|
||||
{
|
||||
real rates[16], current;
|
||||
short chosen;
|
||||
short count = ui_vr_options_refresh_rates(rates, &chosen, ¤t);
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
char text[16];
|
||||
|
||||
snprintf(text, sizeof(text), "%g", rates[(chosen + change + count) % count]);
|
||||
config_write("vr.refresh_rate", text);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case _vr_option_resolution:
|
||||
ui_vr_options_step("vr.resolution_scale", vr_resolution_scales, NUMBEROF(vr_resolution_scales), change);
|
||||
break;
|
||||
case _vr_option_controls:
|
||||
ui_vr_options_globals.controls = TRUE;
|
||||
ui_vr_options_globals.page = _vr_controls_on_foot;
|
||||
@@ -6799,7 +6974,7 @@ static boolean ui_vr_options_take_event(
|
||||
{
|
||||
ui_vr_options_globals.open = TRUE;
|
||||
ui_vr_options_globals.controls = FALSE;
|
||||
ui_vr_options_globals.selection = 0;
|
||||
ui_vr_options_globals.selection = vr_option_pages[0][0];
|
||||
ui_play_audio_feedback_sound(_ui_audio_feedback_forward);
|
||||
return TRUE;
|
||||
}
|
||||
@@ -6857,7 +7032,23 @@ static boolean ui_vr_options_take_event(
|
||||
}
|
||||
else if (move)
|
||||
{
|
||||
ui_vr_options_globals.selection = (short)PIN(ui_vr_options_globals.selection + move, 0, NUMBER_OF_VR_OPTIONS - 1);
|
||||
/* (past a page's last row or first, onto the next page or back) */
|
||||
short row;
|
||||
short page = ui_vr_options_page_of(ui_vr_options_globals.selection, &row);
|
||||
|
||||
row = (short)(row + move);
|
||||
if (row < 0 && page > 0)
|
||||
{
|
||||
page--;
|
||||
row = (short)(ui_vr_options_page_rows(page) - 1);
|
||||
}
|
||||
else if (row >= ui_vr_options_page_rows(page) && page < NUMBER_OF_VR_OPTION_PAGES - 1)
|
||||
{
|
||||
page++;
|
||||
row = 0;
|
||||
}
|
||||
row = (short)PIN(row, 0, ui_vr_options_page_rows(page) - 1);
|
||||
ui_vr_options_globals.selection = vr_option_pages[page][row];
|
||||
ui_vr_options_globals.quit_armed = FALSE;
|
||||
ui_play_audio_feedback_sound(_ui_audio_feedback_cursor);
|
||||
}
|
||||
@@ -6892,7 +7083,7 @@ static boolean ui_vr_options_take_pointer(
|
||||
{
|
||||
ui_vr_options_globals.open = TRUE;
|
||||
ui_vr_options_globals.controls = FALSE;
|
||||
ui_vr_options_globals.selection = 0;
|
||||
ui_vr_options_globals.selection = vr_option_pages[0][0];
|
||||
ui_play_audio_feedback_sound(_ui_audio_feedback_forward);
|
||||
return TRUE;
|
||||
}
|
||||
@@ -6919,21 +7110,38 @@ static boolean ui_vr_options_take_pointer(
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
if (x >= 118 && x < 522 && y >= VR_OPTIONS_TOP - 4 &&
|
||||
y < VR_OPTIONS_TOP - 4 + NUMBER_OF_VR_OPTIONS * VR_OPTIONS_ROW_HEIGHT)
|
||||
{
|
||||
short row = (short)((y - (VR_OPTIONS_TOP - 4)) / VR_OPTIONS_ROW_HEIGHT);
|
||||
short page = ui_vr_options_page_of(ui_vr_options_globals.selection, NULL);
|
||||
|
||||
if (row != ui_vr_options_globals.selection)
|
||||
ui_vr_options_globals.quit_armed = FALSE;
|
||||
ui_vr_options_globals.selection = row;
|
||||
if (pointer->left_clicks)
|
||||
ui_vr_options_change(1);
|
||||
}
|
||||
else if (pointer->left_clicks && x >= 118 && x < 522 && y >= VR_OPTIONS_FOOTER + 4 && y < VR_OPTIONS_FOOTER + 30)
|
||||
{
|
||||
ui_vr_options_close();
|
||||
ui_play_audio_feedback_sound(_ui_audio_feedback_back);
|
||||
if (pointer->left_clicks && y >= 68 && y < 88 && x >= 330 && x < 514)
|
||||
{
|
||||
/* (a tab: its page) */
|
||||
short tab = (short)PIN((x - 330) / 92, 0, NUMBER_OF_VR_OPTION_PAGES - 1);
|
||||
|
||||
if (tab != page)
|
||||
{
|
||||
ui_vr_options_globals.selection = vr_option_pages[tab][0];
|
||||
ui_vr_options_globals.quit_armed = FALSE;
|
||||
ui_play_audio_feedback_sound(_ui_audio_feedback_cursor);
|
||||
}
|
||||
}
|
||||
else if (x >= 118 && x < 522 && y >= VR_OPTIONS_TOP - 4 &&
|
||||
y < VR_OPTIONS_TOP - 4 + ui_vr_options_page_rows(page) * VR_OPTIONS_ROW_HEIGHT)
|
||||
{
|
||||
short option = vr_option_pages[page][(y - (VR_OPTIONS_TOP - 4)) / VR_OPTIONS_ROW_HEIGHT];
|
||||
|
||||
if (option != ui_vr_options_globals.selection)
|
||||
ui_vr_options_globals.quit_armed = FALSE;
|
||||
ui_vr_options_globals.selection = option;
|
||||
if (pointer->left_clicks)
|
||||
ui_vr_options_change(1);
|
||||
}
|
||||
else if (pointer->left_clicks && x >= 118 && x < 522 && y >= VR_OPTIONS_FOOTER + 4 &&
|
||||
y < VR_OPTIONS_FOOTER + 30)
|
||||
{
|
||||
ui_vr_options_close();
|
||||
ui_play_audio_feedback_sound(_ui_audio_feedback_back);
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
@@ -6961,6 +7169,56 @@ static void ui_vr_controls_note(
|
||||
ui_join_code_draw_text(44, 596, y, text, FALSE, 0, &color);
|
||||
}
|
||||
|
||||
/* a column of the controls page: its lines, what is pressed and what it
|
||||
does ("" for a line that goes on the one above) */
|
||||
enum
|
||||
{
|
||||
VR_CONTROLS_COLUMN_LINES = 12,
|
||||
};
|
||||
|
||||
struct ui_vr_controls_column
|
||||
{
|
||||
short count;
|
||||
char const *controls[VR_CONTROLS_COLUMN_LINES];
|
||||
char const *actions[VR_CONTROLS_COLUMN_LINES];
|
||||
};
|
||||
|
||||
static void ui_vr_controls_add(
|
||||
struct ui_vr_controls_column *column,
|
||||
char const *control,
|
||||
char const *action)
|
||||
{
|
||||
if (column->count < VR_CONTROLS_COLUMN_LINES)
|
||||
{
|
||||
column->controls[column->count] = control;
|
||||
column->actions[column->count] = action;
|
||||
column->count++;
|
||||
}
|
||||
}
|
||||
|
||||
static void ui_vr_controls_column_render(
|
||||
short x,
|
||||
short y,
|
||||
struct ui_vr_controls_column const *column)
|
||||
{
|
||||
short line;
|
||||
|
||||
for (line = 0; line < column->count; line++)
|
||||
{
|
||||
if (line > 0)
|
||||
y = (short)(y + (column->controls[line][0] ? 22 : 18));
|
||||
ui_vr_controls_line(x, y, column->controls[line], column->actions[line]);
|
||||
}
|
||||
}
|
||||
|
||||
/* The page of what the controllers' buttons do, as the hands are set
|
||||
(vr.handedness, vr.mirror_controls: vr.c's halo_vr_controllers): the
|
||||
weapon hand's trigger fires and its grip strikes; the free hand's throws
|
||||
grenades and grips the foregrip; the move and turn sticks, and the face
|
||||
buttons (on the Quest's right controller A and B jump and light, on its
|
||||
left X and Y act and switch weapons; on the Steam Frame all four on the
|
||||
right, its left D-pad taking them mirrored) change sides with the mirrored
|
||||
controls. The menus keep their buttons. */
|
||||
static void ui_vr_controls_render(
|
||||
void)
|
||||
{
|
||||
@@ -6968,11 +7226,25 @@ static void ui_vr_controls_render(
|
||||
real_argb_color title_color = { 1.0f, 0.55f, 0.75f, 1.0f };
|
||||
real_argb_color tab_color = { 1.0f, 0.6f, 0.6f, 0.6f };
|
||||
real_argb_color selected_color = { 1.0f, 1.0f, 1.0f, 1.0f };
|
||||
struct ui_vr_controls_column columns[2];
|
||||
boolean frame = halo_vr_controller_layout() == 1;
|
||||
boolean left_handed = ui_vr_left_handed();
|
||||
boolean mirrored = ui_vr_controls_mirrored();
|
||||
/* the columns (0 the left controller, 1 the right) of the weapon hand,
|
||||
the free hand, the turning stick and the jump button */
|
||||
short gun = left_handed ? 0 : 1;
|
||||
short free_column = (short)(1 - gun);
|
||||
short turn = mirrored ? 0 : 1;
|
||||
short move = (short)(1 - turn);
|
||||
char const *wrist_note = left_handed ?
|
||||
"Motion tracker: turn the back of your right wrist towards you." :
|
||||
"Motion tracker: turn the back of your left wrist towards you.";
|
||||
short tab;
|
||||
short left = 44;
|
||||
short right = 328;
|
||||
short y = 112;
|
||||
|
||||
csmemset(columns, 0, sizeof(columns));
|
||||
ui_join_code_draw_quad(24, 40, 616, 440, 0xFF050A18);
|
||||
ui_join_code_draw_quad(24, 40, 616, 42, 0xFF3A6EA5);
|
||||
ui_join_code_draw_quad(24, 438, 616, 440, 0xFF3A6EA5);
|
||||
@@ -6988,111 +7260,134 @@ static void ui_vr_controls_render(
|
||||
ui_join_code_draw_text(left, (short)(left + 270), 86, "LEFT CONTROLLER", FALSE, 0, &title_color);
|
||||
ui_join_code_draw_text(right, (short)(right + 270), 86, "RIGHT CONTROLLER", FALSE, 0, &title_color);
|
||||
ui_join_code_draw_quad(320, 86, 321, 300, 0xFF3A6EA5);
|
||||
if (halo_vr_controller_layout() == 1)
|
||||
if (frame)
|
||||
{
|
||||
/* the Steam Frame's controllers: A, B, X, Y and a menu button on the
|
||||
right one, a D-pad and a View button on the left one */
|
||||
right one, a D-pad and a View button on the left one; mirrored, the
|
||||
D-pad does what A, B, X and Y do (down, left, right, up) */
|
||||
char const *jump = mirrored ? "D-pad down" : "A";
|
||||
char const *light = mirrored ? "D-pad left" : "B";
|
||||
char const *act = mirrored ? "D-pad right" : "X";
|
||||
char const *weapon = mirrored ? "D-pad up" : "Y";
|
||||
short buttons = mirrored ? 0 : 1;
|
||||
|
||||
switch (ui_vr_options_globals.page)
|
||||
{
|
||||
case _vr_controls_on_foot:
|
||||
ui_vr_controls_line(left, y, "Stick", "Move; click: crouch");
|
||||
ui_vr_controls_line(left, y + 22, "Trigger", "Grenade: hold it,");
|
||||
ui_vr_controls_line(left, y + 40, "", "let go as you throw");
|
||||
ui_vr_controls_line(left, y + 62, "Grip", "Weapon in both hands");
|
||||
ui_vr_controls_line(left, y + 84, "Bumper", "Switch grenades");
|
||||
ui_vr_controls_line(left, y + 106, "View", "Scores");
|
||||
ui_vr_controls_line(left, y + 128, "D-pad", "In the menus");
|
||||
ui_vr_controls_line(right, y, "Stick", "Turn; click: zoom");
|
||||
ui_vr_controls_line(right, y + 22, "Trigger", "Fire");
|
||||
ui_vr_controls_line(right, y + 44, "Grip", "Melee (or punch)");
|
||||
ui_vr_controls_line(right, y + 66, "A", "Jump");
|
||||
ui_vr_controls_line(right, y + 88, "B", "Light; held: grenades");
|
||||
ui_vr_controls_line(right, y + 110, "X", "Action, reload");
|
||||
ui_vr_controls_line(right, y + 132, "Y", "Switch weapon");
|
||||
ui_vr_controls_line(right, y + 154, "Menu", "Pause");
|
||||
ui_vr_controls_note(320, "Motion tracker: turn the back of your left wrist towards you.");
|
||||
ui_vr_controls_add(&columns[move], "Stick", "Move; click: crouch");
|
||||
ui_vr_controls_add(&columns[turn], "Stick", "Turn; click: zoom");
|
||||
ui_vr_controls_add(&columns[gun], "Trigger", "Fire");
|
||||
ui_vr_controls_add(&columns[free_column], "Trigger", "Grenade: hold, throw");
|
||||
ui_vr_controls_add(&columns[gun], "Grip", "Melee (or punch)");
|
||||
ui_vr_controls_add(&columns[free_column], "Grip", "Weapon in both hands");
|
||||
ui_vr_controls_add(&columns[buttons], jump, "Jump");
|
||||
ui_vr_controls_add(&columns[buttons], light, "Light; held: grenades");
|
||||
ui_vr_controls_add(&columns[buttons], act, "Action, reload");
|
||||
ui_vr_controls_add(&columns[buttons], weapon, "Switch weapon");
|
||||
ui_vr_controls_add(&columns[mirrored ? 0 : 1], "Bumper", "Flashlight");
|
||||
ui_vr_controls_add(&columns[mirrored ? 1 : 0], "Bumper", "Switch grenades");
|
||||
ui_vr_controls_add(&columns[0], "View", "Scores");
|
||||
if (mirrored)
|
||||
ui_vr_controls_add(&columns[1], "A, B, X, Y", "As the D-pad");
|
||||
else
|
||||
ui_vr_controls_add(&columns[0], "D-pad", "In the menus");
|
||||
ui_vr_controls_add(&columns[1], "Menu", "Pause");
|
||||
ui_vr_controls_note(320, wrist_note);
|
||||
ui_vr_controls_note(340, "Scope: zoom, and look through the disc above the weapon.");
|
||||
ui_vr_controls_note(360, "Right bumper: the flashlight too.");
|
||||
break;
|
||||
case _vr_controls_vehicles:
|
||||
ui_vr_controls_line(left, y, "Stick", "Drive");
|
||||
ui_vr_controls_line(left, y + 22, "Trigger", "Second weapon");
|
||||
ui_vr_controls_line(left, y + 40, "", "(Banshee, tank)");
|
||||
ui_vr_controls_line(right, y, "Stick", "Steer; up and down:");
|
||||
ui_vr_controls_line(right, y + 18, "", "climb, dive");
|
||||
ui_vr_controls_line(right, y + 40, "Trigger", "Fire");
|
||||
ui_vr_controls_line(right, y + 62, "X", "Get in, get out");
|
||||
ui_vr_controls_line(right, y + 84, "B", "Flashlight");
|
||||
ui_vr_controls_note(300, "Warthog: grip the wheel with one hand or both and turn it.");
|
||||
ui_vr_controls_note(320, "Holding it, the right trigger accelerates, the left one brakes");
|
||||
ui_vr_controls_note(338, "and reverses. Let go, and the right stick steers.");
|
||||
ui_vr_controls_note(360, "At a gun or as a passenger: aim with the right controller.");
|
||||
ui_vr_controls_add(&columns[move], "Stick", "Drive");
|
||||
ui_vr_controls_add(&columns[turn], "Stick", "Steer; up and down:");
|
||||
ui_vr_controls_add(&columns[turn], "", "climb, dive");
|
||||
ui_vr_controls_add(&columns[gun], "Trigger", "Fire");
|
||||
ui_vr_controls_add(&columns[free_column], "Trigger", "Second weapon");
|
||||
ui_vr_controls_add(&columns[free_column], "", "(Banshee, tank)");
|
||||
ui_vr_controls_add(&columns[buttons], act, "Get in, get out");
|
||||
ui_vr_controls_add(&columns[buttons], light, "Flashlight");
|
||||
break;
|
||||
default:
|
||||
ui_vr_controls_line(left, y, "Stick", "Choose");
|
||||
ui_vr_controls_line(left, y + 22, "D-pad", "Choose");
|
||||
ui_vr_controls_line(left, y + 44, "Trigger", "Click where it points");
|
||||
ui_vr_controls_line(right, y, "Stick", "Choose, scroll");
|
||||
ui_vr_controls_line(right, y + 22, "A", "Accept");
|
||||
ui_vr_controls_line(right, y + 44, "B", "Back");
|
||||
ui_vr_controls_line(right, y + 66, "X, Y", "As the screen shows");
|
||||
ui_vr_controls_line(right, y + 88, "Trigger", "Click where it points");
|
||||
ui_vr_controls_note(300, "Point at the menus with either controller; its trigger clicks.");
|
||||
ui_vr_controls_note(320, "On the keyboard, BACK and ENTER under it can be clicked too.");
|
||||
ui_vr_controls_note(340, "Y on the main menu or the pause menu: these VR options.");
|
||||
ui_vr_controls_add(&columns[0], "Stick", "Choose");
|
||||
ui_vr_controls_add(&columns[0], "D-pad", "Choose");
|
||||
ui_vr_controls_add(&columns[0], "Trigger", "Click where it points");
|
||||
ui_vr_controls_add(&columns[1], "Stick", "Choose, scroll");
|
||||
ui_vr_controls_add(&columns[1], "A", "Accept");
|
||||
ui_vr_controls_add(&columns[1], "B", "Back");
|
||||
ui_vr_controls_add(&columns[1], "X, Y", "As the screen shows");
|
||||
ui_vr_controls_add(&columns[1], "Trigger", "Click where it points");
|
||||
break;
|
||||
}
|
||||
ui_join_code_draw_text(44, 596, 412, "%b-button Back Left, right: the other pages", TRUE, 0, &tab_color);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* the Quest's: A and B on the right controller, X, Y and the menu
|
||||
button on the left; mirrored, X and Y do what A and B do, and the
|
||||
other way round */
|
||||
short jumps = mirrored ? 0 : 1;
|
||||
short acts = (short)(1 - jumps);
|
||||
|
||||
switch (ui_vr_options_globals.page)
|
||||
{
|
||||
case _vr_controls_on_foot:
|
||||
ui_vr_controls_add(&columns[move], "Stick", "Move");
|
||||
ui_vr_controls_add(&columns[move], "Stick click", "Crouch");
|
||||
ui_vr_controls_add(&columns[turn], "Stick", "Turn");
|
||||
ui_vr_controls_add(&columns[turn], "Stick click", "Zoom");
|
||||
ui_vr_controls_add(&columns[gun], "Trigger", "Fire");
|
||||
ui_vr_controls_add(&columns[gun], "Grip", "Melee (or punch");
|
||||
ui_vr_controls_add(&columns[gun], "", "forward)");
|
||||
ui_vr_controls_add(&columns[free_column], "Trigger", "Grenade: hold it,");
|
||||
ui_vr_controls_add(&columns[free_column], "", "let go as you throw");
|
||||
ui_vr_controls_add(&columns[free_column], "Grip", "Weapon in both hands");
|
||||
ui_vr_controls_add(&columns[jumps], jumps ? "A" : "X", "Jump");
|
||||
ui_vr_controls_add(&columns[jumps], jumps ? "B" : "Y", "Flashlight");
|
||||
ui_vr_controls_add(&columns[jumps], jumps ? "B held" : "Y held", "Switch grenades");
|
||||
ui_vr_controls_add(&columns[acts], acts ? "A" : "X", "Action, reload");
|
||||
ui_vr_controls_add(&columns[acts], acts ? "B" : "Y", "Switch weapon");
|
||||
ui_vr_controls_add(&columns[0], "Menu", "Pause");
|
||||
ui_vr_controls_note(320, wrist_note);
|
||||
ui_vr_controls_note(340, "Scope: zoom, and look through the disc above the weapon.");
|
||||
ui_vr_controls_note(360, "Hold the Meta button to recentre the view.");
|
||||
break;
|
||||
case _vr_controls_vehicles:
|
||||
ui_vr_controls_add(&columns[move], "Stick", "Drive");
|
||||
ui_vr_controls_add(&columns[turn], "Stick", "Steer; up and down:");
|
||||
ui_vr_controls_add(&columns[turn], "", "climb, dive");
|
||||
ui_vr_controls_add(&columns[gun], "Trigger", "Fire");
|
||||
ui_vr_controls_add(&columns[free_column], "Trigger", "Second weapon");
|
||||
ui_vr_controls_add(&columns[free_column], "", "(Banshee, tank)");
|
||||
ui_vr_controls_add(&columns[acts], acts ? "A" : "X", "Get in, get out");
|
||||
ui_vr_controls_add(&columns[jumps], jumps ? "B" : "Y", "Flashlight");
|
||||
break;
|
||||
default:
|
||||
ui_vr_controls_add(&columns[0], "Stick", "Choose");
|
||||
ui_vr_controls_add(&columns[0], "X, Y", "As the screen shows");
|
||||
ui_vr_controls_add(&columns[0], "Trigger", "Click where it points");
|
||||
ui_vr_controls_add(&columns[1], "Stick", "Choose, scroll");
|
||||
ui_vr_controls_add(&columns[1], "A", "Accept");
|
||||
ui_vr_controls_add(&columns[1], "B", "Back");
|
||||
ui_vr_controls_add(&columns[1], "Trigger", "Click where it points");
|
||||
break;
|
||||
}
|
||||
}
|
||||
ui_vr_controls_column_render(left, y, &columns[0]);
|
||||
ui_vr_controls_column_render(right, y, &columns[1]);
|
||||
switch (ui_vr_options_globals.page)
|
||||
{
|
||||
case _vr_controls_on_foot:
|
||||
ui_vr_controls_line(left, y, "Stick", "Move");
|
||||
ui_vr_controls_line(left, y + 22, "Stick click", "Crouch");
|
||||
ui_vr_controls_line(left, y + 44, "Trigger", "Grenade: hold it,");
|
||||
ui_vr_controls_line(left, y + 62, "", "let go as you throw");
|
||||
ui_vr_controls_line(left, y + 84, "Grip", "Weapon in both hands");
|
||||
ui_vr_controls_line(left, y + 106, "X", "Action, reload");
|
||||
ui_vr_controls_line(left, y + 128, "Y", "Switch weapon");
|
||||
ui_vr_controls_line(left, y + 150, "Menu", "Pause");
|
||||
ui_vr_controls_line(right, y, "Stick", "Turn");
|
||||
ui_vr_controls_line(right, y + 22, "Stick click", "Zoom");
|
||||
ui_vr_controls_line(right, y + 44, "Trigger", "Fire");
|
||||
ui_vr_controls_line(right, y + 66, "Grip", "Melee (or punch");
|
||||
ui_vr_controls_line(right, y + 84, "", "forward)");
|
||||
ui_vr_controls_line(right, y + 106, "A", "Jump");
|
||||
ui_vr_controls_line(right, y + 128, "B", "Flashlight");
|
||||
ui_vr_controls_line(right, y + 150, "B held", "Switch grenades");
|
||||
ui_vr_controls_note(320, "Motion tracker: turn the back of your left wrist towards you.");
|
||||
ui_vr_controls_note(340, "Scope: zoom, and look through the disc above the weapon.");
|
||||
ui_vr_controls_note(360, "Hold the Meta button to recentre the view.");
|
||||
break;
|
||||
case _vr_controls_vehicles:
|
||||
ui_vr_controls_line(left, y, "Stick", "Drive");
|
||||
ui_vr_controls_line(left, y + 22, "Trigger", "Second weapon");
|
||||
ui_vr_controls_line(left, y + 40, "", "(Banshee, tank)");
|
||||
ui_vr_controls_line(left, y + 62, "X", "Get in, get out");
|
||||
ui_vr_controls_line(right, y, "Stick", "Steer; up and down:");
|
||||
ui_vr_controls_line(right, y + 18, "", "climb, dive");
|
||||
ui_vr_controls_line(right, y + 40, "Trigger", "Fire");
|
||||
ui_vr_controls_line(right, y + 62, "B", "Flashlight");
|
||||
ui_vr_controls_note(300, "Warthog: grip the wheel with one hand or both and turn it.");
|
||||
ui_vr_controls_note(320, "Holding it, the right trigger accelerates, the left one brakes");
|
||||
ui_vr_controls_note(338, "and reverses. Let go, and the right stick steers.");
|
||||
ui_vr_controls_note(360, "At a gun or as a passenger: aim with the right controller.");
|
||||
ui_vr_controls_note(320, left_handed ? "Holding it, the left trigger accelerates, the right one brakes" :
|
||||
"Holding it, the right trigger accelerates, the left one brakes");
|
||||
ui_vr_controls_note(338, mirrored ? "and reverses. Let go, and the left stick steers." :
|
||||
"and reverses. Let go, and the right stick steers.");
|
||||
ui_vr_controls_note(360, left_handed ? "At a gun or as a passenger: aim with the left controller." :
|
||||
"At a gun or as a passenger: aim with the right controller.");
|
||||
break;
|
||||
default:
|
||||
ui_vr_controls_line(left, y, "Stick", "Choose");
|
||||
ui_vr_controls_line(left, y + 22, "X, Y", "As the screen shows");
|
||||
ui_vr_controls_line(left, y + 44, "Trigger", "Click where it points");
|
||||
ui_vr_controls_line(right, y, "Stick", "Choose, scroll");
|
||||
ui_vr_controls_line(right, y + 22, "A", "Accept");
|
||||
ui_vr_controls_line(right, y + 44, "B", "Back");
|
||||
ui_vr_controls_line(right, y + 66, "Trigger", "Click where it points");
|
||||
ui_vr_controls_note(300, "Point at the menus with either controller; its trigger clicks.");
|
||||
ui_vr_controls_note(320, "On the keyboard, BACK and ENTER under it can be clicked too.");
|
||||
ui_vr_controls_note(340, "Y on the main menu or the pause menu: these VR options.");
|
||||
if (left_handed)
|
||||
ui_vr_controls_note(360, "Left-handed, the menus keep their buttons.");
|
||||
break;
|
||||
}
|
||||
ui_join_code_draw_text(44, 596, 412, "%b-button Back Left, right: the other pages", TRUE, 0, &tab_color);
|
||||
@@ -7107,6 +7402,8 @@ static void ui_vr_options_render(
|
||||
real_argb_color hint_color = { 1.0f, 0.85f, 0.85f, 0.85f };
|
||||
char value[48];
|
||||
short row;
|
||||
short page = ui_vr_options_page_of(ui_vr_options_globals.selection, NULL);
|
||||
short tab;
|
||||
boolean smooth = config_boolean("vr.smooth_turn") != 0;
|
||||
|
||||
if (!ui_vr_options_screen())
|
||||
@@ -7132,15 +7429,26 @@ static void ui_vr_options_render(
|
||||
ui_join_code_draw_quad(110, 62, 530, 396, 0xFF050A18);
|
||||
ui_join_code_draw_quad(110, 62, 530, 64, 0xFF3A6EA5);
|
||||
ui_join_code_draw_quad(110, 394, 530, 396, 0xFF3A6EA5);
|
||||
ui_join_code_draw_text(126, 514, 72, "VR OPTIONS", FALSE, 0, &title_color);
|
||||
for (row = 0; row < NUMBER_OF_VR_OPTIONS; row++)
|
||||
ui_join_code_draw_text(126, 330, 72, "VR OPTIONS", FALSE, 0, &title_color);
|
||||
/* (the pages' tabs, beside the title) */
|
||||
for (tab = 0; tab < NUMBER_OF_VR_OPTION_PAGES; tab++)
|
||||
{
|
||||
short x0 = (short)(330 + tab * 92);
|
||||
|
||||
if (tab == page)
|
||||
ui_join_code_draw_quad(x0, 68, (short)(x0 + 88), 88, 0x803A6EA5);
|
||||
ui_join_code_draw_text(x0, (short)(x0 + 88), 72, vr_option_page_names[tab], FALSE, 2,
|
||||
tab == page ? &text_color : &dim_color);
|
||||
}
|
||||
for (row = 0; row < ui_vr_options_page_rows(page); row++)
|
||||
{
|
||||
short y = (short)(VR_OPTIONS_TOP + row * VR_OPTIONS_ROW_HEIGHT);
|
||||
short option = vr_option_pages[page][row];
|
||||
char const *label = "";
|
||||
boolean unused = FALSE;
|
||||
|
||||
value[0] = 0;
|
||||
switch (row)
|
||||
switch (option)
|
||||
{
|
||||
case _vr_option_turning:
|
||||
label = "Turning";
|
||||
@@ -7167,6 +7475,15 @@ static void ui_vr_options_render(
|
||||
label = "Warthog steering wheel";
|
||||
snprintf(value, sizeof(value), "%s", config_boolean("vr.vehicle_wheel") ? "On" : "Off (stick)");
|
||||
break;
|
||||
case _vr_option_handedness:
|
||||
label = "Weapon hand";
|
||||
snprintf(value, sizeof(value), "%s", ui_vr_left_handed() ? "Left" : "Right");
|
||||
break;
|
||||
case _vr_option_mirror_controls:
|
||||
label = "Mirrored controls";
|
||||
snprintf(value, sizeof(value), "%s", config_boolean("vr.mirror_controls") ? "Sticks, buttons" : "Off");
|
||||
unused = !ui_vr_left_handed();
|
||||
break;
|
||||
case _vr_option_laser:
|
||||
label = "Weapon laser";
|
||||
snprintf(value, sizeof(value), "%s", config_boolean("vr.laser") ? "On" : "Off");
|
||||
@@ -7179,6 +7496,47 @@ static void ui_vr_options_render(
|
||||
label = "Motion tracker";
|
||||
snprintf(value, sizeof(value), "%s", config_boolean("vr.motion_tracker_on_wrist") ? "On the wrist" : "In the HUD");
|
||||
break;
|
||||
case _vr_option_body:
|
||||
label = "Body";
|
||||
snprintf(value, sizeof(value), "%s", vr_body_names[ui_vr_options_body()]);
|
||||
break;
|
||||
case _vr_option_fingers:
|
||||
label = "Fingers";
|
||||
snprintf(value, sizeof(value), "%s", config_boolean("vr.fingers") ? "Tracked" : "Off");
|
||||
unused = ui_vr_options_body() == 2;
|
||||
break;
|
||||
case _vr_option_graphics:
|
||||
label = "Graphics";
|
||||
snprintf(value, sizeof(value), "%s", vr_graphics_names[ui_vr_options_graphics()]);
|
||||
break;
|
||||
case _vr_option_refresh_rate:
|
||||
{
|
||||
real rates[16], current;
|
||||
short chosen;
|
||||
short count = ui_vr_options_refresh_rates(rates, &chosen, ¤t);
|
||||
|
||||
label = "Refresh rate";
|
||||
if (count > 0)
|
||||
snprintf(value, sizeof(value), "%.0f Hz", rates[chosen]);
|
||||
else if (current > 1.0f)
|
||||
snprintf(value, sizeof(value), "%.0f Hz (system)", current);
|
||||
else
|
||||
snprintf(value, sizeof(value), "System's");
|
||||
unused = count <= 1;
|
||||
break;
|
||||
}
|
||||
case _vr_option_resolution:
|
||||
{
|
||||
real scale = vr_resolution_scales[ui_vr_options_nearest((real)config_real("vr.resolution_scale"),
|
||||
vr_resolution_scales, NUMBEROF(vr_resolution_scales))];
|
||||
|
||||
label = "Resolution";
|
||||
if (fabs(scale - halo_vr_started_resolution_scale()) > 0.01f)
|
||||
snprintf(value, sizeof(value), "%.0f%% (restart)", scale * 100.0f);
|
||||
else
|
||||
snprintf(value, sizeof(value), "%.0f%%", scale * 100.0f);
|
||||
break;
|
||||
}
|
||||
case _vr_option_controls:
|
||||
label = "Controller buttons...";
|
||||
break;
|
||||
@@ -7187,7 +7545,7 @@ static void ui_vr_options_render(
|
||||
"Quit Halo? Once more to quit" : "Quit Halo";
|
||||
break;
|
||||
}
|
||||
if (row == ui_vr_options_globals.selection)
|
||||
if (option == ui_vr_options_globals.selection)
|
||||
ui_join_code_draw_quad(118, (short)(y - 4), 522, (short)(y + VR_OPTIONS_ROW_HEIGHT - 6), 0x803A6EA5);
|
||||
ui_join_code_draw_text(128, 330, y, label, FALSE, 0, unused ? &dim_color : &text_color);
|
||||
if (value[0])
|
||||
@@ -7198,6 +7556,20 @@ static void ui_vr_options_render(
|
||||
ui_join_code_draw_text(330, 512, y, shown, FALSE, 1, unused ? &dim_color : &text_color);
|
||||
}
|
||||
}
|
||||
if (page == 1)
|
||||
{
|
||||
/* (what the graphics rows do) */
|
||||
ui_join_code_draw_text(128, 512, 230, "Graphics: Medium and Low leave effects out (shadows,", FALSE, 0,
|
||||
&dim_color);
|
||||
ui_join_code_draw_text(128, 512, 248, "reflections, lens flares; grass, weather...) for a steadier", FALSE,
|
||||
0, &dim_color);
|
||||
ui_join_code_draw_text(128, 512, 266, "frame; Max lights models for each pixel. The refresh rate", FALSE, 0,
|
||||
&dim_color);
|
||||
ui_join_code_draw_text(128, 512, 284, "changes at once where the headset lets it (on the Steam", FALSE, 0,
|
||||
&dim_color);
|
||||
ui_join_code_draw_text(128, 512, 302, "Frame, in SteamOS); the resolution after a restart.", FALSE, 0,
|
||||
&dim_color);
|
||||
}
|
||||
ui_join_code_draw_text(126, 514, VR_OPTIONS_FOOTER + 8, "%a-button Change %b-button Done", TRUE, 0, &text_color);
|
||||
}
|
||||
|
||||
|
||||
@@ -258,6 +258,187 @@ static struct profile_section render_model_section = { "render_model", NONE, TRU
|
||||
|
||||
/* ---------- private code */
|
||||
|
||||
#ifdef HALO_ANDROID
|
||||
/* ---------- VR: the first-person hands' skin
|
||||
|
||||
The VR arms bend the first-person hands model's arms far from where its
|
||||
animations have them; a triangle whose corners hang from bones that move
|
||||
apart (an arm's, the skeleton's root's) stretches across. Read from the
|
||||
part's own buffers, as drawn. */
|
||||
|
||||
void platform_log(char const *format, ...);
|
||||
char *tag_get_name(long tag_index);
|
||||
boolean rasterizer_model_buffer_data(struct vertex_buffer const *vertices, struct triangle_buffer const *triangles,
|
||||
void const **vertex_data, void const **index_data);
|
||||
|
||||
/* the part's vertices and indices, if they are of the kinds read here */
|
||||
static boolean model_vr_part_buffers(
|
||||
struct model_geometry_part const *part,
|
||||
struct model_vertex_compressed const **vertices,
|
||||
word const **indices)
|
||||
{
|
||||
if (!rasterizer_model_buffer_data(&part->vertex_buffer, &part->triangle_buffer,
|
||||
(void const **)vertices, (void const **)indices))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
return *vertices && *indices && part->vertex_buffer.count > 0 && part->vertex_buffer.count <= 65535 &&
|
||||
part->triangle_buffer.count > 0 && part->triangle_buffer.count <= 65535 &&
|
||||
part->vertex_buffer.type == _rasterizer_vertex_type_model_compressed &&
|
||||
(part->triangle_buffer.type == _triangle_buffer_type_precompiled_strip ||
|
||||
part->triangle_buffer.type == _triangle_buffer_type_triangles);
|
||||
}
|
||||
|
||||
/* a triangle's corners (from the strip, or the list) and the nodes they
|
||||
hang from (bits, a node more than a trace of weight); FALSE if malformed */
|
||||
static boolean model_vr_triangle(
|
||||
struct model_geometry_part const *part,
|
||||
struct model_vertex_compressed const *vertices,
|
||||
word const *indices,
|
||||
long triangle_index,
|
||||
short node_count,
|
||||
word corners[3],
|
||||
unsigned long long *nodes)
|
||||
{
|
||||
short corner;
|
||||
|
||||
*nodes = 0;
|
||||
for (corner = 0; corner < 3; corner++)
|
||||
{
|
||||
long at = part->triangle_buffer.type == _triangle_buffer_type_triangles ?
|
||||
triangle_index * 3 + corner : triangle_index + corner;
|
||||
struct model_vertex_compressed const *vertex;
|
||||
real weight;
|
||||
short influence;
|
||||
|
||||
corners[corner] = indices[at];
|
||||
if (corners[corner] >= part->vertex_buffer.count)
|
||||
return FALSE;
|
||||
vertex = &vertices[corners[corner]];
|
||||
/* (the weight is a signed short's fraction, as rasterizer_debug_model_vertices reads it) */
|
||||
weight = (real)vertex->node_weight / 32767.0f;
|
||||
if (weight < 0.0f || weight > 1.0f)
|
||||
return FALSE;
|
||||
for (influence = 0; influence < 2; influence++)
|
||||
{
|
||||
short node = (short)(vertex->nodes[influence] / 3);
|
||||
real amount = influence ? 1.0f - weight : weight;
|
||||
|
||||
if (amount <= 0.001f)
|
||||
continue;
|
||||
if (vertex->nodes[influence] % 3 || node >= node_count || node >= 64)
|
||||
return FALSE;
|
||||
*nodes |= 1ULL << node;
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* once a model: how many triangles hang from each node, and how many of
|
||||
those from its root too (debug's logcat; first_person_weapons.c, for the
|
||||
hands) */
|
||||
void model_vr_log_skin(
|
||||
long model_index)
|
||||
{
|
||||
static long logged[8] = { NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE };
|
||||
struct model *model;
|
||||
long triangles[64];
|
||||
long with_root[64];
|
||||
long total = 0;
|
||||
short slot;
|
||||
short node_index;
|
||||
short region_index;
|
||||
short geometry_seen[MAXIMUM_REGIONS_PER_MODEL];
|
||||
short geometry_seen_count = 0;
|
||||
boolean readable = TRUE;
|
||||
|
||||
for (slot = 0; slot < NUMBEROF(logged); slot++)
|
||||
{
|
||||
if (logged[slot] == model_index)
|
||||
return;
|
||||
if (logged[slot] == NONE)
|
||||
break;
|
||||
}
|
||||
if (slot == NUMBEROF(logged) || model_index == NONE)
|
||||
return;
|
||||
logged[slot] = model_index;
|
||||
model = model_definition_get(model_index);
|
||||
if (model->nodes.count <= 0 || model->nodes.count > 64)
|
||||
return;
|
||||
csmemset(triangles, 0, sizeof(triangles));
|
||||
csmemset(with_root, 0, sizeof(with_root));
|
||||
/* (each region's first permutation, in the finest detail, as drawn) */
|
||||
for (region_index = 0; region_index < model->regions.count && region_index < MAXIMUM_REGIONS_PER_MODEL;
|
||||
region_index++)
|
||||
{
|
||||
struct model_region *region = TAG_BLOCK_GET_ELEMENT(&model->regions, region_index, struct model_region);
|
||||
struct model_region_permutation *permutation;
|
||||
short geometry_index;
|
||||
short seen;
|
||||
struct model_geometry *geometry;
|
||||
short part_index;
|
||||
|
||||
if (region->permutations.count <= 0)
|
||||
continue;
|
||||
permutation = TAG_BLOCK_GET_ELEMENT(®ion->permutations, 0, struct model_region_permutation);
|
||||
geometry_index = permutation->geometry_indices[NUMBER_OF_DETAIL_LEVELS_PER_MODEL - 1];
|
||||
for (seen = 0; seen < geometry_seen_count && geometry_seen[seen] != geometry_index; seen++);
|
||||
if (geometry_index == NONE || seen < geometry_seen_count)
|
||||
continue;
|
||||
geometry_seen[geometry_seen_count++] = geometry_index;
|
||||
geometry = TAG_BLOCK_GET_ELEMENT(&model->geometries, geometry_index, struct model_geometry);
|
||||
for (part_index = 0; part_index < geometry->parts.count; part_index++)
|
||||
{
|
||||
struct model_geometry_part *part = TAG_BLOCK_GET_ELEMENT(&geometry->parts, part_index,
|
||||
struct model_geometry_part);
|
||||
struct model_vertex_compressed const *vertices;
|
||||
word const *indices;
|
||||
long triangle_index;
|
||||
|
||||
if (!model_vr_part_buffers(part, &vertices, &indices))
|
||||
{
|
||||
readable = FALSE;
|
||||
continue;
|
||||
}
|
||||
for (triangle_index = 0; triangle_index < part->triangle_buffer.count; triangle_index++)
|
||||
{
|
||||
word corners[3];
|
||||
unsigned long long nodes;
|
||||
|
||||
if (!model_vr_triangle(part, vertices, indices, triangle_index, model->nodes.count, corners, &nodes))
|
||||
{
|
||||
readable = FALSE;
|
||||
break;
|
||||
}
|
||||
if (corners[0] == corners[1] || corners[1] == corners[2] || corners[0] == corners[2])
|
||||
continue;
|
||||
total++;
|
||||
for (node_index = 0; node_index < model->nodes.count; node_index++)
|
||||
{
|
||||
if (!(nodes & (1ULL << node_index)))
|
||||
continue;
|
||||
triangles[node_index]++;
|
||||
if (nodes & 1)
|
||||
with_root[node_index]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
platform_log("VR skin of %s: %ld triangles%s; %ld hang from its root '%s'", tag_get_name(model_index), total,
|
||||
readable ? "" : " (some parts unreadable)", triangles[0],
|
||||
TAG_BLOCK_GET_ELEMENT(&model->nodes, 0, struct model_node)->name);
|
||||
for (node_index = 1; node_index < model->nodes.count; node_index++)
|
||||
{
|
||||
if (triangles[node_index])
|
||||
{
|
||||
platform_log("VR skin: node %d '%s': %ld triangles, %ld with the root", node_index,
|
||||
TAG_BLOCK_GET_ELEMENT(&model->nodes, node_index, struct model_node)->name, triangles[node_index],
|
||||
with_root[node_index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void render_model_parts(
|
||||
struct model const *model,
|
||||
char const *region_permutation_indices,
|
||||
|
||||
@@ -1227,6 +1227,11 @@ void rasterizer_set_model_lighting(
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef HALO_ANDROID
|
||||
/* port/linux/src/d3d8_gl.c */
|
||||
void d3d8_set_mirrored_skinning(int mirrored);
|
||||
#endif
|
||||
|
||||
void rasterizer_set_model_skinning(
|
||||
struct render_skinning const *skinning)
|
||||
{
|
||||
@@ -1263,6 +1268,21 @@ void rasterizer_set_model_skinning(
|
||||
constants[2][2] = scale * matrix->up.k;
|
||||
constants[2][3] = matrix->position.z;
|
||||
}
|
||||
#ifdef HALO_ANDROID
|
||||
/* port: node matrices that mirror (VR, the left hand's first-person
|
||||
weapon: first_person_weapons.c) turn the triangles' winding over while
|
||||
they are drawn (after HaloCE-Quest-VR, CC0); _rasterizer_model_end and
|
||||
rasterizer_transparent_geometry_group_draw end it */
|
||||
{
|
||||
real_matrix4x3 const *m = &skinning->node_matrices[0];
|
||||
real determinant =
|
||||
m->forward.i * (m->left.j * m->up.k - m->left.k * m->up.j) -
|
||||
m->forward.j * (m->left.i * m->up.k - m->left.k * m->up.i) +
|
||||
m->forward.k * (m->left.i * m->up.j - m->left.j * m->up.i);
|
||||
|
||||
d3d8_set_mirrored_skinning(determinant < 0.0f);
|
||||
}
|
||||
#endif
|
||||
D3DDevice_SetVertexShaderConstant(
|
||||
-36,
|
||||
vsh_constants__nodematrices,
|
||||
|
||||
@@ -73,6 +73,30 @@ symbols in this file:
|
||||
|
||||
/* ---------- public code */
|
||||
|
||||
#ifdef HALO_ANDROID
|
||||
/* VR (models.c, after HaloCE-Quest-VR, CC0): a model's buffers as the
|
||||
renderer has them, for reading (the native ports keep them in memory) */
|
||||
boolean rasterizer_model_buffer_data(
|
||||
struct vertex_buffer const *vertices,
|
||||
struct triangle_buffer const *triangles,
|
||||
void const **vertex_data,
|
||||
void const **index_data)
|
||||
{
|
||||
byte *vertex_bytes = NULL;
|
||||
byte *index_bytes = NULL;
|
||||
|
||||
if (!vertices->hardware_format || !triangles->hardware_format)
|
||||
return FALSE;
|
||||
IDirect3DVertexBuffer8_Lock((D3DVertexBuffer *)vertices->hardware_format, 0, 0, &vertex_bytes, D3DLOCK_READONLY);
|
||||
IDirect3DIndexBuffer8_Lock((D3DIndexBuffer *)triangles->hardware_format, 0, 0, &index_bytes, D3DLOCK_READONLY);
|
||||
IDirect3DVertexBuffer8_Unlock((D3DVertexBuffer *)vertices->hardware_format);
|
||||
IDirect3DIndexBuffer8_Unlock((D3DIndexBuffer *)triangles->hardware_format);
|
||||
*vertex_data = vertex_bytes;
|
||||
*index_data = index_bytes;
|
||||
return vertex_bytes != NULL && index_bytes != NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
void __stdcall code_00158450(
|
||||
void *resource,
|
||||
void *data)
|
||||
|
||||
@@ -596,9 +596,18 @@ void _rasterizer_models_end(
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef HALO_ANDROID
|
||||
/* port/linux/src/d3d8_gl.c: the model's mirroring (rasterizer_xbox.c) ends
|
||||
with it */
|
||||
void d3d8_set_mirrored_skinning(int mirrored);
|
||||
#endif
|
||||
|
||||
void _rasterizer_model_end(
|
||||
void)
|
||||
{
|
||||
#ifdef HALO_ANDROID
|
||||
d3d8_set_mirrored_skinning(0);
|
||||
#endif
|
||||
if (rasterizer_debug_options.draw_models)
|
||||
{
|
||||
match_assert(
|
||||
|
||||
@@ -970,9 +970,83 @@ real_vector4d *offset_vector4d(
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifdef HALO_ANDROID
|
||||
/* port: a group's draw, its mirroring (rasterizer_set_model_skinning) put
|
||||
back after it to what it was: a group drawn in the middle of a model's
|
||||
(not queued) leaves the model's own */
|
||||
int d3d8_mirrored_skinning(void);
|
||||
void d3d8_set_mirrored_skinning(int mirrored);
|
||||
|
||||
/* port: a numeric display drawn mirrored (rasterizer_set_model_skinning:
|
||||
the left hand's first-person weapon in VR; the assault rifle's ammo counter
|
||||
and compass) reads the right way round. Its geometry stays mirrored with the
|
||||
weapon (its vertices hang from the gun's nodes as well as the display's);
|
||||
instead each digit's quad shows its bitmap turned over in u
|
||||
(rasterizer_numeric_mirror_u), and as the quads' order across the display is
|
||||
turned over too, the quad of the n-th digit from the right shows the n-th
|
||||
from the left. The digits: as many as the counter's largest value needs,
|
||||
that value itself wrapping to 0 (the compass's whole turn). FALSE, the
|
||||
digit untouched, when not mirrored. Only for a first map in the model's own
|
||||
u and v (2d, not in screen space), which rasterizer_numeric_mirror_u turns. */
|
||||
static boolean rasterizer_numeric_mirrored_digit(
|
||||
real value,
|
||||
short counter_limit,
|
||||
short frame_count,
|
||||
short digit_index,
|
||||
short *bitmap_sequence_index)
|
||||
{
|
||||
short counter_value;
|
||||
short digit_count;
|
||||
short place;
|
||||
long power;
|
||||
|
||||
if (!d3d8_mirrored_skinning() || frame_count < 2)
|
||||
return FALSE;
|
||||
|
||||
counter_value = PIN(fast_ftol(floor(counter_limit*value + 0.5f)), 0, counter_limit);
|
||||
digit_count = 1;
|
||||
for (power = frame_count; power < counter_limit; power *= frame_count)
|
||||
digit_count++;
|
||||
place = digit_count - 1 - digit_index;
|
||||
if (place < 0)
|
||||
place = digit_index;
|
||||
while (place-- > 0)
|
||||
counter_value /= frame_count;
|
||||
*bitmap_sequence_index = counter_value%frame_count;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* the first map's u turned over (1-u: each digit's quad spans its bitmap) */
|
||||
static void rasterizer_numeric_mirror_u(
|
||||
real u_transform[4])
|
||||
{
|
||||
u_transform[0] = -u_transform[0];
|
||||
u_transform[1] = -u_transform[1];
|
||||
u_transform[2] = -u_transform[2];
|
||||
u_transform[3] = 1.0f - u_transform[3];
|
||||
}
|
||||
|
||||
static void rasterizer_transparent_geometry_group_draw_unmirrored(
|
||||
struct transparent_geometry_group *group,
|
||||
boolean dirty);
|
||||
|
||||
void rasterizer_transparent_geometry_group_draw(
|
||||
struct transparent_geometry_group *group,
|
||||
boolean dirty)
|
||||
{
|
||||
int mirrored = d3d8_mirrored_skinning();
|
||||
|
||||
rasterizer_transparent_geometry_group_draw_unmirrored(group, dirty);
|
||||
d3d8_set_mirrored_skinning(mirrored);
|
||||
}
|
||||
|
||||
static void rasterizer_transparent_geometry_group_draw_unmirrored(
|
||||
#else
|
||||
void rasterizer_transparent_geometry_group_draw(
|
||||
#endif
|
||||
struct transparent_geometry_group *group,
|
||||
boolean dirty)
|
||||
{
|
||||
boolean success = TRUE;
|
||||
boolean draw_active_camouflage_groups2 = FALSE;
|
||||
@@ -1813,6 +1887,9 @@ void rasterizer_transparent_geometry_group_draw(
|
||||
short map_index;
|
||||
short stage_index;
|
||||
long result;
|
||||
#ifdef HALO_ANDROID
|
||||
boolean numeric_mirrored = FALSE;
|
||||
#endif
|
||||
|
||||
for (layer_index = 0;
|
||||
layer_index < shader_transparent_generic->generic.extra_layers.count;
|
||||
@@ -1872,6 +1949,18 @@ void rasterizer_transparent_geometry_group_draw(
|
||||
counter_value /= frame_count;
|
||||
|
||||
bitmap_sequence_index = counter_value%frame_count;
|
||||
#ifdef HALO_ANDROID
|
||||
numeric_mirrored =
|
||||
shader_transparent_generic->generic.type==_shader_transparent_generic_type_2d_map &&
|
||||
!TEST_FLAG(shader_transparent_generic->generic.flags,
|
||||
_shader_transparent_flag_first_map_is_in_screenspace_bit) &&
|
||||
rasterizer_numeric_mirrored_digit(
|
||||
group->animation->values[function_index],
|
||||
counter_limit,
|
||||
frame_count,
|
||||
group->shader_permutation_index,
|
||||
&bitmap_sequence_index);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (shader_transparent_generic->generic.maps.count > 0)
|
||||
@@ -2034,6 +2123,10 @@ void rasterizer_transparent_geometry_group_draw(
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HALO_ANDROID
|
||||
if (numeric_mirrored)
|
||||
rasterizer_numeric_mirror_u(vsh_constants__texanim[0]);
|
||||
#endif
|
||||
result = IDirect3DDevice8_SetVertexShaderConstant(
|
||||
global_d3d_device,
|
||||
VSH_CONSTANTS__TEXANIM_OFFSET,
|
||||
@@ -2300,6 +2393,9 @@ void rasterizer_transparent_geometry_group_draw(
|
||||
short layer_index;
|
||||
short map_index;
|
||||
long result;
|
||||
#ifdef HALO_ANDROID
|
||||
boolean numeric_mirrored = FALSE;
|
||||
#endif
|
||||
|
||||
/* BUG (preserved for exact matching): January never advances layer_index, so the
|
||||
* loop redraws extra layer 0 for as long as the block is non-empty (the bytes push
|
||||
@@ -2374,6 +2470,18 @@ void rasterizer_transparent_geometry_group_draw(
|
||||
counter_value /= frame_count;
|
||||
|
||||
bitmap_sequence_index = counter_value%frame_count;
|
||||
#ifdef HALO_ANDROID
|
||||
numeric_mirrored =
|
||||
shader_transparent_chicago->chicago.type==_shader_transparent_chicago_type_2d_map &&
|
||||
!TEST_FLAG(shader_transparent_chicago->chicago.flags,
|
||||
_shader_transparent_flag_first_map_is_in_screenspace_bit) &&
|
||||
rasterizer_numeric_mirrored_digit(
|
||||
group->animation->values[function_index],
|
||||
counter_limit,
|
||||
frame_count,
|
||||
group->shader_permutation_index,
|
||||
&bitmap_sequence_index);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2548,6 +2656,10 @@ void rasterizer_transparent_geometry_group_draw(
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HALO_ANDROID
|
||||
if (numeric_mirrored)
|
||||
rasterizer_numeric_mirror_u(vsh_constants__texanim[0]);
|
||||
#endif
|
||||
result = IDirect3DDevice8_SetVertexShaderConstant(
|
||||
global_d3d_device,
|
||||
VSH_CONSTANTS__TEXANIM_OFFSET,
|
||||
|
||||
@@ -92,6 +92,9 @@ symbols in this file:
|
||||
#include "physics/collisions.h"
|
||||
#include "units/units.h"
|
||||
#include "halo_vr.h"
|
||||
|
||||
/* port/linux/game/vr_graphics.c */
|
||||
void vr_graphics_update(void);
|
||||
#endif
|
||||
#include "main/main.h"
|
||||
#include "structures/structures.h"
|
||||
@@ -948,6 +951,11 @@ void render_frame(
|
||||
|
||||
render.frame_index++;
|
||||
render.time_delta_since_tick_sec = time_delta_since_tick_sec;
|
||||
#ifdef HALO_ANDROID
|
||||
/* VR: the graphics preset's effects (port/linux/game/vr_graphics.c) */
|
||||
if (halo_vr_active())
|
||||
vr_graphics_update();
|
||||
#endif
|
||||
memset(¶meters, 0, sizeof(parameters));
|
||||
/* continuous between ticks (render_interpolation.c) */
|
||||
parameters.game_time_sec = render_interpolation_game_time_sec(game_time_get());
|
||||
|
||||
Reference in new issue
Block a user