Feat/outline thickness and tp weapon hold #22
@@ -225,10 +225,33 @@ Blender-export `IndexFinger1_L`. Segments are ordered by DEPTH BELOW THE HAND,
|
||||
not by the number in the name — the numbering is not consistent between families,
|
||||
but the hierarchy always runs knuckle to fingertip.
|
||||
|
||||
**The support hand came out upside down**, because its orientation was built as
|
||||
a shortest arc plus a constant twist: align the hand's forearm line to the barrel
|
||||
(`Quaternion(fa_rest_dir, aim_dir)`), then add 0.5 rad of roll. A shortest arc
|
||||
says NOTHING about roll — it is the minimal rotation between two directions — so
|
||||
the entire roll came from that constant, and a constant is right only for the rig
|
||||
it was tuned on.
|
||||
|
||||
Orienting a hand onto something it grips is a FRAME-TO-FRAME problem, and framing
|
||||
it that way leaves nothing free to guess:
|
||||
|
||||
```
|
||||
curl axis must lie along the object's axis, or the fingers close ACROSS the
|
||||
handguard instead of around it
|
||||
palm must face the object — up, for a hand supporting from underneath
|
||||
along falls out of the other two (palm x curl)
|
||||
```
|
||||
|
||||
Map the hand's rest anatomical frame onto that target and the roll is determined,
|
||||
not chosen. Verified on both the Rigify-named mannequin and the VRoid-named
|
||||
Kiyoko: fingers wrap the handguard from below, over the top.
|
||||
|
||||
**Rule:** anything expressed as a constant in a rig's local frame — a mount
|
||||
rotation, a grip offset, a curl axis, a weapon size — is a guess about one
|
||||
skeleton. Derive it from the skeleton, or hand it to a solver that already knows
|
||||
the answer.
|
||||
rotation, a grip offset, a curl axis, a weapon size, a wrist twist — is a guess
|
||||
about one skeleton. Derive it from the skeleton, or hand it to a solver that
|
||||
already knows the answer. And when a rotation needs a specific ROLL, never build
|
||||
it from a shortest arc: that operator has no opinion about roll, so whatever you
|
||||
add afterwards is doing all the work.
|
||||
|
||||
## 6. Some sources are not salvageable, and the gate should say so
|
||||
|
||||
|
||||
@@ -1174,6 +1174,8 @@ class ShooterPoseModifier extends SkeletonModifier3D:
|
||||
var fingers: Dictionary = {}
|
||||
var _fing: Dictionary = {} # same, resolved to bone indices
|
||||
var _curl: Dictionary = {} # "L"/"R" -> curl axis in the rest frame
|
||||
## "L"/"R" -> Basis(along, palm, curl), the hand's anatomy in the rest pose.
|
||||
var _hand_frame: Dictionary = {}
|
||||
|
||||
func _resolve() -> void:
|
||||
var skel := get_skeleton()
|
||||
@@ -1255,7 +1257,19 @@ class ShooterPoseModifier extends SkeletonModifier3D:
|
||||
palm -= along * palm.dot(along)
|
||||
if palm.length() < 0.0001:
|
||||
continue
|
||||
_curl[side] = along.cross(palm.normalized()).normalized()
|
||||
palm = palm.normalized()
|
||||
var curl := along.cross(palm).normalized()
|
||||
_curl[side] = curl
|
||||
# The whole hand as an ANATOMICAL FRAME, not just the curl axis.
|
||||
#
|
||||
# Orienting a hand onto something it is gripping is a frame-to-frame
|
||||
# problem: the fingers have to wrap AROUND the object, so the curl
|
||||
# axis must lie along the object's axis, and the palm has to face it.
|
||||
# Both are answered at once by mapping this frame onto the target's.
|
||||
# Building the rotation from a shortest arc plus a constant twist —
|
||||
# which is what was here — leaves the roll about the barrel
|
||||
# unspecified, so the support hand landed upside down.
|
||||
_hand_frame[side] = Basis(along, palm, curl)
|
||||
if OS.has_environment("HAND_DEBUG"):
|
||||
print("HANDS fingers=%d resolved=%d curl=%s" % [
|
||||
fingers.size(), _fing.size(), _curl])
|
||||
@@ -1467,7 +1481,6 @@ class ShooterPoseModifier extends SkeletonModifier3D:
|
||||
const POLE_L_HIP := Vector3(0.45, -0.90, -0.10)
|
||||
const POLE_L_ADS := Vector3(0.30, -0.95, -0.05)
|
||||
const R_HAND_TWIST := 0.0
|
||||
const L_HAND_TWIST := 0.5
|
||||
|
||||
func _apply_rifle_hold(skel: Skeleton3D) -> void:
|
||||
var ua_r: int = _idx.get("DEF-upper_arm.R", -1)
|
||||
@@ -1570,19 +1583,34 @@ class ShooterPoseModifier extends SkeletonModifier3D:
|
||||
arc = Quaternion(aim_dir, roll + R_HAND_TWIST) * arc
|
||||
_set_global_rot(skel, hand, g_fa_r, arc, _hold_r)
|
||||
|
||||
# 6. Support hand: palm wraps the handguard, following its forearm.
|
||||
if _hold_l > 0.001 and g_fa_l != Quaternion.IDENTITY and ua_l >= 0:
|
||||
# 6. Support hand: WRAP the handguard.
|
||||
#
|
||||
# Built as a frame, not as an arc plus a twist. A hand gripping a
|
||||
# cylinder has its fingers curling AROUND that cylinder, which fixes two
|
||||
# things at once and leaves nothing free:
|
||||
#
|
||||
# curl axis must lie along the BARREL, or the fingers close across
|
||||
# the handguard instead of around it
|
||||
# palm must face the barrel — up, for a hand supporting from
|
||||
# underneath
|
||||
#
|
||||
# The old version aligned the hand's forearm line to the barrel with a
|
||||
# shortest arc and then added a constant 0.5 rad twist. A shortest arc
|
||||
# says nothing about roll, so the roll came entirely from that constant,
|
||||
# and a constant is only ever right for the one rig it was tuned on — the
|
||||
# support hand came out upside down.
|
||||
if _hold_l > 0.001 and g_fa_l != Quaternion.IDENTITY and ua_l >= 0 \
|
||||
and _hand_frame.has("L"):
|
||||
var hand_l: int = _idx.get("DEF-hand.L", -1)
|
||||
var fa_l_idx: int = _idx.get("DEF-forearm.L", -1)
|
||||
if hand_l >= 0 and fa_l_idx >= 0:
|
||||
var fa_o := skel.get_bone_global_rest(fa_l_idx).origin
|
||||
var hand_o := skel.get_bone_global_rest(hand_l).origin
|
||||
var fa_rest_dir := (hand_o - fa_o).normalized()
|
||||
var hand_rest_q := skel.get_bone_global_rest(hand_l).basis.get_rotation_quaternion()
|
||||
# Point the palm along the barrel so the fingers close over it.
|
||||
var g_hand := Quaternion(aim_dir, L_HAND_TWIST) \
|
||||
* Quaternion(fa_rest_dir, aim_dir) * hand_rest_q
|
||||
_set_global_rot(skel, hand_l, g_fa_l, g_hand, _hold_l)
|
||||
if hand_l >= 0:
|
||||
# -aim_dir so the hand comes at the handguard from the body side
|
||||
# rather than reaching over it backwards.
|
||||
var want := Basis(gun_up.cross(-aim_dir).normalized(), gun_up,
|
||||
-aim_dir)
|
||||
var rest: Basis = _hand_frame["L"]
|
||||
var g_hand := (want * rest.inverse()).get_rotation_quaternion() \
|
||||
* skel.get_bone_global_rest(hand_l).basis.get_rotation_quaternion()
|
||||
_set_global_rot(skel, hand_l, g_fa_l, g_hand.normalized(), _hold_l)
|
||||
|
||||
# How far each segment of a finger closes, knuckle -> tip, in radians.
|
||||
#
|
||||
|
||||
Reference in New Issue
Block a user