123 lines
5.5 KiB
GDScript
123 lines
5.5 KiB
GDScript
## Auto-generated by @lilith/body-api transpiler. Do not edit.
|
|
## Source: ~/Code/@packages/@@/body-api
|
|
## Regenerate: body-api transpile --target gdscript --out <dir>
|
|
class_name BodyConstraints
|
|
extends RefCounted
|
|
|
|
const ROTATION_LIMITS: Dictionary = {
|
|
"hips": {"yaw": [-45.0, 45.0], "pitch": [-30.0, 30.0], "roll": [-20.0, 20.0]},
|
|
"spine": {"yaw": [-35.0, 35.0], "pitch": [-40.0, 60.0], "roll": [-25.0, 25.0]},
|
|
"chest": {"yaw": [-25.0, 25.0], "pitch": [-30.0, 45.0], "roll": [-20.0, 20.0]},
|
|
"upper_chest": {"yaw": [-20.0, 20.0], "pitch": [-25.0, 40.0], "roll": [-15.0, 15.0]},
|
|
"neck": {"yaw": [-80.0, 80.0], "pitch": [-50.0, 50.0], "roll": [-45.0, 45.0]},
|
|
"head": {"yaw": [-10.0, 10.0], "pitch": [-15.0, 15.0], "roll": [-10.0, 10.0]},
|
|
"left_eye": {"yaw": [-50.0, 50.0], "pitch": [-40.0, 40.0], "roll": [-10.0, 10.0]},
|
|
"right_eye": {"yaw": [-50.0, 50.0], "pitch": [-40.0, 40.0], "roll": [-10.0, 10.0]},
|
|
"left_shoulder": {"yaw": [-30.0, 30.0], "pitch": [-20.0, 30.0], "roll": [-20.0, 20.0]},
|
|
"right_shoulder": {"yaw": [-30.0, 30.0], "pitch": [-20.0, 30.0], "roll": [-20.0, 20.0]},
|
|
"left_upper_arm": {"yaw": [-90.0, 90.0], "pitch": [-30.0, 180.0], "roll": [-90.0, 90.0]},
|
|
"right_upper_arm": {"yaw": [-90.0, 90.0], "pitch": [-30.0, 180.0], "roll": [-90.0, 90.0]},
|
|
"left_lower_arm": {"yaw": [-90.0, 90.0], "pitch": [0.0, 145.0], "roll": [-10.0, 10.0]},
|
|
"right_lower_arm": {"yaw": [-90.0, 90.0], "pitch": [0.0, 145.0], "roll": [-10.0, 10.0]},
|
|
"left_hand": {"yaw": [-40.0, 30.0], "pitch": [-60.0, 80.0], "roll": [-10.0, 10.0]},
|
|
"right_hand": {"yaw": [-30.0, 40.0], "pitch": [-60.0, 80.0], "roll": [-10.0, 10.0]},
|
|
}
|
|
|
|
const VELOCITY_LIMITS: Dictionary = {
|
|
"hips": {"yaw": 60.0, "pitch": 40.0, "roll": 30.0},
|
|
"spine": {"yaw": 80.0, "pitch": 60.0, "roll": 50.0},
|
|
"chest": {"yaw": 80.0, "pitch": 60.0, "roll": 50.0},
|
|
"upper_chest": {"yaw": 90.0, "pitch": 70.0, "roll": 55.0},
|
|
"neck": {"yaw": 120.0, "pitch": 90.0, "roll": 70.0},
|
|
"head": {"yaw": 150.0, "pitch": 110.0, "roll": 80.0},
|
|
"left_eye": {"yaw": 400.0, "pitch": 350.0, "roll": 60.0},
|
|
"right_eye": {"yaw": 400.0, "pitch": 350.0, "roll": 60.0},
|
|
"left_shoulder": {"yaw": 80.0, "pitch": 60.0, "roll": 60.0},
|
|
"right_shoulder": {"yaw": 80.0, "pitch": 60.0, "roll": 60.0},
|
|
"left_upper_arm": {"yaw": 120.0, "pitch": 200.0, "roll": 160.0},
|
|
"right_upper_arm": {"yaw": 120.0, "pitch": 200.0, "roll": 160.0},
|
|
"left_lower_arm": {"yaw": 200.0, "pitch": 250.0, "roll": 30.0},
|
|
"right_lower_arm": {"yaw": 200.0, "pitch": 250.0, "roll": 30.0},
|
|
"left_hand": {"yaw": 150.0, "pitch": 200.0, "roll": 50.0},
|
|
"right_hand": {"yaw": 150.0, "pitch": 200.0, "roll": 50.0},
|
|
}
|
|
|
|
const COUPLING_RULES: Array = [
|
|
{"driver": "hips", "follower": "spine", "yaw_ratio": 0.6, "pitch_ratio": 0.7},
|
|
{"driver": "spine", "follower": "chest", "yaw_ratio": 0.8, "pitch_ratio": 0.75},
|
|
{"driver": "chest", "follower": "upper_chest", "yaw_ratio": 0.85, "pitch_ratio": 0.8},
|
|
{"driver": "hips", "follower": "neck", "yaw_ratio": 0.3, "lag_seconds": 0.05},
|
|
{
|
|
"driver": "left_eye",
|
|
"follower": "head",
|
|
"yaw_ratio": 0.35,
|
|
"pitch_ratio": 0.3,
|
|
"lag_seconds": -0.15
|
|
},
|
|
{
|
|
"driver": "right_eye",
|
|
"follower": "head",
|
|
"yaw_ratio": 0.35,
|
|
"pitch_ratio": 0.3,
|
|
"lag_seconds": -0.15
|
|
},
|
|
{"driver": "upper_chest", "follower": "left_shoulder", "yaw_ratio": 0.5, "roll_ratio": 0.4},
|
|
{"driver": "upper_chest", "follower": "right_shoulder", "yaw_ratio": 0.5, "roll_ratio": 0.4},
|
|
]
|
|
|
|
|
|
## Clamp yaw/pitch/roll (degrees) to anatomical limits for the given part.
|
|
## Returns Vector3(clamped_yaw, clamped_pitch, clamped_roll).
|
|
static func clamp_rotation(part: String, yaw: float, pitch: float, roll: float) -> Vector3:
|
|
var lim: Dictionary = ROTATION_LIMITS[part]
|
|
return Vector3(
|
|
clampf(yaw, lim["yaw"][0], lim["yaw"][1]),
|
|
clampf(pitch, lim["pitch"][0], lim["pitch"][1]),
|
|
clampf(roll, lim["roll"][0], lim["roll"][1]),
|
|
)
|
|
|
|
|
|
## Clamp a per-frame angular delta (degrees) to max believable velocity.
|
|
## delta_* is the change this frame; dt is the frame time in seconds.
|
|
## Returns Vector3(clamped_yaw_delta, clamped_pitch_delta, clamped_roll_delta).
|
|
static func clamp_angular_velocity(
|
|
part: String, delta_yaw: float, delta_pitch: float, delta_roll: float, dt: float
|
|
) -> Vector3:
|
|
var vel: Dictionary = VELOCITY_LIMITS[part]
|
|
var max_yaw: float = vel["yaw"] * dt
|
|
var max_pitch: float = vel["pitch"] * dt
|
|
var max_roll: float = vel["roll"] * dt
|
|
return Vector3(
|
|
clampf(delta_yaw, -max_yaw, max_yaw),
|
|
clampf(delta_pitch, -max_pitch, max_pitch),
|
|
clampf(delta_roll, -max_roll, max_roll),
|
|
)
|
|
|
|
|
|
## Return suggested coupled yaw angles for all follower bones of the given driver.
|
|
## Result is a Dictionary of {follower_name: suggested_yaw_degrees}.
|
|
## Clamp the results with clamp_rotation() before applying to skeleton.
|
|
static func derive_coupled_yaw(driver: String, driver_yaw: float) -> Dictionary:
|
|
var out: Dictionary = {}
|
|
for rule: Dictionary in COUPLING_RULES:
|
|
if rule["driver"] != driver:
|
|
continue
|
|
var follower: String = rule["follower"]
|
|
var ratio: float = rule.get("yaw_ratio", 0.0)
|
|
out[follower] = out.get(follower, 0.0) + driver_yaw * ratio
|
|
# Clamp each follower to its own limits
|
|
for key: String in out.keys():
|
|
var lim: Dictionary = ROTATION_LIMITS.get(key, {})
|
|
if not lim.is_empty():
|
|
out[key] = clampf(out[key], lim["yaw"][0], lim["yaw"][1])
|
|
return out
|
|
|
|
|
|
## Returns the max believable yaw speed (degrees/second) for the given part.
|
|
static func max_yaw_speed(part: String) -> float:
|
|
return VELOCITY_LIMITS[part]["yaw"]
|
|
|
|
|
|
## Returns the anatomical yaw range as [min, max] degrees.
|
|
static func yaw_range(part: String) -> Array:
|
|
return ROTATION_LIMITS[part]["yaw"]
|