Feat/outline thickness and tp weapon hold #22
@@ -166,35 +166,69 @@ a bad fit degrades to "stiff" rather than to "torn".
|
||||
|
||||
---
|
||||
|
||||
## 5. A single-piece rig still needs its arms checked
|
||||
## 5. A weapon has to be scaled to the arm that holds it
|
||||
|
||||
**Symptom:** the gun is held, but not convincingly — the stock is not in the
|
||||
shoulder and the hands are not on the grip. **Hit:** mannequin.
|
||||
**Confidence: uncertain** — its hand bone resolves, so this is not #2.
|
||||
**Symptom:** hands flat and open, both fists bunched together at the grip, the
|
||||
stock nowhere near the shoulder. **Hit:** every model.
|
||||
**Confidence: certain** — measured and fixed.
|
||||
|
||||
The rifle hold places the weapon from the shoulder joint and the aim direction,
|
||||
then solves both arms onto the grip and foregrip with two-bone IK. It is tuned
|
||||
against proportions like Taila's. A rig with different arm lengths, a different
|
||||
rest pose (A-pose vs T-pose) or a different bone roll will put the hands
|
||||
somewhere plausible for the maths and wrong for the eye.
|
||||
Three separate causes, all of them "a constant where a measurement belonged".
|
||||
|
||||
**The real cause turned out to be #2's sibling, and it affected every model.**
|
||||
`set_weapon` mounted the gun with a fixed `rotation_degrees = (0, 90, -90)`. A
|
||||
bone attachment is expressed in the BONE's axes, and no two rigs agree on those,
|
||||
so a constant there mounts the weapon differently on every character.
|
||||
**The gun was mounted with a constant rotation.** `set_weapon` used
|
||||
`rotation_degrees = (0, 90, -90)`. A bone attachment is expressed in the BONE's
|
||||
axes and no two rigs agree on those, so one constant mounts the weapon
|
||||
differently on every character. It never needed to be right: the pose layer aims
|
||||
the gun by rotating the WRIST until the weapon's forward lies on the aim line, so
|
||||
handing it the IDENTITY means "forward is the hand bone's -Z" — true by
|
||||
construction on any rig — and the wrist absorbs the roll.
|
||||
|
||||
It does not need to be right at all. The pose layer aims the gun by rotating the
|
||||
WRIST until the weapon's forward axis lies on the aim line, so handing it the
|
||||
IDENTITY means "the gun's forward is the hand bone's -Z" — true by construction
|
||||
on any rig — and the wrist absorbs whatever roll that bone happens to have. The
|
||||
grip is placed at the bone's origin, so the gun sits *in* the hand instead of at
|
||||
a fixed offset from a differently-oriented bone.
|
||||
**The gun was full size on a stylised character.** The set is modelled at
|
||||
real-world scale (an M4 is 0.84 m butt to muzzle); these characters have 0.47 m
|
||||
arms against an adult 0.52. That puts the handguard 0.66 m from the support
|
||||
shoulder — 0.2 m beyond reach — so a loop slid the support hand back down the
|
||||
weapon until it fitted. On Taila a support offset authored at 0.35 m collapsed to
|
||||
**0.083 m**: two fists together at the grip, which reads as a two-handed pistol
|
||||
grip, not a rifle.
|
||||
|
||||
**Rule:** never express a mount as a constant in a bone's local frame. Either
|
||||
derive the frame from the skeleton, or hand the downstream solver an identity and
|
||||
let it do the work — it already knows where the gun must point.
|
||||
The fix is not a fixed scale factor. The binding constraint is the SUPPORT arm:
|
||||
its hand must reach `stock + fore` in front of the pocket, from a shoulder half a
|
||||
shoulder-width off the weapon axis. Solve that triangle for the largest gun whose
|
||||
handguard still lands inside the arm's reach. Taila and Kiyoko come out at
|
||||
different scales from the same code, both with the support hand at its full
|
||||
authored handguard distance and no sliding at all.
|
||||
|
||||
---
|
||||
Also give the slide-back loop a FLOOR. A slightly straight support arm looks far
|
||||
better than no handguard hold.
|
||||
|
||||
**Nothing posed the fingers.** Every hand was flat and open — the single loudest
|
||||
tell that a character is not really holding anything. Fingers are now closed by
|
||||
the pose layer, using an axis derived from each hand's OWN anatomy in the rest
|
||||
pose, because no two rigs agree on finger bone orientation:
|
||||
|
||||
```
|
||||
along wrist -> middle knuckle the length of the hand
|
||||
palm middle knuckle -> thumb tip across it; the thumb OPPOSES the
|
||||
fingers, so it is on the palm side by
|
||||
construction — a fact about hands, not
|
||||
a rig convention
|
||||
curl along x palm turning about this swings the fingers
|
||||
into the palm, not sideways
|
||||
```
|
||||
|
||||
The trigger hand's index finger gets a much shallower curl than the rest — it
|
||||
lies along the trigger. Curling it with the others is what makes a character look
|
||||
like they are squeezing a bar of soap.
|
||||
|
||||
Finger bones now resolve by role too (`rig_map.DIGITS`), across all three naming
|
||||
families met so far: Rigify `DEF-f_index.01.L`, VRoid `J_Bip_L_Index1`, and
|
||||
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.
|
||||
|
||||
**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.
|
||||
|
||||
## 6. Some sources are not salvageable, and the gate should say so
|
||||
|
||||
|
||||
@@ -13,20 +13,72 @@
|
||||
],
|
||||
"shin.R": "J_Bip_R_LowerLeg",
|
||||
"shin.L": "J_Bip_L_LowerLeg",
|
||||
"thigh.R": "J_Bip_R_UpperLeg",
|
||||
"thigh.L": "J_Bip_L_UpperLeg",
|
||||
"shoulder.L": "J_Bip_L_Shoulder",
|
||||
"thigh.R": "J_Bip_R_UpperLeg",
|
||||
"shoulder.R": "J_Bip_R_Shoulder",
|
||||
"forearm.R": "J_Bip_R_LowerArm",
|
||||
"shoulder.L": "J_Bip_L_Shoulder",
|
||||
"forearm.L": "J_Bip_L_LowerArm",
|
||||
"upper_arm.L": "J_Bip_L_UpperArm",
|
||||
"forearm.R": "J_Bip_R_LowerArm",
|
||||
"upper_arm.R": "J_Bip_R_UpperArm",
|
||||
"toe.L": "J_Bip_L_ToeBase",
|
||||
"upper_arm.L": "J_Bip_L_UpperArm",
|
||||
"toe.R": "J_Bip_R_ToeBase",
|
||||
"toe.L": "J_Bip_L_ToeBase",
|
||||
"foot.L": "J_Bip_L_Foot",
|
||||
"foot.R": "J_Bip_R_Foot",
|
||||
"hand.L": "J_Bip_L_Hand",
|
||||
"hand.R": "J_Bip_R_Hand"
|
||||
"hand.R": "J_Bip_R_Hand",
|
||||
"hand.L": "J_Bip_L_Hand"
|
||||
},
|
||||
"fingers": {
|
||||
"thumb.L": [
|
||||
"J_Bip_L_Thumb1",
|
||||
"J_Bip_L_Thumb2",
|
||||
"J_Bip_L_Thumb3"
|
||||
],
|
||||
"index.L": [
|
||||
"J_Bip_L_Index1",
|
||||
"J_Bip_L_Index2",
|
||||
"J_Bip_L_Index3"
|
||||
],
|
||||
"middle.L": [
|
||||
"J_Bip_L_Middle1",
|
||||
"J_Bip_L_Middle2",
|
||||
"J_Bip_L_Middle3"
|
||||
],
|
||||
"ring.L": [
|
||||
"J_Bip_L_Ring1",
|
||||
"J_Bip_L_Ring2",
|
||||
"J_Bip_L_Ring3"
|
||||
],
|
||||
"pinky.L": [
|
||||
"J_Bip_L_Little1",
|
||||
"J_Bip_L_Little2",
|
||||
"J_Bip_L_Little3"
|
||||
],
|
||||
"thumb.R": [
|
||||
"J_Bip_R_Thumb1",
|
||||
"J_Bip_R_Thumb2",
|
||||
"J_Bip_R_Thumb3"
|
||||
],
|
||||
"index.R": [
|
||||
"J_Bip_R_Index1",
|
||||
"J_Bip_R_Index2",
|
||||
"J_Bip_R_Index3"
|
||||
],
|
||||
"middle.R": [
|
||||
"J_Bip_R_Middle1",
|
||||
"J_Bip_R_Middle2",
|
||||
"J_Bip_R_Middle3"
|
||||
],
|
||||
"ring.R": [
|
||||
"J_Bip_R_Ring1",
|
||||
"J_Bip_R_Ring2",
|
||||
"J_Bip_R_Ring3"
|
||||
],
|
||||
"pinky.R": [
|
||||
"J_Bip_R_Little1",
|
||||
"J_Bip_R_Little2",
|
||||
"J_Bip_R_Little3"
|
||||
]
|
||||
},
|
||||
"chains": [
|
||||
{
|
||||
|
||||
Binary file not shown.
@@ -15,8 +15,8 @@
|
||||
"shoulder.L": "DEF-shoulder.L",
|
||||
"upper_arm.L": "DEF-upper_arm.L",
|
||||
"upper_arm.R": "DEF-upper_arm.R",
|
||||
"forearm.R": "DEF-forearm.R",
|
||||
"forearm.L": "DEF-forearm.L",
|
||||
"forearm.R": "DEF-forearm.R",
|
||||
"thigh.R": "DEF-thigh.R",
|
||||
"thigh.L": "DEF-thigh.L",
|
||||
"foot.R": "DEF-foot.R",
|
||||
@@ -28,6 +28,58 @@
|
||||
"toe.L": "DEF-toe.L",
|
||||
"toe.R": "DEF-toe.R"
|
||||
},
|
||||
"fingers": {
|
||||
"thumb.L": [
|
||||
"DEF-thumb.01.L",
|
||||
"DEF-thumb.02.L",
|
||||
"DEF-thumb.03.L"
|
||||
],
|
||||
"index.L": [
|
||||
"DEF-f_index.01.L",
|
||||
"DEF-f_index.02.L",
|
||||
"DEF-f_index.03.L"
|
||||
],
|
||||
"middle.L": [
|
||||
"DEF-f_middle.01.L",
|
||||
"DEF-f_middle.02.L",
|
||||
"DEF-f_middle.03.L"
|
||||
],
|
||||
"ring.L": [
|
||||
"DEF-f_ring.01.L",
|
||||
"DEF-f_ring.02.L",
|
||||
"DEF-f_ring.03.L"
|
||||
],
|
||||
"pinky.L": [
|
||||
"DEF-f_pinky.01.L",
|
||||
"DEF-f_pinky.02.L",
|
||||
"DEF-f_pinky.03.L"
|
||||
],
|
||||
"thumb.R": [
|
||||
"DEF-thumb.01.R",
|
||||
"DEF-thumb.02.R",
|
||||
"DEF-thumb.03.R"
|
||||
],
|
||||
"index.R": [
|
||||
"DEF-f_index.01.R",
|
||||
"DEF-f_index.02.R",
|
||||
"DEF-f_index.03.R"
|
||||
],
|
||||
"middle.R": [
|
||||
"DEF-f_middle.01.R",
|
||||
"DEF-f_middle.02.R",
|
||||
"DEF-f_middle.03.R"
|
||||
],
|
||||
"ring.R": [
|
||||
"DEF-f_ring.01.R",
|
||||
"DEF-f_ring.02.R",
|
||||
"DEF-f_ring.03.R"
|
||||
],
|
||||
"pinky.R": [
|
||||
"DEF-f_pinky.01.R",
|
||||
"DEF-f_pinky.02.R",
|
||||
"DEF-f_pinky.03.R"
|
||||
]
|
||||
},
|
||||
"chains": [
|
||||
{
|
||||
"class": "hair",
|
||||
@@ -3414,10 +3466,10 @@
|
||||
"DEF-skirt": 1.8893422721806523,
|
||||
"DEF-skirt.003.seg2": 3.097181898448024,
|
||||
"DEF-skirt.003.seg1": 1.512647745279899,
|
||||
"DEF-skirt.003.seg3": 5.170624213956808,
|
||||
"DEF-skirt.003.seg3": 5.169104631170351,
|
||||
"DEF-skirt.007.seg3": 0.12245594475491542,
|
||||
"DEF-skirt.005": 0.16033306364800098,
|
||||
"DEF-skirt.seg2": 2.792140468584722,
|
||||
"DEF-skirt.seg2": 2.786772327315499,
|
||||
"DEF-skirt.009": 0.09372445010591768,
|
||||
"DEF-skirt.L.seg1": 2.1407108254297182,
|
||||
"DEF-skirt.007": 0.04866960387074015,
|
||||
@@ -3429,8 +3481,8 @@
|
||||
"DEF-skirt.L": 0.12044016764635926
|
||||
},
|
||||
{
|
||||
"DEF-skirt.003.seg3": 5.211532068417141,
|
||||
"DEF-skirt.seg3": 2.1638592614301637,
|
||||
"DEF-skirt.003.seg3": 5.207492995592394,
|
||||
"DEF-skirt.seg3": 2.161155452056163,
|
||||
"DEF-skirt.007.seg2": 0.05601081196344071,
|
||||
"DEF-skirt.L.seg3": 2.7192597439563233,
|
||||
"DEF-skirt.007.seg1": 0.09495247018795672,
|
||||
@@ -3438,7 +3490,7 @@
|
||||
"DEF-skirt.005": 0.4334825864653958,
|
||||
"DEF-skirt.L.004": 0.14029811314042995,
|
||||
"DEF-skirt.009": 0.16869629844680606,
|
||||
"DEF-skirt.seg1": 2.792140468584722,
|
||||
"DEF-skirt.seg1": 2.786772327315499,
|
||||
"DEF-skirt.011": 0.15320185432908007,
|
||||
"DEF-skirt.007.seg3": 0.1889945161820596,
|
||||
"DEF-skirt.009.seg1": 0.0714384640111776,
|
||||
@@ -3452,14 +3504,14 @@
|
||||
},
|
||||
{
|
||||
"DEF-skirt.002": 0.25142821495506595,
|
||||
"DEF-skirt.011.seg1": 5.544001124575805,
|
||||
"DEF-skirt.011.seg1": 5.570096570820647,
|
||||
"DEF-skirt.L.001": 0.4132729913690881,
|
||||
"DEF-skirt.011": 4.6961528319649295,
|
||||
"DEF-skirt.003.seg3": 8.365425825133514,
|
||||
"DEF-skirt.005.seg2": 4.61498402848288,
|
||||
"DEF-skirt.009": 0.7582600522037454,
|
||||
"DEF-skirt.009.seg1": 0.9144382488613035,
|
||||
"DEF-skirt.seg2": 2.1638592614301637,
|
||||
"DEF-skirt.seg2": 2.161155452056163,
|
||||
"DEF-skirt.L.seg3": 4.3393369779026685,
|
||||
"DEF-skirt.011.seg2": 2.8357110381653126,
|
||||
"DEF-skirt.006": 0.1217170026805725,
|
||||
@@ -3760,14 +3812,14 @@
|
||||
"DEF-skirt.009": 1.1286794898007495,
|
||||
"DEF-skirt.009.seg1": 0.8940016644600399,
|
||||
"DEF-skirt.011.seg1": 1.3719167847603355,
|
||||
"DEF-skirt.seg2": 5.211532068417141,
|
||||
"DEF-skirt.seg2": 5.207492995592394,
|
||||
"DEF-skirt.007.seg2": 1.980115868442557,
|
||||
"DEF-skirt.011": 1.6883411692987111,
|
||||
"DEF-skirt.007.seg1": 2.9822736639827294,
|
||||
"DEF-skirt.L.seg3": 0.5467618653323603,
|
||||
"DEF-skirt": 1.595806262183024,
|
||||
"DEF-skirt.003.seg2": 2.4862878647459423,
|
||||
"DEF-skirt.seg1": 5.170624213956808,
|
||||
"DEF-skirt.seg1": 5.169104631170351,
|
||||
"DEF-skirt.007.seg3": 2.7651239767046945,
|
||||
"DEF-skirt.009.seg2": 0.18218562382336984,
|
||||
"DEF-skirt.R.seg2": 0.02413577489838903,
|
||||
@@ -3809,9 +3861,9 @@
|
||||
-0.0
|
||||
],
|
||||
[
|
||||
-0.01201,
|
||||
0.14512,
|
||||
0.03219
|
||||
-0.01353,
|
||||
0.14671,
|
||||
0.03405
|
||||
]
|
||||
],
|
||||
"hulls": [
|
||||
@@ -3973,9 +4025,9 @@
|
||||
],
|
||||
[
|
||||
[
|
||||
-0.07886,
|
||||
0.14877,
|
||||
0.10408
|
||||
-0.07888,
|
||||
0.14866,
|
||||
0.10517
|
||||
],
|
||||
[
|
||||
0.02098,
|
||||
@@ -4072,21 +4124,21 @@
|
||||
"DEF-skirt.L.002": 0.10102962666415694,
|
||||
"DEF-skirt.L.001.seg1": 0.19093190472623134,
|
||||
"DEF-skirt.L.005": 0.04318668795316161,
|
||||
"DEF-skirt.L.004": 3.7163001012175743,
|
||||
"DEF-skirt.L.004": 3.8285142497834164,
|
||||
"DEF-skirt.011.seg3": 4.602221938080493,
|
||||
"DEF-skirt.011.seg2": 3.703707791421389,
|
||||
"DEF-skirt.L.004.seg1": 0.9578381959761514,
|
||||
"DEF-skirt.011.seg2": 3.722823119417367,
|
||||
"DEF-skirt.L.004.seg1": 0.9764221426855458,
|
||||
"DEF-skirt.L.003": 3.6402760699503562,
|
||||
"DEF-skirt.L.seg2": 3.686576538966275,
|
||||
"DEF-skirt.L.001": 1.7430155018692777,
|
||||
"DEF-skirt.seg3": 4.3393369779026685,
|
||||
"DEF-skirt.seg2": 2.7192597439563233,
|
||||
"DEF-skirt.003.seg3": 0.5467618653323603,
|
||||
"DEF-skirt.L.003.seg3": 1.4767131363706594,
|
||||
"DEF-skirt.L.003.seg2": 0.739566607477287,
|
||||
"DEF-skirt.L.003.seg3": 1.533915214427354,
|
||||
"DEF-skirt.L.003.seg2": 0.7716510583968516,
|
||||
"DEF-skirt.005.seg2": 0.03759796665737425,
|
||||
"DEF-skirt.011.seg1": 0.6000672181866031,
|
||||
"DEF-skirt.L.003.seg1": 0.7168149637479433,
|
||||
"DEF-skirt.011.seg1": 0.6236902469398551,
|
||||
"DEF-skirt.L.003.seg1": 0.7488992127140781,
|
||||
"DEF-skirt.011": 0.13239584627096912,
|
||||
"DEF-skirt.003.seg2": 0.024977540935678944
|
||||
}
|
||||
@@ -4118,9 +4170,9 @@
|
||||
0.0
|
||||
],
|
||||
[
|
||||
0.03615,
|
||||
0.14155,
|
||||
-0.03115
|
||||
0.0279,
|
||||
0.14363,
|
||||
-0.02295
|
||||
]
|
||||
],
|
||||
"hulls": [
|
||||
@@ -4287,9 +4339,9 @@
|
||||
0.17881
|
||||
],
|
||||
[
|
||||
0.19114,
|
||||
0.03703,
|
||||
-0.18664
|
||||
0.18049,
|
||||
0.03004,
|
||||
-0.17856
|
||||
],
|
||||
[
|
||||
-0.044,
|
||||
@@ -4297,9 +4349,9 @@
|
||||
0.02135
|
||||
],
|
||||
[
|
||||
0.06809,
|
||||
0.11583,
|
||||
-0.07723
|
||||
0.06877,
|
||||
0.11585,
|
||||
-0.07637
|
||||
],
|
||||
[
|
||||
0.08415,
|
||||
@@ -4356,10 +4408,10 @@
|
||||
{
|
||||
"DEF-skirt.L.003": 6.056427661191389,
|
||||
"DEF-skirt.R.003": 3.5362091427452844,
|
||||
"DEF-skirt.L.003.seg2": 1.4336744309317115,
|
||||
"DEF-skirt.L.003.seg2": 1.4465578586045362,
|
||||
"DEF-skirt.R.003.seg1": 0.0075091120423149416,
|
||||
"DEF-skirt.L.004": 0.28033761075145813,
|
||||
"DEF-skirt.L.seg3": 0.7168149637479433,
|
||||
"DEF-skirt.L.004": 0.3003293425343978,
|
||||
"DEF-skirt.L.seg3": 0.7488992127140781,
|
||||
"DEF-skirt.L.seg2": 0.1957218245564052,
|
||||
"DEF-skirt.L.seg1": 0.6595836843532097,
|
||||
"DEF-skirt.L": 1.5219704851834268,
|
||||
@@ -4368,10 +4420,10 @@
|
||||
{
|
||||
"DEF-skirt.L.003.seg3": 1.7624733424013248,
|
||||
"DEF-skirt.R.003": 5.005937962569201,
|
||||
"DEF-skirt.L.004": 0.6573735970229051,
|
||||
"DEF-skirt.L.seg3": 0.739566607477287,
|
||||
"DEF-skirt.L.004": 0.677365454643255,
|
||||
"DEF-skirt.L.seg3": 0.7716510583968516,
|
||||
"DEF-skirt.L.seg2": 0.19439024437881924,
|
||||
"DEF-skirt.L.003.seg1": 1.4336744309317115,
|
||||
"DEF-skirt.L.003.seg1": 1.4465578586045362,
|
||||
"DEF-skirt.R.003.seg1": 0.1158529500045482,
|
||||
"DEF-skirt.L": 0.9837299947917074,
|
||||
"DEF-skirt.L.seg1": 0.322305533592365,
|
||||
@@ -4389,8 +4441,8 @@
|
||||
"DEF-skirt.R.003.seg3": 5.539891594630126,
|
||||
"DEF-skirt.L.004.seg1": 7.552187895272386,
|
||||
"DEF-skirt.R.004": 1.6662561376102858,
|
||||
"DEF-skirt.L.004": 5.172512625961911,
|
||||
"DEF-skirt.L.seg3": 1.4767131363706594,
|
||||
"DEF-skirt.L.004": 5.274030080809885,
|
||||
"DEF-skirt.L.seg3": 1.533915214427354,
|
||||
"DEF-skirt.L.seg2": 0.009721631050302243,
|
||||
"DEF-skirt.R.004.seg1": 0.2699483450459348,
|
||||
"DEF-skirt.R.003.seg2": 1.2716413510540925,
|
||||
@@ -4650,7 +4702,7 @@
|
||||
"DEF-skirt.007.seg1": 0.7598426687282531,
|
||||
"DEF-skirt.R.seg1": 3.2886585052114654,
|
||||
"DEF-skirt.007.seg3": 0.5922937660442745,
|
||||
"DEF-skirt.R.003.seg1": 0.9644485221168745,
|
||||
"DEF-skirt.R.003.seg1": 0.958425160444825,
|
||||
"DEF-skirt.003.seg3": 0.058546876452437446,
|
||||
"DEF-skirt.007.seg2": 0.17227260051985324,
|
||||
"DEF-skirt.L": 0.7833441613586325,
|
||||
@@ -4665,7 +4717,7 @@
|
||||
"DEF-skirt.R.seg2": 3.9264200827331246,
|
||||
"DEF-skirt.007.seg3": 5.990418387665851,
|
||||
"DEF-skirt.003.seg3": 0.07982489265778714,
|
||||
"DEF-skirt.R.003.seg1": 2.6460365216903683,
|
||||
"DEF-skirt.R.003.seg1": 2.624934006784733,
|
||||
"DEF-skirt.R": 3.2886585052114654,
|
||||
"DEF-skirt.R.003.seg2": 0.26822713323056313,
|
||||
"DEF-skirt.R.003": 1.2574131429974016,
|
||||
@@ -4930,9 +4982,9 @@
|
||||
"DEF-skirt.L.003.seg2": 0.1158529500045482,
|
||||
"DEF-skirt.L.003.seg1": 0.0075091120423149416,
|
||||
"DEF-skirt.L.003.seg3": 1.7362462282224314,
|
||||
"DEF-skirt.R.003.seg2": 2.396364732019504,
|
||||
"DEF-skirt.R.seg1": 2.6460365216903683,
|
||||
"DEF-skirt.R": 0.9644485221168745,
|
||||
"DEF-skirt.R.003.seg2": 2.390378788205351,
|
||||
"DEF-skirt.R.seg1": 2.624934006784733,
|
||||
"DEF-skirt.R": 0.958425160444825,
|
||||
"DEF-skirt.R.003": 1.0807317991880538,
|
||||
"DEF-skirt.R.seg2": 0.04449534258909127
|
||||
},
|
||||
@@ -4941,7 +4993,7 @@
|
||||
"DEF-skirt.R.seg2": 0.7907999176082137,
|
||||
"DEF-skirt.R.001": 0.15603431496875952,
|
||||
"DEF-skirt.L.003.seg3": 1.2716413510540925,
|
||||
"DEF-skirt.R.003.seg1": 2.396364732019504,
|
||||
"DEF-skirt.R.003.seg1": 2.390378788205351,
|
||||
"DEF-skirt.R.004": 0.016111401937035363,
|
||||
"DEF-skirt.R.seg1": 0.26822713323056313,
|
||||
"DEF-skirt.R": 0.0144911590928406
|
||||
@@ -5629,31 +5681,31 @@
|
||||
},
|
||||
{
|
||||
"DEF-skirt.011": 4.58106049623973,
|
||||
"DEF-skirt.011.seg2": 7.19317806733265,
|
||||
"DEF-skirt.011.seg2": 7.206079765921628,
|
||||
"DEF-skirt.L.002": 0.02658579166615116,
|
||||
"DEF-skirt.002.seg2": 0.012493393667064645,
|
||||
"DEF-skirt.002.seg1": 0.07562430091019157,
|
||||
"DEF-skirt.L.001": 1.879150088933239,
|
||||
"DEF-skirt.L.001.seg1": 0.060376459628942836,
|
||||
"DEF-skirt.002": 1.0432309729624893,
|
||||
"DEF-skirt.seg3": 5.544001124575805,
|
||||
"DEF-skirt.seg3": 5.570096570820647,
|
||||
"DEF-skirt.003.seg3": 1.3719167847603355,
|
||||
"DEF-skirt.005.seg2": 3.0893325526066313,
|
||||
"DEF-skirt.005.seg1": 1.0448083213115291,
|
||||
"DEF-skirt.L.seg3": 0.6000672181866031,
|
||||
"DEF-skirt.L.seg3": 0.6236902469398551,
|
||||
"DEF-skirt.005.seg3": 0.031181913198806976
|
||||
},
|
||||
{
|
||||
"DEF-skirt.011.seg1": 7.19317806733265,
|
||||
"DEF-skirt.011.seg1": 7.206079765921628,
|
||||
"DEF-skirt.L.002": 0.1453999495240184,
|
||||
"DEF-skirt.002.seg3": 0.11034352255838153,
|
||||
"DEF-skirt.011.seg3": 2.7509932421896424,
|
||||
"DEF-skirt.011.seg3": 2.7529092791157663,
|
||||
"DEF-skirt.002.seg2": 0.09621669328872662,
|
||||
"DEF-skirt.L.001.seg1": 0.8697016458385258,
|
||||
"DEF-skirt.002.seg1": 0.5566789920191999,
|
||||
"DEF-skirt.002": 0.6140337423742315,
|
||||
"DEF-skirt.L.001": 1.0570456406771491,
|
||||
"DEF-skirt.L.seg3": 3.703707791421389,
|
||||
"DEF-skirt.L.seg3": 3.722823119417367,
|
||||
"DEF-skirt.seg3": 2.8357110381653126,
|
||||
"DEF-skirt.005.seg2": 1.4663347373091402,
|
||||
"DEF-skirt.005.seg1": 0.06153290296288072,
|
||||
@@ -5666,7 +5718,7 @@
|
||||
"DEF-skirt.L.002": 0.2979373224340631,
|
||||
"DEF-skirt.002.seg3": 0.8674111035481489,
|
||||
"DEF-skirt.L.001.seg1": 0.43628207869348157,
|
||||
"DEF-skirt.011.seg2": 2.7509932421896424,
|
||||
"DEF-skirt.011.seg2": 2.7529092791157663,
|
||||
"DEF-skirt.L.seg3": 4.602221938080493,
|
||||
"DEF-skirt.seg3": 0.14448731559887507,
|
||||
"DEF-skirt.005.seg2": 0.06419735157119555,
|
||||
@@ -6389,7 +6441,7 @@
|
||||
"DEF-skirt.R.003.seg3": 0.1715765989223872,
|
||||
"DEF-skirt.R.004": 0.030876031930140435,
|
||||
"DEF-skirt.R.004.seg1": 0.020803836908132034,
|
||||
"DEF-skirt.L.004": 0.22907211825039514,
|
||||
"DEF-skirt.L.004": 0.22954852875263643,
|
||||
"DEF-skirt.L.004.seg1": 1.117666014626619,
|
||||
"DEF-skirt.R.005": 0.00681448318365796
|
||||
},
|
||||
@@ -6609,32 +6661,32 @@
|
||||
],
|
||||
"neighbours": [
|
||||
{
|
||||
"DEF-skirt.L.004.seg1": 4.0505432754151975,
|
||||
"DEF-skirt.L.005": 0.22907211825039514,
|
||||
"DEF-skirt.L.004.seg1": 4.0678154269769955,
|
||||
"DEF-skirt.L.005": 0.22954852875263643,
|
||||
"DEF-skirt.L.001.seg2": 0.6736723993011002,
|
||||
"DEF-skirt.L.002": 0.808795880142356,
|
||||
"DEF-skirt.L.001.seg1": 0.8051463519527622,
|
||||
"DEF-skirt.L.seg3": 3.7163001012175743,
|
||||
"DEF-skirt.L.seg3": 3.8285142497834164,
|
||||
"DEF-skirt.L.003": 0.21646333191305667,
|
||||
"DEF-skirt.L.seg2": 0.06935733543747569,
|
||||
"DEF-skirt.L.003.seg3": 5.172512625961911,
|
||||
"DEF-skirt.L.003.seg3": 5.274030080809885,
|
||||
"DEF-skirt.R.003.seg3": 0.845388225157766,
|
||||
"DEF-skirt.L.003.seg2": 0.6573735970229051,
|
||||
"DEF-skirt.L.003.seg2": 0.677365454643255,
|
||||
"DEF-skirt.seg3": 0.8155590405608446,
|
||||
"DEF-skirt": 0.19603096959511881,
|
||||
"DEF-skirt.seg2": 0.14029811314042995,
|
||||
"DEF-skirt.L.003.seg1": 0.28033761075145813,
|
||||
"DEF-skirt.L.003.seg1": 0.3003293425343978,
|
||||
"DEF-skirt.R.004": 0.13967539384350525,
|
||||
"DEF-skirt.seg1": 0.20676807425859
|
||||
},
|
||||
{
|
||||
"DEF-skirt.L.004": 4.0505432754151975,
|
||||
"DEF-skirt.L.004": 4.0678154269769955,
|
||||
"DEF-skirt.L.005": 1.117666014626619,
|
||||
"DEF-skirt.R.003.seg3": 1.9171418898471628,
|
||||
"DEF-skirt.L.004.seg2": 4.7309382362110455,
|
||||
"DEF-skirt.L.003.seg3": 7.552187895272386,
|
||||
"DEF-skirt.R.004": 1.5419089252454603,
|
||||
"DEF-skirt.L.seg3": 0.9578381959761514,
|
||||
"DEF-skirt.L.seg3": 0.9764221426855458,
|
||||
"DEF-skirt.R.004.seg1": 0.07202823700664285,
|
||||
"DEF-skirt.seg3": 0.029836616482513456
|
||||
},
|
||||
|
||||
@@ -216,6 +216,7 @@ func load_model(path: String) -> void:
|
||||
_pose_mod = ShooterPoseModifier.new()
|
||||
_pose_mod.joint_helpers = _joint_helpers
|
||||
_pose_mod.roles = _rig_info.get("roles", {})
|
||||
_pose_mod.fingers = _rig_info.get("fingers", {})
|
||||
_pose_mod.name = "ShooterPose"
|
||||
skeleton.add_child(_pose_mod)
|
||||
# Cloth and hair last, so the springs react to the FINAL body pose —
|
||||
@@ -976,14 +977,52 @@ func get_muzzle_node() -> Node3D:
|
||||
## viewmodel's hands at GRIP_LOCAL and SUPPORT_LOCAL in weapon space, and every
|
||||
## weapon sets muzzle_flash.position at its barrel tip. Third person simply
|
||||
## reaches for the same points the viewmodel does.
|
||||
## How far a weapon may be scaled down before it reads as a toy.
|
||||
const MIN_WEAPON_SCALE := 0.55
|
||||
## Forward offset of the stock pocket from the shoulder joint — see POCKET_HIP.
|
||||
const POCKET_FORWARD := 0.06
|
||||
|
||||
|
||||
## Scale the weapon so this character can actually reach its handguard.
|
||||
##
|
||||
## Not against a fixed reference size: against the arm that has to hold it. The
|
||||
## set is modelled at real-world scale — an M4 is 0.84 m butt to muzzle — and
|
||||
## these characters are stylised, with arms around 0.47 m against an adult 0.52.
|
||||
##
|
||||
## The binding constraint is the SUPPORT arm. Its hand has to reach a point
|
||||
## (stock + fore) in front of the shoulder pocket, from a shoulder half the
|
||||
## shoulder-width off the weapon's axis. Solve that triangle for the largest gun
|
||||
## whose handguard still lands inside the arm's reach, and the support hand stops
|
||||
## being dragged back down the barrel.
|
||||
##
|
||||
## Measured on Taila before this: a support offset authored at 0.35 m collapsed
|
||||
## to 0.083 m, which puts the support fist on top of the trigger fist — a
|
||||
## two-handed pistol grip, not a rifle. That is what "the hands are not on the
|
||||
## rails" was.
|
||||
func _weapon_scale(span: float) -> float:
|
||||
if not _pose_mod or not skeleton or span <= 0.01:
|
||||
return 1.0
|
||||
var reach: float = _pose_mod._arm_reach(skeleton, "DEF-upper_arm.L",
|
||||
"DEF-forearm.L", "DEF-hand.L") * 0.94
|
||||
if reach <= 0.01:
|
||||
return 1.0
|
||||
var ua_l: int = _pose_mod._idx.get("DEF-upper_arm.L", -1)
|
||||
var ua_r: int = _pose_mod._idx.get("DEF-upper_arm.R", -1)
|
||||
var lateral := 0.14
|
||||
if ua_l >= 0 and ua_r >= 0:
|
||||
# The weapon rides near the right shoulder, so the support arm crosses
|
||||
# about half the shoulder width to get to it.
|
||||
lateral = skeleton.get_bone_global_rest(ua_l).origin.distance_to(
|
||||
skeleton.get_bone_global_rest(ua_r).origin) * 0.5
|
||||
var forward := sqrt(maxf(reach * reach - lateral * lateral, 0.01))
|
||||
return clampf((forward - POCKET_FORWARD) / span, MIN_WEAPON_SCALE, 1.0)
|
||||
|
||||
|
||||
func _measure_weapon(w: Node3D) -> void:
|
||||
var grip: Vector3 = WeaponGrips.GRIP
|
||||
var support: Vector3 = WeaponGrips.SUPPORT
|
||||
var fwd := Vector3(0, 0, -1) # the weapon's own muzzle axis
|
||||
|
||||
# Put the GRIP — not the model origin — in the fist.
|
||||
w.position -= w.transform.basis * grip
|
||||
|
||||
# Barrel length, grip to muzzle. Every weapon marks its own barrel tip.
|
||||
var muzzle_dist: float = WeaponGrips.DEFAULT_MUZZLE_DIST
|
||||
if "muzzle_flash" in w and w.muzzle_flash:
|
||||
@@ -995,12 +1034,20 @@ func _measure_weapon(w: Node3D) -> void:
|
||||
# front of the barrel with nothing to hold, which is why some weapons did
|
||||
# not look supported by the arms.
|
||||
var fore: float = absf((support - grip).dot(fwd))
|
||||
_pose_mod.gun_fore = clampf(minf(fore, muzzle_dist * 0.8), 0.12, 0.45)
|
||||
var fore_u: float = clampf(minf(fore, muzzle_dist * 0.8), 0.12, 0.45)
|
||||
var stock_u: float = clampf(muzzle_dist * 0.5, 0.10, 0.40)
|
||||
|
||||
# Size the gun to the arm that has to hold it, then seat the grip in the fist.
|
||||
var k := _weapon_scale(stock_u + fore_u)
|
||||
w.scale = Vector3.ONE * k
|
||||
w.position -= w.transform.basis * grip
|
||||
|
||||
_pose_mod.gun_fore = fore_u * k
|
||||
|
||||
# Stock: not authored anywhere, so derive it from the barrel. Half the
|
||||
# grip-to-muzzle distance behind the grip lands the butt in the shoulder
|
||||
# pocket for every gun in the set.
|
||||
_pose_mod.gun_stock = clampf(muzzle_dist * 0.5, 0.10, 0.40)
|
||||
_pose_mod.gun_stock = stock_u * k
|
||||
|
||||
|
||||
# ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
@@ -1123,6 +1170,10 @@ class ShooterPoseModifier extends SkeletonModifier3D:
|
||||
## the model was rebound onto the library skeleton, where the names below
|
||||
## already match.
|
||||
var roles: Dictionary = {}
|
||||
## "index.L" -> its bone names, knuckle to fingertip, from the same sidecar.
|
||||
var fingers: Dictionary = {}
|
||||
var _fing: Dictionary = {} # same, resolved to bone indices
|
||||
var _curl: Dictionary = {} # "L"/"R" -> curl axis in the rest frame
|
||||
|
||||
func _resolve() -> void:
|
||||
var skel := get_skeleton()
|
||||
@@ -1153,8 +1204,62 @@ class ShooterPoseModifier extends SkeletonModifier3D:
|
||||
if b < 0:
|
||||
b = skel.find_bone(n)
|
||||
_idx[n] = b
|
||||
_resolve_hands(skel)
|
||||
_resolved = true
|
||||
|
||||
## Finger bones, and the axis a finger closes about, per hand.
|
||||
##
|
||||
## The axis is derived from the hand's OWN ANATOMY in the rest pose, because
|
||||
## no two rigs agree on finger bone orientation and a constant would close
|
||||
## one character's hand and splay another's:
|
||||
##
|
||||
## along wrist -> middle knuckle, the length of the hand
|
||||
## palm middle knuckle -> thumb tip, across it. The thumb opposes the
|
||||
## fingers, so it is on the palm side by construction — which is
|
||||
## true of a hand, not of a rig convention.
|
||||
## curl along x palm, so turning about it swings the fingers into the
|
||||
## palm rather than sideways or backwards.
|
||||
func _resolve_hands(skel: Skeleton3D) -> void:
|
||||
for key in fingers:
|
||||
var list := PackedInt32Array()
|
||||
for n in fingers[key]:
|
||||
var b := skel.find_bone(String(n))
|
||||
if b >= 0:
|
||||
list.append(b)
|
||||
if not list.is_empty():
|
||||
_fing[key] = list
|
||||
for side in ["L", "R"]:
|
||||
var hand: int = _idx.get("DEF-hand." + side, -1)
|
||||
var mid: PackedInt32Array = _fing.get("middle." + side, PackedInt32Array())
|
||||
if hand < 0 or mid.is_empty():
|
||||
continue
|
||||
var wrist := skel.get_bone_global_rest(hand).origin
|
||||
var knuckle := skel.get_bone_global_rest(mid[0]).origin
|
||||
var along := knuckle - wrist
|
||||
if along.length() < 0.0001:
|
||||
continue
|
||||
along = along.normalized()
|
||||
var palm := Vector3.ZERO
|
||||
var thumb: PackedInt32Array = _fing.get("thumb." + side, PackedInt32Array())
|
||||
if not thumb.is_empty():
|
||||
palm = skel.get_bone_global_rest(thumb[thumb.size() - 1]).origin - knuckle
|
||||
else:
|
||||
# No thumb on this rig: fall back to the knuckle line, which
|
||||
# gives a plane but not a side, so the sign may be wrong.
|
||||
var ix: PackedInt32Array = _fing.get("index." + side, PackedInt32Array())
|
||||
var pk: PackedInt32Array = _fing.get("pinky." + side, PackedInt32Array())
|
||||
if ix.is_empty() or pk.is_empty():
|
||||
continue
|
||||
palm = (skel.get_bone_global_rest(pk[0]).origin
|
||||
- skel.get_bone_global_rest(ix[0]).origin).cross(along)
|
||||
palm -= along * palm.dot(along)
|
||||
if palm.length() < 0.0001:
|
||||
continue
|
||||
_curl[side] = along.cross(palm.normalized()).normalized()
|
||||
if OS.has_environment("HAND_DEBUG"):
|
||||
print("HANDS fingers=%d resolved=%d curl=%s" % [
|
||||
fingers.size(), _fing.size(), _curl])
|
||||
|
||||
func _process_modification() -> void:
|
||||
var skel := get_skeleton()
|
||||
if not skel:
|
||||
@@ -1179,6 +1284,7 @@ class ShooterPoseModifier extends SkeletonModifier3D:
|
||||
_apply_grapple(skel)
|
||||
if _hold_r > 0.01 or _hold_l > 0.01:
|
||||
_apply_rifle_hold(skel)
|
||||
_close_hands(skel)
|
||||
if recoil > 0.01:
|
||||
_apply_recoil(skel)
|
||||
recoil = lerpf(recoil, 0.0, 0.25)
|
||||
@@ -1398,12 +1504,20 @@ class ShooterPoseModifier extends SkeletonModifier3D:
|
||||
var l_sh := skel.get_bone_global_pose(ua_l).origin
|
||||
var reach_l := _arm_reach(skel, "DEF-upper_arm.L", "DEF-forearm.L",
|
||||
"DEF-hand.L") * 0.94
|
||||
for _i in 5:
|
||||
# Slide the support hand back down the handguard until the arm can
|
||||
# reach it — but NEVER past this floor. Without one it collapsed to
|
||||
# 0.083 m on Taila, which puts the support fist on top of the trigger
|
||||
# fist: that reads as a two-handed pistol grip, not a rifle. A support
|
||||
# arm that is nearly straight looks far better than no handguard hold.
|
||||
var floor_fore: float = gun_fore * 0.55
|
||||
for _i in 6:
|
||||
if grip_pos.distance_to(l_sh) > reach_l:
|
||||
break # even the grip is out of reach; nothing to slide to
|
||||
if (grip_pos + aim_dir * fore_dist).distance_to(l_sh) <= reach_l:
|
||||
break
|
||||
fore_dist *= 0.75
|
||||
fore_dist = maxf(fore_dist * 0.8, floor_fore)
|
||||
if fore_dist <= floor_fore:
|
||||
break
|
||||
var fore_pos := grip_pos + aim_dir * fore_dist
|
||||
|
||||
# 3. Support hand goes to the mag well during a reload (under the
|
||||
@@ -1432,8 +1546,9 @@ class ShooterPoseModifier extends SkeletonModifier3D:
|
||||
if OS.has_environment("GUN_POSE_DEBUG"):
|
||||
var hr: int = _idx.get("DEF-hand.R", -1)
|
||||
var hl: int = _idx.get("DEF-hand.L", -1)
|
||||
print("HOLD ads=%.2f holdR=%.2f holdL=%.2f rl=%.2f fore=%.3f stock=%.3f foredist=%.3f" % [
|
||||
ads, _hold_r, _hold_l, reload_phase, gun_fore, gun_stock, fore_dist],
|
||||
print("HOLD st=%s tgtL=%.2f ads=%.2f holdR=%.2f holdL=%.2f rl=%.2f fore=%.3f stock=%.3f foredist=%.3f" % [
|
||||
state, hold_l_target, ads, _hold_r, _hold_l, reload_phase,
|
||||
gun_fore, gun_stock, fore_dist],
|
||||
" sh=", shoulder, " grip=", grip_pos, " fore=", fore_pos,
|
||||
" handR=", skel.get_bone_global_pose(hr).origin if hr >= 0 else "-",
|
||||
" handL=", skel.get_bone_global_pose(hl).origin if hl >= 0 else "-",
|
||||
@@ -1469,6 +1584,49 @@ class ShooterPoseModifier extends SkeletonModifier3D:
|
||||
* Quaternion(fa_rest_dir, aim_dir) * hand_rest_q
|
||||
_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.
|
||||
#
|
||||
# Two different grips. The SUPPORT hand wraps a handguard, so all four
|
||||
# fingers close hard and evenly. The TRIGGER hand wraps a pistol grip with
|
||||
# three fingers while the index lies along the trigger, nearly straight —
|
||||
# curling it with the rest is the single thing that most makes a game
|
||||
# character look like they are squeezing a bar of soap rather than holding a
|
||||
# rifle.
|
||||
const CURL_WRAP := [0.85, 1.15, 0.85]
|
||||
const CURL_TRIGGER := [0.42, 0.55, 0.35]
|
||||
# The thumb opposes rather than curls, so it closes less and it is the one
|
||||
# digit whose middle joint barely bends.
|
||||
const CURL_THUMB := [0.45, 0.30, 0.25]
|
||||
const DIGITS := ["index", "middle", "ring", "pinky"]
|
||||
|
||||
## Close both hands around whatever the IK put them on.
|
||||
##
|
||||
## Applied AFTER the hold, so the fingers follow wherever the wrists ended up.
|
||||
## Weighted by the per-arm hold blend, so a hand the clip has taken back opens
|
||||
## again instead of staying clamped shut.
|
||||
func _close_hands(skel: Skeleton3D) -> void:
|
||||
_close_hand(skel, "R", _hold_r)
|
||||
_close_hand(skel, "L", _hold_l)
|
||||
|
||||
func _close_hand(skel: Skeleton3D, side: String, w: float) -> void:
|
||||
if w <= 0.01 or not _curl.has(side):
|
||||
return
|
||||
var axis: Vector3 = _curl[side]
|
||||
for digit in DIGITS:
|
||||
var bones: PackedInt32Array = _fing.get(digit + "." + side,
|
||||
PackedInt32Array())
|
||||
if bones.is_empty():
|
||||
continue
|
||||
# The right index rides the trigger; everything else wraps.
|
||||
var amount: Array = CURL_TRIGGER if (side == "R" and digit == "index") else CURL_WRAP
|
||||
for i in bones.size():
|
||||
var a: float = amount[i] if i < amount.size() else amount[-1]
|
||||
_add_space(skel, bones[i], Quaternion(axis, a * w))
|
||||
var thumb: PackedInt32Array = _fing.get("thumb." + side, PackedInt32Array())
|
||||
for i in thumb.size():
|
||||
var a: float = CURL_THUMB[i] if i < CURL_THUMB.size() else CURL_THUMB[-1]
|
||||
_add_space(skel, thumb[i], Quaternion(axis, a * w))
|
||||
|
||||
## Straight-arm length of an arm chain, from the rest pose.
|
||||
func _arm_reach(skel: Skeleton3D, ua_name: String, fa_name: String,
|
||||
hand_name: String) -> float:
|
||||
|
||||
+8
-1
@@ -1033,7 +1033,14 @@ def describe_rig(arm, roles, mapping, meshes):
|
||||
if b.parent is not None:
|
||||
twist.append({"bone": b.name, "parent": b.parent.name,
|
||||
"child": b.children[0].name if b.children else None})
|
||||
return {"roles": roles_out, "chains": chains, "twist": twist,
|
||||
# Fingers, so the runtime can close a hand around a grip without knowing
|
||||
# how this rig spells "index". Ordered knuckle -> fingertip.
|
||||
finger_out = {}
|
||||
for (digit, side), bones in roles.fingers.items():
|
||||
finger_out["%s.%s" % (digit, side)] = bones
|
||||
|
||||
return {"roles": roles_out, "fingers": finger_out,
|
||||
"chains": chains, "twist": twist,
|
||||
"colliders": _leg_colliders(
|
||||
arm, roles, _dominant_vertices(meshes, arm, min_weight=0.6),
|
||||
_body_points(meshes)),
|
||||
|
||||
@@ -58,6 +58,20 @@ LIMB_ROLES = {
|
||||
# Longest-first inside each role, so "upperarm" is tried before "arm".
|
||||
LIMB_ORDER = ["toe", "foot", "shin", "thigh", "shoulder", "hand", "forearm", "upper_arm"]
|
||||
|
||||
# digit -> spellings. Every rig met so far uses one of three families:
|
||||
# Rigify DEF-f_index.01.L canon "findex01l"
|
||||
# VRoid J_Bip_L_Index1 canon "index1"
|
||||
# Blender IndexFinger1_L canon "indexfinger1l"
|
||||
# so a substring test on the canon form covers all of them. The pinky is called
|
||||
# "little" on VRoid and half the Blender exports.
|
||||
DIGITS = {
|
||||
"thumb": ("thumb",),
|
||||
"index": ("index",),
|
||||
"middle": ("middle",),
|
||||
"ring": ("ring",),
|
||||
"pinky": ("pinky", "little"),
|
||||
}
|
||||
|
||||
_PREFIXES = re.compile(
|
||||
r"^(def[-_]|org[-_]|mch[-_]|ctrl[-_]|mixamorig\d*[:_]|bip\d*[-_ ]|"
|
||||
r"j_bip_[clr]_|j_sec_[clr]_|valvebiped\.|bone_|b_)", re.I)
|
||||
@@ -143,6 +157,7 @@ class RigRoles:
|
||||
self.bones = {b.name: b for b in arm.data.bones}
|
||||
self.names = set(self.bones)
|
||||
self.limb = {} # ("thigh", "L") -> bone name
|
||||
self.fingers = {} # ("index", "L") -> [proximal, middle, distal]
|
||||
self.spine = [] # hips-exclusive chain, ordered, ending on the head
|
||||
self.hips = None
|
||||
self.head = None
|
||||
@@ -150,6 +165,7 @@ class RigRoles:
|
||||
self._resolve_limbs()
|
||||
self._fill_limbs_by_anatomy()
|
||||
self._resolve_spine()
|
||||
self._resolve_fingers()
|
||||
|
||||
# ---------------------------------------------------------------- limbs
|
||||
|
||||
@@ -234,6 +250,37 @@ class RigRoles:
|
||||
continue
|
||||
self.limb[(role, side)] = name
|
||||
|
||||
# -------------------------------------------------------------- fingers
|
||||
|
||||
def _resolve_fingers(self):
|
||||
"""digit+side -> its bones, ordered from the knuckle outwards.
|
||||
|
||||
Ordered by DEPTH BELOW THE HAND rather than by the number in the name.
|
||||
The numbering is not consistent — Rigify counts .01/.02/.03, VRoid counts
|
||||
1/2/3, and some exports number from the tip — but the hierarchy always
|
||||
runs knuckle to fingertip, so walking it is the only spelling-independent
|
||||
way to know which segment is which.
|
||||
"""
|
||||
for side in ("L", "R"):
|
||||
hand = self.limb.get(("hand", side))
|
||||
if not hand or hand not in self.bones:
|
||||
continue
|
||||
depth = {}
|
||||
|
||||
def walk(b, d):
|
||||
depth[b.name] = d
|
||||
for c in b.children:
|
||||
walk(c, d + 1)
|
||||
|
||||
for c in self.bones[hand].children:
|
||||
walk(c, 0)
|
||||
for digit, stems in DIGITS.items():
|
||||
got = [n for n in depth
|
||||
if any(st in canon(n) for st in stems)
|
||||
and side_of(n) == side]
|
||||
if got:
|
||||
self.fingers[(digit, side)] = sorted(got, key=lambda n: depth[n])
|
||||
|
||||
# ---------------------------------------------------------------- spine
|
||||
|
||||
def _resolve_spine(self):
|
||||
|
||||
Reference in New Issue
Block a user