Feat/outline thickness and tp weapon hold #22

Merged
Dotts merged 43 commits from feat/outline-thickness-and-tp-weapon-hold into main 2026-07-27 23:22:53 -07:00
Showing only changes of commit 0bb1db0806 - Show all commits
+24 -86
View File
@@ -1,6 +1,26 @@
extends Object extends Object
class_name SkinLegRepair 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. ## 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: ## 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 ## 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 ## hips/spine influence, and the excess goes to the leg bone that already
## dominates them, so the thigh follows its own bone. ## dominates them, so the thigh follows its own bone.
## 4. Hang DRAPING CLOTH from the hips instead of the legs. The skirt is ## 4. Drop any triangle still spanning the two legs below the knee. Those are
## 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
## the midline band between the ankles, which has no correct pose either ## the midline band between the ankles, which has no correct pose either
## way. ## 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 ## 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. ## rounds the hip off — but past this the thigh stops following its own bone.
const MAX_TORSO := 0.15 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. ## Ignore influences below this — they are rounding, not real weighting.
const EPSILON := 0.005 const EPSILON := 0.005
@@ -139,13 +146,12 @@ static func _repair_mesh(mi: MeshInstance3D, skeleton: Skeleton3D, knee: float,
limb_radius: float) -> Array: limb_radius: float) -> Array:
var side := _side_map(mi.skin, skeleton) var side := _side_map(mi.skin, skeleton)
var torso_bone := _torso_map(mi.skin, skeleton) var torso_bone := _torso_map(mi.skin, skeleton)
var hips_slot := _hips_bind(mi.skin, skeleton)
var surfaces: Array = [] var surfaces: Array = []
var snapped := 0 var snapped := 0
var removed := 0 var removed := 0
for s in range(mi.mesh.get_surface_count()): for s in range(mi.mesh.get_surface_count()):
var arrays: Array = mi.mesh.surface_get_arrays(s) 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] snapped += r[0]
removed += r[1] removed += r[1]
surfaces.append({ surfaces.append({
@@ -167,18 +173,6 @@ static func _repair_mesh(mi: MeshInstance3D, skeleton: Skeleton3D, knee: float,
return [snapped, removed] 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. ## Is each bind a torso bone? Keyed by SKIN BIND index, like _side_map.
static func _torso_map(skin: Skin, skeleton: Skeleton3D) -> Array: static func _torso_map(skin: Skin, skeleton: Skeleton3D) -> Array:
var out: 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, 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, chain_l: PackedVector3Array, chain_r: PackedVector3Array,
limb_radius: float) -> Array: limb_radius: float) -> Array:
var verts: PackedVector3Array = arrays[Mesh.ARRAY_VERTEX] var verts: PackedVector3Array = arrays[Mesh.ARRAY_VERTEX]
@@ -316,63 +310,7 @@ static func _repair_surface(arrays: Array, side: PackedInt32Array,
weights[v * per + k] /= sum weights[v * per + k] /= sum
snapped += 1 snapped += 1
# Step 3 — draping cloth hangs from the hips. A skirt pulled by both thighs # Step 3 — drop triangles that still span the legs below the knee.
# 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.
var removed := 0 var removed := 0
if not idx.is_empty(): if not idx.is_empty():
var keep_idx := PackedInt32Array() var keep_idx := PackedInt32Array()