Compare commits
3 Commits
646b4b2ceb
...
53fee2bd0f
Author | SHA1 | Date | |
---|---|---|---|
53fee2bd0f | |||
fb9143f691 | |||
16d15c65de |
|
@ -95,7 +95,12 @@ interface Closer {
|
|||
transitionProgress: number,
|
||||
}
|
||||
export interface HandAnimation {
|
||||
kind: "Linear" | "Center"
|
||||
kind: "Linear" | "Center" | "None",
|
||||
transitionBeats?: number,
|
||||
}
|
||||
interface HandAnimationInternal {
|
||||
kind: "Linear" | "Center" | "None",
|
||||
transitionProgress: number,
|
||||
}
|
||||
|
||||
export class RotationAnimationSegment extends AnimationSegment {
|
||||
|
@ -109,16 +114,25 @@ export class RotationAnimationSegment extends AnimationSegment {
|
|||
private readonly facing: RotationAnimationFacing;
|
||||
private readonly startFacing: Rotation;
|
||||
private readonly closer?: Closer;
|
||||
private readonly hands: Map<Hand, HandAnimation>;
|
||||
private readonly hands: Map<Hand, HandAnimationInternal>;
|
||||
|
||||
constructor(beats: number, startPosition: DancerSetPosition, endPosition: DancerSetPosition,
|
||||
rotation: number, around: RotationAround, facing: RotationAnimationFacing,
|
||||
closer?: CloserDuringRotation, hands?: Map<Hand, HandAnimation>) {
|
||||
super(beats, startPosition, endPosition);
|
||||
|
||||
this.hands = hands ?? new Map<Hand, HandAnimation>();
|
||||
if (!this.hands.has(Hand.Left)) this.hands.set(Hand.Left, {kind: "Linear"});
|
||||
if (!this.hands.has(Hand.Right)) this.hands.set(Hand.Right, {kind: "Linear"});
|
||||
function convertHandAnimation(anim: HandAnimation | undefined): HandAnimationInternal {
|
||||
if (!anim) {
|
||||
return { kind: "Linear", transitionProgress: 0 };
|
||||
} 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.xRadius = around.width / 2;
|
||||
|
@ -191,9 +205,17 @@ export class RotationAnimationSegment extends AnimationSegment {
|
|||
}
|
||||
|
||||
protected interpolateHandOffset(progress: number, hand: Hand): Offset | undefined {
|
||||
switch (this.hands.get(hand)!.kind) {
|
||||
const handAnim : HandAnimationInternal = this.hands.get(hand)!;
|
||||
let middlePos : Offset;
|
||||
switch (handAnim.kind) {
|
||||
case "Linear":
|
||||
return super.interpolateHandOffset(progress, hand);
|
||||
if (handAnim.transitionProgress === 0) {
|
||||
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":
|
||||
const position = this.interpolateOffset(progress);
|
||||
const offset = {
|
||||
|
@ -202,11 +224,32 @@ export class RotationAnimationSegment extends AnimationSegment {
|
|||
};
|
||||
const rotation = degreesToRadians(this.interpolateRotation(progress));
|
||||
|
||||
return {
|
||||
x: -Math.cos(rotation)*offset.x + Math.sin(rotation)*offset.y,
|
||||
y: -Math.sin(rotation)*offset.x - Math.cos(rotation)*offset.y,
|
||||
const centerPos = {
|
||||
x: -Math.cos(rotation) * offset.x + Math.sin(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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -310,25 +310,16 @@ function moveAsLowLevelMoves(move: Move, startingPos: Map<DancerIdentity, Semant
|
|||
case "none":
|
||||
res.set(id, combine([
|
||||
{
|
||||
beats: 1,
|
||||
endPosition: { ...startPosition, dancerDistance: swingRole },
|
||||
movementPattern: { kind: SemanticAnimationKind.Linear, },
|
||||
},
|
||||
{
|
||||
beats: move.beats - 2,
|
||||
endPosition: { ...endPosition, dancerDistance: swingRole },
|
||||
beats: move.beats,
|
||||
endPosition: endPosition,
|
||||
movementPattern: {
|
||||
kind: SemanticAnimationKind.Swing,
|
||||
minAmount: 360,
|
||||
around: startPos.which.leftRightSide(),
|
||||
endFacing: startPos.which.leftRightSide() === CircleSide.Left ? Facing.Right : Facing.Left,
|
||||
swingRole: id.danceRole,
|
||||
},
|
||||
},
|
||||
{
|
||||
beats: 1,
|
||||
endPosition: endPosition,
|
||||
movementPattern: { kind: SemanticAnimationKind.Linear, },
|
||||
},
|
||||
], startPos));
|
||||
break;
|
||||
case "balance":
|
||||
|
@ -353,37 +344,22 @@ function moveAsLowLevelMoves(move: Move, startingPos: Map<DancerIdentity, Semant
|
|||
movementPattern: { kind: SemanticAnimationKind.Linear, },
|
||||
},
|
||||
prevEnd => ({
|
||||
beats: balancePartBeats - 1,
|
||||
beats: balancePartBeats,
|
||||
endPosition: {
|
||||
...prevEnd,
|
||||
balance: BalanceWeight.Backward,
|
||||
},
|
||||
movementPattern: { kind: SemanticAnimationKind.Linear, },
|
||||
}),
|
||||
prevEnd => ({
|
||||
beats: 1,
|
||||
endPosition: {
|
||||
...prevEnd,
|
||||
balance: undefined,
|
||||
dancerDistance: swingRole,
|
||||
},
|
||||
movementPattern: { kind: SemanticAnimationKind.Linear, },
|
||||
}),
|
||||
{
|
||||
beats: move.beats - 2 * balancePartBeats - 1,
|
||||
endPosition: { ...endPosition, dancerDistance: DancerDistance.Compact },
|
||||
beats: move.beats - 2 * balancePartBeats,
|
||||
endPosition: endPosition,
|
||||
movementPattern: {
|
||||
kind: SemanticAnimationKind.Swing,
|
||||
minAmount: 360,
|
||||
around: startPos.which.leftRightSide(),
|
||||
endFacing: startPos.which.leftRightSide() === CircleSide.Left ? Facing.Right : Facing.Left,
|
||||
},
|
||||
},
|
||||
{
|
||||
beats: 1,
|
||||
endPosition: endPosition,
|
||||
movementPattern: {
|
||||
kind: SemanticAnimationKind.Linear,
|
||||
swingRole: id.danceRole,
|
||||
},
|
||||
},
|
||||
], startPos));
|
||||
|
@ -393,14 +369,12 @@ function moveAsLowLevelMoves(move: Move, startingPos: Map<DancerIdentity, Semant
|
|||
res.set(id, combine([
|
||||
prevEnd => ({
|
||||
beats: meltdownBeats,
|
||||
endPosition: {
|
||||
...prevEnd,
|
||||
dancerDistance: swingRole,
|
||||
},
|
||||
endPosition: prevEnd,
|
||||
movementPattern: {
|
||||
kind: SemanticAnimationKind.RotateAround,
|
||||
minAmount: 360,
|
||||
around: startPos.which.leftRightSide(),
|
||||
byHand: undefined,
|
||||
},
|
||||
}),
|
||||
{
|
||||
|
@ -411,6 +385,7 @@ function moveAsLowLevelMoves(move: Move, startingPos: Map<DancerIdentity, Semant
|
|||
minAmount: 360,
|
||||
around: startPos.which.leftRightSide(),
|
||||
endFacing: startPos.which.leftRightSide() === CircleSide.Left ? Facing.Right : Facing.Left,
|
||||
swingRole: id.danceRole,
|
||||
},
|
||||
},
|
||||
], startPos));
|
||||
|
@ -452,39 +427,15 @@ function moveAsLowLevelMoves(move: Move, startingPos: Map<DancerIdentity, Semant
|
|||
endPosition.lineOffset = (endPosition.lineOffset ?? 0) + withId.relativeLine;
|
||||
}
|
||||
|
||||
// TODO Take more than zero time to change hands?
|
||||
res.set(id, combine([
|
||||
prevEnd => ({
|
||||
beats: 0,
|
||||
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,
|
||||
endPosition,
|
||||
movementPattern: {
|
||||
kind: SemanticAnimationKind.RotateAround,
|
||||
minAmount: byHand === Hand.Right ? move.parameters.circling : -move.parameters.circling,
|
||||
around,
|
||||
byHand,
|
||||
},
|
||||
},
|
||||
], startPos));
|
||||
|
@ -501,21 +452,11 @@ function moveAsLowLevelMoves(move: Move, startingPos: Map<DancerIdentity, Semant
|
|||
}
|
||||
|
||||
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 => ({
|
||||
beats: move.beats - 1,
|
||||
beats: move.beats,
|
||||
endPosition: {
|
||||
...prevEnd,
|
||||
hands: handsInCircle,
|
||||
which: startPos.which.circleLeft(places),
|
||||
},
|
||||
movementPattern: {
|
||||
|
|
|
@ -20,6 +20,7 @@ export enum CircleSide {
|
|||
Left = "Left",
|
||||
Right = "Right",
|
||||
}
|
||||
export type CircleSideOrCenter = CircleSide | "Center";
|
||||
export class CirclePosition {
|
||||
public static readonly TopLeft = new CirclePosition(CirclePositionEnum.TopLeft);
|
||||
public static readonly BottomLeft = new CirclePosition(CirclePositionEnum.BottomLeft);
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import * as animation from "./animation.js";
|
||||
import * as common from "./danceCommon.js";
|
||||
import { DancerIdentity, Rotation } from "./danceCommon.js";
|
||||
import { BalanceWeight, CirclePosition, CircleSide, DancerDistance, Facing, HandConnection, HandTo, PositionKind, SemanticPosition } from "./interpreterCommon.js";
|
||||
import { DanceRole, DancerIdentity, Rotation } from "./danceCommon.js";
|
||||
import { BalanceWeight, CirclePosition, CircleSide, CircleSideOrCenter, DancerDistance, Facing, HandConnection, HandTo, PositionKind, SemanticPosition } from "./interpreterCommon.js";
|
||||
import { Move } from "./libfigureMapper.js";
|
||||
import { DancerSetPosition, Hand, Offset, dancerWidth, lineDistance, setDistance, setHeight, setWidth } from "./rendererConstants.js";
|
||||
import { DancerSetPosition, Hand, Offset, dancerWidth, lineDistance, setDistance, setHeight, setSpacing, setWidth } from "./rendererConstants.js";
|
||||
|
||||
|
||||
export enum SemanticAnimationKind {
|
||||
|
@ -56,6 +56,8 @@ export type SemanticAnimation = {
|
|||
minAmount: number,
|
||||
|
||||
around: CircleSide | "Center",
|
||||
|
||||
byHand: Hand | undefined,
|
||||
} | {
|
||||
kind: SemanticAnimationKind.Swing,
|
||||
|
||||
|
@ -64,6 +66,9 @@ export type SemanticAnimation = {
|
|||
around: CircleSide | "Center",
|
||||
|
||||
endFacing: Facing,
|
||||
|
||||
// Swings are asymmetric. This is usually but not always the dancer's role.
|
||||
swingRole: common.DanceRole,
|
||||
} | {
|
||||
kind: SemanticAnimationKind.TwirlSwap,
|
||||
|
||||
|
@ -103,8 +108,12 @@ function CenterOfSet(setOffset?: number, lineOffset?: number): Offset {
|
|||
|
||||
return position;
|
||||
}
|
||||
function CenterOfSideOfSet(side: CircleSide, setOffset?: number, lineOffset?: number): Offset {
|
||||
function CenterOf(sideOrCenter: CircleSideOrCenter, setOffset?: number, lineOffset?: number): Offset {
|
||||
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 yOffset = side === CircleSide.Top ? -1 : side === CircleSide.Bottom ? +1 : 0;
|
||||
|
||||
|
@ -181,8 +190,8 @@ function SemanticToSetPosition(semantic: SemanticPosition): DancerSetPosition {
|
|||
x: position.x,
|
||||
y: position.y + position.y * (
|
||||
semantic.balance === BalanceWeight.Forward
|
||||
? balanceAmount
|
||||
: -balanceAmount)
|
||||
? -balanceAmount
|
||||
: balanceAmount)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -301,6 +310,11 @@ export function animateLowLevelMove(move: LowLevelMove): animation.AnimationSegm
|
|||
height: setHeight,
|
||||
},
|
||||
animation.RotationAnimationFacing.Center,
|
||||
undefined,
|
||||
new Map<Hand, animation.HandAnimation>([
|
||||
[Hand.Left, { kind: "Linear", transitionBeats: 1 }],
|
||||
[Hand.Right, { kind: "Linear", transitionBeats: 1 }],
|
||||
])
|
||||
),
|
||||
];
|
||||
case SemanticAnimationKind.DoSiDo:
|
||||
|
@ -310,7 +324,7 @@ export function animateLowLevelMove(move: LowLevelMove): animation.AnimationSegm
|
|||
if (move.startPosition.kind !== PositionKind.Circle) {
|
||||
throw "DoSiDo must start in a circle: " + JSON.stringify(move);
|
||||
}
|
||||
const center = CenterOfSideOfSet(move.startPosition.which.leftRightSide(), move.startPosition.setOffset, move.startPosition.lineOffset);
|
||||
const center = CenterOf(move.startPosition.which.leftRightSide(), move.startPosition.setOffset, move.startPosition.lineOffset);
|
||||
return [
|
||||
new animation.RotationAnimationSegment(move.beats,
|
||||
startSetPosition,
|
||||
|
@ -332,9 +346,16 @@ export function animateLowLevelMove(move: LowLevelMove): animation.AnimationSegm
|
|||
throw "Rotate must start in a circle: " + JSON.stringify(move);
|
||||
}
|
||||
// TODO Could rotate around other things.
|
||||
const rotateCenter = move.movementPattern.around === "Center"
|
||||
? CenterOfSet(move.startPosition.setOffset, move.startPosition.lineOffset)
|
||||
: CenterOfSideOfSet(move.movementPattern.around, move.startPosition.setOffset, move.startPosition.lineOffset);
|
||||
const rotateCenter = CenterOf(move.movementPattern.around, move.startPosition.setOffset, move.startPosition.lineOffset);
|
||||
const hands = move.movementPattern.byHand
|
||||
? new Map<Hand, animation.HandAnimation>([
|
||||
[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 [
|
||||
new animation.RotationAnimationSegment(move.beats,
|
||||
startSetPosition,
|
||||
|
@ -350,26 +371,42 @@ export function animateLowLevelMove(move: LowLevelMove): animation.AnimationSegm
|
|||
transitionBeats: 1,
|
||||
minDistance: setWidth,
|
||||
},
|
||||
hands,
|
||||
),
|
||||
];
|
||||
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) {
|
||||
throw "Swing must start in a circle: " + JSON.stringify(move);
|
||||
}
|
||||
// TODO Could rotate around other things.
|
||||
const rotateSwingCenter = move.movementPattern.around === "Center"
|
||||
? CenterOfSet(move.startPosition.setOffset, move.startPosition.lineOffset)
|
||||
: CenterOfSideOfSet(move.movementPattern.around, move.startPosition.setOffset, move.startPosition.lineOffset);
|
||||
const beforeUnfold = SemanticToSetPosition({...move.endPosition, dancerDistance: move.startPosition.dancerDistance });
|
||||
const rotateSwingCenter = CenterOf(move.movementPattern.around, move.startPosition.setOffset, move.startPosition.lineOffset);
|
||||
const dancerDistance = move.movementPattern.swingRole === DanceRole.Lark ? DancerDistance.SwingLark : DancerDistance.SwingRobin;
|
||||
|
||||
const swingStart = SemanticToSetPosition({
|
||||
...move.startPosition,
|
||||
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 [
|
||||
new animation.RotationAnimationSegment(move.beats - 1,
|
||||
new animation.LinearAnimationSegment(1,
|
||||
startSetPosition,
|
||||
swingStart,
|
||||
),
|
||||
new animation.RotationAnimationSegment(move.beats - 3,
|
||||
swingStart,
|
||||
beforeUnfold,
|
||||
move.movementPattern.minAmount,
|
||||
move.movementPattern.minAmount, // TODO base minAmount on number of beats?
|
||||
{
|
||||
center: rotateSwingCenter,
|
||||
width: setWidth / 5,
|
||||
|
@ -379,12 +416,16 @@ export function animateLowLevelMove(move: LowLevelMove): animation.AnimationSegm
|
|||
),
|
||||
new animation.LinearAnimationSegment(1,
|
||||
beforeUnfold,
|
||||
unfolded,
|
||||
),
|
||||
new animation.LinearAnimationSegment(1,
|
||||
unfolded,
|
||||
endSetPosition,
|
||||
),
|
||||
];
|
||||
case SemanticAnimationKind.TwirlSwap:
|
||||
const twirlCenter =
|
||||
CenterOfSideOfSet(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 postTwirlPos =
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue
Block a user