monkeystudio / scripts /modules /rigging.gd
geter62's picture
Upload folder using huggingface_hub
84e8204 verified
Raw History Blame Contribute Delete
6.2 kB
extends Node
##
## RiggingModule - armature creation and automatic vertex weighting (Features 41-60).
##
## Creates a Skeleton3D, lets the user add bones as a transform hierarchy, and
## binds skinning data (ARRAY_BONES + ARRAY_WEIGHTS) to a MeshInstance3D using
## inverse-distance weighting against bone segments. The result is a normal,
## engine-native skinned mesh - no custom shader needed.
##
## Bone definitions the tool builds. Each entry: { name, parent, position, tail }
var _bones: Array[Dictionary] = []
var _skeleton: Skeleton3D = null
var _host: Node = null
var _bound_mesh: MeshInstance3D = null
func _activate(host: Node) -> void:
_host = host
StudioBus.object_selected.connect(_on_object_selected)
print("[Rigging] activated")
func _deactivate() -> void:
if StudioBus.object_selected.is_connected(_on_object_selected):
StudioBus.object_selected.disconnect(_on_object_selected)
_host = null
func _on_object_selected(node: Node3D) -> void:
_bound_mesh = node as MeshInstance3D
## --- Armature creation ------------------------------------------------------
## Add a bone. parent="" means root. Returns the new bone index.
func add_bone(name: String, parent: String, position: Vector3, tail: Vector3 = Vector3.ZERO) -> int:
_ensure_skeleton()
var parent_idx := -1
if parent != "":
parent_idx = _find_bone(parent)
var idx := _skeleton.get_bone_count()
_skeleton.add_bone(name)
if parent_idx >= 0:
_skeleton.set_bone_parent(idx, parent_idx)
var rest := Transform3D()
# Point the bone's -Z toward its tail so rotations look natural.
var dir := (tail - position)
if dir.length_squared() > 0.000001:
rest = Transform3D(_look_at_basis(dir.normalized()), position)
else:
rest = Transform3D(Basis.IDENTITY, position)
_skeleton.set_bone_rest(idx, rest)
_skeleton.set_bone_pose_position(idx, position)
_bones.append({"name": name, "parent": parent, "index": idx})
StudioBus.scene_modified.emit()
return idx
func _ensure_skeleton() -> void:
if _skeleton != null and is_instance_valid(_skeleton):
return
_skeleton = Skeleton3D.new()
_skeleton.name = "Armature"
if _host != null:
_host.get_node_or_null("Rig").add_child(_skeleton) if _host.has_node("Rig") else _host.add_child(_skeleton)
func _find_bone(name: String) -> int:
if _skeleton == null:
return -1
for i in range(_skeleton.get_bone_count()):
if _skeleton.get_bone_name(i) == name:
return i
return -1
func _look_at_basis(dir: Vector3) -> Basis:
var up := Vector3.UP
if absf(dir.dot(up)) > 0.99:
up = Vector3.RIGHT
var right := dir.cross(up).normalized()
var real_up := right.cross(dir).normalized()
return Basis(right, real_up, -dir)
## Build a simple starter humanoid-ish spine + limbs for a mesh's bounds.
func generate_default_armature(mesh: MeshInstance3D) -> void:
if mesh == null or mesh.mesh == null:
return
var size := mesh.mesh.get_aabb().size
var h := size.y
add_bone("root", "", Vector3.ZERO, Vector3(0, h * 0.1, 0))
add_bone("spine", "root", Vector3(0, h * 0.1, 0), Vector3(0, h * 0.35, 0))
add_bone("chest", "spine", Vector3(0, h * 0.35, 0), Vector3(0, h * 0.6, 0))
add_bone("neck", "chest", Vector3(0, h * 0.6, 0), Vector3(0, h * 0.72, 0))
add_bone("head", "neck", Vector3(0, h * 0.72, 0), Vector3(0, h * 0.95, 0))
add_bone("arm_l", "chest", Vector3(-size.x * 0.4, h * 0.6, 0), Vector3(-size.x * 0.9, h * 0.45, 0))
add_bone("arm_r", "chest", Vector3(size.x * 0.4, h * 0.6, 0), Vector3(size.x * 0.9, h * 0.45, 0))
add_bone("leg_l", "root", Vector3(-size.x * 0.2, 0, 0), Vector3(-size.x * 0.2, -h * 0.5, 0))
add_bone("leg_r", "root", Vector3(size.x * 0.2, 0, 0), Vector3(size.x * 0.2, -h * 0.5, 0))
bind_weights(mesh)
## --- Automatic weight binding -----------------------------------------------
## Compute skinning arrays using inverse-distance to bone segments and apply.
## `max_influences` must be <= 4 for the standard mobile skinning path.
func bind_weights(mesh: MeshInstance3D, max_influences: int = 4) -> void:
if mesh == null or _skeleton == null or mesh.mesh == null or mesh.mesh.get_surface_count() == 0:
return
var arr := mesh.mesh.surface_get_arrays(0)
var verts: PackedVector3Array = arr[Mesh.ARRAY_VERTEX]
var n := verts.size()
var bone_count := _skeleton.get_bone_count()
var bone_segments: Array = []
for i in range(bone_count):
var pos := _skeleton.get_bone_global_rest(i).origin
bone_segments.append({"idx": i, "pos": pos})
var vbones := PackedInt32Array()
var vweights := PackedFloat32Array()
vbones.resize(n * 4)
vweights.resize(n * 4)
var to_mesh := mesh.global_transform.affine_inverse()
for vi in range(n):
var p := verts[vi]
# Rank bones by distance to their head position.
var dists: Array = []
for b in bone_segments:
var local_bone: Vector3 = to_mesh * (mesh.global_transform * b["pos"])
dists.append({"idx": b["idx"], "d": p.distance_to(local_bone)})
dists.sort_custom(func(a, b): return a["d"] < b["d"])
var influ: int = mini(max_influences, dists.size())
var wsum := 0.0
var ws: Array[float] = []
for k in range(influ):
var d: float = maxf(0.0001, dists[k]["d"])
var w := 1.0 / (d * d)
ws.append(w)
wsum += w
for slot in range(4):
var base := vi * 4 + slot
if slot < influ and wsum > 0.0:
vbones[base] = dists[slot]["idx"]
vweights[base] = ws[slot] / wsum
else:
vbones[base] = 0
vweights[base] = 0.0
# Rebuild the mesh surface with skinning channels added.
arr[Mesh.ARRAY_BONES] = vbones
arr[Mesh.ARRAY_WEIGHTS] = vweights
var new_mesh := MeshPool.acquire_mesh()
new_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arr)
mesh.mesh = new_mesh
mesh.skeleton = mesh.get_path_to(_skeleton)
# A Skin must be bound so the renderer knows which skeleton supplies the
# bone transforms for these indices. add_bind(bone_index, rest_pose).
var skin := Skin.new()
for i in range(bone_count):
skin.add_bind(i, _skeleton.get_bone_global_rest(i))
mesh.skin = skin
StudioBus.scene_modified.emit()
print("[Rigging] bound %d verts to %d bones" % [n, bone_count])
func bone_count() -> int:
return _skeleton.get_bone_count() if _skeleton != null else 0
func get_skeleton() -> Skeleton3D:
return _skeleton