diff --git a/port/linux/game/vr_arms.c b/port/linux/game/vr_arms.c index 9958c2d1..ed5165ff 100644 --- a/port/linux/game/vr_arms.c +++ b/port/linux/game/vr_arms.c @@ -523,4 +523,230 @@ void vr_distribute_arm_twist( } } +/* ---------- 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); +} + #endif diff --git a/port/linux/game/vr_arms.h b/port/linux/game/vr_arms.h index 565f773f..00abbcf4 100644 --- a/port/linux/game/vr_arms.h +++ b/port/linux/game/vr_arms.h @@ -56,6 +56,21 @@ animated) shared by the forearm and the upper arm */ 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); + #endif #endif diff --git a/port/linux/include/halo_vr.h b/port/linux/include/halo_vr.h index ffea2416..a7a9a5c8 100644 --- a/port/linux/include/halo_vr.h +++ b/port/linux/include/halo_vr.h @@ -125,7 +125,14 @@ int halo_vr_two_hand(short local_player_index, const float foregrip[3], const fl /* - 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; */ + 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, diff --git a/port/linux/src/vr.c b/port/linux/src/vr.c index f0059504..208cb5dd 100644 --- a/port/linux/src/vr.c +++ b/port/linux/src/vr.c @@ -1068,7 +1068,7 @@ int halo_vr_finger_curls(short local_player_index, int hand, float curls[4]) if (!state->active || !(state->flags & HALO_XR_POSE_VALID)) return 0; memcpy(curls, vr.finger_curl[hand], sizeof(vr.finger_curl[hand])); - return 1; + return HALO_VR_FINGERS_CURLED | ((state->buttons & HALO_XR_BUTTON_TOUCH_KNOWN) ? HALO_VR_FINGERS_TOUCH : 0); } int halo_vr_melee(void) diff --git a/source/interface/first_person_weapons.c b/source/interface/first_person_weapons.c index 91ff649e..62684e15 100644 --- a/source/interface/first_person_weapons.c +++ b/source/interface/first_person_weapons.c @@ -1236,6 +1236,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; @@ -1243,7 +1247,7 @@ 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; @@ -1485,6 +1489,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, @@ -1515,6 +1571,7 @@ static void first_person_weapon_vr_arms( boolean gripping; boolean left_free; boolean rigid = !halo_vr_free_arm_bends(); + float hand_position[3], hand_forward[3], hand_up[3]; if (vr_arm_nodes.graph_index != graph_index) { @@ -1637,8 +1694,6 @@ static void first_person_weapon_vr_arms( } 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(&vr_arm_skeleton, node_matrices, rigid, hand_position, hand_forward, hand_up); @@ -1685,6 +1740,7 @@ static void first_person_weapon_vr_arms( 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); } #endif