fix: cut the welded ankle band, seat the gun in the hand, flash at the barrel
1. Ankle cuffs. The previous pass re-weighted the offending vertices to the nearer leg, which was the wrong call: the strip between the ankles is REAL geometry spanning the gap (~10 cm of ClothCAndW crossing x = -0.05 .. +0.05 at ankle height), so re-weighting only tore it in half — a visible seam that still stretched. A triangle with one corner weighted to each leg has no correct pose; it must stretch the moment the legs separate. SkinMeshRepair now deletes those triangles instead (48 on Taila: the cloth band plus its outline shell). The cut is limited to BELOW THE KNEE, taken from the skeleton's own rest pose rather than a hardcoded height, because above the knee cross-leg geometry is legitimate — the skirt and shorts genuinely span left-thigh to right-thigh weights at the crotch. 2. The M4 floated because _measure_weapon derived the grip from mesh AABBs, and the FBX guns report bind-pose bounds tens of metres across — it measured the M4 as 24 m long and pushed the gun 7.5 m in front of the character. Bounding boxes are simply not trustworthy for these meshes. The grip was already authored elsewhere: WeaponManager places the first-person viewmodel's hands at fixed points in weapon space, and every weapon marks its barrel tip with muzzle_flash.position. Those move to WeaponGrips (dependency-free, so both the weapon system and the character models can use it without dragging each other's load order along) and third person now reaches for exactly the points the viewmodel uses. Every weapon's grip now lands 0.073 m from the hand — the M4 included, down from 2.32 m — with barrel lengths that match the models (0.30 m for the MP7, 0.68 m for the DMR). 3. Muzzle flash and tracers were spawned off the viewmodel's muzzle. The viewmodel is parented to the camera, so its muzzle sits inside the player's head — in third person the flash appeared by the character's shoulder. world_muzzle() returns the muzzle of the gun actually in the character's hands whenever the character is what the viewer sees, and the networked fire-effect RPC now sends that position too, so remote players stop seeing tracers leave the shooter's head. Measured: the third-person origin sits at the held gun, 0.54 m below the head, instead of on the camera. 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
35ada4f34a
commit
ec6b8228da
@@ -162,12 +162,13 @@ func load_model(path: String) -> void:
|
||||
push_warning("SkinnedPlayerModel: no skeleton in '%s'" % path)
|
||||
else:
|
||||
_ensure_meshes_bound(scene)
|
||||
# Stray cross-body leg weights make the two ankle cuffs look welded
|
||||
# together as the legs separate. See SkinWeightRepair.
|
||||
var reweighted := SkinWeightRepair.repair(scene, skeleton)
|
||||
if reweighted > 0:
|
||||
print("SkinnedPlayerModel: re-weighted %d cross-leg vertices in '%s'"
|
||||
% [reweighted, path.get_file()])
|
||||
# Below-the-knee geometry welded across both legs can only ever stretch
|
||||
# as they separate — it made the ankle cuffs look linked. See
|
||||
# SkinMeshRepair.
|
||||
var culled := SkinMeshRepair.repair(scene, skeleton)
|
||||
if culled > 0:
|
||||
print("SkinnedPlayerModel: removed %d cross-leg triangles from '%s'"
|
||||
% [culled, path.get_file()])
|
||||
_pose_mod = ShooterPoseModifier.new()
|
||||
_pose_mod.name = "ShooterPose"
|
||||
skeleton.add_child(_pose_mod)
|
||||
@@ -626,43 +627,52 @@ func set_weapon(script_path: String) -> void:
|
||||
add_child(w)
|
||||
|
||||
|
||||
## Measure the held weapon along its own barrel axis so the pose layer knows
|
||||
## where the real foregrip and stock butt are, instead of guessing. Distances
|
||||
## are from the GRIP (the weapon node's origin, which sits in the hand), in
|
||||
## metres of character space.
|
||||
## The muzzle of the gun actually in this character's hand.
|
||||
##
|
||||
## Anything the WORLD sees — tracers, muzzle flash, the shot's audio position —
|
||||
## has to originate here whenever the character model is what the viewer is
|
||||
## looking at. The first-person viewmodel is parented to the camera, so ITS
|
||||
## muzzle sits inside the player's head; using it in third person put the flash
|
||||
## next to the character's shoulder.
|
||||
func get_muzzle_node() -> Node3D:
|
||||
if not _weapon_attachment or _weapon_attachment.get_child_count() == 0:
|
||||
return null
|
||||
var w := _weapon_attachment.get_child(0)
|
||||
if "muzzle_flash" in w and w.muzzle_flash:
|
||||
return w.muzzle_flash
|
||||
return w as Node3D
|
||||
|
||||
|
||||
## Seat the weapon in the hand and tell the pose layer where the support hand
|
||||
## and stock are, using the weapon's OWN authored markers.
|
||||
##
|
||||
## This used to derive everything from mesh AABBs, which silently produced
|
||||
## nonsense: the FBX guns report bind-pose bounds tens of metres across (the M4
|
||||
## measured 24 m long), so the grip offset threw the gun 7.5 m in front of the
|
||||
## character. Nothing about a mesh's bounding box is trustworthy here.
|
||||
##
|
||||
## The reliable data is already authored: WeaponManager places the first-person
|
||||
## 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.
|
||||
func _measure_weapon(w: Node3D) -> void:
|
||||
var local_fwd := Vector3(0, 0, -1) # the weapon's own muzzle axis
|
||||
var min_t := INF # most negative = stock end
|
||||
var max_t := -INF # most positive = muzzle end
|
||||
for mi in w.find_children("*", "MeshInstance3D", true, false):
|
||||
if not mi.mesh:
|
||||
continue
|
||||
var xf: Transform3D = w.global_transform.affine_inverse() * mi.global_transform
|
||||
var aabb: AABB = mi.mesh.get_aabb()
|
||||
for i in 8:
|
||||
var t: float = (xf * aabb.get_endpoint(i)).dot(local_fwd)
|
||||
min_t = minf(min_t, t)
|
||||
max_t = maxf(max_t, t)
|
||||
if min_t > max_t:
|
||||
return
|
||||
var s: float = absf(w.scale.z)
|
||||
var total := max_t - min_t
|
||||
if total < 0.0001:
|
||||
return
|
||||
# Weapon models put their origin wherever the artist left it — for the M4
|
||||
# that is barely 10 cm behind the muzzle end, so hanging the hand there
|
||||
# and then parking the stock in the shoulder shoved the hand INTO the
|
||||
# shoulder and the arm folded up behind the head. Re-seat the weapon so
|
||||
# the hand sits at a realistic pistol-grip point (~a third back from the
|
||||
# muzzle), which puts real length of gun behind the hand to reach the
|
||||
# shoulder with.
|
||||
var grip_at := min_t + total * 0.32
|
||||
w.position -= _pose_mod.gun_fwd_hand * (grip_at * s)
|
||||
var back := (grip_at - min_t) * s # butt of the stock, behind the grip
|
||||
var front := (max_t - grip_at) * s # muzzle, ahead of the grip
|
||||
_pose_mod.gun_stock = clampf(back, 0.10, 0.40)
|
||||
# Support hand rides partway out the handguard, never past the muzzle.
|
||||
_pose_mod.gun_fore = clampf(front * 0.55, 0.14, 0.45)
|
||||
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
|
||||
|
||||
# Support hand: how far along the barrel the viewmodel's off hand rides.
|
||||
var fore: float = absf((support - grip).dot(fwd))
|
||||
# Stock: not authored anywhere, so derive it from the barrel length. Half
|
||||
# the grip-to-muzzle distance behind the grip lands the butt in the
|
||||
# shoulder pocket for every gun in the set.
|
||||
var muzzle_dist: float = WeaponGrips.DEFAULT_MUZZLE_DIST
|
||||
if "muzzle_flash" in w and w.muzzle_flash:
|
||||
muzzle_dist = maxf(absf((w.muzzle_flash.position - grip).dot(fwd)), 0.1)
|
||||
_pose_mod.gun_fore = clampf(fore, 0.14, 0.45)
|
||||
_pose_mod.gun_stock = clampf(muzzle_dist * 0.5, 0.10, 0.40)
|
||||
|
||||
|
||||
# ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user