Compare commits

..
Author SHA1 Message Date
Stefan MaierandClaude Opus 5.5 fbe48a869f VR roadmap: steps 1 and 3a done, in main
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 18:53:21 +02:00
Stefan MaierandClaude Opus 5.5 206eb0b52b VR roadmap: the Steam Frame's tests done
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 18:46:55 +02:00
Stefan MaierandClaude Opus 5.5 1c66715a19 VR roadmap: the Quest's tests done
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 18:34:15 +02:00
Stefan MaierandClaude Opus 5.5 87f336df78 VR roadmap: 3a tried on the Quest
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 18:30:49 +02:00
Stefan MaierandClaude Opus 5.5 0a86e406c8 VR arms: the hand's roll leads the elbow; the free arm bent by default
The elbow could not be lifted out to the side: it bent only towards the
fixed pole (down, out, back). An elbow lifted out rolls the controller's
top in, so the pole now leans towards the hand's underside (the weapon's
for the right arm, the free left controller's for the left; the hand on
the foregrip keeps the fixed pole). With the free arm solved before its
hand is turned it looked right on the Quest: vr.arm_ik defaults to true.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 17:56:47 +02:00
Stefan MaierandClaude Opus 5.5 c09b40a2ef VR arms: the free left arm solved first, its hand turned after
As HaloCE-Quest-VR does it: the free arm is bent to the controller's grip
from the animated pose (the hand free to swing with it), and only then is
the hand turned to the controller and the roll shared along the forearm.
Turning the hand first left the solve to swing it on, which stretched the
arm's skin like a rubber band (Quest, 2026-10-06). vr.arm_ik stays off by
default until it is seen right.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 17:54:15 +02:00
Stefan MaierandClaude Opus 5.5 1e2006d06c VR arms: the elbow bends in the arm's plane, the shoulders higher
Second Quest test: with the elbow out to the side the right elbow's skin
turned inwards: the shortest turn of the solve left the upper arm's hinge
where it was. vr_align_elbow now rolls the upper arm about its length so
the animation's bend lies in the plane the arm bends in, and the upper
arm takes no share of the hand's roll any more. The open sleeve end could
still be seen: the shoulders are 5 cm higher (20 cm below the neck).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 16:47:45 +02:00
Stefan MaierandClaude Opus 5.5 e69e087602 VR arms: the free arm rigid by default, the shoulders further back
First Quest test: the free left arm's skin stretched like a rubber band
with the new elbow IK, so vr.arm_ik now defaults to false (the arm's shape
as before, pointed at the shoulder); and the open end of the right
sleeve came into view looking down to the side: the shoulders now stand
4 cm behind the neck instead of 3 cm in front of it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 15:59:41 +02:00
Stefan MaierandClaude Opus 5.5 87a38e2895 VR roadmap: the pending headset tests, performance among them
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 11:45:44 +02:00
Stefan MaierandClaude Opus 5.5 788ab74a6b VR roadmap: step 3a built in feature/vr-arms
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 11:37:07 +02:00
Stefan MaierandClaude Opus 5.5 6d2dcfaaec VR arms 3a.6: vr.body (arms, hands, off) and vr.fingers in the VR options
- vr.body = "arms" (default): arms and hands as before; "hands": the arms'
  bones shrunk to a point 3.5 cm behind each wrist along the hand's line
  so the cuff closes behind the hand (vr_hide_arm, after HaloCE-Quest-VR's
  vr_hide_forearms, CC0), no arm IK solved; "off": the hands model is not
  drawn (one model draw less an eye) and nothing of the arms posed; the
  weapon, its aim with both hands, barrel, laser and crosshair stay.
- vr.c reads vr.body with the live settings (halo_vr_body_mode).
- The VR options panel gets "Body" (Arms and hands / Hands only / Weapon
  only) and "Fingers" (Tracked / Off) rows; its twelve rows are 22
  pixels apart (were 26) so the panel keeps its box.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 11:35:07 +02:00
Stefan MaierandClaude Opus 5.5 98e848de39 VR arms 3a.5: the fingers posed
- vr_arms.c: vr_finger_nodes, vr_pose_finger (after HaloCE-Quest-VR, CC0,
  nearly verbatim): each finger's three joints found by name under the
  hand, posed by absolute directions in the palm's frame (a little bent
  at rest, a fist at full curl; the thumb up the handle or across the
  palm), each segment's subtree turned with its length kept. Its touch
  of the world (rays per finger) is left out for the Frame's processor.
- The free left hand takes its controller's curls (halo_vr_finger_curls:
  index from the trigger, the others from the grip, the thumb from where
  it rests; round a held grenade). On the foregrip the hand keeps the
  weapon's pose.
- The hand on the weapon keeps its grip; only its index finger is lifted
  part way off the trigger while the controller says it is (touch known
  and the finger off it), never where touch is unknown.
- Joints found once per skeleton; the cost is a few dozen node turns.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 11:33:12 +02:00
Stefan MaierandClaude Opus 5.5 7754532683 VR arms 3a.4: the IK core from HaloCE-Quest-VR, the free left arm bent
- port/linux/game/vr_arms.c: vr_solve_arm and vr_distribute_arm_twist,
  after HaloCE-Quest-VR (CC0), nearly verbatim: the shoulder reaches 8 cm
  and the arm stretches up to 18% out of reach (further, the shoulder
  follows the held hand, as this port had it, so the arm stays whole),
  bends no further than 145 degrees, the elbow's swing eased per arm
  (vr_limb_history), the hand's roll shared by forearm (85%) and upper
  arm (25%) within 110 degrees, continuous across half a turn. The
  skeleton is read through the graph's index; the nodes under each node
  come from one pass a frame (vr_skeleton_build) rather than walking
  ancestors per node; time is the headset's frame's (halo_vr_pose_time).
