fix: Articulate pants with leg bones and fix walking animation clipping
checkpoint / checkpoint (push) Successful in 6m11s
checkpoint / checkpoint (push) Successful in 6m11s
This commit is contained in:
Binary file not shown.
@@ -191,23 +191,52 @@ func load_attachment(slot_id: String, asset_id: String) -> Node3D:
|
|||||||
# skeleton; static props (the common case) are unaffected.
|
# skeleton; static props (the common case) are unaffected.
|
||||||
_retarget_skin(attachment_node)
|
_retarget_skin(attachment_node)
|
||||||
|
|
||||||
# Mount to the appropriate bone via a BoneAttachment3D so it follows the
|
# Check if this attachment has multi-bone children (e.g. Attach_LeftUpperLeg, Attach_Hips).
|
||||||
# skeleton. Falls back to a plain skeleton child if the mount bone is
|
var multi_bone_children: Array[Node] = []
|
||||||
# missing (a bad catalog entry) rather than dropping the attachment.
|
for child in attachment_node.get_children():
|
||||||
var mount_bone: String = asset_meta.get("mount_bone", "")
|
var cname := child.name
|
||||||
if mount_bone.is_empty():
|
if cname.begins_with("Attach_") or cname.begins_with("attach_"):
|
||||||
mount_bone = _slot_to_mount_bone(slot_id)
|
var target_bone := cname.substr(7)
|
||||||
|
if skeleton.find_bone(target_bone) != -1:
|
||||||
|
multi_bone_children.append(child)
|
||||||
|
|
||||||
var mount_node: Node3D
|
var mount_node: Node3D
|
||||||
if not mount_bone.is_empty() and skeleton.find_bone(mount_bone) != -1:
|
if not multi_bone_children.is_empty():
|
||||||
var ba := BoneAttachment3D.new()
|
var created_bas: Array[BoneAttachment3D] = []
|
||||||
ba.name = "Mount_%s" % slot_id if slot_id != "accessory" else "Mount_accessory_%s" % asset_id
|
for child in multi_bone_children:
|
||||||
ba.bone_name = mount_bone
|
var target_bone := child.name.substr(7)
|
||||||
skeleton.add_child(ba)
|
var ba := BoneAttachment3D.new()
|
||||||
ba.add_child(attachment_node)
|
ba.name = "Mount_%s_%s" % [slot_id, target_bone]
|
||||||
mount_node = ba
|
ba.bone_name = target_bone
|
||||||
else:
|
skeleton.add_child(ba)
|
||||||
|
attachment_node.remove_child(child)
|
||||||
|
ba.add_child(child)
|
||||||
|
created_bas.append(ba)
|
||||||
|
|
||||||
skeleton.add_child(attachment_node)
|
skeleton.add_child(attachment_node)
|
||||||
|
attachment_node.set_meta("bone_attachments", created_bas)
|
||||||
|
attachment_node.tree_exiting.connect(func():
|
||||||
|
for ba in created_bas:
|
||||||
|
if is_instance_valid(ba) and not ba.is_queued_for_deletion():
|
||||||
|
ba.queue_free()
|
||||||
|
)
|
||||||
mount_node = attachment_node
|
mount_node = attachment_node
|
||||||
|
else:
|
||||||
|
# Single mount bone via a BoneAttachment3D so it follows the skeleton.
|
||||||
|
# Falls back to a plain skeleton child if the mount bone is missing.
|
||||||
|
var mount_bone: String = asset_meta.get("mount_bone", "")
|
||||||
|
if mount_bone.is_empty():
|
||||||
|
mount_bone = _slot_to_mount_bone(slot_id)
|
||||||
|
if not mount_bone.is_empty() and skeleton.find_bone(mount_bone) != -1:
|
||||||
|
var ba := BoneAttachment3D.new()
|
||||||
|
ba.name = "Mount_%s" % slot_id if slot_id != "accessory" else "Mount_accessory_%s" % asset_id
|
||||||
|
ba.bone_name = mount_bone
|
||||||
|
skeleton.add_child(ba)
|
||||||
|
ba.add_child(attachment_node)
|
||||||
|
mount_node = ba
|
||||||
|
else:
|
||||||
|
skeleton.add_child(attachment_node)
|
||||||
|
mount_node = attachment_node
|
||||||
|
|
||||||
# Store the top-level mount node so unload can free the whole unit.
|
# Store the top-level mount node so unload can free the whole unit.
|
||||||
if slot_id == "accessory":
|
if slot_id == "accessory":
|
||||||
@@ -228,11 +257,19 @@ func unload_attachment(slot_id: String) -> void:
|
|||||||
if slot_id == "accessory":
|
if slot_id == "accessory":
|
||||||
for node in _attachments.get("accessory", []):
|
for node in _attachments.get("accessory", []):
|
||||||
if is_instance_valid(node):
|
if is_instance_valid(node):
|
||||||
|
if node.has_meta("bone_attachments"):
|
||||||
|
for ba in node.get_meta("bone_attachments"):
|
||||||
|
if is_instance_valid(ba):
|
||||||
|
ba.queue_free()
|
||||||
node.queue_free()
|
node.queue_free()
|
||||||
_attachments.erase("accessory")
|
_attachments.erase("accessory")
|
||||||
else:
|
else:
|
||||||
var node = _attachments.get(slot_id)
|
var node = _attachments.get(slot_id)
|
||||||
if is_instance_valid(node):
|
if is_instance_valid(node):
|
||||||
|
if node.has_meta("bone_attachments"):
|
||||||
|
for ba in node.get_meta("bone_attachments"):
|
||||||
|
if is_instance_valid(ba):
|
||||||
|
ba.queue_free()
|
||||||
node.queue_free()
|
node.queue_free()
|
||||||
_attachments.erase(slot_id)
|
_attachments.erase(slot_id)
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ extends Node
|
|||||||
|
|
||||||
const STRIDE := 0.42 ## peak hip swing, radians (~24 deg)
|
const STRIDE := 0.42 ## peak hip swing, radians (~24 deg)
|
||||||
const ARM_SWING := 0.32 ## peak shoulder swing, radians
|
const ARM_SWING := 0.32 ## peak shoulder swing, radians
|
||||||
const KNEE := 0.5 ## peak knee bend, radians
|
const KNEE := 0.0 ## peak knee bend, radians (0.0 for rigid placeholder limbs so feet/shoes don't dislocate)
|
||||||
const CYCLE := 1.9 ## phase (radians) advanced per metre travelled
|
const CYCLE := 1.9 ## phase (radians) advanced per metre travelled
|
||||||
const BLEND := 12.0 ## ease rate toward the target pose
|
const BLEND := 12.0 ## ease rate toward the target pose
|
||||||
const MOVE_EPS := 0.35 ## m/s below which we treat the avatar as idle
|
const MOVE_EPS := 0.35 ## m/s below which we treat the avatar as idle
|
||||||
|
|||||||
@@ -458,27 +458,38 @@ func _gen_pants_cargo() -> void:
|
|||||||
pocket_mat.albedo_color = Color(0.20, 0.24, 0.32) # Dark pocket trim
|
pocket_mat.albedo_color = Color(0.20, 0.24, 0.32) # Dark pocket trim
|
||||||
pocket_mat.roughness = 0.9
|
pocket_mat.roughness = 0.9
|
||||||
|
|
||||||
# 1. Waist section (covering hips across both legs)
|
# 1. Waist mounted to Hips bone (Y=0.76)
|
||||||
|
var attach_hips := Node3D.new()
|
||||||
|
attach_hips.name = "Attach_Hips"
|
||||||
|
root.add_child(attach_hips)
|
||||||
|
|
||||||
var waist := MeshInstance3D.new()
|
var waist := MeshInstance3D.new()
|
||||||
waist.name = "Waist"
|
waist.name = "Waist"
|
||||||
var waist_mesh := BoxMesh.new()
|
var waist_mesh := BoxMesh.new()
|
||||||
waist_mesh.size = Vector3(0.86, 0.20, 0.50)
|
waist_mesh.size = Vector3(0.88, 0.22, 0.52)
|
||||||
waist.mesh = waist_mesh
|
waist.mesh = waist_mesh
|
||||||
waist.position = Vector3(0.0, -0.06, 0.0) # Relative to Hips bone (Y=0.76)
|
waist.position = Vector3(0.0, -0.05, 0.0) # Relative to Hips (Y=0.76), covers world Y 0.60 to 0.82
|
||||||
waist.material_override = pants_mat
|
waist.material_override = pants_mat
|
||||||
root.add_child(waist)
|
attach_hips.add_child(waist)
|
||||||
|
|
||||||
# 2. Left and Right Pant Legs (legs are 0.40 wide, 0.78 tall, 0.46 deep, centers X = +-0.21)
|
# 2. Left and Right Pant Legs mounted to LeftUpperLeg and RightUpperLeg
|
||||||
for side in [-1.0, 1.0]:
|
for side in [-1.0, 1.0]:
|
||||||
var sname := "Right" if side < 0.0 else "Left"
|
var sname := "Right" if side < 0.0 else "Left"
|
||||||
|
var attach_leg := Node3D.new()
|
||||||
|
attach_leg.name = "Attach_%sUpperLeg" % sname
|
||||||
|
root.add_child(attach_leg)
|
||||||
|
|
||||||
var leg := MeshInstance3D.new()
|
var leg := MeshInstance3D.new()
|
||||||
leg.name = "PantLeg_%s" % sname
|
leg.name = "PantLeg_%s" % sname
|
||||||
var leg_mesh := BoxMesh.new()
|
var leg_mesh := BoxMesh.new()
|
||||||
leg_mesh.size = Vector3(0.44, 0.64, 0.50) # Covers down to ankle
|
# Encases leg from hip joint down to ankle/sole.
|
||||||
|
# Relative to UpperLeg bone (Y=0.74), center Y=-0.37, height 0.76 covers Y=-0.75 to +0.01 (world Y=-0.01 to 0.75).
|
||||||
|
# Width 0.44 encloses 0.40 leg, Depth 0.50 encloses 0.46 leg.
|
||||||
|
leg_mesh.size = Vector3(0.44, 0.76, 0.50)
|
||||||
leg.mesh = leg_mesh
|
leg.mesh = leg_mesh
|
||||||
leg.position = Vector3(side * 0.21, -0.34, 0.0)
|
leg.position = Vector3(0.0, -0.37, 0.0)
|
||||||
leg.material_override = pants_mat
|
leg.material_override = pants_mat
|
||||||
root.add_child(leg)
|
attach_leg.add_child(leg)
|
||||||
|
|
||||||
# Cargo pocket on outer side
|
# Cargo pocket on outer side
|
||||||
var pocket := MeshInstance3D.new()
|
var pocket := MeshInstance3D.new()
|
||||||
@@ -486,9 +497,9 @@ func _gen_pants_cargo() -> void:
|
|||||||
var pocket_mesh := BoxMesh.new()
|
var pocket_mesh := BoxMesh.new()
|
||||||
pocket_mesh.size = Vector3(0.08, 0.20, 0.24)
|
pocket_mesh.size = Vector3(0.08, 0.20, 0.24)
|
||||||
pocket.mesh = pocket_mesh
|
pocket.mesh = pocket_mesh
|
||||||
pocket.position = Vector3(side * 0.44, -0.32, 0.0)
|
pocket.position = Vector3(side * 0.23, -0.32, 0.0)
|
||||||
pocket.material_override = pocket_mat
|
pocket.material_override = pocket_mat
|
||||||
root.add_child(pocket)
|
attach_leg.add_child(pocket)
|
||||||
|
|
||||||
_write_glb(root, "pants_cargo_01")
|
_write_glb(root, "pants_cargo_01")
|
||||||
root.free()
|
root.free()
|
||||||
|
|||||||
Reference in New Issue
Block a user