revert: let the skirt move again; keep the rigid-limb work
The previous commit stiffened the skirt onto the hips to stop it flattening at a wide stride. That was the wrong trade: it made the clothes read as a rigid shell welded to the pelvis, which is a worse look than the flattening it fixed, and it broke the model more than the original bug did. Reverted in full. What stays is the work that makes the HUMANOID hold its shape — below-knee side snapping, the above-knee limb pass, the torso cap, the half-angle knee helpers, and the cross-leg triangle drop. Those only ever touch vertices hugging a leg's own bone chain. The header now states the rule the passes are actually built around, because it is what keeps getting violated: limb vertices are cleaned up hard and must not deform; drape vertices are left alone and must be free to move. It also records the skirt-stiffening attempt so it does not get retried. Measured split now, from debug/limb_deform_check.gd: the body holds 0.98 of its cross-section at the knee while the skirt sits at 0.88 — rigid limbs, moving cloth. Taila snaps 1404 vertices (down from 4024 with the skirt pass), Miku 434. 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
20b70e5eda
commit
0bb1db0806
@@ -1,6 +1,26 @@
|
||||
extends Object
|
||||
class_name SkinLegRepair
|
||||
|
||||
## Keeps the HUMANOID rigid while letting the CLOTHES flow.
|
||||
##
|
||||
## That split is the whole design rule here, and it decides what every pass below
|
||||
## is allowed to touch:
|
||||
##
|
||||
## * LIMB vertices — anything hugging a leg's own bone chain: skin, stockings,
|
||||
## boots — are cleaned up hard. They belong to one leg, they follow it, and
|
||||
## they hold their shape. Deforming here is a bug.
|
||||
## * DRAPE vertices — the skirt and anything else hanging clear of both leg
|
||||
## chains — are LEFT ALONE, so they keep swinging with the body.
|
||||
##
|
||||
## DO NOT stiffen the skirt onto the hips. It was tried (to stop it flattening at
|
||||
## a wide stride) and reverted: it makes the clothes read as a rigid shell, which
|
||||
## is worse than the flattening it fixed. Cloth is supposed to move.
|
||||
##
|
||||
## Current split, from debug/limb_deform_check.gd: the body holds 0.98 of its
|
||||
## cross-section at the knee, while the skirt is free at 0.88 — rigid limbs,
|
||||
## moving cloth.
|
||||
##
|
||||
##
|
||||
## Stops below-the-knee geometry being dragged by BOTH legs at once.
|
||||
##
|
||||
## Taila's boots are skinned with weights that bleed across the centre line:
|
||||
@@ -41,17 +61,7 @@ class_name SkinLegRepair
|
||||
## against 0.97-0.99 at the knee). Leg vertices keep at most MAX_TORSO of
|
||||
## hips/spine influence, and the excess goes to the leg bone that already
|
||||
## dominates them, so the thigh follows its own bone.
|
||||
## 4. Hang DRAPING CLOTH from the hips instead of the legs. The skirt is
|
||||
## weighted to both thighs so it can follow them, but at a running or
|
||||
## jumping stride the two thighs pull it in opposite directions and it
|
||||
## collapses flat against the body — its flared volume vanishes and the
|
||||
## whole lower body reads as squashed from the side. Cloth that is clear of
|
||||
## both leg bone chains keeps at most MAX_DRAPE_LEG of leg influence, and
|
||||
## the rest goes to the hips, so the skirt holds its shape and swings as a
|
||||
## unit. This is the "model the skirt differently from the legs" case, and
|
||||
## it is separate from the limb work above: LIMB vertices are snapped to
|
||||
## one leg, DRAPE vertices are taken off the legs almost entirely.
|
||||
## 5. Drop any triangle still spanning the two legs below the knee. Those are
|
||||
## 4. Drop any triangle still spanning the two legs below the knee. Those are
|
||||
## the midline band between the ankles, which has no correct pose either
|
||||
## way.
|
||||
##
|
||||
@@ -63,9 +73,6 @@ const TORSO_BONE_HINTS := ["hips", "spine", "pelvis"]
|
||||
## The most torso influence a leg vertex may keep. Some is wanted — it is what
|
||||
## rounds the hip off — but past this the thigh stops following its own bone.
|
||||
const MAX_TORSO := 0.15
|
||||
## The most LEG influence a draping cloth vertex may keep. A little lets the
|
||||
## skirt react to the legs; more than this and a split stride tears it flat.
|
||||
const MAX_DRAPE_LEG := 0.20
|
||||
## Ignore influences below this — they are rounding, not real weighting.
|
||||
const EPSILON := 0.005
|
||||
|
||||
@@ -139,13 +146,12 @@ static func _repair_mesh(mi: MeshInstance3D, skeleton: Skeleton3D, knee: float,
|
||||
limb_radius: float) -> Array:
|
||||
var side := _side_map(mi.skin, skeleton)
|
||||
var torso_bone := _torso_map(mi.skin, skeleton)
|
||||
var hips_slot := _hips_bind(mi.skin, skeleton)
|
||||
var surfaces: Array = []
|
||||
var snapped := 0
|
||||
var removed := 0
|
||||
for s in range(mi.mesh.get_surface_count()):
|
||||
var arrays: Array = mi.mesh.surface_get_arrays(s)
|
||||
var r := _repair_surface(arrays, side, torso_bone, hips_slot, knee, chain_l, chain_r, limb_radius)
|
||||
var r := _repair_surface(arrays, side, torso_bone, knee, chain_l, chain_r, limb_radius)
|
||||
snapped += r[0]
|
||||
removed += r[1]
|
||||
surfaces.append({
|
||||
@@ -167,18 +173,6 @@ static func _repair_mesh(mi: MeshInstance3D, skeleton: Skeleton3D, knee: float,
|
||||
return [snapped, removed]
|
||||
|
||||
|
||||
## SKIN BIND index of the hips, which is what draping cloth should hang from.
|
||||
static func _hips_bind(skin: Skin, skeleton: Skeleton3D) -> int:
|
||||
for b in skin.get_bind_count():
|
||||
var n := skin.get_bind_name(b)
|
||||
if n == "":
|
||||
var bone := skin.get_bind_bone(b)
|
||||
n = skeleton.get_bone_name(bone) if bone >= 0 else ""
|
||||
if n.findn("hips") != -1 or n.findn("pelvis") != -1:
|
||||
return b
|
||||
return -1
|
||||
|
||||
|
||||
## Is each bind a torso bone? Keyed by SKIN BIND index, like _side_map.
|
||||
static func _torso_map(skin: Skin, skeleton: Skeleton3D) -> Array:
|
||||
var out: Array = []
|
||||
@@ -223,7 +217,7 @@ static func _side_map(skin: Skin, skeleton: Skeleton3D) -> PackedInt32Array:
|
||||
|
||||
|
||||
static func _repair_surface(arrays: Array, side: PackedInt32Array,
|
||||
torso_bone: Array, hips_slot: int, knee: float,
|
||||
torso_bone: Array, knee: float,
|
||||
chain_l: PackedVector3Array, chain_r: PackedVector3Array,
|
||||
limb_radius: float) -> Array:
|
||||
var verts: PackedVector3Array = arrays[Mesh.ARRAY_VERTEX]
|
||||
@@ -316,63 +310,7 @@ static func _repair_surface(arrays: Array, side: PackedInt32Array,
|
||||
weights[v * per + k] /= sum
|
||||
snapped += 1
|
||||
|
||||
# Step 3 — draping cloth hangs from the hips. A skirt pulled by both thighs
|
||||
# at a split stride collapses flat; taking it off the legs keeps its volume.
|
||||
if hips_slot >= 0:
|
||||
for v in verts.size():
|
||||
var leg_total := 0.0
|
||||
for k in per:
|
||||
if side[bones[v * per + k]] != 0:
|
||||
leg_total += weights[v * per + k]
|
||||
if leg_total <= MAX_DRAPE_LEG:
|
||||
continue
|
||||
# ABOVE THE KNEE ONLY. Distance from the bone chain alone is not a
|
||||
# safe test down here: chunky footwear sits further from the ankle
|
||||
# than the limb radius, so a boot reads as "drape" and taking its
|
||||
# leg weight away makes it trail off the hips. A skirt hem is above
|
||||
# the knee; boots and stockings are not.
|
||||
if verts[v].y <= knee:
|
||||
continue
|
||||
# Limb vertices were already handled above; this is only for cloth
|
||||
# that hangs clear of BOTH leg bone chains.
|
||||
if minf(_dist_to_chain(verts[v], chain_l),
|
||||
_dist_to_chain(verts[v], chain_r)) <= limb_radius:
|
||||
continue
|
||||
var k_hips := -1
|
||||
for k in per:
|
||||
if bones[v * per + k] == hips_slot:
|
||||
k_hips = k
|
||||
break
|
||||
if k_hips < 0:
|
||||
# No hips influence yet: take over the weakest leg slot.
|
||||
var weakest := -1
|
||||
var weakest_w := 2.0
|
||||
for k in per:
|
||||
if side[bones[v * per + k]] != 0 and weights[v * per + k] < weakest_w:
|
||||
weakest_w = weights[v * per + k]
|
||||
weakest = k
|
||||
if weakest < 0:
|
||||
continue
|
||||
bones[v * per + weakest] = hips_slot
|
||||
weights[v * per + weakest] = 0.0
|
||||
k_hips = weakest
|
||||
leg_total -= weakest_w
|
||||
if leg_total <= MAX_DRAPE_LEG:
|
||||
continue
|
||||
var keep: float = MAX_DRAPE_LEG / leg_total
|
||||
for k in per:
|
||||
if side[bones[v * per + k]] != 0:
|
||||
weights[v * per + k] *= keep
|
||||
weights[v * per + k_hips] += leg_total - MAX_DRAPE_LEG
|
||||
var total2 := 0.0
|
||||
for k in per:
|
||||
total2 += weights[v * per + k]
|
||||
if total2 > 0.0:
|
||||
for k in per:
|
||||
weights[v * per + k] /= total2
|
||||
snapped += 1
|
||||
|
||||
# Step 4 — drop triangles that still span the legs below the knee.
|
||||
# Step 3 — 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