585 lines
21 KiB
TypeScript
585 lines
21 KiB
TypeScript
import * as animation from "./animation.js";
|
|
import { CoupleRole, DanceRole, DancerIdentity, Rotation } from "./danceCommon.js";
|
|
import * as common from "./danceCommon.js";
|
|
import { Hand, setDistance, setHeight } from "./rendererConstants.js";
|
|
import { nameLibFigureParameters, Move, LibFigureDance, chooser_pairz } from "./libfigureMapper.js";
|
|
import { LowLevelMove, SemanticAnimation, SemanticAnimationKind, animateFromLowLevelMoves } from "./lowLevelMove.js";
|
|
import { BalanceWeight, CirclePosition, CircleSide, DancerDistance, Facing, HandConnection, HandTo, PositionKind, SemanticPosition } from "./interpreterCommon.js";
|
|
|
|
|
|
const handsInCircle = new Map<Hand, HandConnection>([
|
|
[Hand.Left, {
|
|
to: HandTo.LeftInCircle,
|
|
hand: Hand.Right,
|
|
}],
|
|
[Hand.Right, {
|
|
to: HandTo.RightInCircle,
|
|
hand: Hand.Left,
|
|
}],
|
|
]);
|
|
const handsFourImproper: Map<common.DancerIdentity, SemanticPosition> = new Map<common.DancerIdentity, SemanticPosition>([
|
|
[DancerIdentity.OnesLark, {
|
|
kind: PositionKind.Circle,
|
|
which: CirclePosition.TopLeft,
|
|
facing: Facing.CenterOfCircle,
|
|
hands: handsInCircle,
|
|
}],
|
|
[DancerIdentity.OnesRobin, {
|
|
kind: PositionKind.Circle,
|
|
which: CirclePosition.TopRight,
|
|
facing: Facing.CenterOfCircle,
|
|
hands: handsInCircle,
|
|
}],
|
|
[DancerIdentity.TwosLark, {
|
|
kind: PositionKind.Circle,
|
|
which: CirclePosition.BottomRight,
|
|
facing: Facing.CenterOfCircle,
|
|
hands: handsInCircle,
|
|
}],
|
|
[DancerIdentity.TwosRobin, {
|
|
kind: PositionKind.Circle,
|
|
which: CirclePosition.BottomLeft,
|
|
facing: Facing.CenterOfCircle,
|
|
hands: handsInCircle,
|
|
}],
|
|
]);
|
|
|
|
// Two Hearts in Time by Isaac Banner. Selected arbitrarily.
|
|
const exampleDance: LibFigureDance = [{ "parameter_values": [true, 8], "move": "petronella" }, { "parameter_values": [true, 8], "move": "petronella" }, { "parameter_values": ["neighbors", "balance", 16], "move": "swing" }, { "parameter_values": ["ladles", true, 540, 8], "move": "allemande" }, { "parameter_values": ["partners", "none", 8], "move": "swing" }, { "parameter_values": ["gentlespoons", 360, 6], "move": "mad robin" }, { "parameter_values": [true, 270, 6], "move": "circle" }, { "parameter_values": ["partners", 4], "move": "California twirl", "progression": 1 }];
|
|
|
|
function balanceCircleInAndOut(move: Move, startPos: SemanticPosition, balanceBeats?: number): [LowLevelMove, LowLevelMove] {
|
|
if (startPos.kind !== PositionKind.Circle) {
|
|
throw "Balance circle must start in a circle, but starting at " + startPos;
|
|
}
|
|
|
|
balanceBeats ??= 4;
|
|
const balancePartBeats = balanceBeats/2;
|
|
|
|
const holdingHandsInCircle: SemanticPosition = {...startPos,
|
|
facing: Facing.CenterOfCircle,
|
|
hands: new Map<Hand, HandConnection>([
|
|
[Hand.Left, { hand: Hand.Right, to: HandTo.LeftInCircle }],
|
|
[Hand.Right, { hand: Hand.Left, to: HandTo.RightInCircle }],
|
|
]),
|
|
};
|
|
const circleBalancedIn: SemanticPosition = {...holdingHandsInCircle,
|
|
balance: BalanceWeight.Forward,
|
|
};
|
|
|
|
const balanceIn: LowLevelMove = {
|
|
move,
|
|
startBeat: 0,
|
|
beats: balancePartBeats,
|
|
startPosition: holdingHandsInCircle,
|
|
endPosition: circleBalancedIn,
|
|
movementPattern: { kind: SemanticAnimationKind.Linear },
|
|
};
|
|
const balanceOut: LowLevelMove = {...balanceIn,
|
|
startBeat: balancePartBeats,
|
|
startPosition: circleBalancedIn,
|
|
endPosition: holdingHandsInCircle,
|
|
};
|
|
|
|
return [balanceIn, balanceOut];
|
|
}
|
|
|
|
function moveAsLowLevelMoves(move: Move, startingPos: Map<DancerIdentity, SemanticPosition>): Map<DancerIdentity, LowLevelMove[]> {
|
|
type PartialLowLevelMove = {
|
|
remarks?: string,
|
|
beats: number,
|
|
startPosition?: SemanticPosition,
|
|
endPosition: SemanticPosition,
|
|
movementPattern: SemanticAnimation
|
|
};
|
|
|
|
function append(moves: LowLevelMove[],
|
|
newMove: LowLevelMove | PartialLowLevelMove
|
|
| ((prevEnd: SemanticPosition) => PartialLowLevelMove)): LowLevelMove[] {
|
|
const lastMove = moves.at(-1)!;
|
|
const prevEnd = lastMove.endPosition;
|
|
if (typeof newMove === 'function') {
|
|
newMove = newMove(prevEnd);
|
|
}
|
|
|
|
if (!newMove.startPosition) {
|
|
newMove.startPosition = prevEnd;
|
|
}
|
|
moves.push({
|
|
...newMove,
|
|
startPosition: newMove.startPosition ?? prevEnd,
|
|
move: lastMove.move,
|
|
startBeat: lastMove.startBeat + lastMove.beats,
|
|
});
|
|
|
|
return moves;
|
|
}
|
|
|
|
function combine(moves: ((LowLevelMove | PartialLowLevelMove
|
|
| ((prevEnd: SemanticPosition) => PartialLowLevelMove))[]),
|
|
startPos?: SemanticPosition): LowLevelMove[] {
|
|
const res: LowLevelMove[] = [];
|
|
if (moves.length === 0) return res;
|
|
|
|
let firstMove = moves[0];
|
|
if ('move' in firstMove) {
|
|
res.push(firstMove);
|
|
} else {
|
|
if (typeof firstMove === 'function') {
|
|
firstMove = firstMove(startPos!);
|
|
}
|
|
|
|
res.push({...firstMove,
|
|
move: move,
|
|
startBeat: 0,
|
|
startPosition: firstMove.startPosition ?? startPos!,
|
|
});
|
|
}
|
|
|
|
for (const move of moves.slice(1)) {
|
|
append(res, move);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
function findPairOpposite(who: chooser_pairz, id: DancerIdentity): common.ExtendedDancerIdentity | null {
|
|
switch (who) {
|
|
case "partners":
|
|
return id.partner().asExtendedDancerIdentity();
|
|
case "neighbors":
|
|
return id.neighbor().asExtendedDancerIdentity();
|
|
// These three might get used when not with neighbors?
|
|
case "gentlespoons":
|
|
if (id.danceRole === DanceRole.Robin) return null;
|
|
return id.oppositeSameRole().asExtendedDancerIdentity();
|
|
case "ladles":
|
|
if (id.danceRole === DanceRole.Lark) return null;
|
|
return id.oppositeSameRole().asExtendedDancerIdentity();
|
|
case "same roles":
|
|
return id.oppositeSameRole().asExtendedDancerIdentity();
|
|
case "ones":
|
|
if (id.coupleRole === CoupleRole.Twos) return null;
|
|
return id.partner().asExtendedDancerIdentity();
|
|
case "twos":
|
|
if (id.coupleRole === CoupleRole.Ones) return null;
|
|
return id.partner().asExtendedDancerIdentity();
|
|
case "shadows":
|
|
throw "Not sure shadow is consistently the same.";
|
|
case "first corners":
|
|
case "second corners":
|
|
throw "Contra corners are unsupported.";
|
|
default:
|
|
throw "Unsupported who: " + who;
|
|
}
|
|
}
|
|
|
|
// TOOD Type for this? Probably SemanticPosition?
|
|
function findCenterBetween(id: DancerIdentity, other: common.ExtendedDancerIdentity) {
|
|
const selfPos = startingPos.get(id)!;
|
|
selfPos.setOffset ??= 0;
|
|
selfPos.lineOffset ??= 0;
|
|
const otherBasePos = startingPos.get(other.setIdentity)!;
|
|
const otherPos : SemanticPosition = {...otherBasePos,
|
|
setOffset: (otherBasePos.setOffset ?? 0) + other.relativeSet,
|
|
lineOffset: (otherBasePos.lineOffset ?? 0) + other.relativeLine,
|
|
};
|
|
|
|
if (selfPos.kind === PositionKind.Circle && otherPos.kind === PositionKind.Circle) {
|
|
if (!(selfPos.lineOffset === otherPos.lineOffset && selfPos.setOffset === otherPos.setOffset)) {
|
|
throw "Don't know how to find center between different circles.";
|
|
} else if (selfPos.which.leftRightSide() === otherPos.which.leftRightSide()) {
|
|
return selfPos.which.leftRightSide();
|
|
} else if (selfPos.which.topBottomSide() === otherPos.which.topBottomSide()) {
|
|
return selfPos.which.topBottomSide();
|
|
} else {
|
|
return "Center";
|
|
}
|
|
} else {
|
|
throw "Don't know how to find center between positions not in a circle.";
|
|
}
|
|
}
|
|
|
|
const res = new Map<DancerIdentity, LowLevelMove[]>();
|
|
switch (move.move) {
|
|
case "petronella":
|
|
for (const [id, startPos] of startingPos.entries()) {
|
|
if (startPos.kind !== PositionKind.Circle) {
|
|
throw move.move + " must start in a circle, but " + id + " is at " + startPos;
|
|
}
|
|
// TODO This doesn't handle petronella to a new circle. How is that notated?
|
|
const finalPosition = {
|
|
...startPos,
|
|
facing: Facing.CenterOfCircle,
|
|
which: startPos.which.circleRight(1),
|
|
hands: undefined,
|
|
};
|
|
if (move.parameters.bal) {
|
|
const balance: LowLevelMove[] = balanceCircleInAndOut(move, startPos);
|
|
res.set(id, append([...balance], prevEnd => ({
|
|
beats: move.beats - 4,
|
|
startPosition: {
|
|
...prevEnd,
|
|
hands: undefined,
|
|
},
|
|
endPosition: finalPosition,
|
|
movementPattern: {
|
|
kind: SemanticAnimationKind.Linear,
|
|
minRotation: 180,
|
|
handsDuring: "None",
|
|
},
|
|
})));
|
|
} else {
|
|
res.set(id, combine([{
|
|
beats: move.beats,
|
|
startPosition: {
|
|
...startPos,
|
|
facing: Facing.CenterOfCircle,
|
|
hands: undefined,
|
|
},
|
|
endPosition: finalPosition,
|
|
movementPattern: {
|
|
kind: SemanticAnimationKind.Linear,
|
|
minRotation: 180,
|
|
handsDuring: "None",
|
|
},
|
|
}]));
|
|
}
|
|
}
|
|
return res;
|
|
case "mad robin":
|
|
for (const [id, startPos] of startingPos.entries()) {
|
|
if (startPos.kind !== PositionKind.Circle) {
|
|
throw move.move + " must start in a circle, but " + id + " is at " + startPos;
|
|
}
|
|
|
|
// TODO Read who of mad robin to decide direction?
|
|
const startAndEndPos = {
|
|
...startPos,
|
|
facing: startPos.which.isLeft() ? Facing.Right : Facing.Left,
|
|
hands: undefined,
|
|
};
|
|
res.set(id, combine([{
|
|
beats: move.beats,
|
|
startPosition: startAndEndPos,
|
|
endPosition: startAndEndPos,
|
|
movementPattern: {
|
|
kind: SemanticAnimationKind.DoSiDo,
|
|
amount: move.parameters.circling,
|
|
},
|
|
}]));
|
|
}
|
|
return res;
|
|
case "swing":
|
|
for (const [id, startPos] of startingPos.entries()) {
|
|
// TODO swing can start from other positions.
|
|
// TODO swing end is only in notes / looking at next move.
|
|
if (startPos.kind !== PositionKind.Circle) {
|
|
throw move.move + " must start in a circle, but " + id + " is at " + startPos;
|
|
}
|
|
|
|
switch (move.parameters.who) {
|
|
case "ladles":
|
|
case "gentlespoons":
|
|
case "ones":
|
|
case "twos":
|
|
throw "Swing in middle is currently unsupported.";
|
|
case "first corners":
|
|
case "second corners":
|
|
throw "contra corners currently unsupported.";
|
|
case "same roles":
|
|
throw "same role swing currently unsupported."
|
|
case "neighbors":
|
|
case "partners":
|
|
case "shadows":
|
|
// TODO Make sure referenced dancer is using same center.
|
|
break;
|
|
}
|
|
|
|
const startPosition: SemanticPosition = {
|
|
...startPos,
|
|
facing: startPos.which.topBottomSide() === CircleSide.Bottom ? Facing.Up : Facing.Down,
|
|
};
|
|
const endPosition: SemanticPosition = {
|
|
...startPos,
|
|
which: startPos.which.isOnLeftLookingAcross() === (id.danceRole === DanceRole.Lark)
|
|
? startPos.which
|
|
: startPos.which.swapUpAndDown(),
|
|
facing: startPos.which.leftRightSide() === CircleSide.Left ? Facing.Right : Facing.Left,
|
|
balance: undefined,
|
|
dancerDistance: undefined,
|
|
hands: new Map<Hand, HandConnection>([id.danceRole === DanceRole.Lark
|
|
? [Hand.Right, { to: HandTo.DancerRight, hand: Hand.Left }]
|
|
: [Hand.Left, { to: HandTo.DancerLeft, hand: Hand.Right }]]),
|
|
};
|
|
// TODO same role swing currently unsupported.
|
|
const swingRole = id.danceRole === DanceRole.Lark ? DancerDistance.SwingLark : DancerDistance.SwingRobin;
|
|
|
|
switch (move.parameters.prefix) {
|
|
case "none":
|
|
res.set(id, combine([
|
|
{
|
|
beats: move.beats,
|
|
endPosition: endPosition,
|
|
movementPattern: {
|
|
kind: SemanticAnimationKind.Swing,
|
|
minAmount: 360,
|
|
around: startPos.which.leftRightSide(),
|
|
endFacing: startPos.which.leftRightSide() === CircleSide.Left ? Facing.Right : Facing.Left,
|
|
swingRole: id.danceRole,
|
|
},
|
|
},
|
|
], startPos));
|
|
break;
|
|
case "balance":
|
|
// TODO Right length for balance?
|
|
const balancePartBeats = move.beats > 8 ? (move.beats - 8) / 2 : 2;
|
|
const startWithBalHands = {
|
|
...startPosition,
|
|
hands: new Map<Hand, HandConnection>([
|
|
[Hand.Left, { to: HandTo.DancerForward, hand: Hand.Right }],
|
|
[Hand.Right, { to: HandTo.DancerForward, hand: Hand.Left }],
|
|
]),
|
|
};
|
|
res.set(id, combine([
|
|
{
|
|
beats: balancePartBeats,
|
|
startPosition: startWithBalHands,
|
|
endPosition: {
|
|
...startWithBalHands,
|
|
balance: BalanceWeight.Forward,
|
|
dancerDistance: DancerDistance.Compact,
|
|
},
|
|
movementPattern: { kind: SemanticAnimationKind.Linear, },
|
|
},
|
|
prevEnd => ({
|
|
beats: balancePartBeats,
|
|
endPosition: {
|
|
...prevEnd,
|
|
balance: BalanceWeight.Backward,
|
|
},
|
|
movementPattern: { kind: SemanticAnimationKind.Linear, },
|
|
}),
|
|
{
|
|
beats: move.beats - 2 * balancePartBeats,
|
|
endPosition: endPosition,
|
|
movementPattern: {
|
|
kind: SemanticAnimationKind.Swing,
|
|
minAmount: 360,
|
|
around: startPos.which.leftRightSide(),
|
|
endFacing: startPos.which.leftRightSide() === CircleSide.Left ? Facing.Right : Facing.Left,
|
|
swingRole: id.danceRole,
|
|
},
|
|
},
|
|
], startPos));
|
|
break;
|
|
case "meltdown":
|
|
const meltdownBeats = 4; // TODO right number here?
|
|
res.set(id, combine([
|
|
prevEnd => ({
|
|
beats: meltdownBeats,
|
|
endPosition: prevEnd,
|
|
movementPattern: {
|
|
kind: SemanticAnimationKind.RotateAround,
|
|
minAmount: 360,
|
|
around: startPos.which.leftRightSide(),
|
|
byHand: undefined,
|
|
},
|
|
}),
|
|
{
|
|
beats: move.beats - meltdownBeats,
|
|
endPosition: endPosition,
|
|
movementPattern: {
|
|
kind: SemanticAnimationKind.Swing,
|
|
minAmount: 360,
|
|
around: startPos.which.leftRightSide(),
|
|
endFacing: startPos.which.leftRightSide() === CircleSide.Left ? Facing.Right : Facing.Left,
|
|
swingRole: id.danceRole,
|
|
},
|
|
},
|
|
], startPos));
|
|
break;
|
|
}
|
|
}
|
|
return res;
|
|
|
|
case "allemande":
|
|
for (const [id, startPos] of startingPos.entries()) {
|
|
// TODO allemande can start from other positions.
|
|
if (startPos.kind !== PositionKind.Circle) {
|
|
throw move.move + " must start in a circle, but " + id + " is at " + startPos;
|
|
}
|
|
|
|
const withId = findPairOpposite(move.parameters.who, id);
|
|
// findPairOpposite of null means this dancer doesn't participate in this move.
|
|
if (!withId) {
|
|
res.set(id, combine([prevEnd => ({
|
|
beats: move.beats,
|
|
startPosition: { ...prevEnd, hands: undefined },
|
|
endPosition: { ...prevEnd, hands: undefined },
|
|
movementPattern: { kind: SemanticAnimationKind.StandStill },
|
|
})], startPos));
|
|
continue;
|
|
};
|
|
const around = findCenterBetween(id, withId);
|
|
|
|
// TODO Not sure if this is right.
|
|
const byHand = move.parameters.hand ? Hand.Right : Hand.Left;
|
|
const swap = move.parameters.circling % 360 === 180;
|
|
if (!swap && move.parameters.circling % 360 !== 0) {
|
|
throw "Unsupported allemande circle amount: " + move.parameters.circling;
|
|
}
|
|
let endPosition : SemanticPosition = startPos;
|
|
if (swap) {
|
|
endPosition = startingPos.get(withId.setIdentity)!;
|
|
endPosition.setOffset = (endPosition.setOffset ?? 0) + withId.relativeSet;
|
|
endPosition.lineOffset = (endPosition.lineOffset ?? 0) + withId.relativeLine;
|
|
}
|
|
|
|
res.set(id, combine([
|
|
{
|
|
beats: move.beats,
|
|
endPosition,
|
|
movementPattern: {
|
|
kind: SemanticAnimationKind.RotateAround,
|
|
minAmount: byHand === Hand.Right ? move.parameters.circling : -move.parameters.circling,
|
|
around,
|
|
byHand,
|
|
},
|
|
},
|
|
], startPos));
|
|
}
|
|
|
|
return res;
|
|
|
|
case "circle":
|
|
const places = move.parameters.places/90 * (move.parameters.turn ? 1 : -1);
|
|
|
|
for (const [id, startPos] of startingPos.entries()) {
|
|
if (startPos.kind !== PositionKind.Circle) {
|
|
throw move.move + " must start in a circle, but " + id + " is at " + startPos;
|
|
}
|
|
|
|
res.set(id, combine([
|
|
prevEnd => ({
|
|
beats: move.beats,
|
|
endPosition: {
|
|
...prevEnd,
|
|
hands: handsInCircle,
|
|
which: startPos.which.circleLeft(places),
|
|
},
|
|
movementPattern: {
|
|
kind: SemanticAnimationKind.Circle,
|
|
places,
|
|
}
|
|
}),
|
|
], { ...startPos, facing: Facing.CenterOfCircle }));
|
|
}
|
|
|
|
return res;
|
|
|
|
case "California twirl":
|
|
for (const [id, startPos] of startingPos.entries()) {
|
|
if (startPos.kind !== PositionKind.Circle) {
|
|
throw move.move + " must start in a circle, but " + id + " is at " + startPos;
|
|
}
|
|
|
|
const onLeft : boolean = startPos.which.isOnLeftLookingUpAndDown();
|
|
res.set(id, combine([
|
|
{
|
|
beats: 1,
|
|
endPosition: {
|
|
...startPos,
|
|
hands: new Map<Hand, HandConnection>([onLeft
|
|
? [Hand.Right, { to: HandTo.DancerRight, hand: Hand.Left }]
|
|
: [Hand.Left, { to: HandTo.DancerLeft, hand: Hand.Right }]]),
|
|
facing: startPos.which.topBottomSide() === CircleSide.Top ? Facing.Down : Facing.Up,
|
|
},
|
|
movementPattern: {
|
|
kind: SemanticAnimationKind.Linear,
|
|
}
|
|
},
|
|
{
|
|
beats: move.beats - 1,
|
|
endPosition: {
|
|
...startPos,
|
|
which: startPos.which.swapAcross(),
|
|
facing: startPos.which.topBottomSide() === CircleSide.Top ? Facing.Up : Facing.Down,
|
|
},
|
|
movementPattern: {
|
|
kind: SemanticAnimationKind.TwirlSwap,
|
|
around: startPos.which.topBottomSide(),
|
|
hand: onLeft ? Hand.Right : Hand.Left,
|
|
}
|
|
}], startPos));
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
// XXX DEBUG Just leave out unsupported moves for now to allow viewing the known moves.
|
|
//throw "Unknown move: " + move.move + ": " + JSON.stringify(move);
|
|
for (const [id, startPos] of startingPos.entries()) {
|
|
res.set(id, [{
|
|
remarks: "UNKNOWN MOVE: standing still",
|
|
move,
|
|
startBeat: 0,
|
|
beats: move.beats,
|
|
startPosition: startPos,
|
|
endPosition: startPos,
|
|
movementPattern: {
|
|
kind: SemanticAnimationKind.StandStill,
|
|
},
|
|
}]);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
function danceAsLowLevelMoves(moves: Move[], startingPos: Map<DancerIdentity, SemanticPosition>): Map<DancerIdentity, LowLevelMove[]> {
|
|
const res = new Map<DancerIdentity, LowLevelMove[]>([...startingPos.keys()].map(id => [id, []]));
|
|
let currentPos = startingPos;
|
|
for (const move of moves) {
|
|
const newMoves = moveAsLowLevelMoves(move, currentPos);
|
|
for (const [id, newMoveList] of newMoves.entries()) {
|
|
res.get(id)!.push(...newMoveList);
|
|
currentPos.set(id, newMoveList.at(-1)!.endPosition);
|
|
}
|
|
}
|
|
|
|
const progression = animateFromLowLevelMoves(res).progression;
|
|
if (progression.x !== 0) throw "Progressing to different line is unsupported.";
|
|
if (progression.y % setHeight / 2 !== 0) throw "Progression is not an integer number of places.";
|
|
const progressionInSets = progression.y / setDistance;
|
|
|
|
// fixup end positions to match start of next move
|
|
// TODO Handle progression.
|
|
for (const [id, lowLevelMoves] of res.entries()) {
|
|
for (let i = 0; i < lowLevelMoves.length - 1; i++) {
|
|
if (!lowLevelMoves[i].endPosition) throw "endPosition is undefined";
|
|
lowLevelMoves[i].endPosition = lowLevelMoves[i + 1].startPosition;
|
|
if (!lowLevelMoves[i].endPosition) throw "endPosition is undefined now";
|
|
}
|
|
const startPos = lowLevelMoves[0].startPosition;
|
|
lowLevelMoves[lowLevelMoves.length - 1].endPosition = {
|
|
...lowLevelMoves[0].startPosition,
|
|
// progressed
|
|
setOffset: (startPos.setOffset ?? 0) + (id.coupleRole == CoupleRole.Ones ? 1 : -1) * progressionInSets,
|
|
};
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
export let mappedDance: Move[];
|
|
|
|
export let interpretedDance: Map<DancerIdentity, LowLevelMove[]>;
|
|
export let interpretedAnimation: animation.Animation;
|
|
|
|
export function loadDance(dance: LibFigureDance): animation.Animation {
|
|
mappedDance = dance.map(nameLibFigureParameters);
|
|
interpretedDance = danceAsLowLevelMoves(mappedDance, handsFourImproper);
|
|
interpretedAnimation = animateFromLowLevelMoves(interpretedDance);
|
|
|
|
return interpretedAnimation;
|
|
}
|
|
|
|
loadDance(exampleDance); |