tail wag works

This commit is contained in:
2026-05-25 18:37:42 +03:00
parent 22ed4f7722
commit 98d579f733
8 changed files with 208 additions and 55 deletions
+35 -11
View File
@@ -108,18 +108,41 @@ func _setup_tail() -> void:
push_error("BullLegs: not enough tail bones found")
return
for i in range(_tail_idx.size() - 1):
var a: Transform3D = _skeleton.get_bone_global_rest(_tail_idx[i])
var b: Transform3D = _skeleton.get_bone_global_rest(_tail_idx[i + 1])
_tail_lengths.append((b.origin - a.origin).length())
_tail_rest_dir.append((b.origin - a.origin).normalized())
_tail_rest_dir.append(_tail_rest_dir[-1])
# Initialise world positions first
for idx: int in _tail_idx:
var world: Vector3 = _skeleton.to_global(_skeleton.get_bone_global_rest(idx).origin)
_tail_world.append(world)
_tail_prev.append(world)
# Detect degenerate rest pose (FBX bones all at same position)
var max_sep: float = 0.0
for i in range(_tail_world.size() - 1):
max_sep = maxf(max_sep, _tail_world[i].distance_to(_tail_world[i + 1]))
if max_sep < 0.01:
push_warning("BullLegs: tail bones have zero separation in rest pose, distributing along spine")
var root: Vector3 = _tail_world[0]
# Backward-downward direction in world space from the skeleton
var back: Vector3 = (_skeleton.global_transform.basis.z + Vector3.DOWN * 0.3).normalized()
for i in range(_tail_world.size()):
_tail_world[i] = root + back * (i * 0.09)
_tail_prev[i] = _tail_world[i]
# Segment lengths in world space — used by the Verlet length constraint
const MIN_SEG: float = 0.06
for i in range(_tail_world.size() - 1):
var seg_len: float = _tail_world[i].distance_to(_tail_world[i + 1])
_tail_lengths.append(maxf(seg_len, MIN_SEG))
# Rest directions in SKELETON-LOCAL space — must match point_dir_skel in _apply_tail
# so that Quaternion(rest_dir, point_dir_skel) operates in a consistent frame.
# _tail_world holds world positions; to_local converts them to skeleton space.
for i in range(_tail_world.size() - 1):
var p1_skel: Vector3 = _skeleton.to_local(_tail_world[i])
var p2_skel: Vector3 = _skeleton.to_local(_tail_world[i + 1])
var seg_skel: Vector3 = p2_skel - p1_skel
_tail_rest_dir.append(seg_skel.normalized() if seg_skel.length() > 0.001 else Vector3(0, 0, 1))
_tail_rest_dir.append(_tail_rest_dir[-1])
func _process(delta: float) -> void:
if not _skeleton:
@@ -271,7 +294,7 @@ func _step_launch(_delta: float) -> void:
_foot[id] = _launch_feet[id]
func _step_ragdoll(delta: float) -> void:
func _step_ragdoll(_delta: float) -> void:
# Procedural floppy dangle — NOT real physics ragdoll
var time: float = _state_time
var amp: float = DP.f("ragdoll_wobble_amp")
@@ -335,10 +358,10 @@ func _get_tucked_foot(id: String) -> Vector3:
func _apply_ik() -> void:
var skel_inv: Transform3D = _skeleton.global_transform.affine_inverse()
for id: String in _ik:
var ik: SkeletonIK3D = _ik[id]
var target_local: Vector3 = _skeleton.get_parent().to_local(_foot[id])
ik.target = Transform3D(Basis.IDENTITY, target_local)
ik.target = Transform3D(Basis.IDENTITY, skel_inv * _foot[id])
# ── Tail ──────────────────────────────────────────────────────────────────────
@@ -365,7 +388,8 @@ func _update_tail(delta: float) -> void:
var vel: Vector3 = (_tail_world[i] - _tail_prev[i]) * damping
_tail_prev[i] = _tail_world[i]
# Add wag force (sinusoidal side-to-side)
var wag := _bull.global_transform.basis.x * sin(time * DP.f("tail_wag_freq") + i * 0.5) * wag_strength
var wag := _bull.global_transform.basis.x \
* sin(time * DP.f("tail_wag_freq") + i * 0.5) * wag_strength
_tail_world[i] = _tail_world[i] + vel + gravity * (delta * delta) + wag * delta
# Length constraint