forked from perelman/contra-renderer
Compare commits
1 Commits
main
...
exp/perf/c
Author | SHA1 | Date | |
---|---|---|---|
7deda1af48 |
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1,2 +1,2 @@
|
||||||
www/js/**/*.js
|
www/js/*.js
|
||||||
www/js/**/*.js.map
|
www/js/*.js.map
|
||||||
|
|
31
README.md
31
README.md
|
@ -6,34 +6,3 @@ early in development and only works on a single dance, Isaac Banner's
|
||||||
["Two Hearts in Time"](https://contradb.com/dances/2014), chosen as a
|
["Two Hearts in Time"](https://contradb.com/dances/2014), chosen as a
|
||||||
simple dance (i.e. single progression improper, no shadow or
|
simple dance (i.e. single progression improper, no shadow or
|
||||||
next/previous neighbor interactions).
|
next/previous neighbor interactions).
|
||||||
|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
To develop locally, you need
|
|
||||||
[TypeScript](https://www.typescriptlang.org/download/) installed. You
|
|
||||||
can check if you have the **`tsc`** (TypeScript Compiler) command available.
|
|
||||||
|
|
||||||
While developing, leave the [watch-tsc.sh](./watch-tsc.sh) script running
|
|
||||||
or, equivalently, run the following the command from the root of the
|
|
||||||
repository:
|
|
||||||
```sh
|
|
||||||
tsc --watch
|
|
||||||
```
|
|
||||||
|
|
||||||
Also, serve the `www/` directory from a local webserver.
|
|
||||||
The [serve.sh](./serve.sh) script will do this or you can run the
|
|
||||||
following command from the `www/` directory of the repository:
|
|
||||||
```sh
|
|
||||||
python -m http.server --bind localhost 8085
|
|
||||||
```
|
|
||||||
(Nothing special about [Python](https://www.python.org/downloads/) here,
|
|
||||||
just the easiest web server to set up.)
|
|
||||||
|
|
||||||
Then open http://localhost:8085/ in a web browser. The site should work
|
|
||||||
in any modern browser (tested in Firefox and Chromium).
|
|
||||||
|
|
||||||
Any text editor/IDE works, but I find
|
|
||||||
[VS Code](https://code.visualstudio.com/Download)'s
|
|
||||||
[TypeScript support](https://code.visualstudio.com/Docs/languages/typescript)
|
|
||||||
works well.
|
|
||||||
|
|
7
serve.sh
7
serve.sh
|
@ -1,7 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Change directory to www/ directory.
|
|
||||||
cd "$(dirname "$0")/www/" || exit
|
|
||||||
echo "Serving $(pwd)"
|
|
||||||
# From www/
|
|
||||||
python -m http.server --bind localhost 8085
|
|
|
@ -1,7 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Change directory to repo root.
|
|
||||||
cd "$(dirname "$0")" || exit
|
|
||||||
echo "Watching $(pwd)"
|
|
||||||
# From /
|
|
||||||
tsc --watch
|
|
|
@ -58,58 +58,29 @@
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
th.Ones.Lark {
|
||||||
th.Ones {
|
background-color: hsl(0, 80%, 50%);
|
||||||
background-color: hsl(27, 99%, 59%);
|
|
||||||
}
|
}
|
||||||
td.Ones {
|
td.Ones.Lark {
|
||||||
background-color: hsl(27, 99%, 85%);
|
background-color: hsl(0, 90%, 70%);
|
||||||
}
|
}
|
||||||
th.Twos {
|
th.Ones.Robin {
|
||||||
background-color: hsl(249, 42%, 57%);
|
background-color: hsl(39, 80%, 50%);
|
||||||
}
|
}
|
||||||
td.Twos {
|
td.Ones.Robin {
|
||||||
background-color: hsl(249, 52%, 85%);
|
background-color: hsl(39, 90%, 70%);
|
||||||
}
|
}
|
||||||
|
th.Twos.Lark {
|
||||||
th.Lark::before {
|
background-color: hsl(240, 70%, 65%);
|
||||||
content: "◠";
|
|
||||||
text-decoration: line-through;
|
|
||||||
padding-right: 1ex;
|
|
||||||
}
|
}
|
||||||
th.Robin::before {
|
td.Twos.Lark {
|
||||||
content: "△";
|
background-color: hsl(240, 90%, 80%);
|
||||||
padding-right: 1ex;
|
|
||||||
}
|
}
|
||||||
|
th.Twos.Robin {
|
||||||
td.Lark.Ones {
|
background-color: hsl(180, 80%, 50%);
|
||||||
background:
|
|
||||||
radial-gradient(circle at bottom left, hsl(27, 99%, 75%) 15%, transparent 16%),
|
|
||||||
radial-gradient(circle at bottom right, hsl(27, 99%, 75%) 15%, transparent 16%),
|
|
||||||
hsl(27, 99%, 85%);
|
|
||||||
background-size: 6em 3em;
|
|
||||||
}
|
}
|
||||||
td.Lark.Twos {
|
td.Twos.Robin {
|
||||||
background:
|
background-color: hsl(180, 90%, 70%);
|
||||||
radial-gradient(circle at bottom left, hsl(249, 42%, 75%) 15%, transparent 16%),
|
|
||||||
radial-gradient(circle at bottom right,hsl(249, 42%, 75%) 15%, transparent 16%),
|
|
||||||
hsl(249, 52%, 85%);
|
|
||||||
background-size: 6em 3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
td.Robin.Ones {
|
|
||||||
background:
|
|
||||||
linear-gradient(45deg,hsl(27, 99%, 75%) 10%, transparent 10%),
|
|
||||||
linear-gradient(135deg, transparent 90%,hsl(27, 99%, 75%) 90%),
|
|
||||||
hsl(27, 99%, 85%);
|
|
||||||
background-size: 6em 3em;
|
|
||||||
}
|
|
||||||
td.Robin.Twos {
|
|
||||||
background:
|
|
||||||
linear-gradient(45deg,hsl(249, 42%, 75%) 10%, transparent 10%),
|
|
||||||
linear-gradient(135deg, transparent 90%,hsl(249, 42%, 75%) 90%),
|
|
||||||
hsl(249, 52%, 85%);
|
|
||||||
background-size: 6em 3em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.move {
|
.move {
|
||||||
|
|
|
@ -98,8 +98,6 @@ export class LinearAnimationSegment extends AnimationSegment {
|
||||||
|
|
||||||
export interface AnimationTransitionFlags {
|
export interface AnimationTransitionFlags {
|
||||||
rotation?: boolean;
|
rotation?: boolean;
|
||||||
rotationDuring?: "Actual" | "Start" | "End";
|
|
||||||
rotationDirection?: Hand;
|
|
||||||
hands?: boolean;
|
hands?: boolean;
|
||||||
handsDuring?: "Actual" | "None" | "Start" | "End" | Map<Hand, Offset>;
|
handsDuring?: "Actual" | "None" | "Start" | "End" | Map<Hand, Offset>;
|
||||||
}
|
}
|
||||||
|
@ -127,30 +125,20 @@ export class TransitionAnimationSegment extends AnimationSegment {
|
||||||
this.startRotation = this.startPosition.rotation;
|
this.startRotation = this.startPosition.rotation;
|
||||||
this.endRotation = this.endPosition.rotation;
|
this.endRotation = this.endPosition.rotation;
|
||||||
if (this.flags.rotation) {
|
if (this.flags.rotation) {
|
||||||
let rotationDirection = flags.rotationDirection;
|
|
||||||
|
|
||||||
if (!flags.rotationDirection) {
|
|
||||||
const actualStart = this.actualAnimation.interpolateRotation(0);
|
const actualStart = this.actualAnimation.interpolateRotation(0);
|
||||||
const actualEnd = this.actualAnimation.interpolateRotation(1);
|
const actualEnd = this.actualAnimation.interpolateRotation(1);
|
||||||
|
|
||||||
if (actualEnd > actualStart) {
|
|
||||||
rotationDirection = Hand.Right;
|
|
||||||
} else if (actualEnd < actualStart) {
|
|
||||||
rotationDirection = Hand.Left;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const transitionStart = this.actualAnimation.interpolateRotation(this.startTransitionProgress);
|
const transitionStart = this.actualAnimation.interpolateRotation(this.startTransitionProgress);
|
||||||
const transitionEnd = this.actualAnimation.interpolateRotation(1 - this.endTransitionProgress);
|
const transitionEnd = this.actualAnimation.interpolateRotation(1 - this.endTransitionProgress);
|
||||||
|
|
||||||
if (rotationDirection === Hand.Right) {
|
if (actualEnd > actualStart) {
|
||||||
while (transitionStart <= this.startRotation - 180) {
|
while (transitionStart <= this.startRotation - 180) {
|
||||||
this.startRotation -= 360;
|
this.startRotation -= 360;
|
||||||
}
|
}
|
||||||
while (transitionEnd >= this.endRotation + 180) {
|
while (transitionEnd >= this.endRotation + 180) {
|
||||||
this.endRotation += 360;
|
this.endRotation += 360;
|
||||||
}
|
}
|
||||||
} else if (rotationDirection === Hand.Left) {
|
} else if (actualEnd < actualStart) {
|
||||||
while (transitionStart >= this.startRotation + 180) {
|
while (transitionStart >= this.startRotation + 180) {
|
||||||
this.startRotation += 360;
|
this.startRotation += 360;
|
||||||
}
|
}
|
||||||
|
@ -159,9 +147,7 @@ export class TransitionAnimationSegment extends AnimationSegment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.flags.rotationDirection) {
|
// Transitions should be short adjustments, not spins.
|
||||||
// Transitions should be short adjustments, not spins...
|
|
||||||
// ... unless a direction is explicitly specified.
|
|
||||||
while (transitionStart - this.startRotation < -180) {
|
while (transitionStart - this.startRotation < -180) {
|
||||||
this.startRotation -= 360;
|
this.startRotation -= 360;
|
||||||
}
|
}
|
||||||
|
@ -176,17 +162,12 @@ export class TransitionAnimationSegment extends AnimationSegment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override interpolateOffset(progress: number): Offset {
|
override interpolateOffset(progress: number): Offset {
|
||||||
return this.actualAnimation.interpolateOffset(progress);
|
return this.actualAnimation.interpolateOffset(progress);
|
||||||
}
|
}
|
||||||
|
|
||||||
override interpolateRotation(progress: number): number {
|
override interpolateRotation(progress: number): number {
|
||||||
switch (this.flags.rotationDuring) {
|
|
||||||
case undefined:
|
|
||||||
case "Actual":
|
|
||||||
|
|
||||||
const actualRotation = this.actualAnimation.interpolateRotation(progress);
|
const actualRotation = this.actualAnimation.interpolateRotation(progress);
|
||||||
|
|
||||||
if (this.flags.rotation) {
|
if (this.flags.rotation) {
|
||||||
|
@ -198,21 +179,6 @@ export class TransitionAnimationSegment extends AnimationSegment {
|
||||||
}
|
}
|
||||||
|
|
||||||
return actualRotation;
|
return actualRotation;
|
||||||
|
|
||||||
case "Start":
|
|
||||||
if ((1 - progress) < this.endTransitionProgress) {
|
|
||||||
return interpolateLinear((1 - progress) / this.endTransitionProgress, this.endRotation, this.startRotation);
|
|
||||||
} else {
|
|
||||||
return this.startRotation;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "End":
|
|
||||||
if (progress < this.startTransitionProgress) {
|
|
||||||
return interpolateLinear(progress / this.startTransitionProgress, this.startRotation, this.endRotation);
|
|
||||||
} else {
|
|
||||||
return this.endRotation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override interpolateHandOffset(progress: number, hand: Hand): Offset | undefined {
|
override interpolateHandOffset(progress: number, hand: Hand): Offset | undefined {
|
||||||
|
@ -252,21 +218,7 @@ export class TransitionAnimationSegment extends AnimationSegment {
|
||||||
}
|
}
|
||||||
|
|
||||||
override drawDebug(ctx: CanvasRenderingContext2D, progress: number) {
|
override drawDebug(ctx: CanvasRenderingContext2D, progress: number) {
|
||||||
// TODO better way to display transition?
|
// TODO display transition somehow?
|
||||||
if (progress < this.startTransitionProgress) {
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(this.startPosition.position.x, this.startPosition.position.y);
|
|
||||||
const transitionStart = this.actualAnimation.interpolateOffset(this.startTransitionProgress);
|
|
||||||
ctx.lineTo(transitionStart.x, transitionStart.y);
|
|
||||||
ctx.stroke();
|
|
||||||
} else if (progress > 1 - this.endTransitionProgress) {
|
|
||||||
ctx.beginPath();
|
|
||||||
const transitionEnd = this.actualAnimation.interpolateOffset(this.endTransitionProgress);
|
|
||||||
ctx.moveTo(transitionEnd.x, transitionEnd.y);
|
|
||||||
ctx.lineTo(this.endPosition.position.x, this.endPosition.position.y);
|
|
||||||
ctx.stroke();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.actualAnimation.drawDebug(ctx, progress);
|
this.actualAnimation.drawDebug(ctx, progress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -275,8 +227,7 @@ export class TransitionAnimationSegment extends AnimationSegment {
|
||||||
export enum RotationAnimationFacing {
|
export enum RotationAnimationFacing {
|
||||||
Linear = "Linear", // Default, linearly interpolate.
|
Linear = "Linear", // Default, linearly interpolate.
|
||||||
Center = "Center", // Always face the center.
|
Center = "Center", // Always face the center.
|
||||||
CenterRelative = "CenterRelative",
|
CenterRelative = "CenterRelative", // Always face the center.
|
||||||
CenterRelativeOffset = "CenterRelativeOffset",
|
|
||||||
Forward = "Forward", // Always face the direction of the rotation.
|
Forward = "Forward", // Always face the direction of the rotation.
|
||||||
Backward = "Backward", // Opposite of forward.
|
Backward = "Backward", // Opposite of forward.
|
||||||
Start = "Start", // Stay facing the same direction as at the beginning.
|
Start = "Start", // Stay facing the same direction as at the beginning.
|
||||||
|
@ -386,8 +337,6 @@ export class RotationAnimationSegment extends AnimationSegment {
|
||||||
return this.startFacing;
|
return this.startFacing;
|
||||||
case RotationAnimationFacing.Center:
|
case RotationAnimationFacing.Center:
|
||||||
return degrees - 90;
|
return degrees - 90;
|
||||||
case RotationAnimationFacing.CenterRelativeOffset:
|
|
||||||
return degrees - 90 + this.centerRelativeTo;
|
|
||||||
case RotationAnimationFacing.CenterRelative:
|
case RotationAnimationFacing.CenterRelative:
|
||||||
return degrees - this.startRotation + this.centerRelativeTo;
|
return degrees - this.startRotation + this.centerRelativeTo;
|
||||||
case RotationAnimationFacing.Forward:
|
case RotationAnimationFacing.Forward:
|
||||||
|
@ -443,16 +392,6 @@ export class RotationAnimationSegment extends AnimationSegment {
|
||||||
start, end,
|
start, end,
|
||||||
this.endRotation < this.startRotation);
|
this.endRotation < this.startRotation);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
const startPos = this.interpolateOffset(0);
|
|
||||||
const endPos = this.interpolateOffset(1);
|
|
||||||
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.ellipse(startPos.x, startPos.y, 0.1, 0.1, 0, 0, 2*Math.PI);
|
|
||||||
ctx.stroke();
|
|
||||||
const endSize = 0.05;
|
|
||||||
ctx.fillRect(endPos.x - endSize, endPos.y - endSize, endSize*2, endSize*2);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -33,16 +33,6 @@ export class CirclePosition {
|
||||||
this.enumValue = enumValue;
|
this.enumValue = enumValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static fromSides(leftRightSide: CircleSide.Left | CircleSide.Right, topBottomSide: CircleSide.Bottom | CircleSide.Top) {
|
|
||||||
return leftRightSide === CircleSide.Left
|
|
||||||
? topBottomSide === CircleSide.Top
|
|
||||||
? CirclePosition.TopLeft
|
|
||||||
: CirclePosition.BottomLeft
|
|
||||||
: topBottomSide === CircleSide.Top
|
|
||||||
? CirclePosition.TopRight
|
|
||||||
: CirclePosition.BottomRight;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static enumValueToNumber(enumValue: CirclePositionEnum) : number {
|
private static enumValueToNumber(enumValue: CirclePositionEnum) : number {
|
||||||
switch (enumValue) {
|
switch (enumValue) {
|
||||||
case CirclePositionEnum.TopLeft:
|
case CirclePositionEnum.TopLeft:
|
||||||
|
@ -105,38 +95,6 @@ export class CirclePosition {
|
||||||
][CirclePosition.enumValueToNumber(this.enumValue)];
|
][CirclePosition.enumValueToNumber(this.enumValue)];
|
||||||
}
|
}
|
||||||
|
|
||||||
public toShortLines(slideTo: Hand) : ShortLinesPosition {
|
|
||||||
return slideTo === Hand.Left
|
|
||||||
? [
|
|
||||||
ShortLinesPosition.FarLeft,
|
|
||||||
ShortLinesPosition.MiddleLeft,
|
|
||||||
ShortLinesPosition.FarRight,
|
|
||||||
ShortLinesPosition.MiddleRight,
|
|
||||||
][CirclePosition.enumValueToNumber(this.enumValue)]
|
|
||||||
: [
|
|
||||||
ShortLinesPosition.MiddleLeft,
|
|
||||||
ShortLinesPosition.FarLeft,
|
|
||||||
ShortLinesPosition.MiddleRight,
|
|
||||||
ShortLinesPosition.FarRight,
|
|
||||||
][CirclePosition.enumValueToNumber(this.enumValue)];
|
|
||||||
}
|
|
||||||
|
|
||||||
public unfoldToShortLines(center: CircleSide.Bottom | CircleSide.Top) : ShortLinesPosition {
|
|
||||||
return center === CircleSide.Bottom
|
|
||||||
? [
|
|
||||||
ShortLinesPosition.FarLeft,
|
|
||||||
ShortLinesPosition.MiddleLeft,
|
|
||||||
ShortLinesPosition.MiddleRight,
|
|
||||||
ShortLinesPosition.FarRight,
|
|
||||||
][CirclePosition.enumValueToNumber(this.enumValue)]
|
|
||||||
: [
|
|
||||||
ShortLinesPosition.MiddleLeft,
|
|
||||||
ShortLinesPosition.FarLeft,
|
|
||||||
ShortLinesPosition.FarRight,
|
|
||||||
ShortLinesPosition.MiddleRight,
|
|
||||||
][CirclePosition.enumValueToNumber(this.enumValue)];
|
|
||||||
}
|
|
||||||
|
|
||||||
public swapDiagonal() : CirclePosition {
|
public swapDiagonal() : CirclePosition {
|
||||||
return this.swapAcross().swapUpAndDown();
|
return this.swapAcross().swapUpAndDown();
|
||||||
}
|
}
|
||||||
|
@ -262,16 +220,6 @@ export class ShortLinesPosition {
|
||||||
][ShortLinesPosition.enumValueToNumber(this.enumValue)];
|
][ShortLinesPosition.enumValueToNumber(this.enumValue)];
|
||||||
}
|
}
|
||||||
|
|
||||||
public shift(dir: Hand, facing: Facing.Up | Facing.Down): ShortLinesPosition {
|
|
||||||
const shift = (dir === Hand.Left) === (facing === Facing.Down) ? -1 : +1;
|
|
||||||
const newNum = ShortLinesPosition.enumValueToNumber(this.enumValue) + shift;
|
|
||||||
if (newNum < 0 || newNum > 3) {
|
|
||||||
throw new Error("Invalid shift: " + this + " facing " + facing + " to " + dir + ".");
|
|
||||||
}
|
|
||||||
|
|
||||||
return ShortLinesPosition.get(ShortLinesPosition.numberToEnumValue(newNum));
|
|
||||||
}
|
|
||||||
|
|
||||||
public isMiddle() : boolean {
|
public isMiddle() : boolean {
|
||||||
return this.enumValue === ShortLinesPositionEnum.MiddleRight || this.enumValue === ShortLinesPositionEnum.MiddleLeft;
|
return this.enumValue === ShortLinesPositionEnum.MiddleRight || this.enumValue === ShortLinesPositionEnum.MiddleLeft;
|
||||||
}
|
}
|
||||||
|
@ -286,11 +234,6 @@ export class ShortLinesPosition {
|
||||||
return this.leftRightSide() === CircleSide.Left;
|
return this.leftRightSide() === CircleSide.Left;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Of the two positions on the same leftRightSide() is this the one further to the left?
|
|
||||||
public isLeftOfSide() : boolean {
|
|
||||||
return this.enumValue === ShortLinesPositionEnum.FarLeft || this.enumValue === ShortLinesPositionEnum.MiddleRight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public facingSide() : Facing.Left | Facing.Right {
|
public facingSide() : Facing.Left | Facing.Right {
|
||||||
return this.isLeft() === this.isMiddle() ? Facing.Left : Facing.Right;
|
return this.isLeft() === this.isMiddle() ? Facing.Left : Facing.Right;
|
||||||
}
|
}
|
||||||
|
@ -361,14 +304,7 @@ export enum DancerDistance {
|
||||||
SwingRobin = "SwingRobin",
|
SwingRobin = "SwingRobin",
|
||||||
}
|
}
|
||||||
export enum LongLines {
|
export enum LongLines {
|
||||||
// Walked forward into center.
|
|
||||||
Forward = "Forward",
|
Forward = "Forward",
|
||||||
|
|
||||||
// Only a little offset (has walked almost all the way from the other side after a give and take).
|
|
||||||
Near = "Near",
|
|
||||||
|
|
||||||
// Actually in center. May be slightly offset for wavy lines.
|
|
||||||
Center = "Center",
|
|
||||||
}
|
}
|
||||||
export type SemanticPosition = {
|
export type SemanticPosition = {
|
||||||
kind: PositionKind.Circle,
|
kind: PositionKind.Circle,
|
||||||
|
@ -389,7 +325,6 @@ export type SemanticPosition = {
|
||||||
hands?: Map<Hand, HandConnection>,
|
hands?: Map<Hand, HandConnection>,
|
||||||
balance?: BalanceWeight,
|
balance?: BalanceWeight,
|
||||||
dancerDistance?: DancerDistance,
|
dancerDistance?: DancerDistance,
|
||||||
longLines?: undefined,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const handsInCircle = new Map<Hand, HandConnection>([
|
export const handsInCircle = new Map<Hand, HandConnection>([
|
||||||
|
@ -402,7 +337,7 @@ export const handsInCircle = new Map<Hand, HandConnection>([
|
||||||
hand: Hand.Left,
|
hand: Hand.Left,
|
||||||
}],
|
}],
|
||||||
]);
|
]);
|
||||||
export const handsFourImproper: Map<DancerIdentity, SemanticPosition & { kind: PositionKind.Circle }> = new Map<DancerIdentity, SemanticPosition & { kind: PositionKind.Circle }>([
|
export const handsFourImproper: Map<DancerIdentity, SemanticPosition> = new Map<DancerIdentity, SemanticPosition>([
|
||||||
[DancerIdentity.OnesLark, {
|
[DancerIdentity.OnesLark, {
|
||||||
kind: PositionKind.Circle,
|
kind: PositionKind.Circle,
|
||||||
which: CirclePosition.TopLeft,
|
which: CirclePosition.TopLeft,
|
||||||
|
@ -428,38 +363,3 @@ export const handsFourImproper: Map<DancerIdentity, SemanticPosition & { kind: P
|
||||||
hands: handsInCircle,
|
hands: handsInCircle,
|
||||||
}],
|
}],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export function handsInShortLine({ which, facing, wavy }: { which: ShortLinesPosition; facing: Facing.Up | Facing.Down; wavy: boolean; }): Map<Hand, HandConnection> {
|
|
||||||
return which.isMiddle() ? new Map<Hand, HandConnection>([
|
|
||||||
[Hand.Left, { hand: wavy ? Hand.Right : Hand.Left, to: HandTo.DancerLeft }],
|
|
||||||
[Hand.Right, { hand: wavy ? Hand.Left : Hand.Right, to: HandTo.DancerRight }],
|
|
||||||
]) : new Map<Hand, HandConnection>([
|
|
||||||
which.isLeft() === (facing === Facing.Up)
|
|
||||||
? [Hand.Left, { hand: wavy ? Hand.Right : Hand.Left, to: HandTo.DancerLeft }]
|
|
||||||
: [Hand.Right, { hand: wavy ? Hand.Left : Hand.Right, to: HandTo.DancerRight }]
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
export function handsInLine(args: { wavy: boolean, which: ShortLinesPosition | CirclePosition, facing?: Facing }) {
|
|
||||||
if (args.which instanceof ShortLinesPosition && (args.facing === Facing.Up || args.facing === Facing.Down)) {
|
|
||||||
return handsInShortLine({ wavy: args.wavy, which: args.which, facing: args.facing });
|
|
||||||
} else {
|
|
||||||
return new Map<Hand, HandConnection>([
|
|
||||||
[Hand.Left, { hand: args.wavy ? Hand.Right : Hand.Left, to: HandTo.DancerLeft }],
|
|
||||||
[Hand.Right, { hand: args.wavy ? Hand.Left : Hand.Right, to: HandTo.DancerRight }],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export function handToDancerToSideInCircleFacingAcross(which: CirclePosition): Map<Hand, HandConnection> {
|
|
||||||
return new Map<Hand, HandConnection>([
|
|
||||||
which.isOnLeftLookingAcross()
|
|
||||||
? [Hand.Right, { hand: Hand.Left, to: HandTo.DancerRight }]
|
|
||||||
: [Hand.Left, { hand: Hand.Right, to: HandTo.DancerLeft }]
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
export function handToDancerToSideInCircleFacingUpOrDown(which: CirclePosition): Map<Hand, HandConnection> {
|
|
||||||
return new Map<Hand, HandConnection>([
|
|
||||||
which.isOnLeftLookingUpAndDown()
|
|
||||||
? [Hand.Right, { hand: Hand.Left, to: HandTo.DancerRight }]
|
|
||||||
: [Hand.Left, { hand: Hand.Right, to: HandTo.DancerLeft }]
|
|
||||||
]);
|
|
||||||
}
|
|
|
@ -40,7 +40,7 @@ type chooser_dancers = "everyone" | "gentlespoon" | "gentlespoons" | "ladle" | "
|
||||||
type chooser_pair = "gentlespoons" | "ladles" | "ones" | "twos" | "first corners" | "second corners";
|
type chooser_pair = "gentlespoons" | "ladles" | "ones" | "twos" | "first corners" | "second corners";
|
||||||
type chooser_pair_or_everyone = "everyone" | "gentlespoons" | "ladles" | "ones" | "twos" | "first corners" | "second corners";
|
type chooser_pair_or_everyone = "everyone" | "gentlespoons" | "ladles" | "ones" | "twos" | "first corners" | "second corners";
|
||||||
type chooser_pairc_or_everyone = "everyone" | "gentlespoons" | "ladles" | "centers" | "ones" | "twos";
|
type chooser_pairc_or_everyone = "everyone" | "gentlespoons" | "ladles" | "centers" | "ones" | "twos";
|
||||||
export type chooser_pairz = "gentlespoons" | "ladles" | "partners" | "neighbors" | "next neighbors" | "3rd neighbors" | "ones" | "twos" | "same roles" | "first corners" | "second corners" | "shadows";
|
export type chooser_pairz = "gentlespoons" | "ladles" | "partners" | "neighbors" | "next neighbors" | "ones" | "twos" | "same roles" | "first corners" | "second corners" | "shadows";
|
||||||
type chooser_pairz_or_unspecified = "" | "gentlespoons" | "ladles" | "partners" | "neighbors" | "ones" | "twos" | "same roles" | "first corners" | "second corners" | "shadows";
|
type chooser_pairz_or_unspecified = "" | "gentlespoons" | "ladles" | "partners" | "neighbors" | "ones" | "twos" | "same roles" | "first corners" | "second corners" | "shadows";
|
||||||
type chooser_pairs = "partners" | "neighbors" | "same roles" | "shadows";
|
type chooser_pairs = "partners" | "neighbors" | "same roles" | "shadows";
|
||||||
type chooser_pairs_or_ones_or_twos = "partners" | "neighbors" | "same roles" | "ones" | "twos" | "shadows";
|
type chooser_pairs_or_ones_or_twos = "partners" | "neighbors" | "same roles" | "ones" | "twos" | "shadows";
|
||||||
|
|
|
@ -3,7 +3,7 @@ import * as common from "./danceCommon.js";
|
||||||
import { DanceRole, DancerIdentity, Rotation } from "./danceCommon.js";
|
import { DanceRole, DancerIdentity, Rotation } from "./danceCommon.js";
|
||||||
import { BalanceWeight, CirclePosition, CircleSide, CircleSideOrCenter, DancerDistance, Facing, HandConnection, HandTo, LongLines, PositionKind, SemanticPosition, ShortLinesPosition, StarGrip, handsInCircle } from "./interpreterCommon.js";
|
import { BalanceWeight, CirclePosition, CircleSide, CircleSideOrCenter, DancerDistance, Facing, HandConnection, HandTo, LongLines, PositionKind, SemanticPosition, ShortLinesPosition, StarGrip, handsInCircle } from "./interpreterCommon.js";
|
||||||
import { Move } from "./libfigureMapper.js";
|
import { Move } from "./libfigureMapper.js";
|
||||||
import { DancerSetPosition, Hand, Offset, OffsetPlus, OffsetRotate, OffsetTimes, OffsetTranspose, dancerHeight, dancerHeightOffset, dancerWidth, lineDistance, offsetZero, setDistance, setHeight, setSpacing, setWidth } from "./rendererConstants.js";
|
import { DancerSetPosition, Hand, Offset, OffsetPlus, OffsetRotate, OffsetTimes, OffsetTranspose, dancerHeightOffset, dancerWidth, lineDistance, offsetZero, setDistance, setHeight, setSpacing, setWidth } from "./rendererConstants.js";
|
||||||
|
|
||||||
|
|
||||||
export enum SemanticAnimationKind {
|
export enum SemanticAnimationKind {
|
||||||
|
@ -88,9 +88,6 @@ export type SemanticAnimation = {
|
||||||
|
|
||||||
// Swings are asymmetric. This is usually but not always the dancer's role.
|
// Swings are asymmetric. This is usually but not always the dancer's role.
|
||||||
swingRole: common.DanceRole,
|
swingRole: common.DanceRole,
|
||||||
|
|
||||||
// After a take need to fixup the position.
|
|
||||||
afterTake: boolean,
|
|
||||||
} | {
|
} | {
|
||||||
kind: SemanticAnimationKind.TwirlSwap,
|
kind: SemanticAnimationKind.TwirlSwap,
|
||||||
|
|
||||||
|
@ -273,12 +270,8 @@ function SemanticToSetPosition(semantic: SemanticPosition): DancerSetPosition {
|
||||||
throw "Invalid circle position: " + semantic.which;
|
throw "Invalid circle position: " + semantic.which;
|
||||||
}
|
}
|
||||||
switch(semantic.longLines) {
|
switch(semantic.longLines) {
|
||||||
case LongLines.Center:
|
|
||||||
position.x *= 0.1;
|
|
||||||
case LongLines.Forward:
|
case LongLines.Forward:
|
||||||
position.x *= 0.3;
|
position.x *= 0.25;
|
||||||
case LongLines.Near:
|
|
||||||
position.x *= 0.6;
|
|
||||||
}
|
}
|
||||||
let balanceOffset: Offset = offsetZero;
|
let balanceOffset: Offset = offsetZero;
|
||||||
if (semantic.balance) {
|
if (semantic.balance) {
|
||||||
|
@ -366,10 +359,6 @@ function SemanticToSetPosition(semantic: SemanticPosition): DancerSetPosition {
|
||||||
case HandTo.DiagonalAcrossCircle:
|
case HandTo.DiagonalAcrossCircle:
|
||||||
// TODO Is "diagonal" even enough information?
|
// TODO Is "diagonal" even enough information?
|
||||||
return { x: -0.5, y: +0.5 }
|
return { x: -0.5, y: +0.5 }
|
||||||
case HandTo.LeftDiagonalAcrossCircle:
|
|
||||||
return { x: -0.5 - setDistance, y: +0.5 }
|
|
||||||
case HandTo.RightDiagonalAcrossCircle:
|
|
||||||
return { x: -0.5 + setDistance, y: +0.5 }
|
|
||||||
default:
|
default:
|
||||||
throw "Unkown connection: " + connection.to;
|
throw "Unkown connection: " + connection.to;
|
||||||
}
|
}
|
||||||
|
@ -383,15 +372,12 @@ function SemanticToSetPosition(semantic: SemanticPosition): DancerSetPosition {
|
||||||
};
|
};
|
||||||
|
|
||||||
case PositionKind.ShortLines:
|
case PositionKind.ShortLines:
|
||||||
let yOffset = 0;
|
|
||||||
switch (semantic.facing) {
|
switch (semantic.facing) {
|
||||||
case Facing.Up:
|
case Facing.Up:
|
||||||
rotation = Rotation.Up;
|
rotation = Rotation.Up;
|
||||||
yOffset = +dancerHeight;
|
|
||||||
break;
|
break;
|
||||||
case Facing.Down:
|
case Facing.Down:
|
||||||
rotation = Rotation.Down;
|
rotation = Rotation.Down;
|
||||||
yOffset = -dancerHeight;
|
|
||||||
break;
|
break;
|
||||||
case Facing.Left:
|
case Facing.Left:
|
||||||
rotation = Rotation.Left;
|
rotation = Rotation.Left;
|
||||||
|
@ -405,16 +391,16 @@ function SemanticToSetPosition(semantic: SemanticPosition): DancerSetPosition {
|
||||||
|
|
||||||
switch (semantic.which) {
|
switch (semantic.which) {
|
||||||
case ShortLinesPosition.FarLeft:
|
case ShortLinesPosition.FarLeft:
|
||||||
position = { x: -1.5, y: yOffset };
|
position = { x: -1.5, y: 0 };
|
||||||
break;
|
break;
|
||||||
case ShortLinesPosition.MiddleLeft:
|
case ShortLinesPosition.MiddleLeft:
|
||||||
position = { x: -0.5, y: yOffset };
|
position = { x: -0.5, y: 0 };
|
||||||
break;
|
break;
|
||||||
case ShortLinesPosition.MiddleRight:
|
case ShortLinesPosition.MiddleRight:
|
||||||
position = { x: +0.5, y: yOffset };
|
position = { x: +0.5, y: 0 };
|
||||||
break;
|
break;
|
||||||
case ShortLinesPosition.FarRight:
|
case ShortLinesPosition.FarRight:
|
||||||
position = { x: +1.5, y: yOffset };
|
position = { x: +1.5, y: 0 };
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw "Invalid circle position: " + semantic.which;
|
throw "Invalid circle position: " + semantic.which;
|
||||||
|
@ -457,14 +443,12 @@ function SemanticToSetPosition(semantic: SemanticPosition): DancerSetPosition {
|
||||||
// TODO Hands. Might need more info? Or need context of nearby dancer SemanticPositions?
|
// TODO Hands. Might need more info? Or need context of nearby dancer SemanticPositions?
|
||||||
if (!connection) return undefined;
|
if (!connection) return undefined;
|
||||||
|
|
||||||
const balanceYOffset = (semantic.facing === Facing.Up ? yOffset : -yOffset)
|
const balanceYOffset = semantic.balance === BalanceWeight.Forward ? -shortWavesBalanceAmount : semantic.balance === BalanceWeight.Backward ? shortWavesBalanceAmount : 0;
|
||||||
+ (semantic.balance === BalanceWeight.Forward ? -shortWavesBalanceAmount : semantic.balance === BalanceWeight.Backward ? shortWavesBalanceAmount : 0);
|
|
||||||
const balanceXOffset = semantic.balance === BalanceWeight.Left ? -balanceAmount : semantic.balance === BalanceWeight.Right ? balanceAmount : 0;
|
|
||||||
switch (connection.to) {
|
switch (connection.to) {
|
||||||
case HandTo.DancerLeft:
|
case HandTo.DancerLeft:
|
||||||
return { x: -0.5 + balanceXOffset, y: balanceYOffset };
|
return { x: -0.5, y: balanceYOffset };
|
||||||
case HandTo.DancerRight:
|
case HandTo.DancerRight:
|
||||||
return { x: +0.5 + balanceXOffset, y: balanceYOffset };
|
return { x: +0.5, y: balanceYOffset };
|
||||||
case HandTo.DancerForward:
|
case HandTo.DancerForward:
|
||||||
if (hand === connection.hand) {
|
if (hand === connection.hand) {
|
||||||
return { x: 0, y: +0.5 };
|
return { x: 0, y: +0.5 };
|
||||||
|
@ -525,13 +509,12 @@ function animateLowLevelMoveWithoutSlide(move: LowLevelMove): animation.Animatio
|
||||||
];
|
];
|
||||||
case SemanticAnimationKind.Linear:
|
case SemanticAnimationKind.Linear:
|
||||||
let rotation = endSetPosition.rotation - startSetPosition.rotation;
|
let rotation = endSetPosition.rotation - startSetPosition.rotation;
|
||||||
let rotationDuring: boolean = true;
|
|
||||||
const minRotation = move.movementPattern.minRotation;
|
const minRotation = move.movementPattern.minRotation;
|
||||||
try {
|
try {
|
||||||
rotation = common.normalizeRotation(rotation, minRotation);
|
rotation = common.normalizeRotation(rotation, minRotation);
|
||||||
} catch {
|
} catch {
|
||||||
rotation = common.normalizeRotation(rotation, undefined);
|
throw new Error("Expected zero rotation, but start and end positions at different orientations: start="
|
||||||
rotationDuring = false;
|
+ JSON.stringify(move.startPosition) + ", end=" + JSON.stringify(move.endPosition));
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
new animation.TransitionAnimationSegment({
|
new animation.TransitionAnimationSegment({
|
||||||
|
@ -546,28 +529,19 @@ function animateLowLevelMoveWithoutSlide(move: LowLevelMove): animation.Animatio
|
||||||
flags: {
|
flags: {
|
||||||
hands: (move.movementPattern.handsDuring ?? "Linear") !== "Linear",
|
hands: (move.movementPattern.handsDuring ?? "Linear") !== "Linear",
|
||||||
handsDuring: move.movementPattern.handsDuring === "Linear" ? undefined : move.movementPattern.handsDuring,
|
handsDuring: move.movementPattern.handsDuring === "Linear" ? undefined : move.movementPattern.handsDuring,
|
||||||
rotation: !rotationDuring,
|
|
||||||
rotationDuring: rotationDuring ? undefined : "Start",
|
|
||||||
},
|
},
|
||||||
startTransitionBeats: 1,
|
startTransitionBeats: 1,
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
case SemanticAnimationKind.Circle:
|
case SemanticAnimationKind.Circle:
|
||||||
case SemanticAnimationKind.Star:
|
|
||||||
if (move.startPosition.kind !== PositionKind.Circle) {
|
if (move.startPosition.kind !== PositionKind.Circle) {
|
||||||
throw new Error(move.movementPattern.kind + " must start and end in a circle.");
|
throw "Circle must start and end in a circle.";
|
||||||
}
|
}
|
||||||
|
|
||||||
const isCircle = move.movementPattern.kind === SemanticAnimationKind.Circle;
|
const posWithHands = SemanticToSetPosition({...move.endPosition, hands: handsInCircle})
|
||||||
const posWithHands = SemanticToSetPosition({...move.endPosition, hands: isCircle ? handsInCircle : undefined})
|
const circleHands = new Map<Hand, Offset>([
|
||||||
const circleHands = move.movementPattern.kind === SemanticAnimationKind.Circle ? new Map<Hand, Offset>([
|
|
||||||
[Hand.Left, posWithHands.leftArmEnd!],
|
[Hand.Left, posWithHands.leftArmEnd!],
|
||||||
[Hand.Right, posWithHands.rightArmEnd!],
|
[Hand.Right, posWithHands.rightArmEnd!],
|
||||||
]) : new Map<Hand, Offset>([
|
|
||||||
[move.movementPattern.hand, {
|
|
||||||
x: (move.movementPattern.hand === Hand.Right ? +1 : -1) * (1 + (move.movementPattern.grip === StarGrip.HandsAcross ? 0 : 0.15)) * Math.sqrt(2),
|
|
||||||
y: move.movementPattern.grip === StarGrip.HandsAcross ? 0 : 0.25
|
|
||||||
}]
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
@ -582,7 +556,7 @@ function animateLowLevelMoveWithoutSlide(move: LowLevelMove): animation.Animatio
|
||||||
width: setWidth,
|
width: setWidth,
|
||||||
height: setHeight,
|
height: setHeight,
|
||||||
},
|
},
|
||||||
facing: isCircle ? animation.RotationAnimationFacing.Center : animation.RotationAnimationFacing.Forward,
|
facing: animation.RotationAnimationFacing.Center,
|
||||||
closer: undefined,
|
closer: undefined,
|
||||||
hands: new Map<Hand, animation.HandAnimation>([
|
hands: new Map<Hand, animation.HandAnimation>([
|
||||||
[Hand.Left, { kind: "End" }],
|
[Hand.Left, { kind: "End" }],
|
||||||
|
@ -590,7 +564,6 @@ function animateLowLevelMoveWithoutSlide(move: LowLevelMove): animation.Animatio
|
||||||
])
|
])
|
||||||
}),
|
}),
|
||||||
flags: {
|
flags: {
|
||||||
rotation: true,
|
|
||||||
hands: true,
|
hands: true,
|
||||||
handsDuring: circleHands,
|
handsDuring: circleHands,
|
||||||
},
|
},
|
||||||
|
@ -673,35 +646,18 @@ function animateLowLevelMoveWithoutSlide(move: LowLevelMove): animation.Animatio
|
||||||
const rotateSwingCenter = CenterOf(move.movementPattern.around, move.startPosition.setOffset, move.startPosition.lineOffset);
|
const rotateSwingCenter = CenterOf(move.movementPattern.around, move.startPosition.setOffset, move.startPosition.lineOffset);
|
||||||
const dancerDistance = move.movementPattern.swingRole === DanceRole.Lark ? DancerDistance.SwingLark : DancerDistance.SwingRobin;
|
const dancerDistance = move.movementPattern.swingRole === DanceRole.Lark ? DancerDistance.SwingLark : DancerDistance.SwingRobin;
|
||||||
|
|
||||||
const baseSwingStart = {
|
const swingStart = SemanticToSetPosition({
|
||||||
...move.startPosition,
|
...move.startPosition,
|
||||||
dancerDistance: move.movementPattern.afterTake ? undefined : dancerDistance,
|
dancerDistance,
|
||||||
balance: undefined,
|
balance: undefined,
|
||||||
longLines: undefined,
|
});
|
||||||
}
|
|
||||||
const swingStartUnadjusted = SemanticToSetPosition(move.startPosition.longLines === LongLines.Near ? {
|
|
||||||
...baseSwingStart,
|
|
||||||
kind: PositionKind.Circle,
|
|
||||||
which: move.startPosition.which.swapUpAndDown(),
|
|
||||||
} : baseSwingStart);
|
|
||||||
const swingStart = move.movementPattern.afterTake
|
|
||||||
? {
|
|
||||||
...swingStartUnadjusted, position: {
|
|
||||||
x: swingStartUnadjusted.position.x
|
|
||||||
+ ((move.startPosition.longLines === LongLines.Near) === (move.startPosition.which.isLeft())
|
|
||||||
? +0.5
|
|
||||||
: -0.5),
|
|
||||||
y: rotateSwingCenter.y,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
: swingStartUnadjusted;
|
|
||||||
const beforeUnfold = SemanticToSetPosition({
|
const beforeUnfold = SemanticToSetPosition({
|
||||||
...move.endPosition,
|
...move.endPosition,
|
||||||
dancerDistance,
|
dancerDistance,
|
||||||
});
|
});
|
||||||
const unfolded = SemanticToSetPosition({
|
const unfolded = SemanticToSetPosition({
|
||||||
...move.endPosition,
|
...move.endPosition,
|
||||||
dancerDistance: move.endPosition.dancerDistance === DancerDistance.Compact ? DancerDistance.Compact : DancerDistance.Normal,
|
dancerDistance: DancerDistance.Normal,
|
||||||
hands: move.movementPattern.swingRole === DanceRole.Lark
|
hands: move.movementPattern.swingRole === DanceRole.Lark
|
||||||
? new Map<Hand, HandConnection>([[Hand.Right, {hand: Hand.Left, to: HandTo.DancerRight}]])
|
? new Map<Hand, HandConnection>([[Hand.Right, {hand: Hand.Left, to: HandTo.DancerRight}]])
|
||||||
: new Map<Hand, HandConnection>([[Hand.Left, {hand: Hand.Right, to: HandTo.DancerLeft}]]),
|
: new Map<Hand, HandConnection>([[Hand.Left, {hand: Hand.Right, to: HandTo.DancerLeft}]]),
|
||||||
|
@ -713,16 +669,15 @@ function animateLowLevelMoveWithoutSlide(move: LowLevelMove): animation.Animatio
|
||||||
const turns = Math.ceil(Math.abs(move.movementPattern.minAmount / 360));
|
const turns = Math.ceil(Math.abs(move.movementPattern.minAmount / 360));
|
||||||
const swingMinRotation = (minTurns - turns) * (move.movementPattern.minAmount < 0 ? -360 : 360) + move.movementPattern.minAmount;
|
const swingMinRotation = (minTurns - turns) * (move.movementPattern.minAmount < 0 ? -360 : 360) + move.movementPattern.minAmount;
|
||||||
|
|
||||||
const slideAmount = move.movementPattern.afterTake ? { x: 0, y: rotateSwingCenter.y - startSetPosition.position.y } : undefined;
|
return [
|
||||||
|
new animation.TransitionAnimationSegment({
|
||||||
const swingAnimation = new animation.TransitionAnimationSegment({
|
|
||||||
actualAnimation: new animation.RotationAnimationSegment({
|
actualAnimation: new animation.RotationAnimationSegment({
|
||||||
beats: swingBeats,
|
beats: swingBeats,
|
||||||
startPosition: slideAmount ? { ...swingStart, position: OffsetPlus(swingStart.position, OffsetTimes(slideAmount, -1)) } : swingStart,
|
startPosition: startSetPosition,
|
||||||
endPosition: slideAmount ? { ...beforeUnfold, position: OffsetPlus(beforeUnfold.position, OffsetTimes(slideAmount, -1)) } : beforeUnfold,
|
endPosition: beforeUnfold,
|
||||||
rotation: swingMinRotation,
|
rotation: swingMinRotation,
|
||||||
around: {
|
around: {
|
||||||
center: slideAmount ? OffsetPlus(rotateSwingCenter, OffsetTimes(slideAmount, -1)) : rotateSwingCenter,
|
center: rotateSwingCenter,
|
||||||
width: 1,
|
width: 1,
|
||||||
height: 1,
|
height: 1,
|
||||||
},
|
},
|
||||||
|
@ -730,8 +685,8 @@ function animateLowLevelMoveWithoutSlide(move: LowLevelMove): animation.Animatio
|
||||||
minDistance: 1,
|
minDistance: 1,
|
||||||
transitionBeats: 1,
|
transitionBeats: 1,
|
||||||
},
|
},
|
||||||
facing: animation.RotationAnimationFacing.CenterRelativeOffset,
|
facing: animation.RotationAnimationFacing.CenterRelative,
|
||||||
centerRelativeTo: (dancerDistance === DancerDistance.SwingLark ? -45 : +45),
|
centerRelativeTo: startSetPosition.rotation + ((move.startPosition.facing === Facing.Up || move.startPosition.facing === Facing.Down) === (dancerDistance === DancerDistance.SwingLark) ? -45 : +45)
|
||||||
}),
|
}),
|
||||||
flags: {
|
flags: {
|
||||||
rotation: true,
|
rotation: true,
|
||||||
|
@ -740,14 +695,7 @@ function animateLowLevelMoveWithoutSlide(move: LowLevelMove): animation.Animatio
|
||||||
},
|
},
|
||||||
startTransitionBeats: 1,
|
startTransitionBeats: 1,
|
||||||
endTransitionBeats: 0,
|
endTransitionBeats: 0,
|
||||||
});
|
}),
|
||||||
|
|
||||||
return [
|
|
||||||
slideAmount ? new animation.SlideAnimationSegment(
|
|
||||||
swingAnimation.beats,
|
|
||||||
[swingAnimation],
|
|
||||||
slideAmount,
|
|
||||||
) : swingAnimation,
|
|
||||||
new animation.LinearAnimationSegment({
|
new animation.LinearAnimationSegment({
|
||||||
beats: 1,
|
beats: 1,
|
||||||
startPosition: beforeUnfold,
|
startPosition: beforeUnfold,
|
||||||
|
@ -763,7 +711,6 @@ function animateLowLevelMoveWithoutSlide(move: LowLevelMove): animation.Animatio
|
||||||
const twirlCenter =
|
const twirlCenter =
|
||||||
CenterOf(move.movementPattern.around, move.startPosition.setOffset, move.startPosition.lineOffset);
|
CenterOf(move.movementPattern.around, move.startPosition.setOffset, move.startPosition.lineOffset);
|
||||||
const aroundTopOrBottom = move.movementPattern.around === CircleSide.Top || move.movementPattern.around === CircleSide.Bottom;
|
const aroundTopOrBottom = move.movementPattern.around === CircleSide.Top || move.movementPattern.around === CircleSide.Bottom;
|
||||||
const inShortLines = move.startPosition.kind === PositionKind.ShortLines;
|
|
||||||
return [
|
return [
|
||||||
new animation.TransitionAnimationSegment({
|
new animation.TransitionAnimationSegment({
|
||||||
actualAnimation: new animation.RotationAnimationSegment({
|
actualAnimation: new animation.RotationAnimationSegment({
|
||||||
|
@ -780,7 +727,7 @@ function animateLowLevelMoveWithoutSlide(move: LowLevelMove): animation.Animatio
|
||||||
around: {
|
around: {
|
||||||
center: twirlCenter,
|
center: twirlCenter,
|
||||||
width: aroundTopOrBottom ? setWidth : setWidth / 4,
|
width: aroundTopOrBottom ? setWidth : setWidth / 4,
|
||||||
height: aroundTopOrBottom || inShortLines ? setHeight / 4 : setHeight,
|
height: aroundTopOrBottom ? setHeight / 4 : setHeight,
|
||||||
},
|
},
|
||||||
facing: animation.RotationAnimationFacing.Linear,
|
facing: animation.RotationAnimationFacing.Linear,
|
||||||
hands: new Map<Hand, animation.HandAnimation>([
|
hands: new Map<Hand, animation.HandAnimation>([
|
||||||
|
@ -832,7 +779,6 @@ function animateLowLevelMoveWithoutSlide(move: LowLevelMove): animation.Animatio
|
||||||
flags: {
|
flags: {
|
||||||
hands: true,
|
hands: true,
|
||||||
rotation: true,
|
rotation: true,
|
||||||
rotationDirection: move.movementPattern.side,
|
|
||||||
},
|
},
|
||||||
startTransitionBeats: 0.5,
|
startTransitionBeats: 0.5,
|
||||||
})
|
})
|
||||||
|
@ -892,17 +838,10 @@ function animateLowLevelMoveWithoutSlide(move: LowLevelMove): animation.Animatio
|
||||||
startTransitionBeats: 1,
|
startTransitionBeats: 1,
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
case SemanticAnimationKind.Promenade:
|
|
||||||
return [
|
|
||||||
new animation.StepWideLinearAnimationSegment({
|
|
||||||
beats: move.beats,
|
|
||||||
startPosition: startSetPosition,
|
|
||||||
endPosition: endSetPosition,
|
|
||||||
distanceAtMidpoint: setHeight / 2,
|
|
||||||
})
|
|
||||||
]
|
|
||||||
// TODO Unsupported moves, just doing linear for now.
|
// TODO Unsupported moves, just doing linear for now.
|
||||||
case SemanticAnimationKind.RollAway:
|
case SemanticAnimationKind.RollAway:
|
||||||
|
case SemanticAnimationKind.Promenade:
|
||||||
|
case SemanticAnimationKind.Star:
|
||||||
return [
|
return [
|
||||||
new animation.LinearAnimationSegment({
|
new animation.LinearAnimationSegment({
|
||||||
beats: move.beats,
|
beats: move.beats,
|
||||||
|
|
|
@ -146,14 +146,14 @@ wrapperDiv.appendChild(beatDisplay);
|
||||||
function drawAtCurrentBeat() {
|
function drawAtCurrentBeat() {
|
||||||
r.drawSetsWithTrails(beatSlider.valueAsNumber, progressionSelector.valueAsNumber);
|
r.drawSetsWithTrails(beatSlider.valueAsNumber, progressionSelector.valueAsNumber);
|
||||||
|
|
||||||
const moveForCurrent = movesByBeat.at(Math.floor(beatSlider.valueAsNumber));
|
const moveForCurrent = movesByBeat[Math.floor(beatSlider.valueAsNumber)];
|
||||||
if (!moveForCurrent?.classList.contains('currentMove') ?? false) {
|
if (!moveForCurrent.classList.contains('currentMove')) {
|
||||||
for (let i = 0; i < movesByBeat.length; i++) {
|
for (let i = 0; i < movesByBeat.length; i++) {
|
||||||
if (movesByBeat[i].classList.contains('currentMove')) {
|
if (movesByBeat[i].classList.contains('currentMove')) {
|
||||||
movesByBeat[i].classList.remove('currentMove');
|
movesByBeat[i].classList.remove('currentMove');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
moveForCurrent?.classList.add('currentMove');
|
moveForCurrent.classList.add('currentMove');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,7 +289,6 @@ function buildMovesList() {
|
||||||
}
|
}
|
||||||
if (lastItem) {
|
if (lastItem) {
|
||||||
lastItem.classList.add('moveForBeat_' + currentBeat);
|
lastItem.classList.add('moveForBeat_' + currentBeat);
|
||||||
movesByBeat[currentBeat] = lastItem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (r.animation.progressionError) {
|
if (r.animation.progressionError) {
|
||||||
|
@ -421,46 +420,6 @@ displaySettingsDiv.appendChild(document.createElement('br'));
|
||||||
displaySettingsDiv.appendChild(extraLinesLabel);
|
displaySettingsDiv.appendChild(extraLinesLabel);
|
||||||
displaySettingsDiv.appendChild(extraLinesSelector);
|
displaySettingsDiv.appendChild(extraLinesSelector);
|
||||||
|
|
||||||
const trailIncrementsSelector = document.createElement('input');
|
|
||||||
trailIncrementsSelector.type = 'number';
|
|
||||||
trailIncrementsSelector.min = '0';
|
|
||||||
trailIncrementsSelector.step = '1';
|
|
||||||
trailIncrementsSelector.value = r.trailIncrements!.toString();
|
|
||||||
trailIncrementsSelector.id = 'trailIncrements';
|
|
||||||
trailIncrementsSelector.style.width = '3em';
|
|
||||||
trailIncrementsSelector.addEventListener('input', (ev) => {
|
|
||||||
r.trailIncrements = trailIncrementsSelector.valueAsNumber;
|
|
||||||
drawAtCurrentBeat();
|
|
||||||
restartAnimation(false);
|
|
||||||
})
|
|
||||||
const trailIncrementsLabel = document.createElement('label');
|
|
||||||
trailIncrementsLabel.innerText = '# trails (faded previous positions): ';
|
|
||||||
trailIncrementsLabel.htmlFor = 'trailIncrements';
|
|
||||||
|
|
||||||
displaySettingsDiv.appendChild(document.createElement('br'));
|
|
||||||
displaySettingsDiv.appendChild(trailIncrementsLabel);
|
|
||||||
displaySettingsDiv.appendChild(trailIncrementsSelector);
|
|
||||||
|
|
||||||
const trailBeatsSelector = document.createElement('input');
|
|
||||||
trailBeatsSelector.type = 'number';
|
|
||||||
trailBeatsSelector.min = '0';
|
|
||||||
trailBeatsSelector.step = '0.1';
|
|
||||||
trailBeatsSelector.value = r.trailLengthInBeats!.toPrecision(1);
|
|
||||||
trailBeatsSelector.id = 'trailBeats';
|
|
||||||
trailBeatsSelector.style.width = '3em';
|
|
||||||
trailBeatsSelector.addEventListener('input', (ev) => {
|
|
||||||
r.trailLengthInBeats = trailBeatsSelector.valueAsNumber;
|
|
||||||
drawAtCurrentBeat();
|
|
||||||
restartAnimation(false);
|
|
||||||
})
|
|
||||||
const trailBeatsLabel = document.createElement('label');
|
|
||||||
trailBeatsLabel.innerText = '# max age of trails (faded previous positions) in beats: ';
|
|
||||||
trailBeatsLabel.htmlFor = 'trailBeats';
|
|
||||||
|
|
||||||
displaySettingsDiv.appendChild(document.createElement('br'));
|
|
||||||
displaySettingsDiv.appendChild(trailBeatsLabel);
|
|
||||||
displaySettingsDiv.appendChild(trailBeatsSelector);
|
|
||||||
|
|
||||||
displaySettingsDiv.appendChild(document.createElement('br'));
|
displaySettingsDiv.appendChild(document.createElement('br'));
|
||||||
displaySettingsDiv.appendChild(debugRender);
|
displaySettingsDiv.appendChild(debugRender);
|
||||||
displaySettingsDiv.appendChild(debugRenderLabel);
|
displaySettingsDiv.appendChild(debugRenderLabel);
|
||||||
|
@ -503,7 +462,7 @@ verifyButton.addEventListener('click', () => {
|
||||||
libfigureError = 'libfigure ex: ' + e;
|
libfigureError = 'libfigure ex: ' + e;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
progressionError = interpreter.loadDance(dance).progressionError;
|
progressionError = interpreter.loadDance(dance.figures, dance.start_type).progressionError;
|
||||||
const moveError = [...interpreter.interpretedDance.values()].flatMap(moves => moves.filter(m => m.interpreterError !== undefined).map(m => m.interpreterError)).at(0);
|
const moveError = [...interpreter.interpretedDance.values()].flatMap(moves => moves.filter(m => m.interpreterError !== undefined).map(m => m.interpreterError)).at(0);
|
||||||
if (moveError) {
|
if (moveError) {
|
||||||
interpreterError = "interpreter move error: " + moveError;
|
interpreterError = "interpreter move error: " + moveError;
|
||||||
|
@ -578,7 +537,7 @@ function loadDance() {
|
||||||
danceTitle.appendChild(title);
|
danceTitle.appendChild(title);
|
||||||
danceTitle.appendChild(author);
|
danceTitle.appendChild(author);
|
||||||
}
|
}
|
||||||
r.animation = interpreter.loadDance(dance);
|
r.animation = interpreter.loadDance(dance.figures, dance.start_type);
|
||||||
if (cancelAnim !== undefined) {
|
if (cancelAnim !== undefined) {
|
||||||
cancelAnimationFrame(cancelAnim);
|
cancelAnimationFrame(cancelAnim);
|
||||||
playButton.innerText = 'Play';
|
playButton.innerText = 'Play';
|
||||||
|
|
|
@ -1,341 +0,0 @@
|
||||||
import { CoupleRole, DanceRole, DancerIdentity, ExtendedDancerIdentity } from "../danceCommon.js";
|
|
||||||
import { CirclePosition, CircleSideOrCenter, PositionKind, SemanticPosition } from "../interpreterCommon.js";
|
|
||||||
import { Move, chooser_pairz } from "../libfigureMapper.js";
|
|
||||||
import { LowLevelMove, SemanticAnimation, SemanticAnimationKind } from "../lowLevelMove.js";
|
|
||||||
|
|
||||||
type MoveName = string & Move["move"];
|
|
||||||
export type MoveInterpreterCtor<N extends MoveName> = new (args: MoveInterpreterCtorArgs<N>) => MoveInterpreter<N>;
|
|
||||||
export const moveInterpreters: Map<MoveName, MoveInterpreterCtor<MoveName>> = new Map<MoveName, MoveInterpreterCtor<MoveName>>();
|
|
||||||
|
|
||||||
export interface MoveInterpreterCtorArgs<N extends MoveName> {
|
|
||||||
move: Move & { move: N };
|
|
||||||
nextMove: Move;
|
|
||||||
numProgessions: number;
|
|
||||||
}
|
|
||||||
export type SemanticPositionsForAllDancers = Map<DancerIdentity, SemanticPosition>;
|
|
||||||
export interface MoveAsLowLevelMovesArgs {
|
|
||||||
startingPos: SemanticPositionsForAllDancers;
|
|
||||||
}
|
|
||||||
export type LowLevelMovesForAllDancers = Map<DancerIdentity, LowLevelMove[]>;
|
|
||||||
export interface Variant {
|
|
||||||
previousMoveVariant?: string,
|
|
||||||
lowLevelMoves: LowLevelMovesForAllDancers,
|
|
||||||
};
|
|
||||||
export type VariantCollection = Map<string, Variant>;
|
|
||||||
|
|
||||||
export type PartialLowLevelMove = {
|
|
||||||
remarks?: string,
|
|
||||||
beats: number,
|
|
||||||
startPosition?: SemanticPosition,
|
|
||||||
endPosition: SemanticPosition,
|
|
||||||
movementPattern: SemanticAnimation,
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface ISingleVariantMoveInterpreter {
|
|
||||||
moveAsLowLevelMoves: () => LowLevelMovesForAllDancers;
|
|
||||||
moveAsVariants: () => VariantCollection;
|
|
||||||
};
|
|
||||||
|
|
||||||
export abstract class SingleVariantMoveInterpreter<T extends MoveInterpreter<N>, N extends MoveName> implements ISingleVariantMoveInterpreter {
|
|
||||||
protected readonly moveInterpreter: T;
|
|
||||||
protected readonly startingPos: SemanticPositionsForAllDancers;
|
|
||||||
|
|
||||||
constructor(moveInterpreter: T, startingPos: SemanticPositionsForAllDancers) {
|
|
||||||
this.moveInterpreter = moveInterpreter;
|
|
||||||
this.startingPos = startingPos;
|
|
||||||
}
|
|
||||||
|
|
||||||
get move() : Move & { move: N } {
|
|
||||||
return this.moveInterpreter.move;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract moveAsLowLevelMoves(): LowLevelMovesForAllDancers;
|
|
||||||
|
|
||||||
moveAsVariants(): VariantCollection {
|
|
||||||
return new Map<string, Variant>([
|
|
||||||
["default", { lowLevelMoves: this.moveAsLowLevelMoves() }]
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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: this.move,
|
|
||||||
startBeat: 0,
|
|
||||||
startPosition: firstMove.startPosition ?? startPos!,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const move of moves.slice(1)) {
|
|
||||||
SingleVariantMoveInterpreter.append(res, move);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res[0].startPosition === undefined) {
|
|
||||||
throw new Error("combine() called without a startPosition.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
findPairOpposite(who: chooser_pairz, id: DancerIdentity): ExtendedDancerIdentity | null {
|
|
||||||
const pos = this.getPosFor(id.asExtendedDancerIdentity());
|
|
||||||
|
|
||||||
const inSameSet = (proposedId: ExtendedDancerIdentity) => {
|
|
||||||
const proposedPos = this.getPosFor(proposedId);
|
|
||||||
return {
|
|
||||||
...proposedId,
|
|
||||||
// Get the same role dancer in the set the dancer is currently in.
|
|
||||||
relativeSet: proposedId.relativeSet + (pos.setOffset - proposedPos.setOffset)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (who) {
|
|
||||||
case "partners":
|
|
||||||
return id.partner().asExtendedDancerIdentity();
|
|
||||||
case "neighbors":
|
|
||||||
case "next neighbors":
|
|
||||||
case "3rd neighbors":
|
|
||||||
// TODO This isn't quite right... especially if it's "next" to intentionally progress...
|
|
||||||
return inSameSet(id.neighbor().asExtendedDancerIdentity());
|
|
||||||
// These three might get used when not with neighbors?
|
|
||||||
case "gentlespoons":
|
|
||||||
case "ladles":
|
|
||||||
case "same roles":
|
|
||||||
if (who === "gentlespoons" && id.danceRole === DanceRole.Robin
|
|
||||||
|| who === "ladles" && id.danceRole === DanceRole.Lark) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return inSameSet(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 new Error("Not sure shadow is consistently the same.");
|
|
||||||
case "first corners":
|
|
||||||
case "second corners":
|
|
||||||
throw new Error("Contra corners are unsupported.");
|
|
||||||
default:
|
|
||||||
throw new Error("Unsupported who: " + who);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getPosFor(id: ExtendedDancerIdentity): SemanticPosition & { setOffset: number, lineOffset: number } {
|
|
||||||
const basePos = this.startingPos.get(id.setIdentity)!;
|
|
||||||
return {...basePos,
|
|
||||||
setOffset: (basePos.setOffset ?? 0) + id.relativeSet,
|
|
||||||
lineOffset: (basePos.lineOffset ?? 0) + id.relativeLine,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMove(dancerFunc: ((arg: { id: DancerIdentity, startPos: SemanticPosition }) => LowLevelMove[])): Map<DancerIdentity, LowLevelMove[]> {
|
|
||||||
const res = new Map<DancerIdentity, LowLevelMove[]>();
|
|
||||||
let anyProgressed = false;
|
|
||||||
for (const [id, startPos] of this.startingPos.entries()) {
|
|
||||||
const lowLevelMoves = dancerFunc({ id, startPos });
|
|
||||||
|
|
||||||
if (this.move.progression) {
|
|
||||||
const startingPos: SemanticPosition = lowLevelMoves.at(0)?.startPosition!;
|
|
||||||
const endPos: SemanticPosition = lowLevelMoves.at(-1)?.endPosition!;
|
|
||||||
if (startingPos.setOffset !== endPos.setOffset) {
|
|
||||||
anyProgressed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
res.set(id, lowLevelMoves);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.move.progression && !anyProgressed) {
|
|
||||||
for (const [id, lowLevelMoves] of res.entries()) {
|
|
||||||
const startingPos: SemanticPosition = lowLevelMoves.at(0)?.startPosition!;
|
|
||||||
const endPos: SemanticPosition = lowLevelMoves.at(-1)?.endPosition!;
|
|
||||||
if (startingPos.setOffset === endPos.setOffset && endPos.kind === PositionKind.Circle) {
|
|
||||||
const endSetOffset = (endPos.setOffset ?? 0) + (endPos.which.isTop() ? -0.5 : +0.5);
|
|
||||||
const endWhich = endPos.which.swapUpAndDown();
|
|
||||||
lowLevelMoves[lowLevelMoves.length - 1] = {
|
|
||||||
...lowLevelMoves[lowLevelMoves.length - 1],
|
|
||||||
endPosition: {
|
|
||||||
...endPos,
|
|
||||||
setOffset: endSetOffset,
|
|
||||||
which: endWhich,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleCircleMove(dancerFunc: ((arg: { id: DancerIdentity, startPos: SemanticPosition & { kind: PositionKind.Circle } }) => LowLevelMove[])): Map<DancerIdentity, LowLevelMove[]> {
|
|
||||||
return this.handleMove(({ id, startPos }) => {
|
|
||||||
if (startPos.kind !== PositionKind.Circle) {
|
|
||||||
throw new Error(this.move.move + " must start in a circle, but " + id + " is at " + startPos);
|
|
||||||
}
|
|
||||||
|
|
||||||
return dancerFunc({ id, startPos });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handlePairedMove(who: chooser_pairz, dancerFunc: ((arg: {
|
|
||||||
id: DancerIdentity,
|
|
||||||
startPos: SemanticPosition,
|
|
||||||
withPos: SemanticPosition & { setOffset: number, lineOffset: number },
|
|
||||||
withId: ExtendedDancerIdentity,
|
|
||||||
around: CircleSideOrCenter,
|
|
||||||
}) => LowLevelMove[]), meanwhileFunc?: ((arg: {
|
|
||||||
id: DancerIdentity,
|
|
||||||
startPos: SemanticPosition,
|
|
||||||
}) => LowLevelMove[])): Map<DancerIdentity, LowLevelMove[]> {
|
|
||||||
return this.handleMove(({ id, startPos }) => {
|
|
||||||
const withId = this.findPairOpposite(who, id);
|
|
||||||
if (!withId) {
|
|
||||||
if (meanwhileFunc) {
|
|
||||||
return meanwhileFunc({ id, startPos });
|
|
||||||
} else {
|
|
||||||
return this.combine([{
|
|
||||||
beats: this.move.beats,
|
|
||||||
startPosition: { ...startPos, hands: undefined },
|
|
||||||
endPosition: { ...startPos, hands: undefined },
|
|
||||||
// TODO Not sure this is actually a good default...
|
|
||||||
movementPattern: { kind: SemanticAnimationKind.StandStill },
|
|
||||||
}]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const withPos = this.getPosFor(withId);
|
|
||||||
const setDifference = withPos.setOffset - (startPos.setOffset ?? 0);
|
|
||||||
let startPosAdjusted = startPos;
|
|
||||||
if (setDifference !== 0) {
|
|
||||||
// TODO Can move be with a different short line or just a different circle?
|
|
||||||
// PassBy can probably be with the next short line...
|
|
||||||
if (startPos.kind === PositionKind.Circle && (setDifference === 1 || setDifference === -1)) {
|
|
||||||
startPosAdjusted = {
|
|
||||||
...startPos,
|
|
||||||
setOffset: (startPos.setOffset ?? 0) + setDifference / 2,
|
|
||||||
which: startPos.which.swapUpAndDown(),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error("Not near dancer to " + this.move.move + " with.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const startWhich = startPosAdjusted.which;
|
|
||||||
// TODO Can swing be across the set (top or bottom)?
|
|
||||||
const around = withPos.which.leftRightSide() === startWhich.leftRightSide()
|
|
||||||
? startWhich.leftRightSide()
|
|
||||||
: withPos.kind === PositionKind.Circle
|
|
||||||
? (startWhich instanceof CirclePosition && withPos.which.topBottomSide() === startWhich.topBottomSide()
|
|
||||||
? startWhich.topBottomSide()
|
|
||||||
: "Center")
|
|
||||||
: "Center";
|
|
||||||
|
|
||||||
return dancerFunc({ id, startPos: startPosAdjusted, withId, withPos, around });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleCirclePairedMove(who: chooser_pairz, dancerFunc: ((arg: {
|
|
||||||
id: DancerIdentity,
|
|
||||||
startPos: SemanticPosition & { kind: PositionKind.Circle },
|
|
||||||
withPos: SemanticPosition & { setOffset: number, lineOffset: number },
|
|
||||||
withId: ExtendedDancerIdentity,
|
|
||||||
around: CircleSideOrCenter,
|
|
||||||
}) => LowLevelMove[]), meanwhileFunc?: ((arg: {
|
|
||||||
id: DancerIdentity,
|
|
||||||
startPos: SemanticPosition & { kind: PositionKind.Circle },
|
|
||||||
}) => LowLevelMove[])): Map<DancerIdentity, LowLevelMove[]> {
|
|
||||||
return this.handlePairedMove(who, ({ id, startPos, withId, withPos, around }) => {
|
|
||||||
if (startPos.kind !== PositionKind.Circle) {
|
|
||||||
throw new Error(this.move.move + " must start in a circle, but " + id + " is at " + startPos);
|
|
||||||
}
|
|
||||||
|
|
||||||
return dancerFunc({ id, startPos, withId, withPos, around });
|
|
||||||
}, meanwhileFunc ? ({id, startPos}) => {
|
|
||||||
if (startPos.kind !== PositionKind.Circle) {
|
|
||||||
throw new Error(this.move.move + " must start in a circle, but " + id + " is at " + startPos);
|
|
||||||
}
|
|
||||||
|
|
||||||
return meanwhileFunc({id, startPos});
|
|
||||||
} : undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
errorStandStill() {
|
|
||||||
return this.handleMove(({ startPos }) => {
|
|
||||||
return [{
|
|
||||||
interpreterError: "UNKNOWN MOVE '" + this.move.move + "': standing still",
|
|
||||||
move: this.move,
|
|
||||||
startBeat: 0,
|
|
||||||
beats: this.move.beats,
|
|
||||||
startPosition: startPos,
|
|
||||||
endPosition: startPos,
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.StandStill,
|
|
||||||
},
|
|
||||||
}];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export abstract class MoveInterpreter<N extends MoveName> {
|
|
||||||
public readonly move: Move & { move: N };
|
|
||||||
public readonly nextMove: Move;
|
|
||||||
public readonly numProgressions: number;
|
|
||||||
|
|
||||||
constructor({ move, nextMove, numProgessions }: MoveInterpreterCtorArgs<N>) {
|
|
||||||
this.move = move;
|
|
||||||
this.nextMove = nextMove; // TODO Should be able to get rid of this using variants.
|
|
||||||
this.numProgressions = numProgessions;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter;
|
|
||||||
|
|
||||||
moveAsLowLevelMoves({ startingPos }: MoveAsLowLevelMovesArgs): LowLevelMovesForAllDancers {
|
|
||||||
return this.buildSingleVariantMoveInterpreter(startingPos).moveAsLowLevelMoves();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DefaultSingleVariantMoveInterpreter extends SingleVariantMoveInterpreter<DefaultMoveInterpreter, MoveName> {
|
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
|
||||||
return this.errorStandStill();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
class DefaultMoveInterpreter extends MoveInterpreter<MoveName> {
|
|
||||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
|
||||||
throw new Error("Method not implemented.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export const errorMoveInterpreterCtor: MoveInterpreterCtor<MoveName> = DefaultMoveInterpreter;
|
|
|
@ -1,170 +0,0 @@
|
||||||
import { SemanticPosition, PositionKind, CircleSide, Facing, CirclePosition, LongLines, HandConnection } from "../interpreterCommon.js";
|
|
||||||
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
|
||||||
import { Hand } from "../rendererConstants.js";
|
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
|
||||||
|
|
||||||
type allemandeMoves = "allemande" | "allemande orbit" | "gyre";
|
|
||||||
|
|
||||||
class AllemandeSingleVariant extends SingleVariantMoveInterpreter<Allemande, allemandeMoves> {
|
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
|
||||||
// Need to store this locally so checking move.move restricts move.parameters.
|
|
||||||
const move = this.move;
|
|
||||||
|
|
||||||
const allemandeCircling = move.move === "allemande orbit" ? move.parameters.circling1 : move.parameters.circling;
|
|
||||||
const byHandOrShoulder = (move.move === "gyre" ? move.parameters.shoulder : move.parameters.hand) ? Hand.Right : Hand.Left;
|
|
||||||
|
|
||||||
// TODO Not sure if this is right.
|
|
||||||
const swap = allemandeCircling % 360 === 180;
|
|
||||||
const returnToStart = allemandeCircling % 360 === 0;
|
|
||||||
const intoWave = !swap && !returnToStart && allemandeCircling % 90 == 0;
|
|
||||||
const intoWavePositions = !intoWave ? 0 : (allemandeCircling % 360 === 90) === (byHandOrShoulder === Hand.Left) ? 1 : -1;
|
|
||||||
if (!swap && !returnToStart && !intoWave) {
|
|
||||||
// TODO Support allemande that's not a swap or no-op.
|
|
||||||
throw "Unsupported allemande circle amount: " + allemandeCircling;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.handlePairedMove(move.parameters.who, ({ startPos, around, withId, withPos }) => {
|
|
||||||
let endPosition: SemanticPosition = startPos;
|
|
||||||
let startingPos = startPos;
|
|
||||||
if (swap) {
|
|
||||||
// TODO This was more complicated. Is this wrong?
|
|
||||||
endPosition = withPos;
|
|
||||||
} else if (intoWave) {
|
|
||||||
if (startPos.kind === PositionKind.ShortLines) {
|
|
||||||
if (around === CircleSide.Left || around === CircleSide.Right) {
|
|
||||||
// Fix startPos if necessary. Needed because pass through always swaps but sometimes shouldn't.
|
|
||||||
let startWhich = startPos.which;
|
|
||||||
if ((startPos.facing === Facing.Up || startPos.facing === Facing.Down) &&
|
|
||||||
((byHandOrShoulder === Hand.Right)
|
|
||||||
!== (startPos.facing === Facing.Up)
|
|
||||||
!== startPos.which.isLeftOfSide())) {
|
|
||||||
startWhich = startPos.which.swapOnSide()
|
|
||||||
startingPos = {
|
|
||||||
...startPos,
|
|
||||||
which: startWhich,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const endWhich = CirclePosition.fromSides(startingPos.which.leftRightSide(),
|
|
||||||
startWhich.isLeftOfSide()
|
|
||||||
!== (byHandOrShoulder === Hand.Right)
|
|
||||||
!== (intoWavePositions === 1)
|
|
||||||
? CircleSide.Top
|
|
||||||
: CircleSide.Bottom);
|
|
||||||
endPosition = {
|
|
||||||
kind: PositionKind.Circle,
|
|
||||||
which: endWhich,
|
|
||||||
facing: (startingPos.facing === Facing.Up) === (intoWavePositions === 1)
|
|
||||||
? endWhich.facingAcross()
|
|
||||||
: endWhich.facingOut(),
|
|
||||||
setOffset: startingPos.setOffset,
|
|
||||||
lineOffset: startingPos.lineOffset,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error("Allemande from short lines to line line in middle unsupported.");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (around === "Center") {
|
|
||||||
const startCenter = startPos.longLines === LongLines.Center;
|
|
||||||
const endWhich = startPos.which.circleRight(intoWavePositions);
|
|
||||||
endPosition = {
|
|
||||||
kind: PositionKind.Circle,
|
|
||||||
which: endWhich,
|
|
||||||
facing: startPos.which.facingOut(),
|
|
||||||
longLines: startCenter ? undefined : LongLines.Center,
|
|
||||||
setOffset: startPos.setOffset,
|
|
||||||
lineOffset: startPos.lineOffset,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const endWhich = startPos.which.toShortLines(intoWavePositions === 1 ? Hand.Right : Hand.Left);
|
|
||||||
endPosition = {
|
|
||||||
kind: PositionKind.ShortLines,
|
|
||||||
which: endWhich,
|
|
||||||
facing: endWhich.isLeftOfSide() === (byHandOrShoulder === Hand.Left) ? Facing.Up : Facing.Down,
|
|
||||||
setOffset: startPos.setOffset,
|
|
||||||
lineOffset: startPos.lineOffset,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.combine([
|
|
||||||
{
|
|
||||||
beats: move.beats,
|
|
||||||
endPosition,
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.RotateAround,
|
|
||||||
minAmount: byHandOrShoulder === Hand.Right ? allemandeCircling : -allemandeCircling,
|
|
||||||
around,
|
|
||||||
byHand: move.move === "allemande" || move.move === "allemande orbit" ? byHandOrShoulder : undefined,
|
|
||||||
close: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
], {
|
|
||||||
...startingPos,
|
|
||||||
hands: startPos.hands && move.move !== "gyre"
|
|
||||||
? new Map<Hand, HandConnection>([...startPos.hands.entries()].filter(([h, c]) => h === byHandOrShoulder))
|
|
||||||
: undefined
|
|
||||||
});
|
|
||||||
}, move.move !== "allemande orbit" ? undefined : ({ id, startPos }) => {
|
|
||||||
const orbitAmount = move.parameters.circling2;
|
|
||||||
const swap = orbitAmount % 360 === 180;
|
|
||||||
if (!swap && orbitAmount % 360 !== 0) {
|
|
||||||
// TODO Support allemande that's not a swap or no-op.
|
|
||||||
throw "Unsupported allemande orbit amount: " + orbitAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
const startingPos: SemanticPosition = {
|
|
||||||
...startPos,
|
|
||||||
hands: undefined,
|
|
||||||
balance: undefined,
|
|
||||||
dancerDistance: undefined,
|
|
||||||
}
|
|
||||||
let endPosition: SemanticPosition;
|
|
||||||
if (swap) {
|
|
||||||
if (startingPos.kind === PositionKind.Circle) {
|
|
||||||
endPosition =
|
|
||||||
{
|
|
||||||
...startingPos,
|
|
||||||
which: startingPos.which.swapDiagonal(),
|
|
||||||
facing: startingPos.which.isLeft() ? Facing.Left : Facing.Right,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
endPosition =
|
|
||||||
{
|
|
||||||
...startingPos,
|
|
||||||
which: startingPos.which.swapSides(),
|
|
||||||
facing: startingPos.which.isLeft() ? Facing.Left : Facing.Right,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
endPosition = startingPos;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.combine([
|
|
||||||
{
|
|
||||||
beats: move.beats,
|
|
||||||
endPosition,
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.RotateAround,
|
|
||||||
// Orbit is opposite direction of allemande.
|
|
||||||
minAmount: byHandOrShoulder === Hand.Right ? -orbitAmount : +orbitAmount,
|
|
||||||
around: "Center",
|
|
||||||
byHand: undefined,
|
|
||||||
close: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
], startingPos);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Allemande extends MoveInterpreter<allemandeMoves> {
|
|
||||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
|
||||||
return new AllemandeSingleVariant(this, startingPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moveInterpreters.set("allemande", Allemande);
|
|
||||||
moveInterpreters.set("allemande orbit", Allemande);
|
|
||||||
moveInterpreters.set("gyre", Allemande);
|
|
|
@ -1,44 +0,0 @@
|
||||||
import { BalanceWeight } from "../interpreterCommon.js";
|
|
||||||
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, MoveInterpreterCtorArgs, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
|
||||||
|
|
||||||
class BalanceSingleVariant extends SingleVariantMoveInterpreter<Balance, "balance"> {
|
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
|
||||||
return this.handleMove(({ startPos }) => {
|
|
||||||
// TODO Use who to determine facing?
|
|
||||||
// TODO Could be left to right, not back and forth?
|
|
||||||
// TODO How to determine hand... by next move, I guess?
|
|
||||||
|
|
||||||
return this.combine([
|
|
||||||
{
|
|
||||||
beats: this.moveInterpreter.forwardBeats,
|
|
||||||
endPosition: { ...startPos, balance: BalanceWeight.Forward },
|
|
||||||
movementPattern: { kind: SemanticAnimationKind.Linear },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
beats: this.moveInterpreter.backwardBeats,
|
|
||||||
endPosition: { ...startPos, balance: BalanceWeight.Backward },
|
|
||||||
movementPattern: { kind: SemanticAnimationKind.Linear },
|
|
||||||
},
|
|
||||||
], startPos);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Balance extends MoveInterpreter<"balance"> {
|
|
||||||
public readonly forwardBeats: number;
|
|
||||||
public readonly backwardBeats: number;
|
|
||||||
|
|
||||||
constructor(args: MoveInterpreterCtorArgs<"balance">) {
|
|
||||||
super(args);
|
|
||||||
|
|
||||||
this.forwardBeats = this.move.beats / 2;
|
|
||||||
this.backwardBeats = this.move.beats - this.forwardBeats;
|
|
||||||
}
|
|
||||||
|
|
||||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
|
||||||
return new BalanceSingleVariant(this, startingPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moveInterpreters.set("balance", Balance);
|
|
|
@ -1,56 +0,0 @@
|
||||||
import { BalanceWeight, Facing, HandConnection, HandTo, PositionKind, SemanticPosition } from "../interpreterCommon.js";
|
|
||||||
import { Move } from "../libfigureMapper.js";
|
|
||||||
import { LowLevelMove, SemanticAnimationKind } from "../lowLevelMove.js";
|
|
||||||
import { Hand } from "../rendererConstants.js";
|
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
|
||||||
|
|
||||||
|
|
||||||
export 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];
|
|
||||||
}
|
|
||||||
|
|
||||||
class BalanceTheRingSingleVariant extends SingleVariantMoveInterpreter<BalanceTheRing, "balance the ring"> {
|
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
|
||||||
return this.handleCircleMove(({ startPos }) => balanceCircleInAndOut(this.move, startPos));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BalanceTheRing extends MoveInterpreter<"balance the ring"> {
|
|
||||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
|
||||||
return new BalanceTheRingSingleVariant(this, startingPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moveInterpreters.set("balance the ring", BalanceTheRing);
|
|
|
@ -1,83 +0,0 @@
|
||||||
import { DanceRole, CoupleRole } from "../danceCommon.js";
|
|
||||||
import { SemanticPosition, Facing, HandConnection, HandTo, BalanceWeight } from "../interpreterCommon.js";
|
|
||||||
import { Move } from "../libfigureMapper.js";
|
|
||||||
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
|
||||||
import { Hand } from "../rendererConstants.js";
|
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, PartialLowLevelMove, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
|
||||||
|
|
||||||
const moveName: Move["move"] = "box circulate";
|
|
||||||
|
|
||||||
class BoxCirculateSingleVariant extends SingleVariantMoveInterpreter<BoxCirculate, typeof moveName> {
|
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
|
||||||
const circulateRight: boolean = this.move.parameters.hand;
|
|
||||||
const whoCrosses = this.move.parameters.who;
|
|
||||||
return this.handleCircleMove(({ id, startPos }) => {
|
|
||||||
let isCrossing: boolean;
|
|
||||||
switch (whoCrosses) {
|
|
||||||
case "gentlespoons":
|
|
||||||
isCrossing = id.danceRole === DanceRole.Lark;
|
|
||||||
break;
|
|
||||||
case "ladles":
|
|
||||||
isCrossing = id.danceRole === DanceRole.Robin;
|
|
||||||
break;
|
|
||||||
case "ones":
|
|
||||||
isCrossing = id.coupleRole === CoupleRole.Ones;
|
|
||||||
break;
|
|
||||||
case "twos":
|
|
||||||
isCrossing = id.coupleRole === CoupleRole.Twos;
|
|
||||||
break;
|
|
||||||
case "first corners":
|
|
||||||
case "second corners":
|
|
||||||
throw "first/second corner leading box circulate doesn't make sense?";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Starts in long wavy lines.
|
|
||||||
const startingPos: SemanticPosition = {
|
|
||||||
...startPos,
|
|
||||||
facing: isCrossing === startPos.which.isLeft() ? Facing.Right : Facing.Left,
|
|
||||||
hands: new Map<Hand, HandConnection>([
|
|
||||||
[Hand.Left, { hand: Hand.Left, to: HandTo.DancerLeft }],
|
|
||||||
[Hand.Right, { hand: Hand.Right, to: HandTo.DancerRight }],
|
|
||||||
]),
|
|
||||||
balance: undefined,
|
|
||||||
longLines: undefined,
|
|
||||||
dancerDistance: undefined,
|
|
||||||
};
|
|
||||||
const balance: PartialLowLevelMove[] = this.move.parameters.bal ? [
|
|
||||||
{
|
|
||||||
beats: 2,
|
|
||||||
endPosition: { ...startingPos, balance: circulateRight ? BalanceWeight.Right : BalanceWeight.Left },
|
|
||||||
movementPattern: { kind: SemanticAnimationKind.Linear },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
beats: 2,
|
|
||||||
endPosition: { ...startingPos, balance: BalanceWeight.Backward },
|
|
||||||
movementPattern: { kind: SemanticAnimationKind.Linear },
|
|
||||||
},
|
|
||||||
] : [];
|
|
||||||
const circulate: PartialLowLevelMove = {
|
|
||||||
beats: this.move.beats - (this.move.parameters.bal ? 4 : 0),
|
|
||||||
endPosition: {
|
|
||||||
...startingPos,
|
|
||||||
which: isCrossing ? startingPos.which.swapAcross() : startingPos.which.swapUpAndDown(),
|
|
||||||
facing: isCrossing ? startingPos.facing : startingPos.facing === Facing.Right ? Facing.Left : Facing.Right,
|
|
||||||
},
|
|
||||||
movementPattern: {
|
|
||||||
// TODO Not sure loop should really be linear...
|
|
||||||
kind: SemanticAnimationKind.Linear,
|
|
||||||
minRotation: isCrossing ? undefined : circulateRight ? 180 : -180,
|
|
||||||
handsDuring: "None",
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return this.combine([...balance, circulate], startingPos);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BoxCirculate extends MoveInterpreter<typeof moveName> {
|
|
||||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
|
||||||
return new BoxCirculateSingleVariant(this, startingPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moveInterpreters.set(moveName, BoxCirculate);
|
|
|
@ -1,74 +0,0 @@
|
||||||
import { HandConnection, HandTo, BalanceWeight } from "../interpreterCommon.js";
|
|
||||||
import { Move } from "../libfigureMapper.js";
|
|
||||||
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
|
||||||
import { Hand } from "../rendererConstants.js";
|
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, PartialLowLevelMove, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
|
||||||
|
|
||||||
const moveName: Move["move"] = "box the gnat";
|
|
||||||
|
|
||||||
class BoxTheGnatSingleVariant extends SingleVariantMoveInterpreter<BoxTheGnat, typeof moveName> {
|
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
|
||||||
return this.handlePairedMove(this.move.parameters.who, ({ startPos, around, withPos }) => {
|
|
||||||
const hand = this.move.parameters.hand ? Hand.Right : Hand.Left;
|
|
||||||
const balanceBeats = this.move.parameters.bal
|
|
||||||
? this.move.beats > 4
|
|
||||||
? this.move.beats - 4
|
|
||||||
: 2
|
|
||||||
: 0;
|
|
||||||
const balancePartBeats = balanceBeats / 2;
|
|
||||||
const twirlBeats = this.move.beats - balanceBeats;
|
|
||||||
|
|
||||||
// TODO Adjust facing?
|
|
||||||
const startPosition = { ...startPos, hands: new Map<Hand, HandConnection>([[hand, { hand, to: HandTo.DancerForward }]]) };
|
|
||||||
|
|
||||||
if (around === "Center") {
|
|
||||||
throw "TwirlSwap around center is unsupported.";
|
|
||||||
}
|
|
||||||
|
|
||||||
const twirl: PartialLowLevelMove = {
|
|
||||||
beats: twirlBeats,
|
|
||||||
endPosition: withPos,
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.TwirlSwap,
|
|
||||||
around,
|
|
||||||
hand,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.move.parameters.bal) {
|
|
||||||
return this.combine([
|
|
||||||
{
|
|
||||||
beats: balancePartBeats,
|
|
||||||
endPosition: {
|
|
||||||
...startPosition,
|
|
||||||
balance: BalanceWeight.Forward,
|
|
||||||
},
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.Linear,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
beats: balancePartBeats,
|
|
||||||
endPosition: {
|
|
||||||
...startPosition,
|
|
||||||
balance: BalanceWeight.Backward,
|
|
||||||
},
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.Linear,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
twirl], startPosition);
|
|
||||||
} else {
|
|
||||||
return this.combine([twirl], startPosition);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BoxTheGnat extends MoveInterpreter<typeof moveName> {
|
|
||||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
|
||||||
return new BoxTheGnatSingleVariant(this, startingPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moveInterpreters.set(moveName, BoxTheGnat);
|
|
|
@ -1,33 +0,0 @@
|
||||||
import { Move } from "../libfigureMapper.js";
|
|
||||||
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
|
||||||
import { Hand } from "../rendererConstants.js";
|
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
|
||||||
|
|
||||||
const moveName: Move["move"] = "butterfly whirl";
|
|
||||||
|
|
||||||
class ButterflyWhirlSingleVariant extends SingleVariantMoveInterpreter<ButterflyWhirl, typeof moveName> {
|
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
|
||||||
return this.handleCircleMove(({ startPos }) => {
|
|
||||||
return this.combine([{
|
|
||||||
beats: this.move.beats,
|
|
||||||
endPosition: startPos,
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.RotateAround,
|
|
||||||
around: startPos.which.leftRightSide(),
|
|
||||||
// TODO hand around isn't the same as allemande...
|
|
||||||
byHand: startPos.which.isOnLeftLookingAcross() ? Hand.Right : Hand.Left,
|
|
||||||
close: true,
|
|
||||||
minAmount: 360,
|
|
||||||
}
|
|
||||||
}], startPos);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ButterflyWhirl extends MoveInterpreter<typeof moveName> {
|
|
||||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
|
||||||
return new ButterflyWhirlSingleVariant(this, startingPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moveInterpreters.set(moveName, ButterflyWhirl);
|
|
|
@ -1,53 +0,0 @@
|
||||||
import { HandConnection, HandTo, CircleSide, Facing } from "../interpreterCommon.js";
|
|
||||||
import { Move } from "../libfigureMapper.js";
|
|
||||||
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
|
||||||
import { Hand } from "../rendererConstants.js";
|
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
|
||||||
|
|
||||||
const moveName: Move["move"] = "California twirl";
|
|
||||||
|
|
||||||
class CaliforniaTwirlSingleVariant extends SingleVariantMoveInterpreter<CaliforniaTwirl, typeof moveName> {
|
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
|
||||||
return this.handleCirclePairedMove(this.move.parameters.who, ({ startPos }) => {
|
|
||||||
// TODO does "who" matter here or is it entirely positional? At least need to know who to omit.
|
|
||||||
|
|
||||||
const onLeft: boolean = startPos.which.isOnLeftLookingUpAndDown();
|
|
||||||
// TODO get rid of this 1 beat set up and make it part of TwirlSwap?
|
|
||||||
return this.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: this.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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CaliforniaTwirl extends MoveInterpreter<typeof moveName> {
|
|
||||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
|
||||||
return new CaliforniaTwirlSingleVariant(this, startingPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moveInterpreters.set(moveName, CaliforniaTwirl);
|
|
|
@ -1,154 +0,0 @@
|
||||||
import { DanceRole } from "../danceCommon.js";
|
|
||||||
import { HandTo, HandConnection, PositionKind } from "../interpreterCommon.js";
|
|
||||||
import { Move } from "../libfigureMapper.js";
|
|
||||||
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
|
||||||
import { Hand } from "../rendererConstants.js";
|
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
|
||||||
|
|
||||||
const moveName: Move["move"] = "chain";
|
|
||||||
|
|
||||||
class ChainSingleVariant extends SingleVariantMoveInterpreter<Chain, typeof moveName> {
|
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
|
||||||
const mainRole = this.move.parameters.who === "gentlespoons" ? DanceRole.Lark : DanceRole.Robin;
|
|
||||||
const pullToTurnBeats = 2;
|
|
||||||
const pullBeats = this.move.beats / 2 - pullToTurnBeats;
|
|
||||||
const turnBeats = this.move.beats - pullBeats - pullToTurnBeats;
|
|
||||||
const chainHand: Hand = this.move.parameters.hand ? Hand.Right : Hand.Left;
|
|
||||||
const cwCourtesyTurn = chainHand === Hand.Left;
|
|
||||||
|
|
||||||
return this.handleCircleMove(({ id, startPos }) => {
|
|
||||||
if (id.danceRole === mainRole) {
|
|
||||||
const endWhich = startPos.which.swapDiagonal();
|
|
||||||
let endSet = startPos.setOffset ?? 0;
|
|
||||||
let to: HandTo;
|
|
||||||
switch (this.move.parameters.dir) {
|
|
||||||
case "along":
|
|
||||||
throw "Don't know what chaining along the set means.";
|
|
||||||
case "across":
|
|
||||||
to = HandTo.DiagonalAcrossCircle;
|
|
||||||
break;
|
|
||||||
case "right diagonal":
|
|
||||||
to = HandTo.RightDiagonalAcrossCircle;
|
|
||||||
endSet += startPos.which.isLeft() ? -1 : +1;
|
|
||||||
break;
|
|
||||||
case "left diagonal":
|
|
||||||
to = HandTo.LeftDiagonalAcrossCircle;
|
|
||||||
endSet += startPos.which.isLeft() ? +1 : -1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const startPosition = {
|
|
||||||
...startPos,
|
|
||||||
hands: new Map<Hand, HandConnection>([[chainHand, { hand: chainHand, to }]]),
|
|
||||||
facing: startPos.which.facingAcross(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const turnTo = chainHand === Hand.Right ? HandTo.DancerRight : HandTo.DancerLeft;
|
|
||||||
|
|
||||||
return this.combine([
|
|
||||||
{
|
|
||||||
beats: pullBeats,
|
|
||||||
endPosition: {
|
|
||||||
...startPos,
|
|
||||||
which: endWhich,
|
|
||||||
facing: endWhich.facingUpOrDown(),
|
|
||||||
setOffset: endSet,
|
|
||||||
hands: new Map<Hand, HandConnection>([
|
|
||||||
[Hand.Left, { hand: Hand.Left, to: turnTo }],
|
|
||||||
[Hand.Right, { hand: Hand.Right, to: turnTo }],
|
|
||||||
]),
|
|
||||||
},
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.PassBy,
|
|
||||||
around: "Center",
|
|
||||||
side: chainHand,
|
|
||||||
withHands: true,
|
|
||||||
facing: "Forward",
|
|
||||||
otherPath: "Swap",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
beats: pullToTurnBeats,
|
|
||||||
endPosition: {
|
|
||||||
...this.startingPos,
|
|
||||||
kind: PositionKind.Circle,
|
|
||||||
which: endWhich.swapUpAndDown(),
|
|
||||||
facing: endWhich.facingOut(),
|
|
||||||
setOffset: endSet,
|
|
||||||
},
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.PassBy,
|
|
||||||
side: chainHand.opposite(),
|
|
||||||
withHands: true,
|
|
||||||
around: endWhich.leftRightSide(),
|
|
||||||
facing: "Forward", // TODO Is this right?
|
|
||||||
otherPath: "Swap",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
prevEnd => ({
|
|
||||||
beats: turnBeats,
|
|
||||||
endPosition: {
|
|
||||||
kind: PositionKind.Circle,
|
|
||||||
which: endWhich,
|
|
||||||
facing: endWhich.facingAcross(),
|
|
||||||
hands: prevEnd.hands,
|
|
||||||
setOffset: prevEnd.setOffset,
|
|
||||||
lineOffset: prevEnd.lineOffset,
|
|
||||||
},
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.CourtesyTurn,
|
|
||||||
clockwise: cwCourtesyTurn,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
], startPosition);
|
|
||||||
} else {
|
|
||||||
const startingPos = { ...startPos, hands: undefined };
|
|
||||||
return this.combine([
|
|
||||||
{
|
|
||||||
beats: pullBeats,
|
|
||||||
endPosition: { ...startingPos, facing: startingPos.which.facingUpOrDown() },
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.Linear,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
beats: pullToTurnBeats,
|
|
||||||
endPosition: {
|
|
||||||
...startingPos,
|
|
||||||
which: startingPos.which.swapUpAndDown(),
|
|
||||||
facing: startingPos.which.facingOut()
|
|
||||||
},
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.PassBy,
|
|
||||||
side: chainHand.opposite(),
|
|
||||||
withHands: true,
|
|
||||||
around: startingPos.which.leftRightSide(),
|
|
||||||
facing: "Forward", // TODO Is this right?
|
|
||||||
otherPath: "Swap",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
beats: turnBeats,
|
|
||||||
endPosition: {
|
|
||||||
...startingPos,
|
|
||||||
// TODO Does CourtesyTurn always end in same position?
|
|
||||||
which: startPos.which,
|
|
||||||
facing: startPos.which.facingAcross(),
|
|
||||||
},
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.CourtesyTurn,
|
|
||||||
clockwise: cwCourtesyTurn,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
], startingPos);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Chain extends MoveInterpreter<typeof moveName> {
|
|
||||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
|
||||||
return new ChainSingleVariant(this, startingPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moveInterpreters.set(moveName, Chain);
|
|
|
@ -1,38 +0,0 @@
|
||||||
import { Facing, handsInCircle } from "../interpreterCommon.js";
|
|
||||||
import { Move } from "../libfigureMapper.js";
|
|
||||||
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
|
||||||
|
|
||||||
const moveName: Move["move"] = "circle";
|
|
||||||
|
|
||||||
class CircleSingleVariant extends SingleVariantMoveInterpreter<Circle, typeof moveName> {
|
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
|
||||||
return this.handleCircleMove(({ startPos }) => {
|
|
||||||
const places = this.move.parameters.places / 90 * (this.move.parameters.turn ? 1 : -1);
|
|
||||||
|
|
||||||
return this.combine([
|
|
||||||
{
|
|
||||||
beats: this.move.beats,
|
|
||||||
endPosition: {
|
|
||||||
...startPos,
|
|
||||||
facing: Facing.CenterOfCircle,
|
|
||||||
hands: handsInCircle,
|
|
||||||
which: startPos.which.circleLeft(places),
|
|
||||||
},
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.Circle,
|
|
||||||
places,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
], { ...startPos, facing: Facing.CenterOfCircle });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Circle extends MoveInterpreter<typeof moveName> {
|
|
||||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
|
||||||
return new CircleSingleVariant(this, startingPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moveInterpreters.set(moveName, Circle);
|
|
|
@ -1,38 +0,0 @@
|
||||||
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
|
||||||
|
|
||||||
class CustomSingleVariant extends SingleVariantMoveInterpreter<Custom, "custom"> {
|
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
|
||||||
// TODO refactor so this is in separate classes?
|
|
||||||
if (this.move.parameters.custom.includes("mirrored mad robin")) {
|
|
||||||
return this.handleCircleMove(({ id, startPos }) => {
|
|
||||||
// TODO Read custom to decide direction?
|
|
||||||
const startAndEndPos = {
|
|
||||||
...startPos,
|
|
||||||
facing: startPos.which.facingAcross(),
|
|
||||||
hands: undefined,
|
|
||||||
};
|
|
||||||
return this.combine([{
|
|
||||||
beats: this.move.beats,
|
|
||||||
startPosition: startAndEndPos,
|
|
||||||
endPosition: startAndEndPos,
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.DoSiDo,
|
|
||||||
amount: startPos.which.isLeft() ? -360 : 360,
|
|
||||||
around: startPos.which.leftRightSide(),
|
|
||||||
},
|
|
||||||
}]);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return this.errorStandStill();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Custom extends MoveInterpreter<"custom"> {
|
|
||||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
|
||||||
return new CustomSingleVariant(this, startingPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moveInterpreters.set("custom", Custom);
|
|
|
@ -1,80 +0,0 @@
|
||||||
import { SemanticPosition, Facing, CircleSide, PositionKind } from "../interpreterCommon.js";
|
|
||||||
import { Move } from "../libfigureMapper.js";
|
|
||||||
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
|
||||||
import { Hand } from "../rendererConstants.js";
|
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
|
||||||
|
|
||||||
const moveName: Move["move"] = "do si do";
|
|
||||||
|
|
||||||
class DoSiDoSingleVariant extends SingleVariantMoveInterpreter<DoSiDo, typeof moveName> {
|
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
|
||||||
if (!this.move.parameters.shoulder) {
|
|
||||||
throw new Error("do si do by left shoulder is unsupported.");
|
|
||||||
}
|
|
||||||
|
|
||||||
let doSiDoEndKind: "Start" | "Swap" | "ShortLinesLeft" | "ShortLinesRight";
|
|
||||||
const doSiDoCircling = this.move.parameters.circling % 360;
|
|
||||||
if (doSiDoCircling === 0) {
|
|
||||||
doSiDoEndKind = "Start";
|
|
||||||
} else if (doSiDoCircling === 180) {
|
|
||||||
doSiDoEndKind = "Swap";
|
|
||||||
} else if (doSiDoCircling === 90) {
|
|
||||||
doSiDoEndKind = this.move.parameters.shoulder ? "ShortLinesLeft" : "ShortLinesRight";
|
|
||||||
} else if (doSiDoCircling === 270) {
|
|
||||||
doSiDoEndKind = this.move.parameters.shoulder ? "ShortLinesRight" : "ShortLinesLeft";
|
|
||||||
} else {
|
|
||||||
throw new Error("do si do by " + this.move.parameters.circling + " degrees is unsupported.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.handleCirclePairedMove(this.move.parameters.who, ({ startPos, around, withPos }) => {
|
|
||||||
// TODO Use other parameters?
|
|
||||||
const startingPos: SemanticPosition = {
|
|
||||||
...startPos,
|
|
||||||
hands: undefined,
|
|
||||||
facing: around === "Center"
|
|
||||||
? Facing.CenterOfCircle
|
|
||||||
: around === CircleSide.Left || around === CircleSide.Right
|
|
||||||
? startPos.which.facingUpOrDown()
|
|
||||||
: startPos.which.facingAcross(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let endPos: SemanticPosition;
|
|
||||||
switch (doSiDoEndKind) {
|
|
||||||
case "Start":
|
|
||||||
endPos = startingPos;
|
|
||||||
break;
|
|
||||||
case "Swap":
|
|
||||||
endPos = { ...withPos, facing: startingPos.facing };
|
|
||||||
break;
|
|
||||||
case "ShortLinesLeft":
|
|
||||||
case "ShortLinesRight":
|
|
||||||
endPos = {
|
|
||||||
kind: PositionKind.ShortLines,
|
|
||||||
which: startPos.which.toShortLines(doSiDoEndKind === "ShortLinesLeft" ? Hand.Left : Hand.Right),
|
|
||||||
facing: startingPos.facing,
|
|
||||||
setOffset: startingPos.setOffset,
|
|
||||||
lineOffset: startingPos.lineOffset,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.combine([{
|
|
||||||
beats: this.move.beats,
|
|
||||||
startPosition: startingPos,
|
|
||||||
endPosition: endPos,
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.DoSiDo,
|
|
||||||
amount: this.move.parameters.circling,
|
|
||||||
around,
|
|
||||||
},
|
|
||||||
}]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DoSiDo extends MoveInterpreter<typeof moveName> {
|
|
||||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
|
||||||
return new DoSiDoSingleVariant(this, startingPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moveInterpreters.set(moveName, DoSiDo);
|
|
|
@ -1,93 +0,0 @@
|
||||||
import { Facing, ShortLinesPosition, PositionKind, CirclePosition, SemanticPosition, HandConnection, HandTo, oppositeFacing } from "../interpreterCommon.js";
|
|
||||||
import { Move } from "../libfigureMapper.js";
|
|
||||||
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
|
||||||
import { Hand } from "../rendererConstants.js";
|
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
|
||||||
|
|
||||||
const moveName: Move["move"] = "down the hall";
|
|
||||||
|
|
||||||
class DownTheHallSingleVariant extends SingleVariantMoveInterpreter<DownTheHall, typeof moveName> {
|
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
|
||||||
if (this.move.parameters.who !== "everyone") {
|
|
||||||
throw new Error("Don't know what it means for not everyone to go down the hall.");
|
|
||||||
}
|
|
||||||
if (this.move.parameters.moving !== "all") {
|
|
||||||
throw new Error("Not sure what it means for not all to be moving in down the hall.");
|
|
||||||
}
|
|
||||||
if (this.move.parameters.ender !== "turn-alone" && this.move.parameters.ender !== "turn-couple") {
|
|
||||||
throw new Error("Unsupported down the hall ender: " + this.move.parameters.ender);
|
|
||||||
}
|
|
||||||
if (this.move.parameters.facing === "forward then backward") {
|
|
||||||
throw new Error("Not sure what " + this.move.parameters.facing + " means for down the hall.");
|
|
||||||
}
|
|
||||||
return this.handleMove(({ startPos }) => {
|
|
||||||
const startFacing = this.move.parameters.facing === "backward" ? Facing.Up : Facing.Down;
|
|
||||||
const startWhich: ShortLinesPosition = startPos.kind === PositionKind.ShortLines
|
|
||||||
? startPos.which
|
|
||||||
// TODO Is this always the right way to convert circle to short lines?
|
|
||||||
// (Does it even matter except for dance starting formations?)
|
|
||||||
: new Map<CirclePosition, ShortLinesPosition>([
|
|
||||||
[CirclePosition.TopLeft, ShortLinesPosition.FarLeft],
|
|
||||||
[CirclePosition.BottomLeft, ShortLinesPosition.MiddleLeft],
|
|
||||||
[CirclePosition.BottomRight, ShortLinesPosition.MiddleRight],
|
|
||||||
[CirclePosition.TopRight, ShortLinesPosition.FarRight],
|
|
||||||
]).get(startPos.which)!;
|
|
||||||
const startingPos: SemanticPosition & { kind: PositionKind.ShortLines, setOffset: number } = {
|
|
||||||
kind: PositionKind.ShortLines,
|
|
||||||
facing: startFacing,
|
|
||||||
which: startWhich,
|
|
||||||
hands: startWhich.isMiddle() ? new Map<Hand, HandConnection>([
|
|
||||||
[Hand.Left, { hand: Hand.Left, to: HandTo.DancerLeft }],
|
|
||||||
[Hand.Right, { hand: Hand.Right, to: HandTo.DancerRight }],
|
|
||||||
]) : new Map<Hand, HandConnection>([
|
|
||||||
startWhich.isLeft() === (this.move.parameters.facing === "backward")
|
|
||||||
? [Hand.Left, { hand: Hand.Left, to: HandTo.DancerLeft }]
|
|
||||||
: [Hand.Right, { hand: Hand.Right, to: HandTo.DancerRight }]
|
|
||||||
]),
|
|
||||||
setOffset: startPos.setOffset ?? 0,
|
|
||||||
lineOffset: startPos.lineOffset,
|
|
||||||
};
|
|
||||||
|
|
||||||
return this.combine([
|
|
||||||
{
|
|
||||||
beats: 4,
|
|
||||||
endPosition: {
|
|
||||||
...startingPos,
|
|
||||||
setOffset: startingPos.setOffset + 1
|
|
||||||
},
|
|
||||||
movementPattern: { kind: SemanticAnimationKind.Linear },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
beats: this.move.beats - 4,
|
|
||||||
endPosition: {
|
|
||||||
...startingPos,
|
|
||||||
setOffset: startingPos.setOffset + 1,
|
|
||||||
facing: oppositeFacing(startFacing),
|
|
||||||
which: this.move.parameters.ender === "turn-alone" ? startWhich : startWhich.swapOnSide(),
|
|
||||||
},
|
|
||||||
movementPattern: this.move.parameters.ender === "turn-couple"
|
|
||||||
? {
|
|
||||||
kind: SemanticAnimationKind.TwirlSwap,
|
|
||||||
around: startWhich.leftRightSide(),
|
|
||||||
// !== is NXOR, each of these booleans being flipped flips which hand to use.
|
|
||||||
hand: startWhich.isMiddle() !== (startWhich.isLeft() !== (this.move.parameters.facing === "forward"))
|
|
||||||
? Hand.Left
|
|
||||||
: Hand.Right
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
kind: SemanticAnimationKind.Linear,
|
|
||||||
minRotation: startWhich.isMiddle() === startWhich.isLeft() ? -180 : +180,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
], startingPos);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DownTheHall extends MoveInterpreter<typeof moveName> {
|
|
||||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
|
||||||
return new DownTheHallSingleVariant(this, startingPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moveInterpreters.set(moveName, DownTheHall);
|
|
|
@ -1,131 +0,0 @@
|
||||||
import { SemanticPosition, BalanceWeight, ShortLinesPosition, Facing, PositionKind, handsInShortLine, handsInLine } from "../interpreterCommon.js";
|
|
||||||
import { Move } from "../libfigureMapper.js";
|
|
||||||
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
|
||||||
import { Hand } from "../rendererConstants.js";
|
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, PartialLowLevelMove, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
|
||||||
|
|
||||||
const moveName: Move["move"] = "form an ocean wave";
|
|
||||||
|
|
||||||
class FormAnOceanWaveSingleVariant extends SingleVariantMoveInterpreter<FormAnOceanWave, typeof moveName> {
|
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
|
||||||
if (this.move.parameters.dir !== "across") {
|
|
||||||
throw new Error("Diagonal ocean waves are unsupported.");
|
|
||||||
}
|
|
||||||
const centerHand = this.move.parameters["c.hand"] ? Hand.Right : Hand.Left;
|
|
||||||
if (this.move.parameters["pass thru"]) {
|
|
||||||
return this.handleCircleMove(({ id, startPos }) => {
|
|
||||||
const balBeats = this.move.parameters.bal ? this.move.beats / 2 : 0;
|
|
||||||
const balPartBeats = balBeats / 2;
|
|
||||||
// TODO balance direction?
|
|
||||||
const balance: ((prevEnd: SemanticPosition) => PartialLowLevelMove)[] = this.move.parameters.bal ? [
|
|
||||||
prevEnd => ({
|
|
||||||
beats: balPartBeats,
|
|
||||||
endPosition: { ...prevEnd, balance: BalanceWeight.Forward },
|
|
||||||
movementPattern: { kind: SemanticAnimationKind.Linear },
|
|
||||||
}),
|
|
||||||
prevEnd => ({
|
|
||||||
beats: balPartBeats,
|
|
||||||
endPosition: { ...prevEnd, balance: BalanceWeight.Backward },
|
|
||||||
movementPattern: { kind: SemanticAnimationKind.Linear },
|
|
||||||
}),
|
|
||||||
] : [];
|
|
||||||
|
|
||||||
const isCenter = this.findPairOpposite(this.move.parameters.center, id) !== null;
|
|
||||||
const which = startPos.which.isLeft()
|
|
||||||
? isCenter
|
|
||||||
? ShortLinesPosition.MiddleRight
|
|
||||||
: ShortLinesPosition.FarRight
|
|
||||||
: isCenter
|
|
||||||
? ShortLinesPosition.MiddleLeft
|
|
||||||
: ShortLinesPosition.FarLeft;
|
|
||||||
// TODO Not sure this facing computation is right.
|
|
||||||
const facing = (centerHand === Hand.Left) !== isCenter !== which.isLeft() ? Facing.Up : Facing.Down;
|
|
||||||
return this.combine([{
|
|
||||||
beats: this.move.beats - balBeats,
|
|
||||||
endPosition: {
|
|
||||||
kind: PositionKind.ShortLines,
|
|
||||||
which,
|
|
||||||
facing,
|
|
||||||
hands: handsInShortLine({ which, facing, wavy: true }),
|
|
||||||
setOffset: startPos.setOffset,
|
|
||||||
lineOffset: startPos.lineOffset,
|
|
||||||
},
|
|
||||||
movementPattern: which.isMiddle()
|
|
||||||
? { kind: SemanticAnimationKind.Linear }
|
|
||||||
: {
|
|
||||||
kind: SemanticAnimationKind.RotateAround,
|
|
||||||
around: "Center",
|
|
||||||
minAmount: centerHand === Hand.Left ? 1 : -1,
|
|
||||||
close: false,
|
|
||||||
byHand: undefined,
|
|
||||||
},
|
|
||||||
}, ...balance], startPos);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
|
|
||||||
return this.handleMove(({ id, startPos }) => {
|
|
||||||
const isCenter = this.findPairOpposite(this.move.parameters.center, id) !== null;
|
|
||||||
|
|
||||||
const which = startPos.which.isLeft()
|
|
||||||
? (isCenter ? ShortLinesPosition.MiddleLeft : ShortLinesPosition.FarLeft)
|
|
||||||
: (isCenter ? ShortLinesPosition.MiddleRight : ShortLinesPosition.FarRight);
|
|
||||||
const facing = (centerHand === Hand.Right) === (which === ShortLinesPosition.MiddleLeft || which === ShortLinesPosition.FarRight) ? Facing.Down : Facing.Up;
|
|
||||||
|
|
||||||
const linePos: SemanticPosition = {
|
|
||||||
kind: PositionKind.ShortLines,
|
|
||||||
which,
|
|
||||||
facing,
|
|
||||||
hands: handsInLine({ wavy: true, which, facing }),
|
|
||||||
setOffset: startPos.setOffset,
|
|
||||||
lineOffset: startPos.lineOffset,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.move.parameters.bal) {
|
|
||||||
// TODO Is balance weight always forward/backward here?
|
|
||||||
const balanceBeats = Math.min(this.move.beats, 4);
|
|
||||||
const transitionBeats = this.move.beats - balanceBeats;
|
|
||||||
const balanceForwardBeats = balanceBeats / 2;
|
|
||||||
const balanceBackwardBeats = balanceBeats - balanceForwardBeats;
|
|
||||||
|
|
||||||
const balance: [PartialLowLevelMove, PartialLowLevelMove] = [
|
|
||||||
{
|
|
||||||
beats: balanceForwardBeats,
|
|
||||||
endPosition: { ...linePos, balance: BalanceWeight.Forward },
|
|
||||||
movementPattern: { kind: SemanticAnimationKind.Linear },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
beats: balanceBackwardBeats,
|
|
||||||
endPosition: { ...linePos, balance: BalanceWeight.Backward },
|
|
||||||
movementPattern: { kind: SemanticAnimationKind.Linear },
|
|
||||||
},
|
|
||||||
];
|
|
||||||
if (transitionBeats === 0) {
|
|
||||||
// No transition, just balance.
|
|
||||||
return this.combine(balance, linePos);
|
|
||||||
} else {
|
|
||||||
return this.combine([{
|
|
||||||
beats: transitionBeats,
|
|
||||||
endPosition: linePos,
|
|
||||||
movementPattern: { kind: SemanticAnimationKind.Linear },
|
|
||||||
},
|
|
||||||
...balance], startPos);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return this.combine([{
|
|
||||||
beats: this.move.beats,
|
|
||||||
endPosition: linePos,
|
|
||||||
movementPattern: { kind: SemanticAnimationKind.Linear },
|
|
||||||
}], startPos);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class FormAnOceanWave extends MoveInterpreter<typeof moveName> {
|
|
||||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
|
||||||
return new FormAnOceanWaveSingleVariant(this, startingPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moveInterpreters.set(moveName, FormAnOceanWave);
|
|
|
@ -1,42 +0,0 @@
|
||||||
import { SemanticPosition, PositionKind, handsInLine } from "../interpreterCommon.js";
|
|
||||||
import { Move } from "../libfigureMapper.js";
|
|
||||||
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
|
||||||
|
|
||||||
const moveName: Move["move"] = "form long waves";
|
|
||||||
|
|
||||||
class FormLongWavesSingleVariant extends SingleVariantMoveInterpreter<FormLongWaves, typeof moveName> {
|
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
|
||||||
// TODO A zero beat move should just be selecting a variant? Or maybe not because this just changes facing/hands.
|
|
||||||
if (this.move.beats !== 0) {
|
|
||||||
throw new Error(this.move.move + " unsupported except for zero beats marking end of previous move.");
|
|
||||||
}
|
|
||||||
return this.handleCircleMove(({ id, startPos }) => {
|
|
||||||
const facingIn = this.findPairOpposite(this.move.parameters.who, id) !== null;
|
|
||||||
|
|
||||||
const startAndEndPos: SemanticPosition = {
|
|
||||||
kind: PositionKind.Circle,
|
|
||||||
which: startPos.which,
|
|
||||||
facing: facingIn ? startPos.which.facingAcross() : startPos.which.facingOut(),
|
|
||||||
hands: handsInLine({ wavy: true, which: startPos.which }),
|
|
||||||
setOffset: startPos.setOffset,
|
|
||||||
lineOffset: startPos.lineOffset,
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.combine([{
|
|
||||||
beats: this.move.beats,
|
|
||||||
endPosition: startAndEndPos,
|
|
||||||
movementPattern: { kind: SemanticAnimationKind.StandStill },
|
|
||||||
}
|
|
||||||
], startAndEndPos);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class FormLongWaves extends MoveInterpreter<typeof moveName> {
|
|
||||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
|
||||||
return new FormLongWavesSingleVariant(this, startingPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moveInterpreters.set(moveName, FormLongWaves);
|
|
|
@ -1,56 +0,0 @@
|
||||||
import { LongLines, HandConnection, HandTo } from "../interpreterCommon.js";
|
|
||||||
import { Move } from "../libfigureMapper.js";
|
|
||||||
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
|
||||||
import { Hand } from "../rendererConstants.js";
|
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, PartialLowLevelMove, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
|
||||||
|
|
||||||
const moveName: Move["move"] = "give & take";
|
|
||||||
|
|
||||||
class GiveTakeSingleVariant extends SingleVariantMoveInterpreter<GiveTake, typeof moveName> {
|
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
|
||||||
const give: boolean = this.move.parameters.give;
|
|
||||||
const takeToSide = this.move.parameters.who;
|
|
||||||
|
|
||||||
const giveBeats = give ? this.move.beats / 2 : 0;
|
|
||||||
const takeBeats = this.move.beats - giveBeats;
|
|
||||||
|
|
||||||
return this.handleCircleMove(({ id, startPos }) => {
|
|
||||||
const isTaker = this.findPairOpposite(takeToSide, id) !== null;
|
|
||||||
|
|
||||||
const maybeGive: [] | [PartialLowLevelMove] = give ? [{
|
|
||||||
beats: giveBeats,
|
|
||||||
endPosition: { ...startPos, longLines: LongLines.Forward },
|
|
||||||
movementPattern: { kind: SemanticAnimationKind.Linear, minRotation: 0 },
|
|
||||||
}] : [];
|
|
||||||
const takeHands = new Map<Hand, HandConnection>([
|
|
||||||
[Hand.Left, { hand: Hand.Right, to: HandTo.DancerForward }],
|
|
||||||
[Hand.Right, { hand: Hand.Left, to: HandTo.DancerForward }],
|
|
||||||
]);
|
|
||||||
|
|
||||||
return this.combine([...maybeGive, (prevEnd) => ({
|
|
||||||
beats: takeBeats,
|
|
||||||
startPosition: { ...prevEnd, hands: undefined },
|
|
||||||
endPosition: isTaker ? {
|
|
||||||
...startPos,
|
|
||||||
which: startPos.which,
|
|
||||||
longLines: undefined,
|
|
||||||
hands: takeHands,
|
|
||||||
} : {
|
|
||||||
...startPos,
|
|
||||||
which: startPos.which.swapAcross(),
|
|
||||||
longLines: LongLines.Near,
|
|
||||||
hands: takeHands,
|
|
||||||
},
|
|
||||||
movementPattern: { kind: SemanticAnimationKind.Linear, minRotation: 0 },
|
|
||||||
})], startPos);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class GiveTake extends MoveInterpreter<typeof moveName> {
|
|
||||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
|
||||||
return new GiveTakeSingleVariant(this, startingPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moveInterpreters.set(moveName, GiveTake);
|
|
|
@ -1,339 +0,0 @@
|
||||||
import { DancerIdentity } from "../danceCommon.js";
|
|
||||||
import { SemanticPosition, PositionKind, ShortLinesPosition, CirclePosition, CircleSide, Facing } from "../interpreterCommon.js";
|
|
||||||
import { Move } from "../libfigureMapper.js";
|
|
||||||
import { LowLevelMove, SemanticAnimationKind } from "../lowLevelMove.js";
|
|
||||||
import { Hand } from "../rendererConstants.js";
|
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, PartialLowLevelMove, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
|
||||||
import { dancerIsPair } from "../libfigure/util.js";
|
|
||||||
|
|
||||||
const moveName: Move["move"] = "hey";
|
|
||||||
|
|
||||||
class HeySingleVariant extends SingleVariantMoveInterpreter<Hey, typeof moveName> {
|
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
|
||||||
// Needed for inner functions... that probably should be methods.
|
|
||||||
const move = this.move;
|
|
||||||
|
|
||||||
type HeyStep = {
|
|
||||||
kind: "StandStill" | "Loop" | "CenterPass" | "EndsPassIn" | "EndsPassOut" | "Ricochet",
|
|
||||||
endPosition: SemanticPosition,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.move.parameters.dir !== "across") {
|
|
||||||
throw new Error("Unsupported hey direction: " + this.move.parameters.dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
let heyParts: number;
|
|
||||||
switch (this.move.parameters.until) {
|
|
||||||
case "half":
|
|
||||||
heyParts = 4;
|
|
||||||
break;
|
|
||||||
case "full":
|
|
||||||
heyParts = 8;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error("Unsupported hey 'until': " + this.move.parameters.until);
|
|
||||||
}
|
|
||||||
const heyPartBeats: number = this.move.beats / heyParts;
|
|
||||||
// TODO is this right?
|
|
||||||
const firstPassInCenter: boolean = dancerIsPair(this.move.parameters.who);
|
|
||||||
const centerShoulder = firstPassInCenter === this.move.parameters.shoulder ? Hand.Right : Hand.Left;
|
|
||||||
const endsShoulder = centerShoulder.opposite();
|
|
||||||
|
|
||||||
function fixupHeyOtherPath(withoutOtherPath: Map<DancerIdentity, (LowLevelMove & { heyStep?: HeyStep })[]>): Map<DancerIdentity, LowLevelMove[]> {
|
|
||||||
const numSteps = withoutOtherPath.get(DancerIdentity.OnesLark)!.length;
|
|
||||||
for (let i = 0; i < numSteps; i++) {
|
|
||||||
for (const id of withoutOtherPath.keys()) {
|
|
||||||
const lowLevelMove = withoutOtherPath.get(id)![i];
|
|
||||||
if (lowLevelMove.movementPattern.kind !== SemanticAnimationKind.PassBy
|
|
||||||
|| !lowLevelMove.heyStep
|
|
||||||
|| lowLevelMove.movementPattern.otherPath) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const heyStepKind = lowLevelMove.heyStep.kind;
|
|
||||||
let foundPair = false;
|
|
||||||
for (const otherId of withoutOtherPath.keys()) {
|
|
||||||
const otherLowLevelMove = withoutOtherPath.get(otherId)![i];
|
|
||||||
if (id === otherId
|
|
||||||
|| otherLowLevelMove.movementPattern.kind !== SemanticAnimationKind.PassBy
|
|
||||||
|| !otherLowLevelMove.heyStep
|
|
||||||
|| otherLowLevelMove.movementPattern.otherPath) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const otherHeyStepKind = otherLowLevelMove.heyStep.kind;
|
|
||||||
|
|
||||||
if (heyStepKind === "CenterPass" && otherHeyStepKind === "CenterPass"
|
|
||||||
|| (lowLevelMove.startPosition.which.leftRightSide() === otherLowLevelMove.startPosition.which.leftRightSide()
|
|
||||||
&& (heyStepKind === "EndsPassIn" && otherHeyStepKind === "EndsPassOut"
|
|
||||||
|| heyStepKind === "EndsPassOut" && otherHeyStepKind === "EndsPassIn"))) {
|
|
||||||
lowLevelMove.movementPattern.otherPath = {
|
|
||||||
start: { ...otherLowLevelMove.startPosition, setOffset: lowLevelMove.startPosition.setOffset, lineOffset: lowLevelMove.startPosition.lineOffset },
|
|
||||||
end: { ...otherLowLevelMove.endPosition, setOffset: lowLevelMove.endPosition.setOffset, lineOffset: lowLevelMove.endPosition.lineOffset },
|
|
||||||
}
|
|
||||||
otherLowLevelMove.movementPattern.otherPath = {
|
|
||||||
start: { ...lowLevelMove.startPosition, setOffset: otherLowLevelMove.startPosition.setOffset, lineOffset: otherLowLevelMove.startPosition.lineOffset },
|
|
||||||
end: { ...lowLevelMove.endPosition, setOffset: otherLowLevelMove.endPosition.setOffset, lineOffset: otherLowLevelMove.endPosition.lineOffset },
|
|
||||||
}
|
|
||||||
foundPair = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!foundPair && heyStepKind === "EndsPassOut") {
|
|
||||||
// Then other is standing still.
|
|
||||||
const pos = {
|
|
||||||
...([...withoutOtherPath.values()]
|
|
||||||
.map(otherMoves => otherMoves[i])
|
|
||||||
.filter(m => m.movementPattern.kind === SemanticAnimationKind.StandStill
|
|
||||||
&& m.endPosition.which.leftRightSide() === lowLevelMove.endPosition.which.leftRightSide())
|
|
||||||
[0].endPosition),
|
|
||||||
setOffset: lowLevelMove.startPosition.setOffset, lineOffset: lowLevelMove.startPosition.lineOffset
|
|
||||||
}
|
|
||||||
lowLevelMove.movementPattern.otherPath = { start: pos, end: pos };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const id of withoutOtherPath.keys()) {
|
|
||||||
const lowLevelMove = withoutOtherPath.get(id)![i];
|
|
||||||
if (lowLevelMove.movementPattern.kind === SemanticAnimationKind.PassBy
|
|
||||||
&& !lowLevelMove.movementPattern.otherPath) {
|
|
||||||
throw new Error("Failed to fill in otherPath for " + id + " on hey step " + i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Object was mutated.
|
|
||||||
return withoutOtherPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
return fixupHeyOtherPath(this.handleMove(({ id, startPos }) => {
|
|
||||||
const endsInCircle = startPos.kind === PositionKind.Circle;
|
|
||||||
function heyStepToPartialLowLevelMove(heyStep: HeyStep): PartialLowLevelMove & { heyStep: HeyStep } {
|
|
||||||
return {
|
|
||||||
beats: heyPartBeats,
|
|
||||||
// TODO use circle positions on ends? ... unless hey ends in a box the gnat or similar...
|
|
||||||
endPosition: heyStep.endPosition,
|
|
||||||
movementPattern: heyStep.kind === "StandStill" ? {
|
|
||||||
kind: SemanticAnimationKind.StandStill,
|
|
||||||
} : heyStep.kind === "Loop" ? {
|
|
||||||
// TODO Loop should probably be its own kind? Or RotateAround?
|
|
||||||
kind: SemanticAnimationKind.Linear,
|
|
||||||
minRotation: endsShoulder === Hand.Right ? +180 : -180,
|
|
||||||
} : heyStep.kind === "Ricochet" ? {
|
|
||||||
// TODO This is a hack.
|
|
||||||
kind: SemanticAnimationKind.PassBy,
|
|
||||||
around: heyStep.endPosition.which.leftRightSide(),
|
|
||||||
withHands: false,
|
|
||||||
otherPath: "Swap",
|
|
||||||
facing: "Start",
|
|
||||||
side: endsShoulder,
|
|
||||||
} : {
|
|
||||||
kind: SemanticAnimationKind.PassBy,
|
|
||||||
around: heyStep.kind === "CenterPass" ? "Center" : heyStep.endPosition.which.leftRightSide(),
|
|
||||||
withHands: false,
|
|
||||||
side: heyStep.kind === "CenterPass" ? centerShoulder : endsShoulder,
|
|
||||||
facing: "Start",
|
|
||||||
otherPath: undefined!, // Placeholder, fixup later.
|
|
||||||
},
|
|
||||||
heyStep,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
function continueHey(prevStep: HeyStep, stepsLeft: number, beenInCenter: boolean): HeyStep {
|
|
||||||
// TODO Not sure why type checker requires rechecking this here.
|
|
||||||
if (move.move !== "hey") throw new Error("Unreachable.");
|
|
||||||
|
|
||||||
// Continuing hey so everyone is either passing (in center or on ends) or looping on ends.
|
|
||||||
if (prevStep.endPosition.kind === PositionKind.Circle) {
|
|
||||||
if (prevStep.endPosition.facing === prevStep.endPosition.which.facingAcross()) {
|
|
||||||
if (stepsLeft === 0) {
|
|
||||||
return {
|
|
||||||
kind: "StandStill",
|
|
||||||
endPosition: prevStep.endPosition,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
kind: "EndsPassIn",
|
|
||||||
endPosition: {
|
|
||||||
kind: PositionKind.ShortLines,
|
|
||||||
which: prevStep.endPosition.which.isLeft() ? ShortLinesPosition.MiddleLeft : ShortLinesPosition.MiddleRight,
|
|
||||||
facing: prevStep.endPosition.which.facingAcross(),
|
|
||||||
setOffset: prevStep.endPosition.setOffset,
|
|
||||||
lineOffset: prevStep.endPosition.lineOffset,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (stepsLeft === 1 && !endsInCircle) {
|
|
||||||
return {
|
|
||||||
kind: "Loop",
|
|
||||||
endPosition: {
|
|
||||||
kind: PositionKind.ShortLines,
|
|
||||||
which: prevStep.endPosition.which.isLeft() ? ShortLinesPosition.FarLeft : ShortLinesPosition.FarRight,
|
|
||||||
facing: prevStep.endPosition.which.facingAcross(),
|
|
||||||
setOffset: prevStep.endPosition.setOffset,
|
|
||||||
lineOffset: prevStep.endPosition.lineOffset,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
kind: "Loop",
|
|
||||||
endPosition: {
|
|
||||||
...prevStep.endPosition,
|
|
||||||
which: prevStep.endPosition.which.swapUpAndDown(),
|
|
||||||
facing: prevStep.endPosition.which.facingAcross()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (prevStep.endPosition.kind === PositionKind.ShortLines) {
|
|
||||||
const isFacingSide = prevStep.endPosition.facing === prevStep.endPosition.which.facingSide();
|
|
||||||
const inMiddle = prevStep.endPosition.which.isMiddle();
|
|
||||||
if (!inMiddle && !isFacingSide) {
|
|
||||||
return {
|
|
||||||
kind: "Loop",
|
|
||||||
endPosition: { ...prevStep.endPosition, facing: prevStep.endPosition.which.facingSide() },
|
|
||||||
}
|
|
||||||
} else if (inMiddle && isFacingSide) {
|
|
||||||
return {
|
|
||||||
kind: "EndsPassOut",
|
|
||||||
endPosition: {
|
|
||||||
...prevStep.endPosition,
|
|
||||||
kind: PositionKind.Circle,
|
|
||||||
which: prevStep.endPosition.which.isLeft()
|
|
||||||
? (endsShoulder === Hand.Right ? CirclePosition.TopLeft : CirclePosition.BottomLeft)
|
|
||||||
: (endsShoulder === Hand.Right ? CirclePosition.BottomRight : CirclePosition.TopRight),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!isFacingSide) {
|
|
||||||
const rico = inCenterFirst
|
|
||||||
? beenInCenter
|
|
||||||
? move.parameters.rico3
|
|
||||||
: move.parameters.rico1
|
|
||||||
: beenInCenter
|
|
||||||
? move.parameters.rico4
|
|
||||||
: move.parameters.rico2;
|
|
||||||
|
|
||||||
if (rico) {
|
|
||||||
const onLeftSide = prevStep.endPosition.which.isLeft();
|
|
||||||
return {
|
|
||||||
kind: "Ricochet",
|
|
||||||
endPosition: {
|
|
||||||
...prevStep.endPosition,
|
|
||||||
kind: PositionKind.Circle,
|
|
||||||
which: CirclePosition.fromSides(prevStep.endPosition.which.leftRightSide(),
|
|
||||||
// TODO might be swapped
|
|
||||||
(endsShoulder === Hand.Left) === onLeftSide ? CircleSide.Top : CircleSide.Bottom),
|
|
||||||
facing: onLeftSide ? Facing.Left : Facing.Right,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
kind: "CenterPass",
|
|
||||||
endPosition: {
|
|
||||||
...prevStep.endPosition,
|
|
||||||
which: prevStep.endPosition.which.swapSides()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return {
|
|
||||||
kind: inMiddle ? "EndsPassOut" : "EndsPassIn",
|
|
||||||
endPosition: {
|
|
||||||
...prevStep.endPosition,
|
|
||||||
which: prevStep.endPosition.which.swapOnSide()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error("Unexpected PositionKind: " + (<any>prevStep.endPosition).kind);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const inCenterFirst = firstPassInCenter && this.findPairOpposite(this.move.parameters.who, id) !== null
|
|
||||||
|| this.move.parameters.who2 && this.findPairOpposite(this.move.parameters.who2, id) !== null;
|
|
||||||
|
|
||||||
let firstHeyStep: HeyStep;
|
|
||||||
let startingPos: SemanticPosition;
|
|
||||||
if (firstPassInCenter) {
|
|
||||||
if (startPos.kind !== PositionKind.Circle) {
|
|
||||||
throw new Error("Hey starting in center not from circle is unsupported.");
|
|
||||||
}
|
|
||||||
startingPos = {
|
|
||||||
kind: startPos.kind,
|
|
||||||
which: startPos.which,
|
|
||||||
facing: startPos.which.isLeft() ? Facing.Right : Facing.Left,
|
|
||||||
setOffset: startPos.setOffset,
|
|
||||||
lineOffset: startPos.lineOffset,
|
|
||||||
};
|
|
||||||
if (inCenterFirst) {
|
|
||||||
if (this.move.parameters.rico1) {
|
|
||||||
firstHeyStep = {
|
|
||||||
kind: "Ricochet",
|
|
||||||
endPosition: {
|
|
||||||
kind: PositionKind.Circle,
|
|
||||||
which: startPos.which.swapUpAndDown(),
|
|
||||||
facing: startPos.which.facingOut(),
|
|
||||||
setOffset: startPos.setOffset,
|
|
||||||
lineOffset: startPos.lineOffset,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
firstHeyStep = {
|
|
||||||
kind: "CenterPass",
|
|
||||||
endPosition: {
|
|
||||||
kind: PositionKind.ShortLines,
|
|
||||||
which: startPos.which.isLeft() ? ShortLinesPosition.MiddleRight : ShortLinesPosition.MiddleLeft,
|
|
||||||
facing: startingPos.facing,
|
|
||||||
setOffset: startPos.setOffset,
|
|
||||||
lineOffset: startPos.lineOffset,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
firstHeyStep = {
|
|
||||||
kind: "StandStill",
|
|
||||||
endPosition: startingPos,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (startPos.kind !== PositionKind.ShortLines) {
|
|
||||||
throw new Error("Hey with first pass on ends must start approximately in short lines.");
|
|
||||||
}
|
|
||||||
|
|
||||||
const startFacing = startPos.which.facingSide();
|
|
||||||
startingPos = {
|
|
||||||
kind: startPos.kind,
|
|
||||||
which: startPos.which,
|
|
||||||
facing: startFacing,
|
|
||||||
setOffset: startPos.setOffset,
|
|
||||||
lineOffset: startPos.lineOffset,
|
|
||||||
};
|
|
||||||
firstHeyStep = {
|
|
||||||
kind: startingPos.which.isMiddle() ? "EndsPassOut" : "EndsPassIn",
|
|
||||||
endPosition: { ...startingPos, which: startPos.which.swapOnSide() },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const heySteps: HeyStep[] = [firstHeyStep];
|
|
||||||
let beenInCenter = firstHeyStep.kind === "CenterPass" || firstHeyStep.kind === "Ricochet";
|
|
||||||
for (let i = 1; i < heyParts; i++) {
|
|
||||||
const isLast = i === heyParts - 1;
|
|
||||||
const nextHeyStep = continueHey(heySteps[i - 1], heyParts - i - 1, beenInCenter);
|
|
||||||
beenInCenter ||= nextHeyStep.kind === "CenterPass" || nextHeyStep.kind === "Ricochet";
|
|
||||||
heySteps.push(nextHeyStep);
|
|
||||||
}
|
|
||||||
return this.combine(heySteps.map(heyStepToPartialLowLevelMove), { ...startingPos, hands: undefined });
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Hey extends MoveInterpreter<typeof moveName> {
|
|
||||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
|
||||||
return new HeySingleVariant(this, startingPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moveInterpreters.set(moveName, Hey);
|
|
|
@ -1,55 +0,0 @@
|
||||||
import { SemanticPosition, Facing, HandConnection, HandTo, LongLines } from "../interpreterCommon.js";
|
|
||||||
import { Move } from "../libfigureMapper.js";
|
|
||||||
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
|
||||||
import { Hand } from "../rendererConstants.js";
|
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
|
||||||
|
|
||||||
const moveName: Move["move"] = "long lines";
|
|
||||||
|
|
||||||
class LongLinesSingleVariant extends SingleVariantMoveInterpreter<LongLinesInterpreter, typeof moveName> {
|
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
|
||||||
return this.handleCircleMove(({ startPos }) => {
|
|
||||||
const startPosition: SemanticPosition = {
|
|
||||||
...startPos,
|
|
||||||
longLines: undefined,
|
|
||||||
// TODO Occassionally dances have long lines facing out. This will get that wrong.
|
|
||||||
facing: startPos.which.isLeft() ? Facing.Right : Facing.Left,
|
|
||||||
hands: new Map<Hand, HandConnection>([
|
|
||||||
[Hand.Left, { hand: Hand.Right, to: HandTo.DancerLeft }],
|
|
||||||
[Hand.Right, { hand: Hand.Left, to: HandTo.DancerRight }],
|
|
||||||
]),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.move.parameters.go) {
|
|
||||||
const forwardBeats = this.move.beats / 2;
|
|
||||||
const backwardBeats = this.move.beats - forwardBeats;
|
|
||||||
return this.combine([
|
|
||||||
{
|
|
||||||
beats: forwardBeats,
|
|
||||||
endPosition: { ...startPosition, longLines: LongLines.Forward },
|
|
||||||
movementPattern: { kind: SemanticAnimationKind.Linear },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
beats: backwardBeats,
|
|
||||||
endPosition: startPosition,
|
|
||||||
movementPattern: { kind: SemanticAnimationKind.Linear, minRotation: 0 },
|
|
||||||
},
|
|
||||||
], startPosition);
|
|
||||||
} else {
|
|
||||||
return this.combine([{
|
|
||||||
beats: this.move.beats,
|
|
||||||
endPosition: { ...startPosition, longLines: LongLines.Forward },
|
|
||||||
movementPattern: { kind: SemanticAnimationKind.Linear },
|
|
||||||
}], startPosition);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class LongLinesInterpreter extends MoveInterpreter<typeof moveName> {
|
|
||||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
|
||||||
return new LongLinesSingleVariant(this, startingPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moveInterpreters.set(moveName, LongLinesInterpreter);
|
|
|
@ -1,46 +0,0 @@
|
||||||
import { SemanticPosition, PositionKind } from "../interpreterCommon.js";
|
|
||||||
import { Move } from "../libfigureMapper.js";
|
|
||||||
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
|
||||||
|
|
||||||
const moveName: Move["move"] = "mad robin";
|
|
||||||
|
|
||||||
class MadRobinSingleVariant extends SingleVariantMoveInterpreter<MadRobin, typeof moveName> {
|
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
|
||||||
if (this.move.parameters.circling !== 360) {
|
|
||||||
throw new Error("mad robin circling not exactly once is unsupported.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.handleCircleMove(({ id, startPos }) => {
|
|
||||||
// Read who of mad robin to decide direction.
|
|
||||||
const madRobinClockwise: boolean = (this.findPairOpposite(this.move.parameters.who, id) !== null) === startPos.which.isOnLeftLookingAcross();
|
|
||||||
|
|
||||||
const startAndEndPos: SemanticPosition = {
|
|
||||||
kind: PositionKind.Circle,
|
|
||||||
which: startPos.which,
|
|
||||||
facing: startPos.which.facingAcross(),
|
|
||||||
setOffset: startPos.setOffset,
|
|
||||||
lineOffset: startPos.lineOffset,
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.combine([{
|
|
||||||
beats: this.move.beats,
|
|
||||||
startPosition: startAndEndPos,
|
|
||||||
endPosition: startAndEndPos,
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.DoSiDo,
|
|
||||||
amount: madRobinClockwise ? this.move.parameters.circling : -this.move.parameters.circling,
|
|
||||||
around: startPos.which.leftRightSide(),
|
|
||||||
},
|
|
||||||
}]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MadRobin extends MoveInterpreter<typeof moveName> {
|
|
||||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
|
||||||
return new MadRobinSingleVariant(this, startingPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moveInterpreters.set(moveName, MadRobin);
|
|
|
@ -1,35 +0,0 @@
|
||||||
import { Move } from "../libfigureMapper.js";
|
|
||||||
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
|
||||||
import { Hand } from "../rendererConstants.js";
|
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
|
||||||
|
|
||||||
const moveName: Move["move"] = "pass by";
|
|
||||||
|
|
||||||
class PassBySingleVariant extends SingleVariantMoveInterpreter<PassBy, typeof moveName> {
|
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
|
||||||
const passByShoulder = this.move.parameters.shoulder ? Hand.Left : Hand.Right;
|
|
||||||
return this.handlePairedMove(this.move.parameters.who, ({ startPos, around, withPos }) => {
|
|
||||||
return this.combine([{
|
|
||||||
beats: this.move.beats,
|
|
||||||
// TODO Is pass by always a swap?
|
|
||||||
endPosition: withPos,
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.PassBy,
|
|
||||||
around,
|
|
||||||
facing: "Forward",
|
|
||||||
otherPath: "Swap",
|
|
||||||
side: passByShoulder,
|
|
||||||
withHands: false,
|
|
||||||
}
|
|
||||||
}], startPos);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PassBy extends MoveInterpreter<typeof moveName> {
|
|
||||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
|
||||||
return new PassBySingleVariant(this, startingPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moveInterpreters.set(moveName, PassBy);
|
|
|
@ -1,122 +0,0 @@
|
||||||
import { CoupleRole } from "../danceCommon.js";
|
|
||||||
import { Facing, SemanticPosition, PositionKind, CircleSide } from "../interpreterCommon.js";
|
|
||||||
import { Move } from "../libfigureMapper.js";
|
|
||||||
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
|
||||||
import { Hand } from "../rendererConstants.js";
|
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
|
||||||
|
|
||||||
const moveName: Move["move"] = "pass through";
|
|
||||||
|
|
||||||
class PassThroughSingleVariant extends SingleVariantMoveInterpreter<PassThrough, typeof moveName> {
|
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
|
||||||
if (this.move.parameters.dir === "left diagonal" || this.move.parameters.dir === "right diagonal") {
|
|
||||||
// TODO There's logic for this below, but unsure it's right.
|
|
||||||
throw new Error(this.move.move + " with dir of " + this.move.parameters.dir + " is unsupported.");
|
|
||||||
}
|
|
||||||
const alongSet = this.move.parameters.dir === "along";
|
|
||||||
const passShoulder = this.move.parameters.shoulder ? Hand.Right : Hand.Left;
|
|
||||||
|
|
||||||
// Special-case this.
|
|
||||||
if (this.move.note?.includes("2s shooting the 1s down the center") ?? false) {
|
|
||||||
return this.handleCircleMove(({ id, startPos }) => {
|
|
||||||
const facing = id.coupleRole === CoupleRole.Ones ? Facing.Down : Facing.Up;
|
|
||||||
const endPos: SemanticPosition = {
|
|
||||||
kind: PositionKind.ShortLines,
|
|
||||||
which: startPos.which.unfoldToShortLines(CircleSide.Top),
|
|
||||||
facing,
|
|
||||||
setOffset: (startPos.setOffset ?? 0) + (facing === Facing.Up ? -0.5 : +0.5),
|
|
||||||
lineOffset: startPos.lineOffset,
|
|
||||||
};
|
|
||||||
|
|
||||||
return this.combine([{
|
|
||||||
beats: this.move.beats,
|
|
||||||
endPosition: endPos,
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.PassBy,
|
|
||||||
around: startPos.which.leftRightSide(),
|
|
||||||
side: endPos.which.isLeft() ? Hand.Left : Hand.Right,
|
|
||||||
withHands: false,
|
|
||||||
facing: "Forward",
|
|
||||||
otherPath: "Swap",
|
|
||||||
},
|
|
||||||
}], startPos);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.handleMove(({ startPos }) => {
|
|
||||||
if (alongSet && startPos.kind === PositionKind.Circle) {
|
|
||||||
const facing = startPos.which.facingUpOrDown();
|
|
||||||
const endPos: SemanticPosition = {
|
|
||||||
kind: PositionKind.Circle,
|
|
||||||
which: startPos.which,
|
|
||||||
facing,
|
|
||||||
setOffset: (startPos.setOffset ?? 0) + (facing === Facing.Up ? -0.5 : +0.5),
|
|
||||||
lineOffset: startPos.lineOffset,
|
|
||||||
};
|
|
||||||
|
|
||||||
return this.combine([{
|
|
||||||
beats: this.move.beats,
|
|
||||||
endPosition: endPos,
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.PassBy,
|
|
||||||
around: startPos.which.leftRightSide(),
|
|
||||||
side: passShoulder,
|
|
||||||
withHands: false,
|
|
||||||
facing: "Start",
|
|
||||||
otherPath: "Swap",
|
|
||||||
},
|
|
||||||
}], startPos);
|
|
||||||
} else if (!alongSet && startPos.kind === PositionKind.Circle) {
|
|
||||||
const facing = startPos.which.facingAcross();
|
|
||||||
const endPos: SemanticPosition = {
|
|
||||||
kind: PositionKind.Circle,
|
|
||||||
which: startPos.which.swapAcross(),
|
|
||||||
facing,
|
|
||||||
setOffset: (startPos.setOffset ?? 0) + (this.move.parameters.dir === "across"
|
|
||||||
? 0
|
|
||||||
: (this.move.parameters.dir === "left diagonal") === startPos.which.isLeft() ? -1 : +1),
|
|
||||||
lineOffset: startPos.lineOffset,
|
|
||||||
};
|
|
||||||
|
|
||||||
return this.combine([{
|
|
||||||
beats: this.move.beats,
|
|
||||||
endPosition: endPos,
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.PassBy,
|
|
||||||
around: startPos.which.topBottomSide(),
|
|
||||||
side: passShoulder,
|
|
||||||
withHands: false,
|
|
||||||
facing: "Forward",
|
|
||||||
otherPath: "Swap",
|
|
||||||
},
|
|
||||||
}], startPos);
|
|
||||||
} else if (alongSet && startPos.kind === PositionKind.ShortLines) {
|
|
||||||
// TODO This assumes short *wavy* lines.
|
|
||||||
|
|
||||||
const endPos: SemanticPosition = {
|
|
||||||
...startPos,
|
|
||||||
balance: undefined,
|
|
||||||
// TODO only swap sometimes...
|
|
||||||
which: startPos.which.swapOnSide(),
|
|
||||||
setOffset: (startPos.setOffset ?? 0) + (startPos.facing === Facing.Up ? -0.5 : +0.5),
|
|
||||||
};
|
|
||||||
|
|
||||||
return this.combine([{
|
|
||||||
beats: this.move.beats,
|
|
||||||
endPosition: endPos,
|
|
||||||
movementPattern: { kind: SemanticAnimationKind.Linear },
|
|
||||||
}], startPos);
|
|
||||||
} else {
|
|
||||||
throw new Error(this.move.move + " with dir of " + this.move.parameters.dir + " starting from " + startPos.kind + " is unsupported.");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PassThrough extends MoveInterpreter<typeof moveName> {
|
|
||||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
|
||||||
return new PassThroughSingleVariant(this, startingPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moveInterpreters.set(moveName, PassThrough);
|
|
|
@ -1,59 +0,0 @@
|
||||||
import { Facing, SemanticPosition } from "../interpreterCommon.js";
|
|
||||||
import { SemanticAnimationKind, LowLevelMove } from "../lowLevelMove.js";
|
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, PartialLowLevelMove, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
|
||||||
import { balanceCircleInAndOut } from "./balanceTheRing.js";
|
|
||||||
|
|
||||||
class PetronellaSingleVariant extends SingleVariantMoveInterpreter<Petronella, "petronella"> {
|
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
|
||||||
// TODO These should be actual parameters, not parsing the notes...
|
|
||||||
const rightShoulder: boolean = !(this.move.note?.includes('left') ?? false);
|
|
||||||
const newCircle: boolean = this.move.note?.includes('end facing') ?? this.move.progression;
|
|
||||||
|
|
||||||
return this.handleCircleMove(({ startPos }) => {
|
|
||||||
let finalPosition = {
|
|
||||||
...startPos,
|
|
||||||
facing: Facing.CenterOfCircle,
|
|
||||||
which: startPos.which.circleRight(rightShoulder ? 1 : -1),
|
|
||||||
hands: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (newCircle) {
|
|
||||||
finalPosition = {
|
|
||||||
...finalPosition,
|
|
||||||
which: finalPosition.which.swapUpAndDown(),
|
|
||||||
setOffset: (finalPosition.setOffset ?? 0) + (finalPosition.which.isTop() ? -0.5 : +0.5),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const spin: ((prevEnd: SemanticPosition) => PartialLowLevelMove) = prevEnd => ({
|
|
||||||
beats: this.move.beats - (this.move.parameters.bal ? 4 : 0),
|
|
||||||
startPosition: {
|
|
||||||
...prevEnd,
|
|
||||||
facing: Facing.CenterOfCircle,
|
|
||||||
hands: undefined,
|
|
||||||
},
|
|
||||||
endPosition: finalPosition,
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.Linear,
|
|
||||||
minRotation: rightShoulder ? 180 : -180,
|
|
||||||
handsDuring: "None",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.move.parameters.bal) {
|
|
||||||
const balance: LowLevelMove[] = balanceCircleInAndOut(this.move, startPos);
|
|
||||||
return PetronellaSingleVariant.append([...balance], spin);
|
|
||||||
} else {
|
|
||||||
return this.combine([spin]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Petronella extends MoveInterpreter<"petronella"> {
|
|
||||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
|
||||||
return new PetronellaSingleVariant(this, startingPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moveInterpreters.set("petronella", Petronella);
|
|
|
@ -1,79 +0,0 @@
|
||||||
import { DanceRole } from "../danceCommon.js";
|
|
||||||
import { HandTo, SemanticPosition, DancerDistance, HandConnection, PositionKind } from "../interpreterCommon.js";
|
|
||||||
import { Move } from "../libfigureMapper.js";
|
|
||||||
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
|
||||||
import { Hand } from "../rendererConstants.js";
|
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, PartialLowLevelMove, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
|
||||||
|
|
||||||
const moveName: Move["move"] = "promenade";
|
|
||||||
|
|
||||||
class PromenadeSingleVariant extends SingleVariantMoveInterpreter<Promenade, typeof moveName> {
|
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
|
||||||
if (this.move.parameters.dir !== "across") {
|
|
||||||
// TODO "along" would be the bicycle chain? Not sure what left/right diagonal means here.
|
|
||||||
throw "Promenade not across the set is unsupported."
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO How to know?
|
|
||||||
const twirlAfterPromenade = true;
|
|
||||||
const twirlBeats = twirlAfterPromenade ? this.move.beats / 2 : 0;
|
|
||||||
const promenadeBeats = this.move.beats - twirlBeats;
|
|
||||||
|
|
||||||
return this.handleCirclePairedMove(this.move.parameters.who, ({ startPos }) => {
|
|
||||||
const handTo = startPos.which.isOnLeftLookingAcross() ? HandTo.DancerRight : HandTo.DancerLeft;
|
|
||||||
const startingPos: SemanticPosition = {
|
|
||||||
...startPos,
|
|
||||||
facing: startPos.which.facingAcross(),
|
|
||||||
dancerDistance: DancerDistance.Compact,
|
|
||||||
hands: new Map<Hand, HandConnection>([
|
|
||||||
[Hand.Left, { hand: Hand.Left, to: handTo }],
|
|
||||||
[Hand.Right, { hand: Hand.Right, to: handTo }],
|
|
||||||
]),
|
|
||||||
};
|
|
||||||
|
|
||||||
const endSetOffset = this.move.progression
|
|
||||||
? (startPos.setOffset ?? 0) + (startPos.which.isLeft() ? +0.5 : -0.5)
|
|
||||||
: startPos.setOffset;
|
|
||||||
const beforeTwirlPos: SemanticPosition & { kind: PositionKind.Circle } = {
|
|
||||||
...startingPos,
|
|
||||||
which: startPos.which.swapAcross(),
|
|
||||||
facing: startPos.which.facingAcross(),
|
|
||||||
setOffset: endSetOffset,
|
|
||||||
};
|
|
||||||
const endPos: SemanticPosition = {
|
|
||||||
...beforeTwirlPos,
|
|
||||||
which: beforeTwirlPos.which.swapUpAndDown(),
|
|
||||||
facing: beforeTwirlPos.which.facingAcross(),
|
|
||||||
dancerDistance: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
const maybeTwirl: [] | [PartialLowLevelMove] = (twirlAfterPromenade ? [{
|
|
||||||
beats: twirlBeats,
|
|
||||||
endPosition: endPos,
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.CourtesyTurn,
|
|
||||||
clockwise: this.move.parameters.turn, // TODO or ! this?
|
|
||||||
}
|
|
||||||
}] : [])
|
|
||||||
|
|
||||||
return this.combine([{
|
|
||||||
beats: promenadeBeats,
|
|
||||||
endPosition: beforeTwirlPos,
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.Promenade,
|
|
||||||
swingRole: startPos.which.isOnLeftLookingAcross() ? DanceRole.Lark : DanceRole.Robin,
|
|
||||||
twirl: true,
|
|
||||||
passBy: this.move.parameters.turn ? Hand.Left : Hand.Right,
|
|
||||||
}
|
|
||||||
}, ...maybeTwirl], startingPos);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Promenade extends MoveInterpreter<typeof moveName> {
|
|
||||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
|
||||||
return new PromenadeSingleVariant(this, startingPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moveInterpreters.set(moveName, Promenade);
|
|
|
@ -1,83 +0,0 @@
|
||||||
import { HandConnection, HandTo, BalanceWeight } from "../interpreterCommon.js";
|
|
||||||
import { Move } from "../libfigureMapper.js";
|
|
||||||
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
|
||||||
import { Hand } from "../rendererConstants.js";
|
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, PartialLowLevelMove, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
|
||||||
|
|
||||||
const moveName: Move["move"] = "pull by dancers";
|
|
||||||
|
|
||||||
class PullByDancersSingleVariant extends SingleVariantMoveInterpreter<PullByDancers, typeof moveName> {
|
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
|
||||||
// TODO Might make sense to think of pull by as not a full swap?
|
|
||||||
// e.g., in Blue and Green Candles, it's treated as only getting to
|
|
||||||
// ShortLinesPosition.Middle* before doing an allemande.
|
|
||||||
return this.handlePairedMove(this.move.parameters.who, ({ startPos, around, withPos }) => {
|
|
||||||
const hand = this.move.parameters.hand ? Hand.Right : Hand.Left;
|
|
||||||
const balanceBeats = this.move.parameters.bal
|
|
||||||
? this.move.beats > 4
|
|
||||||
? this.move.beats - 4
|
|
||||||
: 2
|
|
||||||
: 0;
|
|
||||||
const balancePartBeats = balanceBeats / 2;
|
|
||||||
const pullBeats = this.move.beats - balanceBeats;
|
|
||||||
|
|
||||||
// TODO Adjust facing?
|
|
||||||
const startPosition = {
|
|
||||||
...startPos,
|
|
||||||
hands: new Map<Hand, HandConnection>([
|
|
||||||
[
|
|
||||||
hand,
|
|
||||||
{ hand, to: around === "Center" ? HandTo.DiagonalAcrossCircle : HandTo.DancerForward }
|
|
||||||
]])
|
|
||||||
};
|
|
||||||
|
|
||||||
const passBy: PartialLowLevelMove = {
|
|
||||||
beats: pullBeats,
|
|
||||||
endPosition: { ...withPos, facing: startPos.facing },
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.PassBy,
|
|
||||||
around,
|
|
||||||
side: hand,
|
|
||||||
withHands: true,
|
|
||||||
facing: "Start",
|
|
||||||
otherPath: "Swap",
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.move.parameters.bal) {
|
|
||||||
return this.combine([
|
|
||||||
{
|
|
||||||
beats: balancePartBeats,
|
|
||||||
endPosition: {
|
|
||||||
...startPosition,
|
|
||||||
balance: BalanceWeight.Forward,
|
|
||||||
},
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.Linear,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
beats: balancePartBeats,
|
|
||||||
endPosition: {
|
|
||||||
...startPosition,
|
|
||||||
balance: BalanceWeight.Backward,
|
|
||||||
},
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.Linear,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
passBy], startPosition);
|
|
||||||
} else {
|
|
||||||
return this.combine([passBy], startPosition);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PullByDancers extends MoveInterpreter<typeof moveName> {
|
|
||||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
|
||||||
return new PullByDancersSingleVariant(this, startingPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moveInterpreters.set(moveName, PullByDancers);
|
|
|
@ -1,74 +0,0 @@
|
||||||
import { Move } from "../libfigureMapper.js";
|
|
||||||
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
|
||||||
import { Hand } from "../rendererConstants.js";
|
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
|
||||||
|
|
||||||
const moveName: Move["move"] = "revolving door";
|
|
||||||
|
|
||||||
class RevolvingDoorSingleVariant extends SingleVariantMoveInterpreter<RevolvingDoor, typeof moveName> {
|
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
|
||||||
const byHand = this.move.parameters.hand ? Hand.Right : Hand.Left;
|
|
||||||
// TODO More parts? Or define an animation kind?
|
|
||||||
const waitBeats = 2;
|
|
||||||
const carryBeats = this.move.beats / 2;
|
|
||||||
const returnBeats = this.move.beats - carryBeats - waitBeats;
|
|
||||||
return this.handleCirclePairedMove(this.move.parameters.whom, ({ id, startPos }) => {
|
|
||||||
const isCarried = this.findPairOpposite(this.move.parameters.who, id) === null;
|
|
||||||
const startingPos = { ...startPos, hands: undefined };
|
|
||||||
|
|
||||||
// TODO animation here needs work.
|
|
||||||
if (isCarried) {
|
|
||||||
const endWhich = startPos.which.swapDiagonal();
|
|
||||||
return this.combine([
|
|
||||||
prevEnd => ({
|
|
||||||
beats: waitBeats,
|
|
||||||
endPosition: prevEnd,
|
|
||||||
movementPattern: { kind: SemanticAnimationKind.StandStill },
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
beats: carryBeats,
|
|
||||||
endPosition: {
|
|
||||||
...startPos,
|
|
||||||
which: endWhich,
|
|
||||||
facing: endWhich.facingAcross(),
|
|
||||||
},
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.RotateAround,
|
|
||||||
minAmount: byHand === Hand.Right ? 180 : -180,
|
|
||||||
around: "Center",
|
|
||||||
byHand,
|
|
||||||
close: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
prevEnd => ({
|
|
||||||
beats: returnBeats,
|
|
||||||
endPosition: prevEnd,
|
|
||||||
movementPattern: { kind: SemanticAnimationKind.StandStill },
|
|
||||||
}),
|
|
||||||
], startingPos);
|
|
||||||
} else {
|
|
||||||
return this.combine([
|
|
||||||
{
|
|
||||||
beats: this.move.beats,
|
|
||||||
endPosition: startPos,
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.RotateAround,
|
|
||||||
minAmount: byHand === Hand.Right ? 180 : -180,
|
|
||||||
around: "Center",
|
|
||||||
byHand,
|
|
||||||
close: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
], startingPos);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class RevolvingDoor extends MoveInterpreter<typeof moveName> {
|
|
||||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
|
||||||
return new RevolvingDoorSingleVariant(this, startingPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moveInterpreters.set(moveName, RevolvingDoor);
|
|
|
@ -1,52 +0,0 @@
|
||||||
import { Move } from "../libfigureMapper.js";
|
|
||||||
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
|
||||||
import { Hand } from "../rendererConstants.js";
|
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
|
||||||
|
|
||||||
const moveName: Move["move"] = "right left through";
|
|
||||||
|
|
||||||
class RightLeftThroughSingleVariant extends SingleVariantMoveInterpreter<RightLeftThrough, typeof moveName> {
|
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
|
||||||
if (this.move.parameters.dir !== "across") {
|
|
||||||
throw new Error(this.move.move + " with dir " + this.move.parameters.dir + " is unsupported.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.handleCircleMove(({ startPos }) => {
|
|
||||||
const startingPos = { ...startPos, facing: startPos.which.facingAcross() };
|
|
||||||
const swappedPos = { ...startingPos, which: startingPos.which.swapAcross() };
|
|
||||||
return this.combine([
|
|
||||||
{
|
|
||||||
beats: this.move.beats / 2,
|
|
||||||
endPosition: swappedPos,
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.PassBy,
|
|
||||||
side: Hand.Right,
|
|
||||||
withHands: true,
|
|
||||||
facing: "Start",
|
|
||||||
around: startingPos.which.topBottomSide(),
|
|
||||||
otherPath: "Swap",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
beats: this.move.beats / 2,
|
|
||||||
endPosition: {
|
|
||||||
...startingPos,
|
|
||||||
which: startingPos.which.swapDiagonal(),
|
|
||||||
facing: startingPos.which.facingOut()
|
|
||||||
},
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.CourtesyTurn,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
], startingPos);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class RightLeftThrough extends MoveInterpreter<typeof moveName> {
|
|
||||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
|
||||||
return new RightLeftThroughSingleVariant(this, startingPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moveInterpreters.set(moveName, RightLeftThrough);
|
|
|
@ -1,74 +0,0 @@
|
||||||
import { DanceRole, CoupleRole } from "../danceCommon.js";
|
|
||||||
import { PositionKind } from "../interpreterCommon.js";
|
|
||||||
import { Move } from "../libfigureMapper.js";
|
|
||||||
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
|
||||||
|
|
||||||
const moveName: Move["move"] = "roll away";
|
|
||||||
|
|
||||||
class RollAwaySingleVariant extends SingleVariantMoveInterpreter<RollAway, typeof moveName> {
|
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
|
||||||
// TODO maybe can roll away in short lines?
|
|
||||||
return this.handleCirclePairedMove(this.move.parameters.who, ({ id, startPos, withPos }) => {
|
|
||||||
let isRoller: boolean;
|
|
||||||
switch (this.move.parameters.who) {
|
|
||||||
case "gentlespoons":
|
|
||||||
isRoller = id.danceRole === DanceRole.Lark;
|
|
||||||
break;
|
|
||||||
case "ladles":
|
|
||||||
isRoller = id.danceRole === DanceRole.Robin;
|
|
||||||
break;
|
|
||||||
case "ones":
|
|
||||||
isRoller = id.coupleRole === CoupleRole.Ones;
|
|
||||||
break;
|
|
||||||
case "twos":
|
|
||||||
isRoller = id.coupleRole === CoupleRole.Twos;
|
|
||||||
break;
|
|
||||||
case "first corners":
|
|
||||||
case "second corners":
|
|
||||||
throw "Roll away in contra corners is unsupported.";
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO This isn't quite right if there's no 1/2 sash?
|
|
||||||
let swapPos = withPos;
|
|
||||||
if (swapPos.kind === PositionKind.Circle && swapPos.longLines) {
|
|
||||||
swapPos = { ...swapPos, longLines: undefined };
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO animate hands?
|
|
||||||
if (isRoller) {
|
|
||||||
if (this.move.parameters["½sash"]) {
|
|
||||||
// swap positions by sliding
|
|
||||||
return this.combine([{
|
|
||||||
beats: this.move.beats,
|
|
||||||
endPosition: swapPos,
|
|
||||||
movementPattern: { kind: SemanticAnimationKind.Linear },
|
|
||||||
}], startPos);
|
|
||||||
} else {
|
|
||||||
// just stand still
|
|
||||||
return this.combine([{
|
|
||||||
beats: this.move.beats,
|
|
||||||
endPosition: { ...startPos, longLines: undefined },
|
|
||||||
movementPattern: { kind: SemanticAnimationKind.Linear },
|
|
||||||
}], startPos);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// being rolled away, so do a spin
|
|
||||||
return this.combine([{
|
|
||||||
beats: this.move.beats,
|
|
||||||
// TODO Is this the right end position logic?
|
|
||||||
endPosition: this.move.parameters["½sash"] ? swapPos : { ...startPos, which: startPos.which.swapDiagonal() },
|
|
||||||
movementPattern: { kind: SemanticAnimationKind.RollAway },
|
|
||||||
}], startPos);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class RollAway extends MoveInterpreter<typeof moveName> {
|
|
||||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
|
||||||
return new RollAwaySingleVariant(this, startingPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moveInterpreters.set(moveName, RollAway);
|
|
|
@ -1,76 +0,0 @@
|
||||||
import { PositionKind, SemanticPosition, Facing, BalanceWeight, handsInLine } from "../interpreterCommon.js";
|
|
||||||
import { Move } from "../libfigureMapper.js";
|
|
||||||
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
|
||||||
import { Hand } from "../rendererConstants.js";
|
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, PartialLowLevelMove, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
|
||||||
|
|
||||||
const moveName: Move["move"] = "Rory O'More";
|
|
||||||
class RoryOMoreSingleVariant extends SingleVariantMoveInterpreter<RoryOMore, typeof moveName> {
|
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
|
||||||
if (this.move.parameters.who !== "everyone") {
|
|
||||||
throw new Error(this.move.move + " that doesn't include everyone is unsupported.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO Could be in long or short lines.
|
|
||||||
const roryDir = this.move.parameters.slide ? Hand.Left : Hand.Right;
|
|
||||||
const balBeats = this.move.parameters.bal ? this.move.beats / 2 : 0;
|
|
||||||
const balPartBeats = balBeats / 2;
|
|
||||||
const roryBeats = this.move.beats - balBeats;
|
|
||||||
return this.handleMove(({ startPos }) => {
|
|
||||||
const isShortLines: boolean = startPos.kind === PositionKind.ShortLines;
|
|
||||||
|
|
||||||
const startingPos: SemanticPosition = {
|
|
||||||
...startPos,
|
|
||||||
hands: handsInLine({ wavy: true, which: startPos.which, facing: startPos.facing })
|
|
||||||
};
|
|
||||||
|
|
||||||
let endPos: SemanticPosition;
|
|
||||||
if (startPos.kind === PositionKind.ShortLines) {
|
|
||||||
if (startPos.facing !== Facing.Up && startPos.facing !== Facing.Down) {
|
|
||||||
throw new Error("To be in wavy lines, must be facing up or down, not " + startPos.facing);
|
|
||||||
}
|
|
||||||
const endWhich = startPos.which.shift(roryDir, startPos.facing);
|
|
||||||
endPos = {
|
|
||||||
...startPos,
|
|
||||||
which: endWhich,
|
|
||||||
hands: handsInLine({ wavy: true, which: endWhich, facing: startPos.facing })
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
throw new Error(this.move.move + " is currently only supported in short lines.");
|
|
||||||
}
|
|
||||||
|
|
||||||
const maybeBalance: PartialLowLevelMove[] = (this.move.parameters.bal ? [
|
|
||||||
{
|
|
||||||
beats: balPartBeats,
|
|
||||||
endPosition: { ...startingPos, balance: roryDir === Hand.Left ? BalanceWeight.Left : BalanceWeight.Right },
|
|
||||||
movementPattern: { kind: SemanticAnimationKind.Linear },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
beats: balPartBeats,
|
|
||||||
endPosition: { ...startingPos, balance: roryDir === Hand.Left ? BalanceWeight.Right : BalanceWeight.Left },
|
|
||||||
movementPattern: { kind: SemanticAnimationKind.Linear },
|
|
||||||
},
|
|
||||||
] : []);
|
|
||||||
|
|
||||||
return this.combine([...maybeBalance,
|
|
||||||
{
|
|
||||||
beats: roryBeats,
|
|
||||||
endPosition: endPos,
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.Linear,
|
|
||||||
minRotation: roryDir === Hand.Right ? +360 : -360,
|
|
||||||
handsDuring: "None",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
], startingPos);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class RoryOMore extends MoveInterpreter<typeof moveName> {
|
|
||||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
|
||||||
return new RoryOMoreSingleVariant(this, startingPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moveInterpreters.set(moveName, RoryOMore);
|
|
|
@ -1,56 +0,0 @@
|
||||||
import { SemanticPosition, PositionKind, handToDancerToSideInCircleFacingAcross, LongLines } from "../interpreterCommon.js";
|
|
||||||
import { Move } from "../libfigureMapper.js";
|
|
||||||
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, PartialLowLevelMove, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
|
||||||
|
|
||||||
const moveName: Move["move"] = "slice";
|
|
||||||
|
|
||||||
class SliceSingleVariant extends SingleVariantMoveInterpreter<Slice, typeof moveName> {
|
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
|
||||||
if (this.move.parameters["slice increment"] === "dancer") {
|
|
||||||
// TODO Maybe this only actually gets used to move an entire couple by going diagonal back?
|
|
||||||
throw new Error("Slicing by a single dancer is unsupported.");
|
|
||||||
}
|
|
||||||
|
|
||||||
const sliceLeft = this.move.parameters.slide;
|
|
||||||
const sliceReturns = this.move.parameters["slice return"] !== "none";
|
|
||||||
|
|
||||||
const sliceForwardBeats = sliceReturns ? this.move.beats / 2 : this.move.beats;
|
|
||||||
const sliceBackwardBeats = this.move.beats - sliceForwardBeats;
|
|
||||||
|
|
||||||
return this.handleCircleMove(({ startPos }) => {
|
|
||||||
const startingPos: SemanticPosition & { setOffset: number } = {
|
|
||||||
kind: PositionKind.Circle,
|
|
||||||
which: startPos.which,
|
|
||||||
facing: startPos.which.facingAcross(),
|
|
||||||
hands: handToDancerToSideInCircleFacingAcross(startPos.which),
|
|
||||||
setOffset: startPos.setOffset ?? 0,
|
|
||||||
lineOffset: startPos.lineOffset,
|
|
||||||
};
|
|
||||||
const sliceAmount = startingPos.which.isLeft() === sliceLeft ? +0.5 : -0.5;
|
|
||||||
const forwardOffset = startingPos.setOffset + sliceAmount;
|
|
||||||
const endOffset = this.move.parameters["slice return"] === "diagonal" ? forwardOffset + sliceAmount : forwardOffset;
|
|
||||||
|
|
||||||
const sliceForward: PartialLowLevelMove = {
|
|
||||||
beats: sliceForwardBeats,
|
|
||||||
endPosition: { ...startingPos, setOffset: forwardOffset, longLines: LongLines.Forward },
|
|
||||||
movementPattern: { kind: SemanticAnimationKind.Linear },
|
|
||||||
};
|
|
||||||
const maybeSliceBackward: PartialLowLevelMove[] = sliceReturns ? [{
|
|
||||||
beats: sliceBackwardBeats,
|
|
||||||
endPosition: { ...startingPos, setOffset: endOffset },
|
|
||||||
movementPattern: { kind: SemanticAnimationKind.Linear },
|
|
||||||
}] : [];
|
|
||||||
|
|
||||||
return this.combine([sliceForward, ...maybeSliceBackward], startingPos);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Slice extends MoveInterpreter<typeof moveName> {
|
|
||||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
|
||||||
return new SliceSingleVariant(this, startingPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moveInterpreters.set(moveName, Slice);
|
|
|
@ -1,35 +0,0 @@
|
||||||
import { Move } from "../libfigureMapper.js";
|
|
||||||
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
|
||||||
|
|
||||||
const moveName : Move["move"] = "slide along set";
|
|
||||||
|
|
||||||
class SlideAlongSetSingleVariant extends SingleVariantMoveInterpreter<SlideAlongSet, typeof moveName> {
|
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
|
||||||
const slideLeft = this.move.parameters.slide;
|
|
||||||
|
|
||||||
return this.handleCircleMove(({ startPos }) => {
|
|
||||||
const startingPos = {
|
|
||||||
...startPos,
|
|
||||||
facing: startPos.which.facingAcross(),
|
|
||||||
};
|
|
||||||
|
|
||||||
return this.combine([{
|
|
||||||
beats: this.move.beats,
|
|
||||||
endPosition: {
|
|
||||||
...startingPos,
|
|
||||||
setOffset: (startingPos.setOffset ?? 0) + (startingPos.which.isLeft() === slideLeft ? +0.5 : -0.5),
|
|
||||||
},
|
|
||||||
movementPattern: { kind: SemanticAnimationKind.Linear },
|
|
||||||
}], startingPos);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SlideAlongSet extends MoveInterpreter<typeof moveName> {
|
|
||||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
|
||||||
return new SlideAlongSetSingleVariant(this, startingPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moveInterpreters.set(moveName, SlideAlongSet);
|
|
|
@ -1,49 +0,0 @@
|
||||||
import { StarGrip, Facing } from "../interpreterCommon.js";
|
|
||||||
import { Move } from "../libfigureMapper.js";
|
|
||||||
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
|
||||||
import { Hand } from "../rendererConstants.js";
|
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
|
||||||
|
|
||||||
const moveName: Move["move"] = "star";
|
|
||||||
|
|
||||||
class StarSingleVariant extends SingleVariantMoveInterpreter<Star, typeof moveName> {
|
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
|
||||||
return this.handleCircleMove(({ startPos }) => {
|
|
||||||
const hand = this.move.parameters.hand ? Hand.Right : Hand.Left;
|
|
||||||
const grip = this.move.parameters.grip === "hands across"
|
|
||||||
? StarGrip.HandsAcross
|
|
||||||
: this.move.parameters.grip === "wrist grip"
|
|
||||||
? StarGrip.WristGrip
|
|
||||||
: undefined;
|
|
||||||
const facing = hand === Hand.Left ? Facing.LeftInCircle : Facing.RightInCircle;
|
|
||||||
|
|
||||||
const places = this.move.parameters.places / 90 * (hand === Hand.Right ? 1 : -1);
|
|
||||||
|
|
||||||
return this.combine([
|
|
||||||
{
|
|
||||||
beats: this.move.beats,
|
|
||||||
endPosition: {
|
|
||||||
...startPos,
|
|
||||||
facing,
|
|
||||||
hands: undefined,
|
|
||||||
which: startPos.which.circleLeft(places),
|
|
||||||
},
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.Star,
|
|
||||||
hand,
|
|
||||||
grip,
|
|
||||||
places,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
], { ...startPos, facing });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Star extends MoveInterpreter<typeof moveName> {
|
|
||||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
|
||||||
return new StarSingleVariant(this, startingPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moveInterpreters.set(moveName, Star);
|
|
|
@ -1,51 +0,0 @@
|
||||||
import { DancerDistance, handToDancerToSideInCircleFacingAcross } from "../interpreterCommon.js";
|
|
||||||
import { Move } from "../libfigureMapper.js";
|
|
||||||
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
|
||||||
import { Hand } from "../rendererConstants.js";
|
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
|
||||||
|
|
||||||
const moveName: Move["move"] = "star promenade";
|
|
||||||
|
|
||||||
class StarPromenadeSingleVariant extends SingleVariantMoveInterpreter<StarPromenade, typeof moveName> {
|
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
|
||||||
const starPromenadeHand = this.move.parameters.hand ? Hand.Right : Hand.Left;
|
|
||||||
const starPromenadeSwap = (this.move.parameters.circling % 360) === 180;
|
|
||||||
if (!starPromenadeSwap && (this.move.parameters.circling % 360 !== 0)) {
|
|
||||||
throw new Error(this.move.move + " circling by not a multiple of 180 degrees is unsupported.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO start promenade hands/show dancers close
|
|
||||||
return this.handleCircleMove(({ id, startPos }) => {
|
|
||||||
const inCenter = this.findPairOpposite(this.move.parameters.who, id) !== null;
|
|
||||||
// TODO Actually, does star promenade end facing out and butterfly whirl swaps?
|
|
||||||
const endWhich = starPromenadeSwap ? startPos.which.swapDiagonal() : startPos.which;
|
|
||||||
const endFacing = endWhich.facingAcross();
|
|
||||||
return this.combine([{
|
|
||||||
beats: this.move.beats,
|
|
||||||
endPosition: {
|
|
||||||
...startPos,
|
|
||||||
which: endWhich,
|
|
||||||
facing: endFacing,
|
|
||||||
dancerDistance: DancerDistance.Compact,
|
|
||||||
// TODO Perhaps different hands indication for "scooped"?
|
|
||||||
hands: handToDancerToSideInCircleFacingAcross(endWhich),
|
|
||||||
},
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.RotateAround,
|
|
||||||
around: "Center",
|
|
||||||
byHand: inCenter ? starPromenadeHand : undefined,
|
|
||||||
close: inCenter,
|
|
||||||
minAmount: this.move.parameters.hand ? this.move.parameters.circling : -this.move.parameters.circling,
|
|
||||||
}
|
|
||||||
}], startPos);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class StarPromenade extends MoveInterpreter<typeof moveName> {
|
|
||||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
|
||||||
return new StarPromenadeSingleVariant(this, startingPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moveInterpreters.set(moveName, StarPromenade);
|
|
|
@ -1,187 +0,0 @@
|
||||||
import { CoupleRole, DanceRole } from "../danceCommon.js";
|
|
||||||
import { LongLines, CircleSide, SemanticPosition, CirclePosition, Facing, PositionKind, HandConnection, HandTo, ShortLinesPosition, handsInLine, BalanceWeight, DancerDistance } from "../interpreterCommon.js";
|
|
||||||
import { Move } from "../libfigureMapper.js";
|
|
||||||
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
|
||||||
import { Hand } from "../rendererConstants.js";
|
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, PartialLowLevelMove, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
|
||||||
|
|
||||||
const moveName: Move["move"] = "swing";
|
|
||||||
|
|
||||||
class SwingSingleVariant extends SingleVariantMoveInterpreter<Swing, typeof moveName> {
|
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
|
||||||
// TODO Use variants instead of nextMove
|
|
||||||
const nextMove = this.moveInterpreter.nextMove;
|
|
||||||
|
|
||||||
return this.handlePairedMove(this.move.parameters.who, ({ id, startPos, around, withId, withPos }) => {
|
|
||||||
// TODO swing can start from non-circle positions.
|
|
||||||
// TODO swing end is only in notes / looking at next move.
|
|
||||||
|
|
||||||
// TODO better way to detect swing end?
|
|
||||||
// TODO more structured way to do this than enumerating next moves here?
|
|
||||||
// maybe instead of nextMove an optional endPosition for fixing up positions?
|
|
||||||
// ... but then every move would have to handle that...
|
|
||||||
const afterTake = startPos.longLines === LongLines.Near || withPos.longLines === LongLines.Near;
|
|
||||||
const toShortLines = nextMove.move === "down the hall" || nextMove.move === "up the hall";
|
|
||||||
const endFacingAcross = (around === CircleSide.Left || around === CircleSide.Right) && !toShortLines;
|
|
||||||
|
|
||||||
const startWhich = startPos.which;
|
|
||||||
const startPosition: SemanticPosition = {
|
|
||||||
...startPos,
|
|
||||||
facing: around === CircleSide.Left || CircleSide.Right
|
|
||||||
? (startWhich instanceof CirclePosition
|
|
||||||
? afterTake
|
|
||||||
? startPos.longLines === LongLines.Near ? startWhich.facingOut() : startWhich.facingAcross()
|
|
||||||
: (startWhich.topBottomSide() === CircleSide.Bottom ? Facing.Up : Facing.Down)
|
|
||||||
: startWhich.facingSide())
|
|
||||||
: (startWhich.isLeft() ? Facing.Right : Facing.Left),
|
|
||||||
};
|
|
||||||
|
|
||||||
const swingRole = id.danceRole != withId.setIdentity.danceRole
|
|
||||||
? id.danceRole
|
|
||||||
// Make some arbitrary choice for same-role swings
|
|
||||||
: id.coupleRole !== withId.setIdentity.coupleRole
|
|
||||||
? (id.coupleRole === CoupleRole.Ones ? DanceRole.Lark : DanceRole.Robin)
|
|
||||||
: withId.relativeSet !== 0
|
|
||||||
? (withId.relativeSet > 0 ? DanceRole.Lark : DanceRole.Robin)
|
|
||||||
: withId.relativeLine !== 0
|
|
||||||
? (withId.relativeLine > 0 ? DanceRole.Lark : DanceRole.Robin)
|
|
||||||
: /* should be unreachable as this means withId is equal to id */ DanceRole.Lark;
|
|
||||||
|
|
||||||
// TODO This assumes swing around right/left, not center or top/bottom.
|
|
||||||
let endPosition: SemanticPosition;
|
|
||||||
if (endFacingAcross) {
|
|
||||||
endPosition = {
|
|
||||||
...startPos,
|
|
||||||
kind: PositionKind.Circle,
|
|
||||||
which: startWhich instanceof CirclePosition
|
|
||||||
? (startWhich.isOnLeftLookingAcross() === (swingRole === DanceRole.Lark)
|
|
||||||
? startWhich
|
|
||||||
: startWhich.swapUpAndDown())
|
|
||||||
: (startWhich.isLeft()
|
|
||||||
? (swingRole === DanceRole.Lark ? CirclePosition.BottomLeft : CirclePosition.TopLeft)
|
|
||||||
: (swingRole === DanceRole.Lark ? CirclePosition.TopRight : CirclePosition.BottomRight)),
|
|
||||||
facing: startWhich.leftRightSide() === CircleSide.Left ? Facing.Right : Facing.Left,
|
|
||||||
balance: undefined,
|
|
||||||
dancerDistance: undefined,
|
|
||||||
longLines: undefined,
|
|
||||||
hands: new Map<Hand, HandConnection>([swingRole === DanceRole.Lark
|
|
||||||
? [Hand.Right, { to: HandTo.DancerRight, hand: Hand.Left }]
|
|
||||||
: [Hand.Left, { to: HandTo.DancerLeft, hand: Hand.Right }]]),
|
|
||||||
};
|
|
||||||
} else if (toShortLines) {
|
|
||||||
const endFacing = nextMove.move === "down the hall" !== (nextMove.parameters.facing === "backward")
|
|
||||||
? Facing.Down
|
|
||||||
: Facing.Up;
|
|
||||||
const endWhich = startWhich.isLeft()
|
|
||||||
? ((endFacing === Facing.Down) === (swingRole === DanceRole.Lark) ? ShortLinesPosition.FarLeft : ShortLinesPosition.MiddleLeft)
|
|
||||||
: ((endFacing === Facing.Down) === (swingRole === DanceRole.Lark) ? ShortLinesPosition.MiddleRight : ShortLinesPosition.FarRight)
|
|
||||||
endPosition = {
|
|
||||||
...startPos,
|
|
||||||
kind: PositionKind.ShortLines,
|
|
||||||
which: endWhich,
|
|
||||||
facing: endFacing,
|
|
||||||
balance: undefined,
|
|
||||||
dancerDistance: undefined,
|
|
||||||
longLines: undefined,
|
|
||||||
hands: handsInLine({ wavy: false, which: endWhich, facing: endFacing }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// TODO Need to figure out the logic of knowing if this should be facing up or down.
|
|
||||||
// Probably based on knowing Ones vs. Twos? Also then the not-participating-dancers need their
|
|
||||||
// "standing still" to update that they are in a new set...
|
|
||||||
//throw new Error("Swing to new circle currently unsupported.");
|
|
||||||
endPosition = {
|
|
||||||
// end not facing across or in short lines, so transitioning to new circle like in many contra corners dances.
|
|
||||||
...startPos,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const swingBeats = this.move.parameters.prefix === "none" ? this.move.beats
|
|
||||||
: this.move.parameters.prefix === "balance"
|
|
||||||
? this.move.beats > 8 ? 8 : this.move.beats - 4
|
|
||||||
: this.move.parameters.prefix === "meltdown"
|
|
||||||
? this.move.beats - 4
|
|
||||||
: (() => { throw "Unknown swing prefix: " + this.move.parameters.prefix })();
|
|
||||||
|
|
||||||
const swing: PartialLowLevelMove = {
|
|
||||||
beats: swingBeats,
|
|
||||||
endPosition: endPosition,
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.Swing,
|
|
||||||
minAmount: 360,
|
|
||||||
around,
|
|
||||||
endFacing: startWhich.leftRightSide() === CircleSide.Left ? Facing.Right : Facing.Left,
|
|
||||||
swingRole,
|
|
||||||
afterTake,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (this.move.parameters.prefix) {
|
|
||||||
case "none":
|
|
||||||
return this.combine([swing,], startPosition);
|
|
||||||
case "balance":
|
|
||||||
// TODO Right length for balance?
|
|
||||||
const balancePartBeats = this.move.beats > 8 ? (this.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 }],
|
|
||||||
]),
|
|
||||||
};
|
|
||||||
const balForwardPos = startWithBalHands.kind === PositionKind.Circle
|
|
||||||
? {
|
|
||||||
...startWithBalHands,
|
|
||||||
balance: BalanceWeight.Forward,
|
|
||||||
dancerDistance: DancerDistance.Compact,
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
...startWithBalHands,
|
|
||||||
balance: BalanceWeight.Forward,
|
|
||||||
};
|
|
||||||
return this.combine([
|
|
||||||
{
|
|
||||||
beats: balancePartBeats,
|
|
||||||
startPosition: startWithBalHands,
|
|
||||||
endPosition: balForwardPos,
|
|
||||||
movementPattern: { kind: SemanticAnimationKind.Linear, },
|
|
||||||
},
|
|
||||||
prevEnd => ({
|
|
||||||
beats: balancePartBeats,
|
|
||||||
endPosition: {
|
|
||||||
...prevEnd,
|
|
||||||
balance: BalanceWeight.Backward,
|
|
||||||
},
|
|
||||||
movementPattern: { kind: SemanticAnimationKind.Linear, },
|
|
||||||
}),
|
|
||||||
swing,
|
|
||||||
], startPosition);
|
|
||||||
case "meltdown":
|
|
||||||
const meltdownBeats = 4; // TODO right number here?
|
|
||||||
return this.combine([
|
|
||||||
prevEnd => ({
|
|
||||||
beats: meltdownBeats,
|
|
||||||
endPosition: { ...prevEnd, dancerDistance: DancerDistance.Compact },
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.RotateAround,
|
|
||||||
minAmount: 360,
|
|
||||||
around,
|
|
||||||
byHand: undefined,
|
|
||||||
close: true,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
swing,
|
|
||||||
], startPosition);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Swing extends MoveInterpreter<typeof moveName> {
|
|
||||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
|
||||||
return new SwingSingleVariant(this, startingPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moveInterpreters.set(moveName, Swing);
|
|
|
@ -1,37 +0,0 @@
|
||||||
import { SemanticPosition } from "../interpreterCommon.js";
|
|
||||||
import { Move } from "../libfigureMapper.js";
|
|
||||||
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
|
||||||
|
|
||||||
const moveName: Move["move"] = "turn alone";
|
|
||||||
|
|
||||||
class TurnAloneSingleVariant extends SingleVariantMoveInterpreter<TurnAlone, typeof moveName> {
|
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
|
||||||
if (this.move.parameters.who !== "everyone" || this.move.beats !== 0) {
|
|
||||||
throw new Error("turn alone unsupported except for changing to new circle.");
|
|
||||||
}
|
|
||||||
return this.handleCircleMove(({ startPos }) => {
|
|
||||||
const which = startPos.which.swapUpAndDown();
|
|
||||||
const startAndEndPos: SemanticPosition = {
|
|
||||||
...startPos,
|
|
||||||
which,
|
|
||||||
facing: which.facingUpOrDown(),
|
|
||||||
setOffset: (startPos.setOffset ?? 0) + (startPos.which.isTop() ? +0.5 : -0.5),
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.combine([{
|
|
||||||
beats: this.move.beats,
|
|
||||||
endPosition: startAndEndPos,
|
|
||||||
movementPattern: { kind: SemanticAnimationKind.StandStill },
|
|
||||||
}], startAndEndPos);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TurnAlone extends MoveInterpreter<typeof moveName> {
|
|
||||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
|
||||||
return new TurnAloneSingleVariant(this, startingPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moveInterpreters.set(moveName, TurnAlone);
|
|
|
@ -1,95 +0,0 @@
|
||||||
import { Facing, ShortLinesPosition, PositionKind, CirclePosition, SemanticPosition, HandConnection, HandTo, handsInCircle } from "../interpreterCommon.js";
|
|
||||||
import { Move } from "../libfigureMapper.js";
|
|
||||||
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
|
||||||
import { Hand } from "../rendererConstants.js";
|
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
|
||||||
|
|
||||||
const moveName: Move["move"] = "up the hall";
|
|
||||||
|
|
||||||
// TODO Share implementation between up/down the hall?
|
|
||||||
class UpTheHallSingleVariant extends SingleVariantMoveInterpreter<UpTheHall, typeof moveName> {
|
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
|
||||||
if (this.move.parameters.who !== "everyone") {
|
|
||||||
throw new Error("Don't know what it means for not everyone to go up the hall.");
|
|
||||||
}
|
|
||||||
if (this.move.parameters.moving !== "all") {
|
|
||||||
throw new Error("Not sure what it means for not all to be moving in up the hall.");
|
|
||||||
}
|
|
||||||
if (this.move.parameters.ender !== "circle") {
|
|
||||||
throw new Error("Unsupported up the hall ender: " + this.move.parameters.ender);
|
|
||||||
}
|
|
||||||
if (this.move.parameters.facing !== "forward") {
|
|
||||||
throw new Error("Unsupported up the hall facing: " + this.move.parameters.facing);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.handleMove(({ startPos }) => {
|
|
||||||
const startFacing = this.move.parameters.facing === "backward" ? Facing.Down : Facing.Up;
|
|
||||||
const startWhich: ShortLinesPosition = startPos.kind === PositionKind.ShortLines
|
|
||||||
? startPos.which
|
|
||||||
// TODO Is this always the right way to convert circle to short lines?
|
|
||||||
// (Does it even matter except for dance starting formations?)
|
|
||||||
: new Map<CirclePosition, ShortLinesPosition>([
|
|
||||||
[CirclePosition.TopLeft, ShortLinesPosition.MiddleLeft],
|
|
||||||
[CirclePosition.BottomLeft, ShortLinesPosition.FarLeft],
|
|
||||||
[CirclePosition.BottomRight, ShortLinesPosition.FarRight],
|
|
||||||
[CirclePosition.TopRight, ShortLinesPosition.MiddleRight],
|
|
||||||
]).get(startPos.which)!;
|
|
||||||
const startingPos: SemanticPosition & { kind: PositionKind.ShortLines, setOffset: number } = {
|
|
||||||
kind: PositionKind.ShortLines,
|
|
||||||
facing: startFacing,
|
|
||||||
which: startWhich,
|
|
||||||
hands: startWhich.isMiddle() ? new Map<Hand, HandConnection>([
|
|
||||||
[Hand.Left, { hand: Hand.Left, to: HandTo.DancerLeft }],
|
|
||||||
[Hand.Right, { hand: Hand.Right, to: HandTo.DancerRight }],
|
|
||||||
]) : new Map<Hand, HandConnection>([
|
|
||||||
startWhich.isLeft() === (this.move.parameters.facing === "backward")
|
|
||||||
? [Hand.Right, { hand: Hand.Right, to: HandTo.DancerRight }]
|
|
||||||
: [Hand.Left, { hand: Hand.Left, to: HandTo.DancerLeft }]
|
|
||||||
]),
|
|
||||||
setOffset: startPos.setOffset ?? 0,
|
|
||||||
lineOffset: startPos.lineOffset,
|
|
||||||
};
|
|
||||||
const endWhich = new Map<ShortLinesPosition, CirclePosition>([
|
|
||||||
[ShortLinesPosition.FarLeft, CirclePosition.TopLeft],
|
|
||||||
[ShortLinesPosition.MiddleLeft, CirclePosition.BottomLeft],
|
|
||||||
[ShortLinesPosition.MiddleRight, CirclePosition.BottomRight],
|
|
||||||
[ShortLinesPosition.FarRight, CirclePosition.TopRight],
|
|
||||||
]).get(startWhich)!;
|
|
||||||
const endingPos: SemanticPosition & { kind: PositionKind.Circle } = {
|
|
||||||
kind: PositionKind.Circle,
|
|
||||||
which: endWhich,
|
|
||||||
facing: Facing.CenterOfCircle,
|
|
||||||
setOffset: startingPos.setOffset - 1,
|
|
||||||
lineOffset: startingPos.lineOffset,
|
|
||||||
hands: handsInCircle,
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.combine([
|
|
||||||
{
|
|
||||||
beats: 4,
|
|
||||||
endPosition: {
|
|
||||||
...startingPos,
|
|
||||||
setOffset: startingPos.setOffset - 1
|
|
||||||
},
|
|
||||||
movementPattern: { kind: SemanticAnimationKind.Linear },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
beats: this.move.beats - 4,
|
|
||||||
endPosition: endingPos,
|
|
||||||
// TODO Is bend the line just linear?
|
|
||||||
movementPattern: {
|
|
||||||
kind: SemanticAnimationKind.Linear,
|
|
||||||
minRotation: startingPos.which.isLeft() ? -1 : +1
|
|
||||||
},
|
|
||||||
}], startingPos);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class UpTheHall extends MoveInterpreter<typeof moveName> {
|
|
||||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
|
||||||
return new UpTheHallSingleVariant(this, startingPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moveInterpreters.set(moveName, UpTheHall);
|
|
|
@ -1,59 +1,99 @@
|
||||||
import { Animation } from "./animation.js";
|
import { Animation } from "./animation.js";
|
||||||
import { CoupleRole, DanceRole, DancerIdentity, ExtendedDancerIdentity } from "./danceCommon.js";
|
import { CoupleRole, DanceRole, DancerIdentity, ExtendedDancerIdentity } from "./danceCommon.js";
|
||||||
|
import * as exampleAnimations from "./exampleAnimations.js";
|
||||||
import { DancerSetPosition, DancersSetPositions, dancerHeight, dancerHeightOffset, leftShoulder, lineDistance, rightShoulder, setDistance, degreesToRadians } from "./rendererConstants.js";
|
import { DancerSetPosition, DancersSetPositions, dancerHeight, dancerHeightOffset, leftShoulder, lineDistance, rightShoulder, setDistance, degreesToRadians } from "./rendererConstants.js";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function baseColorForDancer(identity: ExtendedDancerIdentity): {hue: number, sat: number, lum: number} {
|
function hueForDancer(identity: DancerIdentity): number {
|
||||||
if (identity.setIdentity.coupleRole == CoupleRole.Ones) {
|
if (identity.coupleRole == CoupleRole.Ones) {
|
||||||
if (identity.relativeSet < 0)
|
if (identity.danceRole == DanceRole.Lark) {
|
||||||
return { hue: 340, sat: 67, lum: 56 };
|
return 0; //red
|
||||||
if (identity.relativeSet === 0)
|
|
||||||
return { hue: 27, sat: 99, lum: 59 };
|
|
||||||
if (identity.relativeSet > 0)
|
|
||||||
return { hue: 54, sat: 97, lum: 49 };
|
|
||||||
} else {
|
} else {
|
||||||
if (identity.relativeSet < 0)
|
return 39; //orange
|
||||||
return { hue: 183, sat: 88, lum: 23 };
|
}
|
||||||
if (identity.relativeSet === 0)
|
} else {
|
||||||
return { hue: 249, sat: 42, lum: 37 };
|
if (identity.danceRole == DanceRole.Lark) {
|
||||||
if (identity.relativeSet > 0)
|
return 240; //blue
|
||||||
return { hue: 13, sat: 33, lum: 29 };
|
} else {
|
||||||
|
return 180; //teal
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error("Unreachable: relativeSet must be one of <, ===, or > 0.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function colorForDancer(identity: ExtendedDancerIdentity) : string {
|
const colorForDancerCache = new Map<DancerIdentity, string[]>(
|
||||||
const baseColor = baseColorForDancer(identity);
|
DancerIdentity.all().map(id => [id,
|
||||||
|
[...Array(49).keys()]
|
||||||
|
.map(reverseColorCacheKey)
|
||||||
|
.map(({relativeSet, relativeLine}) => colorForDancer(id, relativeSet, relativeLine))]));
|
||||||
|
|
||||||
const hue = baseColor.hue;
|
const colorForDancerLabelCache = new Map<DancerIdentity, string[]>(
|
||||||
const sat = baseColor.sat - Math.abs(identity.relativeLine * 40);
|
DancerIdentity.all().map(id => [id,
|
||||||
const unclampedLum = baseColor.lum + (Math.abs(identity.relativeSet) <= 1 ? 0 : identity.relativeSet * 20);
|
[...Array(49).keys()]
|
||||||
|
.map(i => reverseColorCacheKey(i))
|
||||||
|
.map(({relativeSet, relativeLine}) => colorForDancerLabel(id, relativeSet, relativeLine))]));
|
||||||
|
|
||||||
|
function colorCacheKey(relativeSet: number, relativeLine: number) {
|
||||||
|
relativeSet = Math.max(-3, Math.min(3, relativeSet)) + 3;
|
||||||
|
relativeLine = Math.max(-3, Math.min(3, relativeLine)) + 3;
|
||||||
|
|
||||||
|
return relativeLine * 7 + relativeSet;
|
||||||
|
}
|
||||||
|
function reverseColorCacheKey(key: number): { relativeSet: number, relativeLine: number } {
|
||||||
|
return {
|
||||||
|
relativeSet: (key % 7) - 3,
|
||||||
|
relativeLine: Math.floor(key / 7) - 3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function colorForDancer(setIdentity: DancerIdentity, relativeSet: number, relativeLine: number) : string {
|
||||||
|
const hue = hueForDancer(setIdentity);
|
||||||
|
const sat = 100 - Math.abs(relativeLine * 40);
|
||||||
|
const unclampedLum = 50 + relativeSet * 20;
|
||||||
const lum = unclampedLum < 10 ? 10 : unclampedLum > 90 ? 90 : unclampedLum;
|
const lum = unclampedLum < 10 ? 10 : unclampedLum > 90 ? 90 : unclampedLum;
|
||||||
return `hsl(${hue}, ${sat}%, ${lum}%)`;
|
return `hsl(${hue}, ${sat}%, ${lum}%)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function colorForDancerLabel(identity: ExtendedDancerIdentity) : string {
|
function colorForDancerCached(setIdentity: DancerIdentity, relativeSet: number, relativeLine: number) : string {
|
||||||
const dancerColor = baseColorForDancer(identity);
|
return colorForDancerCache.get(setIdentity)![colorCacheKey(relativeSet, relativeLine)];
|
||||||
const hue = (dancerColor.hue + 180) % 360;
|
}
|
||||||
|
|
||||||
|
function colorForDancerById(identity: ExtendedDancerIdentity) : string {
|
||||||
|
return colorForDancer(identity.setIdentity, identity.relativeSet, identity.relativeLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
function colorForDancerLabel(setIdentity: DancerIdentity, relativeSet: number, relativeLine: number) : string {
|
||||||
|
const dancerHue = hueForDancer(setIdentity);
|
||||||
|
const hue = (dancerHue + 180) % 360;
|
||||||
const sat = 100;
|
const sat = 100;
|
||||||
const unclampedLum = ((dancerColor.hue >= 215 && dancerColor.hue <= 285) || (identity.relativeSet < 0)
|
const lum = dancerHue === 240 && relativeSet < 2 || relativeSet < 0
|
||||||
|| dancerColor.lum < 40) && identity.relativeSet < 2
|
|
||||||
? 100
|
? 100
|
||||||
: 20 - identity.relativeSet * 40;
|
: 20 - relativeSet * 40;
|
||||||
const lum = unclampedLum < 0 ? 0 : unclampedLum > 100 ? 100 : unclampedLum;
|
|
||||||
return `hsl(${hue}, ${sat}%, ${lum}%)`;
|
return `hsl(${hue}, ${sat}%, ${lum}%)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function colorForDancerLabelCached(setIdentity: DancerIdentity, relativeSet: number, relativeLine: number) : string {
|
||||||
|
return colorForDancerLabelCache.get(setIdentity)![colorCacheKey(relativeSet, relativeLine)];
|
||||||
|
}
|
||||||
|
|
||||||
|
function colorForDancerLabelById(identity: ExtendedDancerIdentity) : string {
|
||||||
|
return colorForDancerLabel(identity.setIdentity, identity.relativeSet, identity.relativeLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
const positiveNumberStrings: string[] = [...Array(10).keys()].map(i => i.toString());
|
||||||
|
const negativeNumberStrings: string[] = [...Array(10).keys()].map(i => (-i).toString());
|
||||||
|
function cachedToString(smallNumber: number) {
|
||||||
|
if (Math.abs(smallNumber) >= 10) return smallNumber.toString();
|
||||||
|
if (smallNumber < 0) return negativeNumberStrings[-smallNumber];
|
||||||
|
else return positiveNumberStrings[smallNumber];
|
||||||
|
}
|
||||||
|
|
||||||
export class Renderer {
|
export class Renderer {
|
||||||
canvas: HTMLCanvasElement;
|
canvas: HTMLCanvasElement;
|
||||||
ctx: CanvasRenderingContext2D;
|
ctx: CanvasRenderingContext2D;
|
||||||
animation?: Animation;
|
animation?: Animation;
|
||||||
extraSets?: number;
|
extraSets?: number;
|
||||||
extraLines?: number;
|
extraLines?: number;
|
||||||
trailIncrements: number = 6;
|
|
||||||
trailLengthInBeats: number = 1;
|
|
||||||
drawDebug: boolean = false;
|
drawDebug: boolean = false;
|
||||||
|
|
||||||
constructor(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) {
|
constructor(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) {
|
||||||
|
@ -61,41 +101,33 @@ export class Renderer {
|
||||||
this.ctx = ctx;
|
this.ctx = ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
drawDancerBody(identity: ExtendedDancerIdentity, drawText: boolean) {
|
drawDancerBody(setIdentity: DancerIdentity, relativeSet: number, relativeLine: number, drawText: boolean) {
|
||||||
this.ctx.beginPath();
|
this.ctx.beginPath();
|
||||||
this.ctx.moveTo(leftShoulder.x, leftShoulder.y);
|
this.ctx.moveTo(leftShoulder.x, leftShoulder.y);
|
||||||
if (identity.setIdentity.danceRole === DanceRole.Robin) {
|
|
||||||
// Draw triangle for robin.
|
|
||||||
this.ctx.lineTo(rightShoulder.x, rightShoulder.y);
|
this.ctx.lineTo(rightShoulder.x, rightShoulder.y);
|
||||||
this.ctx.lineTo(0, dancerHeight-dancerHeightOffset);
|
this.ctx.lineTo(0, dancerHeight-dancerHeightOffset);
|
||||||
} else {
|
|
||||||
// Draw dome for lark.
|
|
||||||
this.ctx.arcTo(0, dancerHeight*2-dancerHeightOffset, rightShoulder.x, rightShoulder.y, dancerHeight * 1.5);
|
|
||||||
this.ctx.lineTo(rightShoulder.x, rightShoulder.y);
|
|
||||||
}
|
|
||||||
this.ctx.fill();
|
this.ctx.fill();
|
||||||
|
|
||||||
// Draw dot at origin to identify "center" point of dancer.
|
// Draw dot at origin to identify "center" point of dancer.
|
||||||
const backupFillStyle = this.ctx.fillStyle;
|
const backupFillStyle = this.ctx.fillStyle;
|
||||||
this.ctx.fillStyle = 'black';
|
this.ctx.fillStyle = 'black';
|
||||||
|
|
||||||
const pointSize = 0.05;
|
const pointSize = 0.05;
|
||||||
this.ctx.fillRect(-pointSize/2, -pointSize/2, pointSize, pointSize);
|
this.ctx.fillRect(-pointSize/2, -pointSize/2, pointSize, pointSize);
|
||||||
|
|
||||||
if (drawText && identity) {
|
if (drawText) {
|
||||||
this.ctx.save();
|
this.ctx.save();
|
||||||
|
|
||||||
this.ctx.scale(-1, 1);
|
this.ctx.scale(-1, 1);
|
||||||
this.ctx.rotate(Math.PI);
|
this.ctx.rotate(Math.PI);
|
||||||
this.ctx.fillStyle = colorForDancerLabel(identity);
|
this.ctx.fillStyle = colorForDancerLabelCached(setIdentity, relativeSet, relativeLine);
|
||||||
|
|
||||||
this.ctx.font = '0.15px sans'
|
this.ctx.font = '0.15px sans'
|
||||||
this.ctx.fillText(identity.setIdentity.danceRole === DanceRole.Lark ? 'L' : 'R', -0.14, +0.04);
|
this.ctx.fillText(setIdentity.danceRole === DanceRole.Lark ? 'L' : 'R', -0.14, +0.04);
|
||||||
this.ctx.fillText(identity.setIdentity.coupleRole === CoupleRole.Ones ? '1' : '2', +0.04, +0.04);
|
this.ctx.fillText(setIdentity.coupleRole === CoupleRole.Ones ? '1' : '2', +0.04, +0.04);
|
||||||
|
|
||||||
this.ctx.font = '0.1px sans'
|
this.ctx.font = '0.1px sans'
|
||||||
this.ctx.fillText(identity.relativeLine.toString(), +0.14, +0.04);
|
this.ctx.fillText(cachedToString(relativeLine), +0.14, +0.04);
|
||||||
this.ctx.fillText(identity.relativeSet.toString(), -0.22, +0.04);
|
this.ctx.fillText(cachedToString(relativeSet), -0.22, +0.04);
|
||||||
|
|
||||||
this.ctx.restore();
|
this.ctx.restore();
|
||||||
}
|
}
|
||||||
|
@ -108,11 +140,8 @@ export class Renderer {
|
||||||
|
|
||||||
this.ctx.translate(identity.relativeLine * lineDistance,
|
this.ctx.translate(identity.relativeLine * lineDistance,
|
||||||
identity.relativeSet * setDistance);
|
identity.relativeSet * setDistance);
|
||||||
const realIdentity = {
|
const relativeSet = identity.relativeSet + (offsetSets * (identity.setIdentity.coupleRole === CoupleRole.Ones ? 1 : -1));
|
||||||
...identity,
|
this.ctx.fillStyle = this.ctx.strokeStyle = colorForDancerCached(identity.setIdentity, relativeSet, identity.relativeLine);
|
||||||
relativeSet: identity.relativeSet + (offsetSets * (identity.setIdentity.coupleRole === CoupleRole.Ones ? 1 : -1))
|
|
||||||
};
|
|
||||||
this.ctx.fillStyle = this.ctx.strokeStyle = colorForDancer(realIdentity);
|
|
||||||
|
|
||||||
if (drawDebug) {
|
if (drawDebug) {
|
||||||
if (this.drawDebug && position.drawDebug) {
|
if (this.drawDebug && position.drawDebug) {
|
||||||
|
@ -125,7 +154,7 @@ export class Renderer {
|
||||||
this.ctx.translate(position.position.x, position.position.y);
|
this.ctx.translate(position.position.x, position.position.y);
|
||||||
this.ctx.rotate(-degreesToRadians(position.rotation));
|
this.ctx.rotate(-degreesToRadians(position.rotation));
|
||||||
|
|
||||||
this.drawDancerBody(realIdentity, drawText);
|
this.drawDancerBody(identity.setIdentity, relativeSet, identity.relativeLine, drawText);
|
||||||
|
|
||||||
// Draw arms.
|
// Draw arms.
|
||||||
this.ctx.lineWidth = 0.03;
|
this.ctx.lineWidth = 0.03;
|
||||||
|
@ -172,15 +201,16 @@ export class Renderer {
|
||||||
if (!this.animation) throw new Error("Attempted to render before setting animation.");
|
if (!this.animation) throw new Error("Attempted to render before setting animation.");
|
||||||
|
|
||||||
this.clear();
|
this.clear();
|
||||||
const increments = this.trailLengthInBeats > 0 && this.trailIncrements > 0 ? this.trailIncrements : 0;
|
const increments = 7;
|
||||||
const incrementLength = this.trailLengthInBeats / (increments + 1);
|
const trailLengthInBeats = 1;
|
||||||
|
const incrementLength = trailLengthInBeats / increments;
|
||||||
progression ??= 0;
|
progression ??= 0;
|
||||||
const offsetSets = this.animation.progression.y === 0
|
const offsetSets = this.animation.progression.y === 0
|
||||||
? 0
|
? 0
|
||||||
: -((progression - (progression % 2)) / 2) / ((this.animation.progression.y * 2) / setDistance);
|
: -((progression - (progression % 2)) / 2) / ((this.animation.progression.y * 2) / setDistance);
|
||||||
for (var i = increments; i >= 0; i--) {
|
for (var i = increments; i >= 0; i--) {
|
||||||
const beatToDraw = i == 0 ? beat : beat - i*incrementLength;
|
const beatToDraw = beat - i*incrementLength;
|
||||||
this.ctx.globalAlpha = i == 0 ? 1 : (1 - i / (increments + 1)) * 0.3;
|
this.ctx.globalAlpha = i == 0 ? 1 : (1 - i/increments)*0.3;
|
||||||
const positions = this.animation.positionsAtBeat(beatToDraw, progression % 2);
|
const positions = this.animation.positionsAtBeat(beatToDraw, progression % 2);
|
||||||
if (this.drawDebug) this.drawSets(positions, offsetSets, true, true);
|
if (this.drawDebug) this.drawSets(positions, offsetSets, true, true);
|
||||||
this.drawSets(positions, offsetSets, i === 0, false);
|
this.drawSets(positions, offsetSets, i === 0, false);
|
||||||
|
@ -216,9 +246,9 @@ export class Renderer {
|
||||||
for (var relativeLine = -extraLines; relativeLine <= extraLines; relativeLine++) {
|
for (var relativeLine = -extraLines; relativeLine <= extraLines; relativeLine++) {
|
||||||
for (var relativeSet = -extraSets; relativeSet <= extraSets; relativeSet++) {
|
for (var relativeSet = -extraSets; relativeSet <= extraSets; relativeSet++) {
|
||||||
this.ctx.save();
|
this.ctx.save();
|
||||||
const hue = 0;
|
const hue = (relativeLine + relativeSet) % 2 === 0 ? 60 : 170;
|
||||||
const sat = 0;
|
const sat = 100 - Math.abs(relativeLine * 40);
|
||||||
const lum = Math.min(98, 90 + Math.abs(Math.abs(relativeSet) + Math.abs(relativeLine)) * 5);
|
const lum = Math.min(98, 90 + Math.abs(relativeSet) * 5);
|
||||||
this.ctx.fillStyle = `hsl(${hue}, ${sat}%, ${lum}%)`;
|
this.ctx.fillStyle = `hsl(${hue}, ${sat}%, ${lum}%)`;
|
||||||
this.ctx.translate(relativeLine * lineDistance,
|
this.ctx.translate(relativeLine * lineDistance,
|
||||||
relativeSet * setDistance);
|
relativeSet * setDistance);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user