Compare commits

..

No commits in common. "53fee2bd0f1d6d57aa14cbd77c1e8521ff541763" and "646b4b2ceb5f994613e1f883dc1b4536ee7ff656" have entirely different histories.

4 changed files with 107 additions and 133 deletions

View File

@ -95,12 +95,7 @@ interface Closer {
transitionProgress: number, transitionProgress: number,
} }
export interface HandAnimation { export interface HandAnimation {
kind: "Linear" | "Center" | "None", kind: "Linear" | "Center"
transitionBeats?: number,
}
interface HandAnimationInternal {
kind: "Linear" | "Center" | "None",
transitionProgress: number,
} }
export class RotationAnimationSegment extends AnimationSegment { export class RotationAnimationSegment extends AnimationSegment {
@ -114,25 +109,16 @@ export class RotationAnimationSegment extends AnimationSegment {
private readonly facing: RotationAnimationFacing; private readonly facing: RotationAnimationFacing;
private readonly startFacing: Rotation; private readonly startFacing: Rotation;
private readonly closer?: Closer; private readonly closer?: Closer;
private readonly hands: Map<Hand, HandAnimationInternal>; private readonly hands: Map<Hand, HandAnimation>;
constructor(beats: number, startPosition: DancerSetPosition, endPosition: DancerSetPosition, constructor(beats: number, startPosition: DancerSetPosition, endPosition: DancerSetPosition,
rotation: number, around: RotationAround, facing: RotationAnimationFacing, rotation: number, around: RotationAround, facing: RotationAnimationFacing,
closer?: CloserDuringRotation, hands?: Map<Hand, HandAnimation>) { closer?: CloserDuringRotation, hands?: Map<Hand, HandAnimation>) {
super(beats, startPosition, endPosition); super(beats, startPosition, endPosition);
function convertHandAnimation(anim: HandAnimation | undefined): HandAnimationInternal { this.hands = hands ?? new Map<Hand, HandAnimation>();
if (!anim) { if (!this.hands.has(Hand.Left)) this.hands.set(Hand.Left, {kind: "Linear"});
return { kind: "Linear", transitionProgress: 0 }; if (!this.hands.has(Hand.Right)) this.hands.set(Hand.Right, {kind: "Linear"});
} else {
return {
...anim,
transitionProgress: (anim.transitionBeats ?? 0) / beats
}
}
}
this.hands = new Map<Hand, HandAnimationInternal>(
[Hand.Left, Hand.Right].map(h => [h, convertHandAnimation(hands?.get(h))]));
this.facing = facing; this.facing = facing;
this.xRadius = around.width / 2; this.xRadius = around.width / 2;
@ -205,17 +191,9 @@ export class RotationAnimationSegment extends AnimationSegment {
} }
protected interpolateHandOffset(progress: number, hand: Hand): Offset | undefined { protected interpolateHandOffset(progress: number, hand: Hand): Offset | undefined {
const handAnim : HandAnimationInternal = this.hands.get(hand)!; switch (this.hands.get(hand)!.kind) {
let middlePos : Offset;
switch (handAnim.kind) {
case "Linear": case "Linear":
if (handAnim.transitionProgress === 0) { return super.interpolateHandOffset(progress, hand);
return super.interpolateHandOffset(progress, hand);
} else if (progress < handAnim.transitionProgress) {
return super.interpolateHandOffset(progress / handAnim.transitionProgress, hand);
} else {
return super.interpolateHandOffset(1, hand);
}
case "Center": case "Center":
const position = this.interpolateOffset(progress); const position = this.interpolateOffset(progress);
const offset = { const offset = {
@ -224,32 +202,11 @@ export class RotationAnimationSegment extends AnimationSegment {
}; };
const rotation = degreesToRadians(this.interpolateRotation(progress)); const rotation = degreesToRadians(this.interpolateRotation(progress));
const centerPos = { return {
x: -Math.cos(rotation) * offset.x + Math.sin(rotation) * offset.y, x: -Math.cos(rotation)*offset.x + Math.sin(rotation)*offset.y,
y: -Math.sin(rotation) * offset.x - Math.cos(rotation) * offset.y, y: -Math.sin(rotation)*offset.x - Math.cos(rotation)*offset.y,
} }
middlePos = centerPos;
break;
case "None":
middlePos = hand === Hand.Left ? leftShoulder : rightShoulder;
break;
} }
if (handAnim.transitionProgress) {
if (progress < handAnim.transitionProgress) {
const startHand = hand === Hand.Left
? this.startPosition.leftArmEnd ?? leftShoulder
: this.startPosition.rightArmEnd ?? rightShoulder;
return interpolateLinearOffset(progress / handAnim.transitionProgress, startHand, middlePos);
} else if (1 - progress < handAnim.transitionProgress) {
const endHand = hand === Hand.Left
? this.endPosition.leftArmEnd ?? leftShoulder
: this.endPosition.rightArmEnd ?? rightShoulder;
return interpolateLinearOffset((1 - progress) / handAnim.transitionProgress, endHand, middlePos);
}
}
return middlePos;
} }
} }

View File

@ -310,16 +310,25 @@ function moveAsLowLevelMoves(move: Move, startingPos: Map<DancerIdentity, Semant
case "none": case "none":
res.set(id, combine([ res.set(id, combine([
{ {
beats: move.beats, beats: 1,
endPosition: endPosition, endPosition: { ...startPosition, dancerDistance: swingRole },
movementPattern: { kind: SemanticAnimationKind.Linear, },
},
{
beats: move.beats - 2,
endPosition: { ...endPosition, dancerDistance: swingRole },
movementPattern: { movementPattern: {
kind: SemanticAnimationKind.Swing, kind: SemanticAnimationKind.Swing,
minAmount: 360, minAmount: 360,
around: startPos.which.leftRightSide(), around: startPos.which.leftRightSide(),
endFacing: startPos.which.leftRightSide() === CircleSide.Left ? Facing.Right : Facing.Left, endFacing: startPos.which.leftRightSide() === CircleSide.Left ? Facing.Right : Facing.Left,
swingRole: id.danceRole,
}, },
}, },
{
beats: 1,
endPosition: endPosition,
movementPattern: { kind: SemanticAnimationKind.Linear, },
},
], startPos)); ], startPos));
break; break;
case "balance": case "balance":
@ -344,22 +353,37 @@ function moveAsLowLevelMoves(move: Move, startingPos: Map<DancerIdentity, Semant
movementPattern: { kind: SemanticAnimationKind.Linear, }, movementPattern: { kind: SemanticAnimationKind.Linear, },
}, },
prevEnd => ({ prevEnd => ({
beats: balancePartBeats, beats: balancePartBeats - 1,
endPosition: { endPosition: {
...prevEnd, ...prevEnd,
balance: BalanceWeight.Backward, balance: BalanceWeight.Backward,
}, },
movementPattern: { kind: SemanticAnimationKind.Linear, }, movementPattern: { kind: SemanticAnimationKind.Linear, },
}), }),
prevEnd => ({
beats: 1,
endPosition: {
...prevEnd,
balance: undefined,
dancerDistance: swingRole,
},
movementPattern: { kind: SemanticAnimationKind.Linear, },
}),
{ {
beats: move.beats - 2 * balancePartBeats, beats: move.beats - 2 * balancePartBeats - 1,
endPosition: endPosition, endPosition: { ...endPosition, dancerDistance: DancerDistance.Compact },
movementPattern: { movementPattern: {
kind: SemanticAnimationKind.Swing, kind: SemanticAnimationKind.Swing,
minAmount: 360, minAmount: 360,
around: startPos.which.leftRightSide(), around: startPos.which.leftRightSide(),
endFacing: startPos.which.leftRightSide() === CircleSide.Left ? Facing.Right : Facing.Left, endFacing: startPos.which.leftRightSide() === CircleSide.Left ? Facing.Right : Facing.Left,
swingRole: id.danceRole, },
},
{
beats: 1,
endPosition: endPosition,
movementPattern: {
kind: SemanticAnimationKind.Linear,
}, },
}, },
], startPos)); ], startPos));
@ -369,12 +393,14 @@ function moveAsLowLevelMoves(move: Move, startingPos: Map<DancerIdentity, Semant
res.set(id, combine([ res.set(id, combine([
prevEnd => ({ prevEnd => ({
beats: meltdownBeats, beats: meltdownBeats,
endPosition: prevEnd, endPosition: {
...prevEnd,
dancerDistance: swingRole,
},
movementPattern: { movementPattern: {
kind: SemanticAnimationKind.RotateAround, kind: SemanticAnimationKind.RotateAround,
minAmount: 360, minAmount: 360,
around: startPos.which.leftRightSide(), around: startPos.which.leftRightSide(),
byHand: undefined,
}, },
}), }),
{ {
@ -385,7 +411,6 @@ function moveAsLowLevelMoves(move: Move, startingPos: Map<DancerIdentity, Semant
minAmount: 360, minAmount: 360,
around: startPos.which.leftRightSide(), around: startPos.which.leftRightSide(),
endFacing: startPos.which.leftRightSide() === CircleSide.Left ? Facing.Right : Facing.Left, endFacing: startPos.which.leftRightSide() === CircleSide.Left ? Facing.Right : Facing.Left,
swingRole: id.danceRole,
}, },
}, },
], startPos)); ], startPos));
@ -427,15 +452,39 @@ function moveAsLowLevelMoves(move: Move, startingPos: Map<DancerIdentity, Semant
endPosition.lineOffset = (endPosition.lineOffset ?? 0) + withId.relativeLine; endPosition.lineOffset = (endPosition.lineOffset ?? 0) + withId.relativeLine;
} }
// TODO Take more than zero time to change hands?
res.set(id, combine([ res.set(id, combine([
{ prevEnd => ({
beats: move.beats, beats: 0,
endPosition, endPosition: {
...prevEnd,
hands: new Map<Hand, HandConnection>([[byHand, { hand: byHand, to: HandTo.DancerForward }]]),
},
movementPattern: {
kind: SemanticAnimationKind.RotateAround,
minAmount: byHand === Hand.Right ? move.parameters.circling : -move.parameters.circling,
around,
},
}),
{
beats: move.beats,
endPosition: {
...endPosition,
hands: new Map<Hand, HandConnection>([[byHand, { hand: byHand, to: HandTo.DancerForward }]]),
},
movementPattern: {
kind: SemanticAnimationKind.RotateAround,
minAmount: byHand === Hand.Right ? move.parameters.circling : -move.parameters.circling,
around,
},
},
{
beats: 0,
endPosition: endPosition,
movementPattern: { movementPattern: {
kind: SemanticAnimationKind.RotateAround, kind: SemanticAnimationKind.RotateAround,
minAmount: byHand === Hand.Right ? move.parameters.circling : -move.parameters.circling, minAmount: byHand === Hand.Right ? move.parameters.circling : -move.parameters.circling,
around, around,
byHand,
}, },
}, },
], startPos)); ], startPos));
@ -452,11 +501,21 @@ function moveAsLowLevelMoves(move: Move, startingPos: Map<DancerIdentity, Semant
} }
res.set(id, combine([ res.set(id, combine([
// TODO Is this right? Or should the circling and hands/positioning start at the same time?
{
beats: 1,
endPosition: {...startPos,
hands: handsInCircle,
facing: Facing.CenterOfCircle,
},
movementPattern: {
kind: SemanticAnimationKind.Linear,
}
},
prevEnd => ({ prevEnd => ({
beats: move.beats, beats: move.beats - 1,
endPosition: { endPosition: {
...prevEnd, ...prevEnd,
hands: handsInCircle,
which: startPos.which.circleLeft(places), which: startPos.which.circleLeft(places),
}, },
movementPattern: { movementPattern: {

View File

@ -20,7 +20,6 @@ export enum CircleSide {
Left = "Left", Left = "Left",
Right = "Right", Right = "Right",
} }
export type CircleSideOrCenter = CircleSide | "Center";
export class CirclePosition { export class CirclePosition {
public static readonly TopLeft = new CirclePosition(CirclePositionEnum.TopLeft); public static readonly TopLeft = new CirclePosition(CirclePositionEnum.TopLeft);
public static readonly BottomLeft = new CirclePosition(CirclePositionEnum.BottomLeft); public static readonly BottomLeft = new CirclePosition(CirclePositionEnum.BottomLeft);

View File

@ -1,9 +1,9 @@
import * as animation from "./animation.js"; import * as animation from "./animation.js";
import * as common from "./danceCommon.js"; import * as common from "./danceCommon.js";
import { DanceRole, DancerIdentity, Rotation } from "./danceCommon.js"; import { DancerIdentity, Rotation } from "./danceCommon.js";
import { BalanceWeight, CirclePosition, CircleSide, CircleSideOrCenter, DancerDistance, Facing, HandConnection, HandTo, PositionKind, SemanticPosition } from "./interpreterCommon.js"; import { BalanceWeight, CirclePosition, CircleSide, DancerDistance, Facing, HandConnection, HandTo, PositionKind, SemanticPosition } from "./interpreterCommon.js";
import { Move } from "./libfigureMapper.js"; import { Move } from "./libfigureMapper.js";
import { DancerSetPosition, Hand, Offset, dancerWidth, lineDistance, setDistance, setHeight, setSpacing, setWidth } from "./rendererConstants.js"; import { DancerSetPosition, Hand, Offset, dancerWidth, lineDistance, setDistance, setHeight, setWidth } from "./rendererConstants.js";
export enum SemanticAnimationKind { export enum SemanticAnimationKind {
@ -56,8 +56,6 @@ export type SemanticAnimation = {
minAmount: number, minAmount: number,
around: CircleSide | "Center", around: CircleSide | "Center",
byHand: Hand | undefined,
} | { } | {
kind: SemanticAnimationKind.Swing, kind: SemanticAnimationKind.Swing,
@ -66,9 +64,6 @@ export type SemanticAnimation = {
around: CircleSide | "Center", around: CircleSide | "Center",
endFacing: Facing, endFacing: Facing,
// Swings are asymmetric. This is usually but not always the dancer's role.
swingRole: common.DanceRole,
} | { } | {
kind: SemanticAnimationKind.TwirlSwap, kind: SemanticAnimationKind.TwirlSwap,
@ -108,12 +103,8 @@ function CenterOfSet(setOffset?: number, lineOffset?: number): Offset {
return position; return position;
} }
function CenterOf(sideOrCenter: CircleSideOrCenter, setOffset?: number, lineOffset?: number): Offset { function CenterOfSideOfSet(side: CircleSide, setOffset?: number, lineOffset?: number): Offset {
const setCenter = CenterOfSet(setOffset, lineOffset); const setCenter = CenterOfSet(setOffset, lineOffset);
if (sideOrCenter === "Center") return setCenter;
const side : CircleSide = sideOrCenter;
const xOffset = side === CircleSide.Left ? -1 : side === CircleSide.Right ? +1 : 0; const xOffset = side === CircleSide.Left ? -1 : side === CircleSide.Right ? +1 : 0;
const yOffset = side === CircleSide.Top ? -1 : side === CircleSide.Bottom ? +1 : 0; const yOffset = side === CircleSide.Top ? -1 : side === CircleSide.Bottom ? +1 : 0;
@ -190,8 +181,8 @@ function SemanticToSetPosition(semantic: SemanticPosition): DancerSetPosition {
x: position.x, x: position.x,
y: position.y + position.y * ( y: position.y + position.y * (
semantic.balance === BalanceWeight.Forward semantic.balance === BalanceWeight.Forward
? -balanceAmount ? balanceAmount
: balanceAmount) : -balanceAmount)
}; };
} }
} }
@ -310,11 +301,6 @@ export function animateLowLevelMove(move: LowLevelMove): animation.AnimationSegm
height: setHeight, height: setHeight,
}, },
animation.RotationAnimationFacing.Center, animation.RotationAnimationFacing.Center,
undefined,
new Map<Hand, animation.HandAnimation>([
[Hand.Left, { kind: "Linear", transitionBeats: 1 }],
[Hand.Right, { kind: "Linear", transitionBeats: 1 }],
])
), ),
]; ];
case SemanticAnimationKind.DoSiDo: case SemanticAnimationKind.DoSiDo:
@ -324,7 +310,7 @@ export function animateLowLevelMove(move: LowLevelMove): animation.AnimationSegm
if (move.startPosition.kind !== PositionKind.Circle) { if (move.startPosition.kind !== PositionKind.Circle) {
throw "DoSiDo must start in a circle: " + JSON.stringify(move); throw "DoSiDo must start in a circle: " + JSON.stringify(move);
} }
const center = CenterOf(move.startPosition.which.leftRightSide(), move.startPosition.setOffset, move.startPosition.lineOffset); const center = CenterOfSideOfSet(move.startPosition.which.leftRightSide(), move.startPosition.setOffset, move.startPosition.lineOffset);
return [ return [
new animation.RotationAnimationSegment(move.beats, new animation.RotationAnimationSegment(move.beats,
startSetPosition, startSetPosition,
@ -346,16 +332,9 @@ export function animateLowLevelMove(move: LowLevelMove): animation.AnimationSegm
throw "Rotate must start in a circle: " + JSON.stringify(move); throw "Rotate must start in a circle: " + JSON.stringify(move);
} }
// TODO Could rotate around other things. // TODO Could rotate around other things.
const rotateCenter = CenterOf(move.movementPattern.around, move.startPosition.setOffset, move.startPosition.lineOffset); const rotateCenter = move.movementPattern.around === "Center"
const hands = move.movementPattern.byHand ? CenterOfSet(move.startPosition.setOffset, move.startPosition.lineOffset)
? new Map<Hand, animation.HandAnimation>([ : CenterOfSideOfSet(move.movementPattern.around, move.startPosition.setOffset, move.startPosition.lineOffset);
[move.movementPattern.byHand, { kind: "Center", transitionBeats: 1 }],
[move.movementPattern.byHand.opposite(), { kind: "None", transitionBeats: 1 }],
])
: new Map<Hand, animation.HandAnimation>([
[Hand.Left, { kind: "None", transitionBeats: 1 }],
[Hand.Right, { kind: "None", transitionBeats: 1 }],
]);
return [ return [
new animation.RotationAnimationSegment(move.beats, new animation.RotationAnimationSegment(move.beats,
startSetPosition, startSetPosition,
@ -371,42 +350,26 @@ export function animateLowLevelMove(move: LowLevelMove): animation.AnimationSegm
transitionBeats: 1, transitionBeats: 1,
minDistance: setWidth, minDistance: setWidth,
}, },
hands,
), ),
]; ];
case SemanticAnimationKind.Swing: case SemanticAnimationKind.Swing:
// TODO Handle swing.
// TODO Rotation
// TODO Parameters: rotation, amount, direction, diagonal?
if (move.startPosition.kind !== PositionKind.Circle || move.endPosition.kind !== PositionKind.Circle) { if (move.startPosition.kind !== PositionKind.Circle || move.endPosition.kind !== PositionKind.Circle) {
throw "Swing must start in a circle: " + JSON.stringify(move); throw "Swing must start in a circle: " + JSON.stringify(move);
} }
const rotateSwingCenter = CenterOf(move.movementPattern.around, move.startPosition.setOffset, move.startPosition.lineOffset); // TODO Could rotate around other things.
const dancerDistance = move.movementPattern.swingRole === DanceRole.Lark ? DancerDistance.SwingLark : DancerDistance.SwingRobin; const rotateSwingCenter = move.movementPattern.around === "Center"
? CenterOfSet(move.startPosition.setOffset, move.startPosition.lineOffset)
const swingStart = SemanticToSetPosition({ : CenterOfSideOfSet(move.movementPattern.around, move.startPosition.setOffset, move.startPosition.lineOffset);
...move.startPosition, const beforeUnfold = SemanticToSetPosition({...move.endPosition, dancerDistance: move.startPosition.dancerDistance });
dancerDistance,
balance: undefined,
});
const beforeUnfold = SemanticToSetPosition({
...move.endPosition,
dancerDistance,
});
const unfolded = SemanticToSetPosition({
...move.endPosition,
dancerDistance: DancerDistance.Normal,
hands: move.movementPattern.swingRole === DanceRole.Lark
? new Map<Hand, HandConnection>([[Hand.Right, {hand: Hand.Left, to: HandTo.DancerRight}]])
: new Map<Hand, HandConnection>([[Hand.Left, {hand: Hand.Right, to: HandTo.DancerLeft}]]),
});
return [ return [
new animation.LinearAnimationSegment(1, new animation.RotationAnimationSegment(move.beats - 1,
startSetPosition, startSetPosition,
swingStart,
),
new animation.RotationAnimationSegment(move.beats - 3,
swingStart,
beforeUnfold, beforeUnfold,
move.movementPattern.minAmount, // TODO base minAmount on number of beats? move.movementPattern.minAmount,
{ {
center: rotateSwingCenter, center: rotateSwingCenter,
width: setWidth / 5, width: setWidth / 5,
@ -416,16 +379,12 @@ export function animateLowLevelMove(move: LowLevelMove): animation.AnimationSegm
), ),
new animation.LinearAnimationSegment(1, new animation.LinearAnimationSegment(1,
beforeUnfold, beforeUnfold,
unfolded,
),
new animation.LinearAnimationSegment(1,
unfolded,
endSetPosition, endSetPosition,
), ),
]; ];
case SemanticAnimationKind.TwirlSwap: case SemanticAnimationKind.TwirlSwap:
const twirlCenter = const twirlCenter =
CenterOf(move.movementPattern.around, move.startPosition.setOffset, move.startPosition.lineOffset); CenterOfSideOfSet(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 postTwirlPos = const postTwirlPos =
{ {