This commit is contained in:
Nicholas Butzke
2026-08-02 02:20:02 -04:00
parent 61669627db
commit 922983429e
226 changed files with 34032 additions and 18521 deletions
+24 -161
View File
@@ -1,182 +1,43 @@
extends Object
class_name DanceRoutines
## The five emotes, as data.
## Five authored emotes available through the radial wheel.
##
## They are procedural rather than authored clips because the shared animation
## library ships exactly one `Dance_Loop`, and five copies of one clip is not
## five dances. What the runtime DOES have is a working procedural pose layer
## over a real skeleton with spring-driven hair and cloth, which is enough to
## build a dance out of if the motion is constructed the way an animator would
## construct it rather than the way a programmer reaches for first.
##
## ── Why not just wire sine waves to the bones ───────────────────────────────
##
## Because that is what "programmer animation" looks like, and everyone can tell.
## A raw sine moves fastest through the middle and slowest at the ends by exactly
## the same amount on every channel, all in phase, forever. The result floats. It
## has no weight, no accent, and no sense that one part of the body is driving and
## the rest is following.
##
## Four principles fix that, and all four are cheap:
##
## OVERLAP the body is a chain, and a chain does not move as one piece.
## Hips lead, spine follows a beat later, chest later still,
## head last. `lag` below is that, in seconds per link. It is
## the single largest difference between "a rig oscillating"
## and "a person moving", and the spring bones then carry it
## out through the hair and the skirt for free.
## ACCENT a dance HITS poses. `shape` bends the wave so it hangs at
## the extremes and snaps between them — the same asymmetry a
## key-and-breakdown pass produces by hand. `sin` is shape 1.0;
## above that it gets punchier.
## WEIGHT a body that never leaves its own axis reads as a puppet. Real
## dances move the HIPS — side to side, up and down — and the
## rest of the body reacts. `hip_swing` and `hip_bob` are
## translations, not rotations, and they are what make the legs
## look like they are carrying someone.
## ANTICIPATION the counter-move before the move. Handled per routine by
## running a channel at a fraction of a beat AHEAD of the one it
## precedes, rather than by a separate mechanism.
##
## ── The shape of a routine ──────────────────────────────────────────────────
##
## Everything is derived from one `bpm`, so no two channels can drift apart no
## matter how long the emote runs — which is the other thing that goes wrong when
## channels are given independent frequencies that are not exact ratios.
##
## Channel amounts are radians (rotations) or metres (the two hip translations).
## `beats` is how many beats that channel takes for one full cycle, so 2 is a
## side-to-side that takes two beats to return, 1 is once per beat, and 0.5 is
## twice per beat. Fractions of a beat are how a routine gets a cross-rhythm
## without leaving the grid.
## `id` is what gets networked, so these strings must stay stable.
## Every entry names an imported motion-capture clip. The previous version
## synthesized five routines by oscillating individual bones over one dance
## clip; that produced the programmer-animation quality this project is moving
## away from. Network IDs stay stable so existing peers and saved selections
## remain compatible.
const ROUTINES := [
{
"id": "two_step",
"name": "Two-Step",
"name": "Dance",
"icon": "",
"bpm": 104.0,
"lag": 0.055,
"shape": 1.35,
# The foundation step: weight rocks side to side, the shoulders counter
# the hips, the arms hang and swing off the shoulders a beat behind.
"hip_swing": 0.075,
"hip_bob": 0.022,
"hip_bob_beats": 1.0,
"hip_roll": 0.16,
"hip_yaw": 0.20,
"spine_roll": 0.13,
"spine_counter": 0.55,
"head_roll": 0.22,
"head_bob": 0.10,
"arm_swing": 0.55,
"arm_out": 0.42,
"arm_beats": 2.0,
"elbow": 0.75,
"knee": 0.30,
"clip": "Dance",
},
{
"id": "body_wave",
"name": "Body Wave",
"icon": "",
"bpm": 88.0,
# The whole point of this one is the lag: a wave travelling up the spine
# IS overlap, made visible. At 0.13 s per link the crest takes most of a
# beat to get from the hips to the head.
"lag": 0.13,
"shape": 1.15,
"hip_swing": 0.03,
"hip_bob": 0.045,
"hip_bob_beats": 2.0,
"hip_pitch": 0.26,
"spine_pitch": 0.30,
"spine_counter": 0.0,
"head_pitch": 0.22,
"head_roll": 0.06,
"arm_out": 0.85,
"arm_swing": 0.20,
"arm_beats": 4.0,
"elbow": 0.55,
"knee": 0.18,
"name": "Chest Stretch",
"icon": "",
"clip": "EmoteStretch",
},
{
"id": "robot",
"name": "Robot",
"icon": "",
"bpm": 112.0,
# No lag, and the motion is QUANTISED — see `steps`. Both are deliberate
# violations of everything above, and they work for exactly that reason:
# the robot reads as mechanical because the viewer has been shown four
# other routines that do not.
"lag": 0.0,
"shape": 1.0,
"steps": 4,
"hip_swing": 0.04,
"hip_bob": 0.012,
"hip_bob_beats": 1.0,
"hip_yaw": 0.30,
"spine_yaw": 0.34,
"spine_counter": 0.0,
"head_yaw": 0.42,
"arm_out": 1.05,
"arm_swing": 0.85,
"arm_beats": 2.0,
"elbow": 1.35,
"knee": 0.10,
"name": "Call Out",
"icon": "!",
"clip": "EmoteCall",
},
{
"id": "bounce",
"name": "Bounce",
"icon": "",
"bpm": 128.0,
"lag": 0.035,
# The punchiest shape in the set. A bounce lives entirely in the accent:
# the body hangs at the top and slams through the bottom, which is a
# gravity read, and a plain sine cannot express it.
"shape": 2.2,
"hip_swing": 0.03,
"hip_bob": 0.070,
"hip_bob_beats": 1.0,
"hip_roll": 0.08,
"spine_pitch": 0.14,
"spine_counter": 0.30,
"head_bob": 0.16,
"head_roll": 0.10,
"arm_swing": 0.95,
"arm_out": 0.30,
"arm_beats": 1.0,
"elbow": 1.05,
# Deep knees. This is the routine where the legs do the work, and a bounce
# with straight legs looks like a character being shaken.
"knee": 0.85,
"name": "Yes!",
"icon": "",
"clip": "EmoteYes",
},
{
"id": "spin",
"name": "Spin",
"icon": "",
"bpm": 96.0,
"lag": 0.07,
"shape": 1.5,
"hip_swing": 0.05,
"hip_bob": 0.030,
"hip_bob_beats": 2.0,
"hip_yaw": 0.85,
"spine_yaw": 0.30,
"spine_counter": 0.0,
"spine_roll": 0.14,
# SPOTTING: the head holds its heading while the body turns under it, then
# whips round to catch up. It is what a real dancer does to keep from
# getting dizzy, and it is the most recognisable thing about a turn. See
# `spot` in DanceModifier — this is not a wave, it is a hold and a snap.
"spot": 1.0,
"head_yaw": 0.0,
"arm_out": 1.15,
"arm_swing": 0.25,
"arm_beats": 4.0,
"elbow": 0.35,
"knee": 0.22,
"name": "No",
"icon": "×",
"clip": "EmoteNo",
},
]
@@ -185,14 +46,16 @@ static func count() -> int:
return ROUTINES.size()
## A routine by index, wrapped so an out-of-range network value cannot crash a
## remote peer's model.
static func get_routine(index: int) -> Dictionary:
if ROUTINES.is_empty():
return {}
return ROUTINES[posmod(index, ROUTINES.size())]
static func clip_of(index: int) -> String:
return String(get_routine(index).get("clip", "Dance"))
static func name_of(index: int) -> String:
return String(get_routine(index).get("name", ""))