fix: no limb vertex may carry opposite-leg weight — thigh collapse gone
The thighs going thin was never the knee bend. It was inner-thigh vertices
keeping ~20% of the weight of the OPPOSITE leg, so a split stride tore them
across the body. Printing the weight composition of the worst-collapsing
vertices is what finally showed it: a right-thigh vertex reading
DEF-thigh.R=0.58 DEF-shin.R=0.21 DEF-thigh.L=0.13 DEF-shin.L=0.08.
Every earlier pass had a guard that let these through. The below-knee pass does
not reach them; the above-knee pass demanded one leg chain be 1.25x nearer than
the other, which excludes everything near the centre line — precisely where the
damage was; and the limb-radius gates are measured from the bone AXIS, so a
vertex on the front or back of a thigh clears them easily.
SkinLegRepair now ends with an unconditional pass: any vertex within a generous
radius of either leg chain is forced onto the leg that actually drives it, at
any height. It runs last so none of the staged rules can reintroduce the
problem.
Measured per vertex, across jump, fall, run and dash:
before after
Body below 0.80 70 0
Body worst 0.46 0.82
ClothCAndW below 0.80 107 0
ClothCAndW worst 0.66 0.85
ClothB worst 0.81 0.87
No vertex anywhere on the legs now loses more than 18% of its thickness, down
from 54%. The skirt still deforms freely (0.87-0.88) — clothes flow, limbs hold.
Also fixed in SkinJointHelper along the way: it picked the FIRST joint chain
that matched a vertex rather than the one holding most of its weight, which
bound right-thigh vertices with stray left-leg weight to the LEFT knee's helpers
and dragged them across the body. It now picks the dominant joint and requires
the joint to own at least half the vertex.
Taila snaps 2021 vertices, Miku 659. FSM tests 11/11, spawn smoke test 0
failures.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
d33f9cd812
commit
afc954e129
@@ -255,12 +255,14 @@ static func _repair_surface(arrays: Array, side: PackedInt32Array,
|
||||
# is left blended so it can keep draping.
|
||||
var dl: float = _dist_to_chain(verts[v], chain_l)
|
||||
var dr: float = _dist_to_chain(verts[v], chain_r)
|
||||
var near: float = minf(dl, dr)
|
||||
var far: float = maxf(dl, dr)
|
||||
if near > limb_radius or far < near * 1.25:
|
||||
continue # drapes over both, or hugs neither — leave it alone
|
||||
keep = -1 if dl < dr else 1
|
||||
vside[v] = keep
|
||||
if minf(dl, dr) > limb_radius:
|
||||
continue # hugs neither chain — cloth, leave it alone
|
||||
# Side comes from WEIGHT, not from which chain is nearer. An earlier
|
||||
# version also demanded one chain be 1.25x closer than the other,
|
||||
# which skipped everything near the centre line — and that is exactly
|
||||
# where the damage was: inner-thigh vertices kept 21% of the OPPOSITE
|
||||
# leg and were torn apart when the legs split (measured 0.46). A limb
|
||||
# vertex belongs to whichever leg actually drives it.
|
||||
# Drop the losing leg's influence and renormalise what remains.
|
||||
var total := 0.0
|
||||
for k in per:
|
||||
@@ -310,7 +312,46 @@ static func _repair_surface(arrays: Array, side: PackedInt32Array,
|
||||
weights[v * per + k] /= sum
|
||||
snapped += 1
|
||||
|
||||
# Step 3 — drop triangles that still span the legs below the knee.
|
||||
# Step 3 — belt and braces: NO vertex that sits on a limb may carry any
|
||||
# weight from the opposite leg, at any height. The staged rules above each
|
||||
# have their own guards and between them they were still letting inner-thigh
|
||||
# vertices through with ~20% of the far leg, which tears them apart when the
|
||||
# legs split (measured 0.46 — the worst collapse left on the model). This is
|
||||
# unconditional and runs last so nothing can reintroduce it.
|
||||
for v in verts.size():
|
||||
var near_l: float = _dist_to_chain(verts[v], chain_l)
|
||||
var near_r: float = _dist_to_chain(verts[v], chain_r)
|
||||
# Generous radius: the limb radius is measured from the bone AXIS, so a
|
||||
# vertex on the front or back of a thigh clears it easily, and those were
|
||||
# exactly the ones slipping through with opposite-leg weight. The skirt
|
||||
# hangs far enough out to stay outside even this.
|
||||
if minf(near_l, near_r) > limb_radius * 1.6:
|
||||
continue # cloth
|
||||
var own: int = -1 if near_l < near_r else 1
|
||||
# Prefer the leg that actually drives it; fall back to the nearer chain.
|
||||
var wl2 := 0.0
|
||||
var wr2 := 0.0
|
||||
for k in per:
|
||||
match side[bones[v * per + k]]:
|
||||
-1: wl2 += weights[v * per + k]
|
||||
1: wr2 += weights[v * per + k]
|
||||
if maxf(wl2, wr2) > 0.0:
|
||||
own = -1 if wl2 >= wr2 else 1
|
||||
if minf(wl2, wr2) <= 0.0:
|
||||
continue # already single-legged
|
||||
var tot := 0.0
|
||||
for k in per:
|
||||
var b2: int = bones[v * per + k]
|
||||
if side[b2] != 0 and side[b2] != own:
|
||||
weights[v * per + k] = 0.0
|
||||
tot += weights[v * per + k]
|
||||
if tot > 0.0:
|
||||
for k in per:
|
||||
weights[v * per + k] /= tot
|
||||
vside[v] = own
|
||||
snapped += 1
|
||||
|
||||
# Step 4 — drop triangles that still span the legs below the knee.
|
||||
var removed := 0
|
||||
if not idx.is_empty():
|
||||
var keep_idx := PackedInt32Array()
|
||||
|
||||
Reference in New Issue
Block a user