big
This commit is contained in:
+24
-161
@@ -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", ""))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user