Feat/outline thickness and tp weapon hold #22
@@ -19,14 +19,22 @@ class_name SkinLegRepair
|
|||||||
## not. Freezing the AnimationTree at the rest pose renders the boots perfectly,
|
## not. Freezing the AnimationTree at the rest pose renders the boots perfectly,
|
||||||
## which is what proves it is a skinning problem rather than a pose one.
|
## which is what proves it is a skinning problem rather than a pose one.
|
||||||
##
|
##
|
||||||
## Two steps, both limited to BELOW THE KNEE (taken from the skeleton's own rest
|
## Three steps:
|
||||||
## pose, so it scales to any character). Above the knee, cross-leg weighting is
|
|
||||||
## legitimate — the skirt and shorts really do span both thighs.
|
|
||||||
##
|
##
|
||||||
## 1. Snap each vertex to the leg that already dominates it and renormalise,
|
## 1. BELOW THE KNEE (height taken from the skeleton's own rest pose, so this
|
||||||
## so nothing is pulled in two directions.
|
## scales to any character): snap each vertex to the leg that already
|
||||||
## 2. Drop any triangle still spanning the two legs afterwards. Those are the
|
## dominates it and renormalise, so nothing is pulled in two directions.
|
||||||
## midline band between the ankles, which has no correct pose either way.
|
## 2. ABOVE THE KNEE, only for vertices that are part of a LEG rather than
|
||||||
|
## draping cloth: same snap, to the nearer leg. 122 vertices on Taila's
|
||||||
|
## thigh mesh carry up to 35% of the opposite leg — that is the squashing
|
||||||
|
## that survived the below-knee pass and showed up when running and
|
||||||
|
## jumping split the legs. Limb membership is decided by distance to the
|
||||||
|
## leg's own bone chain, NOT by height or by surface name: a thigh vertex
|
||||||
|
## hugs its bone, while a skirt vertex hangs well clear of both and is
|
||||||
|
## left blended, which is what lets a skirt drape across both legs.
|
||||||
|
## 3. 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.
|
||||||
|
|
||||||
const LEG_BONE_HINTS := ["thigh", "shin", "foot", "toe"]
|
const LEG_BONE_HINTS := ["thigh", "shin", "foot", "toe"]
|
||||||
## Ignore influences below this — they are rounding, not real weighting.
|
## Ignore influences below this — they are rounding, not real weighting.
|
||||||
@@ -38,6 +46,14 @@ static func repair(root: Node, skeleton: Skeleton3D) -> Array:
|
|||||||
var knee := _knee_height(skeleton)
|
var knee := _knee_height(skeleton)
|
||||||
if is_nan(knee):
|
if is_nan(knee):
|
||||||
return [0, 0]
|
return [0, 0]
|
||||||
|
var chain_l := _leg_chain(skeleton, ".L")
|
||||||
|
var chain_r := _leg_chain(skeleton, ".R")
|
||||||
|
if chain_l.is_empty() or chain_r.is_empty():
|
||||||
|
return [0, 0]
|
||||||
|
# "Part of a leg" means within roughly half the gap between the two legs of
|
||||||
|
# that leg's bone chain — self-scaling to the character's proportions.
|
||||||
|
var limb_radius: float = maxf(absf(chain_l[0].x - chain_r[0].x) * 0.5, 0.02)
|
||||||
|
|
||||||
var snapped_total := 0
|
var snapped_total := 0
|
||||||
var removed_total := 0
|
var removed_total := 0
|
||||||
for mi in root.find_children("*", "MeshInstance3D", true, false):
|
for mi in root.find_children("*", "MeshInstance3D", true, false):
|
||||||
@@ -47,12 +63,38 @@ static func repair(root: Node, skeleton: Skeleton3D) -> Array:
|
|||||||
# rig) is left alone rather than silently losing its expressions.
|
# rig) is left alone rather than silently losing its expressions.
|
||||||
if mi.mesh.get_blend_shape_count() > 0:
|
if mi.mesh.get_blend_shape_count() > 0:
|
||||||
continue
|
continue
|
||||||
var r := _repair_mesh(mi, skeleton, knee)
|
var r := _repair_mesh(mi, skeleton, knee, chain_l, chain_r, limb_radius)
|
||||||
snapped_total += r[0]
|
snapped_total += r[0]
|
||||||
removed_total += r[1]
|
removed_total += r[1]
|
||||||
return [snapped_total, removed_total]
|
return [snapped_total, removed_total]
|
||||||
|
|
||||||
|
|
||||||
|
## Rest-pose joint positions down one leg, used as a polyline to measure how
|
||||||
|
## close a vertex sits to that limb.
|
||||||
|
static func _leg_chain(skeleton: Skeleton3D, suffix: String) -> PackedVector3Array:
|
||||||
|
var out := PackedVector3Array()
|
||||||
|
for stem in ["DEF-thigh", "DEF-shin", "DEF-foot", "DEF-toe"]:
|
||||||
|
var i := skeleton.find_bone(stem + suffix)
|
||||||
|
if i < 0:
|
||||||
|
i = skeleton.find_bone(stem.trim_prefix("DEF-") + suffix)
|
||||||
|
if i >= 0:
|
||||||
|
out.append(skeleton.get_bone_global_rest(i).origin)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
## Distance from a point to a polyline.
|
||||||
|
static func _dist_to_chain(p: Vector3, chain: PackedVector3Array) -> float:
|
||||||
|
var best := INF
|
||||||
|
for i in range(chain.size() - 1):
|
||||||
|
var a: Vector3 = chain[i]
|
||||||
|
var b: Vector3 = chain[i + 1]
|
||||||
|
var ab: Vector3 = b - a
|
||||||
|
var len2: float = ab.length_squared()
|
||||||
|
var t: float = 0.0 if len2 < 0.000001 else clampf((p - a).dot(ab) / len2, 0.0, 1.0)
|
||||||
|
best = minf(best, p.distance_to(a + ab * t))
|
||||||
|
return best
|
||||||
|
|
||||||
|
|
||||||
static func _knee_height(skeleton: Skeleton3D) -> float:
|
static func _knee_height(skeleton: Skeleton3D) -> float:
|
||||||
if skeleton == null:
|
if skeleton == null:
|
||||||
return NAN
|
return NAN
|
||||||
@@ -63,15 +105,16 @@ static func _knee_height(skeleton: Skeleton3D) -> float:
|
|||||||
return NAN
|
return NAN
|
||||||
|
|
||||||
|
|
||||||
static func _repair_mesh(mi: MeshInstance3D, skeleton: Skeleton3D,
|
static func _repair_mesh(mi: MeshInstance3D, skeleton: Skeleton3D, knee: float,
|
||||||
knee: float) -> Array:
|
chain_l: PackedVector3Array, chain_r: PackedVector3Array,
|
||||||
|
limb_radius: float) -> Array:
|
||||||
var side := _side_map(mi.skin, skeleton)
|
var side := _side_map(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, knee)
|
var r := _repair_surface(arrays, side, knee, chain_l, chain_r, limb_radius)
|
||||||
snapped += r[0]
|
snapped += r[0]
|
||||||
removed += r[1]
|
removed += r[1]
|
||||||
surfaces.append({
|
surfaces.append({
|
||||||
@@ -119,8 +162,9 @@ static func _side_map(skin: Skin, skeleton: Skeleton3D) -> PackedInt32Array:
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
static func _repair_surface(arrays: Array, side: PackedInt32Array,
|
static func _repair_surface(arrays: Array, side: PackedInt32Array, knee: float,
|
||||||
knee: float) -> Array:
|
chain_l: PackedVector3Array, chain_r: PackedVector3Array,
|
||||||
|
limb_radius: float) -> Array:
|
||||||
var verts: PackedVector3Array = arrays[Mesh.ARRAY_VERTEX]
|
var verts: PackedVector3Array = arrays[Mesh.ARRAY_VERTEX]
|
||||||
var bones: PackedInt32Array = arrays[Mesh.ARRAY_BONES]
|
var bones: PackedInt32Array = arrays[Mesh.ARRAY_BONES]
|
||||||
var weights: PackedFloat32Array = arrays[Mesh.ARRAY_WEIGHTS]
|
var weights: PackedFloat32Array = arrays[Mesh.ARRAY_WEIGHTS]
|
||||||
@@ -148,10 +192,20 @@ static func _repair_surface(arrays: Array, side: PackedInt32Array,
|
|||||||
continue
|
continue
|
||||||
var keep: int = -1 if wl >= wr else 1
|
var keep: int = -1 if wl >= wr else 1
|
||||||
vside[v] = keep
|
vside[v] = keep
|
||||||
if verts[v].y > knee:
|
|
||||||
continue
|
|
||||||
if minf(wl, wr) <= EPSILON:
|
if minf(wl, wr) <= EPSILON:
|
||||||
continue # already single-legged
|
continue # already single-legged
|
||||||
|
if verts[v].y > knee:
|
||||||
|
# Above the knee, only repair vertices that belong to a LEG. Cloth
|
||||||
|
# that drapes across both legs sits clear of either bone chain and
|
||||||
|
# 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
|
||||||
# Drop the losing leg's influence and renormalise what remains.
|
# Drop the losing leg's influence and renormalise what remains.
|
||||||
var total := 0.0
|
var total := 0.0
|
||||||
for k in per:
|
for k in per:
|
||||||
|
|||||||
@@ -940,11 +940,16 @@ class ShooterPoseModifier extends SkeletonModifier3D:
|
|||||||
# the handguard, instead of both arms waving at art-directed angles near it.
|
# the handguard, instead of both arms waving at art-directed angles near it.
|
||||||
# Skeleton space: character faces +Z, up +Y, character-right -X.
|
# Skeleton space: character faces +Z, up +Y, character-right -X.
|
||||||
|
|
||||||
const GUN_PITCH_HIP := 0.38 # muzzle tilts down this much at low-ready
|
# Muzzle tilt at low-ready. Kept shallow: at the old 0.38 rad (22 degrees)
|
||||||
|
# the gun read as dangling from the hands rather than being carried.
|
||||||
|
const GUN_PITCH_HIP := 0.16
|
||||||
# Where the butt of the stock sits, relative to the right shoulder joint.
|
# Where the butt of the stock sits, relative to the right shoulder joint.
|
||||||
# The pocket is on the FRONT of the shoulder, slightly inboard of the joint.
|
# The pocket is on the FRONT of the shoulder, slightly inboard of the joint.
|
||||||
|
# Low-ready used to drop the butt 0.20 m to the ribs, which pulled the whole
|
||||||
|
# weapon off the shoulder and out of the arms — it never looked held. It now
|
||||||
|
# stays in the pocket and only relaxes slightly out of ADS.
|
||||||
const POCKET_ADS := Vector3(0.05, 0.01, 0.07) # in the shoulder pocket
|
const POCKET_ADS := Vector3(0.05, 0.01, 0.07) # in the shoulder pocket
|
||||||
const POCKET_HIP := Vector3(0.02, -0.20, 0.05) # tucked down at the ribs
|
const POCKET_HIP := Vector3(0.03, -0.07, 0.06) # still shouldered, relaxed
|
||||||
# Elbow bend hints (skeleton space). At the hip the firing elbow rides
|
# Elbow bend hints (skeleton space). At the hip the firing elbow rides
|
||||||
# down by the ribs; shouldered it flares OUT and level (the classic
|
# down by the ribs; shouldered it flares OUT and level (the classic
|
||||||
# "chicken wing"), which is what keeps the tight fold from folding the
|
# "chicken wing"), which is what keeps the tight fold from folding the
|
||||||
|
|||||||
Reference in New Issue
Block a user