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,
|
not by the number in the name — the numbering is not consistent between families,
|
||||||
but the hierarchy always runs knuckle to fingertip.
|
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
|
**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
|
rotation, a grip offset, a curl axis, a weapon size, a wrist twist — is a guess
|
||||||
skeleton. Derive it from the skeleton, or hand it to a solver that already knows
|
about one skeleton. Derive it from the skeleton, or hand it to a solver that
|
||||||
the answer.
|
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
|
## 6. Some sources are not salvageable, and the gate should say so
|
||||||
|
|
||||||
|
|||||||
@@ -1174,6 +1174,8 @@ class ShooterPoseModifier extends SkeletonModifier3D:
|
|||||||
var fingers: Dictionary = {}
|
var fingers: Dictionary = {}
|
||||||
var _fing: Dictionary = {} # same, resolved to bone indices
|
var _fing: Dictionary = {} # same, resolved to bone indices
|
||||||
var _curl: Dictionary = {} # "L"/"R" -> curl axis in the rest frame
|
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:
|
func _resolve() -> void:
|
||||||
var skel := get_skeleton()
|
var skel := get_skeleton()
|
||||||
@@ -1255,7 +1257,19 @@ class ShooterPoseModifier extends SkeletonModifier3D:
|
|||||||
palm -= along * palm.dot(along)
|
palm -= along * palm.dot(along)
|
||||||
if palm.length() < 0.0001:
|
if palm.length() < 0.0001:
|
||||||
continue
|
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"):
|
if OS.has_environment("HAND_DEBUG"):
|
||||||
print("HANDS fingers=%d resolved=%d curl=%s" % [
|
print("HANDS fingers=%d resolved=%d curl=%s" % [
|
||||||
fingers.size(), _fing.size(), _curl])
|
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_HIP := Vector3(0.45, -0.90, -0.10)
|
||||||
const POLE_L_ADS := Vector3(0.30, -0.95, -0.05)
|
const POLE_L_ADS := Vector3(0.30, -0.95, -0.05)
|
||||||
const R_HAND_TWIST := 0.0
|
const R_HAND_TWIST := 0.0
|
||||||
const L_HAND_TWIST := 0.5
|
|
||||||
|
|
||||||
func _apply_rifle_hold(skel: Skeleton3D) -> void:
|
func _apply_rifle_hold(skel: Skeleton3D) -> void:
|
||||||
var ua_r: int = _idx.get("DEF-upper_arm.R", -1)
|
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
|
arc = Quaternion(aim_dir, roll + R_HAND_TWIST) * arc
|
||||||
_set_global_rot(skel, hand, g_fa_r, arc, _hold_r)
|
_set_global_rot(skel, hand, g_fa_r, arc, _hold_r)
|
||||||
|
|
||||||
# 6. Support hand: palm wraps the handguard, following its forearm.
|
# 6. Support hand: WRAP the handguard.
|
||||||
if _hold_l > 0.001 and g_fa_l != Quaternion.IDENTITY and ua_l >= 0:
|
#
|
||||||
|
# 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 hand_l: int = _idx.get("DEF-hand.L", -1)
|
||||||
var fa_l_idx: int = _idx.get("DEF-forearm.L", -1)
|
if hand_l >= 0:
|
||||||
if hand_l >= 0 and fa_l_idx >= 0:
|
# -aim_dir so the hand comes at the handguard from the body side
|
||||||
var fa_o := skel.get_bone_global_rest(fa_l_idx).origin
|
# rather than reaching over it backwards.
|
||||||
var hand_o := skel.get_bone_global_rest(hand_l).origin
|
var want := Basis(gun_up.cross(-aim_dir).normalized(), gun_up,
|
||||||
var fa_rest_dir := (hand_o - fa_o).normalized()
|
-aim_dir)
|
||||||
var hand_rest_q := skel.get_bone_global_rest(hand_l).basis.get_rotation_quaternion()
|
var rest: Basis = _hand_frame["L"]
|
||||||
# Point the palm along the barrel so the fingers close over it.
|
var g_hand := (want * rest.inverse()).get_rotation_quaternion() \
|
||||||
var g_hand := Quaternion(aim_dir, L_HAND_TWIST) \
|
* skel.get_bone_global_rest(hand_l).basis.get_rotation_quaternion()
|
||||||
* Quaternion(fa_rest_dir, aim_dir) * hand_rest_q
|
_set_global_rot(skel, hand_l, g_fa_l, g_hand.normalized(), _hold_l)
|
||||||
_set_global_rot(skel, hand_l, g_fa_l, g_hand, _hold_l)
|
|
||||||
|
|
||||||
# How far each segment of a finger closes, knuckle -> tip, in radians.
|
# How far each segment of a finger closes, knuckle -> tip, in radians.
|
||||||
#
|
#
|
||||||
|
|||||||
Reference in New Issue
Block a user