feat: Implement draggable torso and head IK targets in the test harness
- Added draggable handles for the torso and head to the test harness. - Updated `IK_HANDLE_PATHS` to include new entries for "Head" and "Torso". - Implemented distinct colors for the torso (magenta) and head (yellow) markers. - Added a visual aid (aim line) to indicate the head's LookAt target direction. - Ensured that dragging the torso moves only the torso marker, allowing for limb stretching towards stationary targets.
This commit is contained in:
+72
-4
@@ -24,6 +24,8 @@ const BONE_COLOR_RIGHT := Color(1.00, 0.50, 0.20)
|
||||
const BONE_COLOR_CENTRAL := Color(1.00, 1.00, 1.00)
|
||||
const HANDLE_COLOR_HAND := Color(0.00, 1.00, 0.00) # green
|
||||
const HANDLE_COLOR_FOOT := Color(0.00, 0.50, 1.00) # blue
|
||||
const HANDLE_COLOR_HEAD := Color(1.00, 1.00, 0.00) # yellow
|
||||
const HANDLE_COLOR_TORSO := Color(1.00, 0.00, 1.00) # magenta
|
||||
|
||||
const SKELETON_PATH := "Skeleton2D"
|
||||
|
||||
@@ -35,11 +37,28 @@ const QUICK_LOADS: Array = [
|
||||
]
|
||||
|
||||
## IK handle node paths (relative to rig root), keyed by handle name.
|
||||
## The four limb targets drive TwoBoneIK; "Head" is the LookAt aim point and
|
||||
## "Torso" is the hip anchor (dragging it translates the whole rig via its
|
||||
## RemoteTransform2D — no target-following logic).
|
||||
const IK_HANDLE_PATHS: Dictionary = {
|
||||
"Left_Hand": "IK_Targets/Left_Hand",
|
||||
"Right_Hand": "IK_Targets/Right_Hand",
|
||||
"Left_Leg": "IK_Targets/Left_Leg",
|
||||
"Right_Leg": "IK_Targets/Right_Leg",
|
||||
"Head": "IK_Targets/Head",
|
||||
"Torso": "IK_Targets/Torso",
|
||||
}
|
||||
|
||||
## Leaf bone → IK target node path (relative to rig root), keyed by bone name.
|
||||
## Used by the bone overlay to draw the forearm/shin segments out to their
|
||||
## wrist/ankle targets. The Head leaf is NOT listed here — its IK target is a
|
||||
## LookAt aim point, so it draws along its own bone direction instead (see
|
||||
## _draw_bones' leaf fallback).
|
||||
const LEAF_BONE_IK_PATHS: Dictionary = {
|
||||
"LeftLowerArm": "IK_Targets/Left_Hand",
|
||||
"RightLowerArm": "IK_Targets/Right_Hand",
|
||||
"LeftLowerLeg": "IK_Targets/Left_Leg",
|
||||
"RightLowerLeg": "IK_Targets/Right_Leg",
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -282,9 +301,35 @@ func _draw_bones() -> void:
|
||||
var origin := bone.global_position
|
||||
_debug_overlay.draw_circle(origin, dot_r, color)
|
||||
|
||||
var parent := bone.get_parent()
|
||||
if parent is Bone2D:
|
||||
_debug_overlay.draw_line((parent as Bone2D).global_position, origin, color, line_w)
|
||||
# Collect the bone's Bone2D children (true bone segments, including the
|
||||
# nested lower bones).
|
||||
var bone_children: Array[Bone2D] = []
|
||||
for child in bone.get_children():
|
||||
if child is Bone2D:
|
||||
bone_children.append(child as Bone2D)
|
||||
|
||||
if bone_children.is_empty():
|
||||
# Leaf bone — draw out to its IK target (forearm/shin), falling
|
||||
# back to the bone's own direction (bone_angle + rotation) when
|
||||
# there is no target (e.g. the Head, whose target is a LookAt aim
|
||||
# point, not a joint).
|
||||
var target := _leaf_bone_target(bone)
|
||||
if target != null:
|
||||
_debug_overlay.draw_line(origin, target.global_position, color, line_w)
|
||||
else:
|
||||
var fallback := origin + Vector2(bone.get_length(), 0.0).rotated(deg_to_rad(bone.bone_angle)).rotated(bone.global_rotation)
|
||||
_debug_overlay.draw_line(origin, fallback, color, line_w)
|
||||
else:
|
||||
for child_bone in bone_children:
|
||||
_debug_overlay.draw_line(origin, child_bone.global_position, color, line_w)
|
||||
|
||||
|
||||
func _leaf_bone_target(bone: Bone2D) -> Node2D:
|
||||
if not LEAF_BONE_IK_PATHS.has(bone.name):
|
||||
return null
|
||||
if _rig == null or not is_instance_valid(_rig):
|
||||
return null
|
||||
return _rig.get_node_or_null(NodePath(str(LEAF_BONE_IK_PATHS[bone.name]))) as Node2D
|
||||
|
||||
|
||||
func _draw_ik_handles() -> void:
|
||||
@@ -296,11 +341,34 @@ func _draw_ik_handles() -> void:
|
||||
var handle := _ik_handles[handle_name] as Marker2D
|
||||
if handle == null or not is_instance_valid(handle):
|
||||
continue
|
||||
var color := HANDLE_COLOR_HAND if handle_name.ends_with("Hand") else HANDLE_COLOR_FOOT
|
||||
var color := _handle_color(handle_name)
|
||||
var pos := handle.global_position
|
||||
_debug_overlay.draw_circle(pos, r, color)
|
||||
_debug_overlay.draw_arc(pos, r, 0.0, TAU, 24, Color(1.0, 1.0, 1.0, 0.8), outline_w)
|
||||
|
||||
# Head aim line — visual aid showing what the head bone is aiming at.
|
||||
var head_handle: Marker2D = _ik_handles.get("Head", null) as Marker2D
|
||||
if head_handle != null and is_instance_valid(head_handle) \
|
||||
and _skeleton != null and is_instance_valid(_skeleton):
|
||||
var head_bone := _skeleton.get_node_or_null(NodePath("Torso/Head")) as Bone2D
|
||||
if head_bone != null and is_instance_valid(head_bone):
|
||||
_debug_overlay.draw_line(
|
||||
head_bone.global_position,
|
||||
head_handle.global_position,
|
||||
Color(1.0, 1.0, 0.0, 0.5),
|
||||
1.5 / zoom
|
||||
)
|
||||
|
||||
|
||||
func _handle_color(handle_name: String) -> Color:
|
||||
match handle_name:
|
||||
"Head":
|
||||
return HANDLE_COLOR_HEAD
|
||||
"Torso":
|
||||
return HANDLE_COLOR_TORSO
|
||||
_:
|
||||
return HANDLE_COLOR_HAND if handle_name.ends_with("Hand") else HANDLE_COLOR_FOOT
|
||||
|
||||
|
||||
func _bone_color(bone_name: String) -> Color:
|
||||
if bone_name.begins_with("Left"):
|
||||
|
||||
Reference in New Issue
Block a user