- Both arms use it; the right hand and a left hand on the foregrip stay
  where the weapon puts them (hand_stays), so shots, barrel and laser do
  not move.
- The free left hand: its wrist and fingers are set on the controller as
  before, and the arm now bends to it from the shoulder instead of being
  carried rigidly. The ribbons of e377e4dc did not come from skin on the
  root (3a.0: none hangs there), so no triangle filter is needed; the
  rigid arm stays behind vr.arm_ik = false, read live.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 11:31:16 +02:00
Stefan MaierandClaude Opus 5.5 19b0bcb42e VR arms 3a.3: the shoulders follow the torso, not the head
- vr.c keeps the torso's heading in LOCAL space (torso_update, after
  HaloCE-Quest-VR's vr_body_heading, CC0): the head's heading, steady at
  any pitch, drawn halfway towards the hands when both are ahead (at most
  45 degrees), followed beyond a 15 degree cone, eased at 10 a second;
  reset on a recentre or a gap. In LOCAL space the stick's snap and
  smooth turns carry it at once (HaloCE-Quest-VR's "arm IK lags as the
  body turns").
- halo_vr_body_frame gives the torso's heading and the neck the head
  turns about (9 cm behind the eyes); the shoulders hang from the neck,
  square to the torso, so turning the head no longer swings them (as the
  head faces the torso, where they were before).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 11:26:24 +02:00
Stefan MaierandClaude Opus 5.5 f30dd50317 VR arms 3a.2: the fingers' input (touch, trigger, grip)
- host_xr: two actions, the thumb's touch (stick, buttons, thumb rest,
  the Frame's D-pad and View) and the index finger's (trigger), bound
  for the Touch, Index and Steam Frame profiles (paths after
  HaloCE-Quest-VR, CC0, and Valve's "Steam Frame Input" page) as optional
  bindings: a profile refusing them is suggested again without them.
- ABI: HALO_XR_BUTTON_THUMB_TOUCH, _INDEX_TOUCH and _TOUCH_KNOWN (the
  runtime gives the thumb's touch at all) in the buttons' free bits; the
  structures keep their size (android_abi_check: none unsafe).
- vr.c: each hand's thumb, index, middle and last two fingers, curled
  from those (the thumb half curled where touch is unknown, the hand
  round a held grenade), eased by HaloCE-Quest-VR's critically damped
  spring; halo_vr_finger_curls() hands them to the game; vr.fingers
  (default on) turns it off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 11:24:58 +02:00
Stefan MaierandClaude Opus 5.5 07a15190b4 host_xr: suggest() takes up to 64 bindings, says when it cannot, and retries
suggest() copied a profile's bindings into an array of 32 and silently
dropped the rest; the Steam Frame's profile already has 26, so a few
more would have lost bindings without a word. It takes 64 now and logs
an error past that, logs the runtime's result when a profile is
refused, and suggest_bindings() takes optional bindings besides: should
the runtime refuse the profile with them (one unknown path refuses the
whole suggestion), it is suggested again without them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 11:23:11 +02:00
Stefan MaierandClaude Opus 5.5 f254be4b45 VR arms 3a.1: the first-person pose built once a frame, not per eye
Posed from the camera at the right controller, the first-person weapon
and the arms are the same for both eyes and the scope's view, yet were
built (animation, interpolation, arm IK) for each: twice a frame on
SteamVR, three times zoomed in. The pose is kept for the rest of the
headset's frame (halo_vr_frame_serial) once built from the controller,
for the same weapon; posed from an eye's own camera (the hand not
tracked), each eye still builds its own. vr.log_performance counts the
builds and the poses kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 11:22:45 +02:00
Stefan MaierandClaude Opus 5.5 a6350d207a VR arms 3a.0: time the first-person pose, log the hands' skin
- vr.log_performance reports how long the first-person pose takes a frame
  (its animation and the arms), how often it is built, and the arms' part
  of it (halo_vr_timing_start/_end).
- Once per hands model it logs how many triangles hang from each node and
  how many of those from the skeleton's root ('frame bone24'), read from
  the part's own buffers (rasterizer_model_buffer_data, after
  HaloCE-Quest-VR, CC0). In the emulator the cyborg's hands have none on
  the root: the ribbons of e377e4dc did not come from root skin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 11:22:08 +02:00
13 changed files with 1964 additions and 218 deletions

No files matched your search

+86 -5
View File
@@ -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
View File
@@ -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)
+6
View File
@@ -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 */
+875
View File
@@ -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(&centre, &knuckles, &back);
along = vr_unit_vector(&back);
}
if (!along)
{
vr_point_minus(&matrices[bones[_vr_arm_fore]].position, &centre, &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
+83
View File
@@ -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
+50 -3
View File
@@ -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);
+12
View File
@@ -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
View File
@@ -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])
+2 -2
View File
@@ -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
+248 -176
View File
@@ -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
}
+37 -2
View File
@@ -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;
+181
View File
@@ -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(&region->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)