feat(client): add BoneAttachment3D overhead anchor above Head bone (#712)
Infrastructure for floating UI elements (status indicators, thought bubbles, alert markers). Marker3D at Vector3(0, 0.3, 0) offset from Head bone, exposed via get_overhead_anchor() public API. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,7 @@ extends Node3D
|
||||
## get_clothing_node_count() — number of clothing MeshInstance3D nodes
|
||||
## get_accessory_node_count() — number of accessory BoneAttachment3D nodes
|
||||
## get_skin_tone_texture_name(index) — skin tone texture filename key for index
|
||||
## get_overhead_anchor() — Marker3D above Head bone for floating UI (#712)
|
||||
##
|
||||
## D-159 (11 body types), D-160 (18 segments), D-161 (head separate),
|
||||
## D-162 (clothing pre-fitted), D-163 (heads via BoneAttachment3D), D-164 (skeleton fork)
|
||||
@@ -101,6 +102,8 @@ var _clothing_meshes: Array[MeshInstance3D] = []
|
||||
var _bone_attachments: Array[BoneAttachment3D] = []
|
||||
var _outline_nodes: Array[MeshInstance3D] = []
|
||||
var _accessory_attachments: Array[BoneAttachment3D] = []
|
||||
var _overhead_anchor: Marker3D = null
|
||||
var _overhead_attachment: BoneAttachment3D = null
|
||||
|
||||
# Inspectable state for tests
|
||||
var _active_torso_variant: String = "full"
|
||||
@@ -218,6 +221,13 @@ func get_skin_tone_texture_name(index: int) -> String:
|
||||
return SKIN_TONES[clampi(index, 0, SKIN_TONES.size() - 1)]["tex"]
|
||||
|
||||
|
||||
## Return the overhead anchor Marker3D (#712). Null if skeleton not loaded.
|
||||
## Anchor point for floating UI elements: status indicators, thought bubbles,
|
||||
## alert markers, speech icons. Positioned ~0.3m above the Head bone.
|
||||
func get_overhead_anchor() -> Marker3D:
|
||||
return _overhead_anchor
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Internal — teardown
|
||||
# =============================================================================
|
||||
@@ -234,6 +244,13 @@ func _clear() -> void:
|
||||
node.free()
|
||||
_outline_nodes.clear()
|
||||
|
||||
# #712: free overhead anchor before general bone attachments
|
||||
if is_instance_valid(_overhead_attachment) and _overhead_attachment.get_parent():
|
||||
_overhead_attachment.get_parent().remove_child(_overhead_attachment)
|
||||
_overhead_attachment.free()
|
||||
_overhead_attachment = null
|
||||
_overhead_anchor = null
|
||||
|
||||
for att in _bone_attachments:
|
||||
if is_instance_valid(att) and att.get_parent():
|
||||
att.get_parent().remove_child(att)
|
||||
@@ -291,6 +308,7 @@ func _load_skeleton() -> void:
|
||||
for m in armature_meshes:
|
||||
print(" armature mesh: ", m.name, " visible=", m.visible)
|
||||
_validate_slot_bones()
|
||||
_create_overhead_anchor()
|
||||
|
||||
|
||||
## Warn on any SLOT_TO_BONE entry that doesn't exist in the loaded skeleton.
|
||||
@@ -302,6 +320,27 @@ func _validate_slot_bones() -> void:
|
||||
push_warning("CharacterVisual: SLOT_TO_BONE['%s'] = '%s' — bone not found in skeleton" % [slot, bone_name])
|
||||
|
||||
|
||||
## #712: Create a Marker3D anchored ~0.3m above the Head bone via BoneAttachment3D.
|
||||
## Anchor point for floating UI elements (status indicators, thought bubbles, etc.).
|
||||
func _create_overhead_anchor() -> void:
|
||||
if _skeleton == null:
|
||||
return
|
||||
var bone_idx := _skeleton.find_bone("Head")
|
||||
if bone_idx == -1:
|
||||
push_warning("CharacterVisual: Head bone not found — overhead anchor not created")
|
||||
return
|
||||
_overhead_attachment = BoneAttachment3D.new()
|
||||
_overhead_attachment.bone_name = "Head"
|
||||
_overhead_attachment.bone_idx = bone_idx
|
||||
_overhead_attachment.name = "OverheadAttachment"
|
||||
_skeleton.add_child(_overhead_attachment)
|
||||
|
||||
_overhead_anchor = Marker3D.new()
|
||||
_overhead_anchor.name = "OverheadAnchor"
|
||||
_overhead_anchor.position = Vector3(0, 0.3, 0)
|
||||
_overhead_attachment.add_child(_overhead_anchor)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Internal — body segments (D-160)
|
||||
# =============================================================================
|
||||
|
||||
Reference in New Issue
Block a user