mirror of
https://github.com/Cesarus85/ringfall-mjolnir-vr.git
synced 2026-10-18 01:00:26 +02:00
Compare commits
18
Commits
No files matched your search
@@ -25,19 +25,73 @@ 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 |
|
||||
| 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 | |
|
||||
| 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 | |
|
||||
| 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 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 +101,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).
|
||||
+125
-15
@@ -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;
|
||||
@@ -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 };
|
||||
@@ -881,6 +980,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)
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
@@ -110,16 +110,46 @@ where the controller aims */
|
||||
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 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 left hand's pose;
|
||||
- the left 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 aims along both hands; */
|
||||
int halo_vr_body_frame(short local_player_index, float head[3], float forward[3]);
|
||||
int halo_vr_body_frame(short local_player_index, float head[3], float neck[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_two_hand(short local_player_index, const float foregrip[3], const float reference[3]);
|
||||
/* - the fingers of a hand (0 the left, 1 the right), 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;
|
||||
@@ -262,6 +292,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);
|
||||
|
||||
@@ -198,6 +198,18 @@ static const struct config_setting config_settings[] =
|
||||
{ "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"
|
||||
"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." },
|
||||
|
||||
+235
-15
@@ -77,6 +77,9 @@ static struct
|
||||
unsigned long long phase_us[2][HALO_VR_PHASE_COUNT + 1];
|
||||
int phase_frames[2];
|
||||
int phase_late[2];
|
||||
/* (parts of the drawing, halo_vr_timing_end: their time and count) */
|
||||
unsigned long long timing_us[HALO_VR_TIMING_COUNT];
|
||||
int timing_count[HALO_VR_TIMING_COUNT];
|
||||
|
||||
/* the view's turning, gathered for halo_vr_look_delta */
|
||||
float look_yaw;
|
||||
@@ -228,6 +231,20 @@ static struct
|
||||
float lean_limit;
|
||||
float crouch_limit;
|
||||
float rise_limit;
|
||||
/* the fingers (fingers_update, vr.fingers): each hand's thumb, index,
|
||||
middle and the last two, curled 0 (open) to 1, and how fast */
|
||||
int fingers;
|
||||
int arm_ik; /* vr.arm_ik: the free arm bends at the elbow */
|
||||
int body_mode; /* vr.body: HALO_VR_BODY_* */
|
||||
float finger_curl[HALO_XR_HAND_COUNT][4];
|
||||
float finger_velocity[HALO_XR_HAND_COUNT][4];
|
||||
unsigned long long fingers_last_us;
|
||||
/* the torso's heading (torso_update): in LOCAL space, so that turning
|
||||
the body by the stick turns it too; and the head's, level */
|
||||
float torso_yaw;
|
||||
int torso_valid;
|
||||
unsigned long long torso_last_us;
|
||||
float head_level_yaw;
|
||||
} vr;
|
||||
|
||||
/* d3d8_gl.c */
|
||||
@@ -451,6 +468,14 @@ static void vr_read_live_settings(void)
|
||||
vr.laser = config_boolean("vr.laser");
|
||||
vr.vehicle_wheel = config_boolean("vr.vehicle_wheel");
|
||||
vr.comfort_vignette = (float)config_real("vr.comfort_vignette");
|
||||
vr.fingers = config_boolean("vr.fingers");
|
||||
vr.arm_ik = config_boolean("vr.arm_ik");
|
||||
{
|
||||
const char *body = config_string("vr.body");
|
||||
|
||||
vr.body_mode = !strcmp(body, "hands") ? HALO_VR_BODY_HANDS :
|
||||
!strcmp(body, "off") ? HALO_VR_BODY_OFF : HALO_VR_BODY_ARMS;
|
||||
}
|
||||
}
|
||||
|
||||
void halo_vr_initialize(void)
|
||||
@@ -568,6 +593,24 @@ void halo_vr_phase(int phase, int ticks)
|
||||
vr.frame_ticks = ticks;
|
||||
}
|
||||
|
||||
unsigned long long halo_vr_timing_start(void)
|
||||
{
|
||||
return vr.active && vr.log_performance ? now_us() : 0;
|
||||
}
|
||||
|
||||
unsigned long halo_vr_frame_serial(void)
|
||||
{
|
||||
return vr.active ? vr.frame_number : 0;
|
||||
}
|
||||
|
||||
void halo_vr_timing_end(int part, unsigned long long start)
|
||||
{
|
||||
if (!start || part < 0 || part >= HALO_VR_TIMING_COUNT)
|
||||
return;
|
||||
vr.timing_us[part] += now_us() - start;
|
||||
vr.timing_count[part]++;
|
||||
}
|
||||
|
||||
/* the frame's parts, from the marks halo_vr_phase left (a part not marked
|
||||
this frame: none) */
|
||||
static void performance_phases(unsigned long long now)
|
||||
@@ -633,6 +676,15 @@ static void performance_report(int stereo)
|
||||
memset(vr.phase_us, 0, sizeof(vr.phase_us));
|
||||
memset(vr.phase_frames, 0, sizeof(vr.phase_frames));
|
||||
memset(vr.phase_late, 0, sizeof(vr.phase_late));
|
||||
/* (the first-person pose: built how often a frame and in how long,
|
||||
the arms' part of it, and how often taken as built) */
|
||||
platform_log("VR: first person: %.2f ms a frame in %.2f builds (arms %.3f ms), %.2f kept",
|
||||
vr.timing_us[HALO_VR_TIMING_FIRST_PERSON] * 1e-3f / frames,
|
||||
vr.timing_count[HALO_VR_TIMING_FIRST_PERSON] / frames,
|
||||
vr.timing_us[HALO_VR_TIMING_ARMS] * 1e-3f / frames,
|
||||
vr.timing_count[HALO_VR_TIMING_FIRST_PERSON_KEPT] / frames);
|
||||
memset(vr.timing_us, 0, sizeof(vr.timing_us));
|
||||
memset(vr.timing_count, 0, sizeof(vr.timing_count));
|
||||
}
|
||||
vr.report_start_us = now;
|
||||
vr.report_frames = vr.report_stereo_frames = 0;
|
||||
@@ -883,6 +935,154 @@ static void melee_update(void)
|
||||
}
|
||||
}
|
||||
|
||||
/* The fingers, from where they rest on the controller (after
|
||||
HaloCE-Quest-VR, CC0, its vr_frame.c): the thumb curled while it touches
|
||||
the stick, a button or the rest, half curled where the controller does not
|
||||
tell; the index finger with the trigger, a little curled on it; the others
|
||||
with the grip; holding a grenade, the hand closes round it. Each eased
|
||||
towards that by a critically damped spring (no overshoot however the
|
||||
frames' lengths vary). */
|
||||
static void fingers_update(void)
|
||||
{
|
||||
unsigned long long now = now_us();
|
||||
float seconds = vr.fingers_last_us ? (float)(now - vr.fingers_last_us) * 1e-6f : 0.0f;
|
||||
int hand;
|
||||
|
||||
vr.fingers_last_us = now;
|
||||
if (seconds > 0.05f)
|
||||
seconds = 0.05f;
|
||||
for (hand = 0; hand < HALO_XR_HAND_COUNT; hand++)
|
||||
{
|
||||
const struct halo_xr_hand *state = &vr.frame.hands[hand];
|
||||
float target[4];
|
||||
int finger;
|
||||
|
||||
if (!(state->buttons & HALO_XR_BUTTON_TOUCH_KNOWN))
|
||||
target[0] = 0.4f;
|
||||
else
|
||||
target[0] = (state->buttons & HALO_XR_BUTTON_THUMB_TOUCH) ? 0.75f : 0.0f;
|
||||
target[1] = fmaxf(state->trigger, (state->buttons & HALO_XR_BUTTON_INDEX_TOUCH) ? 0.22f : 0.0f);
|
||||
target[2] = target[3] = state->squeeze;
|
||||
if (hand == HALO_XR_HAND_LEFT && vr.grenade_held)
|
||||
{
|
||||
for (finger = 0; finger < 4; finger++)
|
||||
target[finger] = 0.8f;
|
||||
}
|
||||
for (finger = 0; finger < 4; finger++)
|
||||
{
|
||||
float omega = finger == 0 ? 20.0f : 26.0f;
|
||||
float x = vr.finger_curl[hand][finger] - target[finger];
|
||||
float v = vr.finger_velocity[hand][finger];
|
||||
float j = v + omega * x, decay = expf(-omega * seconds);
|
||||
|
||||
vr.finger_curl[hand][finger] = fmaxf(0.0f, fminf(1.0f, target[finger] + (x + j * seconds) * decay));
|
||||
vr.finger_velocity[hand][finger] = (v - omega * j * seconds) * decay;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* The torso's heading (after HaloCE-Quest-VR, CC0, its vr_body_heading):
|
||||
the head's, steady at any pitch (looking down, the top of the head points
|
||||
forward; up, the back of it), drawn halfway towards the hands when both
|
||||
are ahead of it, by at most 45 degrees (turning the body turns both,
|
||||
looking about only the head). The torso follows it beyond a 15 degree cone,
|
||||
the turn eased (10 a second, whatever the frame rate). Kept in LOCAL
|
||||
space, which the stick's turns carry (body_yaw): the shoulders turn with
|
||||
them at once. */
|
||||
#define TORSO_CONE (15.0f * PI / 180.0f)
|
||||
|
||||
static void torso_update(void)
|
||||
{
|
||||
static const float local_forward[3] = { 0.0f, 0.0f, -1.0f };
|
||||
static const float local_up[3] = { 0.0f, 1.0f, 0.0f };
|
||||
const struct halo_xr_hand *hands = vr.frame.hands;
|
||||
unsigned long long now = now_us();
|
||||
float seconds = vr.torso_valid ? (float)(now - vr.torso_last_us) * 1e-6f : -1.0f;
|
||||
float forward[3], up[3], level[3], wanted;
|
||||
int axis;
|
||||
|
||||
vr.torso_last_us = now;
|
||||
if (!(vr.frame.head_flags & HALO_XR_POSE_VALID))
|
||||
{
|
||||
vr.torso_valid = 0;
|
||||
return;
|
||||
}
|
||||
quaternion_rotate(vr.frame.head.orientation, local_forward, forward);
|
||||
quaternion_rotate(vr.frame.head.orientation, local_up, up);
|
||||
for (axis = 0; axis < 3; axis++)
|
||||
level[axis] = forward[axis] - forward[1] * up[axis];
|
||||
level[1] = 0.0f;
|
||||
if (normalize3(level) > 1e-3f)
|
||||
{
|
||||
wanted = atan2f(-level[0], -level[2]);
|
||||
}
|
||||
else
|
||||
{
|
||||
float pitch;
|
||||
|
||||
head_angles(&wanted, &pitch);
|
||||
}
|
||||
vr.head_level_yaw = wanted;
|
||||
if (hands[0].active && (hands[0].flags & HALO_XR_POSE_VALID) && hands[1].active &&
|
||||
(hands[1].flags & HALO_XR_POSE_VALID))
|
||||
{
|
||||
float x = (hands[0].grip.position[0] + hands[1].grip.position[0]) * 0.5f - vr.frame.head.position[0];
|
||||
float z = (hands[0].grip.position[2] + hands[1].grip.position[2]) * 0.5f - vr.frame.head.position[2];
|
||||
|
||||
if (sqrtf(x * x + z * z) > 0.12f)
|
||||
{
|
||||
float difference = wrap_angle(atan2f(-x, -z) - wanted);
|
||||
|
||||
if (fabsf(difference) < 1.745f)
|
||||
wanted += 0.5f * fmaxf(-0.785f, fminf(0.785f, difference));
|
||||
}
|
||||
}
|
||||
if (!vr.torso_valid || seconds < 0.0f || seconds > 0.25f || vr.recentre_frames > 0)
|
||||
{
|
||||
vr.torso_yaw = wrap_angle(wanted);
|
||||
}
|
||||
else
|
||||
{
|
||||
float difference = wrap_angle(wanted - vr.torso_yaw);
|
||||
|
||||
if (fabsf(difference) > TORSO_CONE)
|
||||
{
|
||||
float turn = difference - copysignf(TORSO_CONE, difference);
|
||||
|
||||
vr.torso_yaw = wrap_angle(vr.torso_yaw + turn * (1.0f - expf(-10.0f * fminf(seconds, 0.05f))));
|
||||
}
|
||||
}
|
||||
vr.torso_valid = 1;
|
||||
}
|
||||
|
||||
int halo_vr_free_arm_bends(void)
|
||||
{
|
||||
return vr.active && vr.arm_ik;
|
||||
}
|
||||
|
||||
int halo_vr_body_mode(void)
|
||||
{
|
||||
return vr.active ? vr.body_mode : HALO_VR_BODY_ARMS;
|
||||
}
|
||||
|
||||
double halo_vr_pose_time(void)
|
||||
{
|
||||
return (double)vr.frame_start_us * 1e-6;
|
||||
}
|
||||
|
||||
int halo_vr_finger_curls(short local_player_index, int hand, float curls[4])
|
||||
{
|
||||
const struct halo_xr_hand *state;
|
||||
|
||||
if (!vr.active || !vr.fingers || local_player_index != 0 || hand < 0 || hand >= HALO_XR_HAND_COUNT)
|
||||
return 0;
|
||||
state = &vr.frame.hands[hand];
|
||||
if (!state->active || !(state->flags & HALO_XR_POSE_VALID))
|
||||
return 0;
|
||||
memcpy(curls, vr.finger_curl[hand], sizeof(vr.finger_curl[hand]));
|
||||
return HALO_VR_FINGERS_CURLED | ((state->buttons & HALO_XR_BUTTON_TOUCH_KNOWN) ? HALO_VR_FINGERS_TOUCH : 0);
|
||||
}
|
||||
|
||||
int halo_vr_melee(void)
|
||||
{
|
||||
return vr.active && now_us() < vr.melee_until_us;
|
||||
@@ -1299,6 +1499,8 @@ void halo_vr_frame_begin(void)
|
||||
look_update();
|
||||
grenade_update();
|
||||
melee_update();
|
||||
fingers_update();
|
||||
torso_update();
|
||||
}
|
||||
|
||||
void halo_vr_present(unsigned int back_buffer_texture)
|
||||
@@ -2031,27 +2233,34 @@ void halo_vr_debug_look(short local_player_index, const float unit_looking[3], i
|
||||
head[0], head[1], head[2], unit_looking[0], unit_looking[1], unit_looking[2], in_vehicle ? ", in a vehicle" : "");
|
||||
}
|
||||
|
||||
/* the body: where the head is (world units) and its heading, flat */
|
||||
int halo_vr_body_frame(short local_player_index, float head[3], float forward[3])
|
||||
/* the body: where the head is (world units), the neck it turns about (a
|
||||
little behind the eyes, at their height) and the torso's heading, flat */
|
||||
#define NECK_BEHIND_EYES 0.09f
|
||||
|
||||
int halo_vr_body_frame(short local_player_index, float head[3], float neck[3], float forward[3])
|
||||
{
|
||||
float offset[3], look[3];
|
||||
float offset[3], local[3], back[3];
|
||||
int axis;
|
||||
|
||||
if (local_player_index != 0 || !halo_vr_stereo_frame() || !(vr.frame.head_flags & HALO_XR_POSE_VALID))
|
||||
if (local_player_index != 0 || !halo_vr_stereo_frame() || !(vr.frame.head_flags & HALO_XR_POSE_VALID) ||
|
||||
!vr.torso_valid)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
tracked_offset(vr.frame.head.position, offset);
|
||||
for (axis = 0; axis < 3; axis++)
|
||||
head[axis] = vr.game_camera[axis] + offset[axis] / METRES_PER_WORLD_UNIT;
|
||||
if (!halo_vr_look_vector(local_player_index, look))
|
||||
return 0;
|
||||
forward[0] = look[0];
|
||||
forward[1] = look[1];
|
||||
local[0] = sinf(vr.head_level_yaw) * NECK_BEHIND_EYES;
|
||||
local[1] = 0.0f;
|
||||
local[2] = cosf(vr.head_level_yaw) * NECK_BEHIND_EYES;
|
||||
local_to_game(local, vr.body_yaw, back);
|
||||
for (axis = 0; axis < 3; axis++)
|
||||
neck[axis] = head[axis] + back[axis] / METRES_PER_WORLD_UNIT;
|
||||
local[0] = -sinf(vr.torso_yaw);
|
||||
local[2] = -cosf(vr.torso_yaw);
|
||||
local_to_game(local, vr.body_yaw, forward);
|
||||
forward[2] = 0.0f;
|
||||
if (normalize3(forward) < 1e-3f)
|
||||
{
|
||||
forward[0] = cosf(vr.body_yaw);
|
||||
forward[1] = sinf(vr.body_yaw);
|
||||
}
|
||||
normalize3(forward);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -2579,6 +2788,9 @@ int halo_vr_controllers(struct halo_xr_hand hands[HALO_XR_HAND_COUNT], int *menu
|
||||
|
||||
int halo_vr_controller_layout(void) { return 0; }
|
||||
int halo_vr_active(void) { return 0; }
|
||||
unsigned long long halo_vr_timing_start(void) { return 0; }
|
||||
unsigned long halo_vr_frame_serial(void) { return 0; }
|
||||
void halo_vr_timing_end(int part, unsigned long long start) { (void)part; (void)start; }
|
||||
int halo_vr_stereo_frame(void) { return 0; }
|
||||
int halo_vr_stereo_player(short local_player_index, int cinematic) { (void)local_player_index; (void)cinematic; return 0; }
|
||||
void halo_vr_eye_camera(int eye, const float game_position[3], float position[3], float forward[3], float up[3],
|
||||
@@ -2634,6 +2846,14 @@ void halo_vr_debug_look(short local_player_index, const float unit_looking[3], i
|
||||
(void)local_player_index; (void)unit_looking; (void)in_vehicle;
|
||||
}
|
||||
int halo_vr_melee(void) { return 0; }
|
||||
int halo_vr_finger_curls(short local_player_index, int hand, float curls[4])
|
||||
{
|
||||
(void)local_player_index; (void)hand; (void)curls;
|
||||
return 0;
|
||||
}
|
||||
int halo_vr_free_arm_bends(void) { return 0; }
|
||||
int halo_vr_body_mode(void) { return HALO_VR_BODY_ARMS; }
|
||||
double halo_vr_pose_time(void) { return 0.0; }
|
||||
void halo_vr_set_seat(short local_player_index, int seat, int armed, float vehicle_yaw)
|
||||
{
|
||||
(void)local_player_index; (void)seat; (void)armed; (void)vehicle_yaw;
|
||||
@@ -2662,9 +2882,9 @@ int halo_vr_grenade_throw(short local_player_index, float origin[3], float direc
|
||||
}
|
||||
float halo_vr_melee_strength(void) { return 0.0f; }
|
||||
int halo_vr_look_delta(float *yaw, float *pitch) { *yaw = *pitch = 0.0f; return 0; }
|
||||
int halo_vr_body_frame(short local_player_index, float head[3], float forward[3])
|
||||
int halo_vr_body_frame(short local_player_index, float head[3], float neck[3], float forward[3])
|
||||
{
|
||||
(void)local_player_index; (void)head; (void)forward;
|
||||
(void)local_player_index; (void)head; (void)neck; (void)forward;
|
||||
return 0;
|
||||
}
|
||||
int halo_vr_left_hand(short local_player_index, float position[3], float forward[3], float up[3])
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,6 +468,26 @@ long first_person_weapon_get_local_index(
|
||||
}
|
||||
|
||||
#ifdef HALO_ANDROID
|
||||
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 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
|
||||
@@ -636,6 +657,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 +762,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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1168,9 +1221,10 @@ 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. */
|
||||
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 left controller, its fingers with it. */
|
||||
|
||||
#define VR_METRES_PER_WORLD_UNIT 3.048f
|
||||
|
||||
@@ -1186,6 +1240,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 +1251,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 +1289,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 +1307,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 +1328,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 +1344,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 +1395,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1570,6 +1493,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 +1566,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 +1651,15 @@ 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 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 +1676,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 +1700,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))
|
||||
{
|
||||
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 +1721,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 +1740,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2122,8 +2189,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 +2202,7 @@ static void first_person_weapon_build_node_matrices(
|
||||
(short)animation_graph->nodes.count,
|
||||
camera,
|
||||
first_person_weapon->state);
|
||||
halo_vr_timing_end(HALO_VR_TIMING_ARMS, timing);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -6658,12 +6659,15 @@ enum
|
||||
_vr_option_laser,
|
||||
_vr_option_crosshair,
|
||||
_vr_option_motion_tracker,
|
||||
_vr_option_body,
|
||||
_vr_option_fingers,
|
||||
_vr_option_controls,
|
||||
_vr_option_quit,
|
||||
NUMBER_OF_VR_OPTIONS,
|
||||
|
||||
VR_OPTIONS_TOP = 98,
|
||||
VR_OPTIONS_ROW_HEIGHT = 26,
|
||||
/* (twelve rows between the title and the footer, in the box) */
|
||||
VR_OPTIONS_TOP = 94,
|
||||
VR_OPTIONS_ROW_HEIGHT = 22,
|
||||
VR_OPTIONS_FOOTER = 362,
|
||||
|
||||
_vr_controls_on_foot = 0,
|
||||
@@ -6688,6 +6692,23 @@ 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" };
|
||||
|
||||
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)
|
||||
@@ -6753,6 +6774,11 @@ static void ui_vr_options_change(
|
||||
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_controls:
|
||||
ui_vr_options_globals.controls = TRUE;
|
||||
ui_vr_options_globals.page = _vr_controls_on_foot;
|
||||
@@ -7179,6 +7205,15 @@ 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_controls:
|
||||
label = "Controller buttons...";
|
||||
break;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user