Compare commits

..

No commits in common. "a294baf6cfac7ebf0910e0a664c75aafe40e64f3" and "61da3d55aa81e3a89b300a98be14f6a7209d84bd" have entirely different histories.

2 changed files with 155 additions and 231 deletions

View File

@ -25,10 +25,10 @@ export abstract class AnimationSegment {
this.endPosition = endPosition; this.endPosition = endPosition;
} }
abstract interpolateOffset(progress: number); protected abstract interpolateOffset(progress: number);
// TODO Not sure this interpolation is actually right for arms... // TODO Not sure this interpolation is actually right for arms...
interpolateHandOffset(progress: number, hand: Hand) : Offset | undefined { protected interpolateHandOffset(progress: number, hand: Hand) : Offset | undefined {
let prev = hand === Hand.Left ? this.startPosition.leftArmEnd : this.startPosition.rightArmEnd; let prev = hand === Hand.Left ? this.startPosition.leftArmEnd : this.startPosition.rightArmEnd;
let next = hand === Hand.Left ? this.endPosition.leftArmEnd : this.endPosition.rightArmEnd; let next = hand === Hand.Left ? this.endPosition.leftArmEnd : this.endPosition.rightArmEnd;
@ -39,7 +39,7 @@ export abstract class AnimationSegment {
return interpolateLinearOffset(progress, prev, next); return interpolateLinearOffset(progress, prev, next);
} }
interpolateRotation(progress: number) { protected interpolateRotation(progress: number) {
return interpolateLinear(progress, this.startPosition.rotation, this.endPosition.rotation); return interpolateLinear(progress, this.startPosition.rotation, this.endPosition.rotation);
} }
@ -65,120 +65,19 @@ function interpolateLinearOffset(progress: number, prev: Offset, next: Offset):
} }
export class LinearAnimationSegment extends AnimationSegment { export class LinearAnimationSegment extends AnimationSegment {
constructor({ beats, startPosition, endPosition }: { constructor(beats: number, startPosition: DancerSetPosition, endPosition: DancerSetPosition) {
beats: number;
startPosition: DancerSetPosition;
endPosition: DancerSetPosition;
}) {
super(beats, startPosition, endPosition); super(beats, startPosition, endPosition);
} }
public static standStill(startAndEndPosition: DancerSetPosition, beats?: number) { public static standStill(startAndEndPosition: DancerSetPosition, beats?: number) {
return new LinearAnimationSegment({ return new LinearAnimationSegment(beats ?? 0, startAndEndPosition, startAndEndPosition);
beats: beats ?? 0,
startPosition: startAndEndPosition,
endPosition: startAndEndPosition
});
} }
override interpolateOffset(progress: number) { protected interpolateOffset(progress: number) {
return interpolateLinearOffset(progress, this.startPosition.position, this.endPosition.position); return interpolateLinearOffset(progress, this.startPosition.position, this.endPosition.position);
} }
} }
export interface AnimationTransitionFlags {
rotation?: boolean;
hands?: boolean;
}
export class TransitionAnimationSegment extends AnimationSegment {
private readonly actualAnimation: AnimationSegment;
private readonly flags: AnimationTransitionFlags;
private readonly startTransitionProgress: number;
private readonly endTransitionProgress: number;
private readonly startRotation: number;
private readonly endRotation: number;
constructor({ actualAnimation, flags, startTransitionBeats, endTransitionBeats }: {
actualAnimation: AnimationSegment;
flags: AnimationTransitionFlags;
startTransitionBeats: number;
endTransitionBeats?: number;
}) {
super(actualAnimation.beats, actualAnimation.startPosition, actualAnimation.endPosition);
this.actualAnimation = actualAnimation;
this.flags = flags;
this.startTransitionProgress = startTransitionBeats / actualAnimation.beats;
this.endTransitionProgress = endTransitionBeats === undefined ? this.startTransitionProgress : endTransitionBeats / actualAnimation.beats;
this.startRotation = this.startPosition.rotation;
this.endRotation = this.endPosition.rotation;
if (this.flags.rotation) {
const actualStart = this.actualAnimation.interpolateRotation(0);
const actualEnd = this.actualAnimation.interpolateRotation(1);
const rotationPositive = actualEnd > actualStart;
const transitionStart = this.actualAnimation.interpolateRotation(this.startTransitionProgress);
const transitionEnd = this.actualAnimation.interpolateRotation(1 - this.endTransitionProgress);
if (rotationPositive) {
while (transitionStart < this.startRotation) {
this.startRotation -= 360;
}
while (transitionEnd > this.endRotation) {
this.endRotation += 360;
}
} else {
while (transitionStart > this.startRotation) {
this.startRotation += 360;
}
while (transitionEnd < this.endRotation) {
this.endRotation -= 360;
}
}
}
}
override interpolateOffset(progress: number) {
return this.actualAnimation.interpolateOffset(progress);
}
override interpolateRotation(progress: number): number {
const actualRotation = this.actualAnimation.interpolateRotation(progress);
if (this.flags.rotation) {
if (progress < this.startTransitionProgress) {
return interpolateLinear(progress / this.startTransitionProgress, this.startRotation, actualRotation);
} else if ((1 - progress) < this.endTransitionProgress) {
return interpolateLinear((1 - progress) / this.endTransitionProgress, this.endRotation, actualRotation);
}
}
return actualRotation;
}
override interpolateHandOffset(progress: number, hand: Hand): Offset | undefined {
const actualHandOffset = this.actualAnimation.interpolateHandOffset(progress, hand);
if (this.flags.hands) {
if (progress < this.startTransitionProgress) {
const startHand = hand === Hand.Left
? this.startPosition.leftArmEnd ?? leftShoulder
: this.startPosition.rightArmEnd ?? rightShoulder;
return interpolateLinearOffset(progress / this.startTransitionProgress, startHand, actualHandOffset ?? hand.shoulderPosition());
} else if (1 - progress < this.endTransitionProgress) {
const endHand = hand === Hand.Left
? this.endPosition.leftArmEnd ?? leftShoulder
: this.endPosition.rightArmEnd ?? rightShoulder;
return interpolateLinearOffset((1 - progress) / this.endTransitionProgress, endHand, actualHandOffset ?? hand.shoulderPosition());
}
}
return actualHandOffset;
}
}
// TODO Not sure this really belongs here instead of on some Semantic* type. // TODO Not sure this really belongs here instead of on some Semantic* type.
export enum RotationAnimationFacing { export enum RotationAnimationFacing {
Linear = "Linear", // Default, linearly interpolate. Linear = "Linear", // Default, linearly interpolate.
@ -196,7 +95,12 @@ interface Closer {
transitionProgress: number, transitionProgress: number,
} }
export interface HandAnimation { export interface HandAnimation {
kind: "Linear" | "Center" | "None" | "Start" | "End" kind: "Linear" | "Center" | "None",
transitionBeats?: number,
}
interface HandAnimationInternal {
kind: "Linear" | "Center" | "None",
transitionProgress: number,
} }
export class RotationAnimationSegment extends AnimationSegment { export class RotationAnimationSegment extends AnimationSegment {
@ -210,21 +114,27 @@ 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, HandAnimation>; private readonly hands: Map<Hand, HandAnimationInternal>;
private readonly rotationTransitionProgress: number;
constructor({ beats, startPosition, endPosition, rotation, around, facing, closer, hands }: { constructor(beats: number, startPosition: DancerSetPosition, endPosition: DancerSetPosition,
beats: number; rotation: number, around: RotationAround, facing: RotationAnimationFacing,
startPosition: DancerSetPosition; closer?: CloserDuringRotation, hands?: Map<Hand, HandAnimation>,
endPosition: DancerSetPosition; rotationTransitionBeats?: number) {
rotation: number; around: RotationAround;
facing: RotationAnimationFacing;
closer?: CloserDuringRotation;
hands?: Map<Hand, HandAnimation>;
}) {
super(beats, startPosition, endPosition); super(beats, startPosition, endPosition);
this.hands = new Map<Hand, HandAnimation>( function convertHandAnimation(anim: HandAnimation | undefined): HandAnimationInternal {
[Hand.Left, Hand.Right].map(h => [h, hands?.get(h) ?? { kind: "Linear" }])); 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.facing = facing;
this.xRadius = around.width / 2; this.xRadius = around.width / 2;
@ -238,6 +148,7 @@ export class RotationAnimationSegment extends AnimationSegment {
// Canvas rotation is backwards of what we expect, so take the negative here. // Canvas rotation is backwards of what we expect, so take the negative here.
return -radiansToDegrees(radians); return -radiansToDegrees(radians);
} }
this.rotationTransitionProgress = (rotationTransitionBeats ?? 0) / beats;
this.startRotation = positionToDegrees(startPosition.position); this.startRotation = positionToDegrees(startPosition.position);
this.startFacing = startPosition.rotation; this.startFacing = startPosition.rotation;
const actualRotation = normalizeRotation(positionToDegrees(endPosition.position) - this.startRotation, const actualRotation = normalizeRotation(positionToDegrees(endPosition.position) - this.startRotation,
@ -258,7 +169,7 @@ export class RotationAnimationSegment extends AnimationSegment {
} }
} }
override interpolateOffset(progress: number) { protected interpolateOffset(progress: number) {
const radians = -degreesToRadians(interpolateLinear(progress, this.startRotation, this.endRotation)); const radians = -degreesToRadians(interpolateLinear(progress, this.startRotation, this.endRotation));
let distance: number; let distance: number;
if (!this.closer) { if (!this.closer) {
@ -278,7 +189,7 @@ export class RotationAnimationSegment extends AnimationSegment {
} }
} }
override interpolateRotation(progress: number): number { protected interpolateRotation(progress: number): number {
if (this.facing === RotationAnimationFacing.Linear) { if (this.facing === RotationAnimationFacing.Linear) {
return super.interpolateRotation(progress); return super.interpolateRotation(progress);
} else { } else {
@ -286,27 +197,41 @@ export class RotationAnimationSegment extends AnimationSegment {
let rotation : number; let rotation : number;
switch (this.facing) { switch (this.facing) {
case RotationAnimationFacing.Center: case RotationAnimationFacing.Center:
return degrees - 90; rotation = degrees - 90;
break;
case RotationAnimationFacing.CenterRelative: case RotationAnimationFacing.CenterRelative:
return degrees - this.startRotation + this.startFacing; rotation = degrees - this.startRotation + this.startFacing;
break;
case RotationAnimationFacing.Forward: case RotationAnimationFacing.Forward:
return degrees + 180; rotation = degrees + 180;
break;
case RotationAnimationFacing.Backward: case RotationAnimationFacing.Backward:
return degrees; rotation = degrees;
break;
}
if (progress < this.rotationTransitionProgress) {
return interpolateLinear(progress / this.rotationTransitionProgress, this.startRotation, rotation);
} else if ((1 - progress) < this.rotationTransitionProgress) {
return interpolateLinear((1 - progress) / this.rotationTransitionProgress, this.endRotation, rotation);
} else {
return rotation;
} }
} }
} }
override interpolateHandOffset(progress: number, hand: Hand): Offset | undefined { protected interpolateHandOffset(progress: number, hand: Hand): Offset | undefined {
const handAnim : HandAnimation = this.hands.get(hand)!; const handAnim : HandAnimationInternal = this.hands.get(hand)!;
let middlePos : Offset; let middlePos : Offset;
switch (handAnim.kind) { switch (handAnim.kind) {
case "Start":
return hand === Hand.Left ? this.startPosition.leftArmEnd : this.startPosition.rightArmEnd;
case "End":
return hand === Hand.Left ? this.endPosition.leftArmEnd : this.endPosition.rightArmEnd;
case "Linear": 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": case "Center":
const position = this.interpolateOffset(progress); const position = this.interpolateOffset(progress);
const offset = { const offset = {
@ -319,10 +244,28 @@ export class RotationAnimationSegment extends AnimationSegment {
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,
} }
return centerPos; middlePos = centerPos;
break;
case "None": case "None":
return hand === Hand.Left ? leftShoulder : rightShoulder; 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

@ -287,14 +287,12 @@ export function animateLowLevelMove(move: LowLevelMove): animation.AnimationSegm
+ JSON.stringify(move.startPosition) + ", end=" + JSON.stringify(move.endPosition); + JSON.stringify(move.startPosition) + ", end=" + JSON.stringify(move.endPosition);
} }
return [ return [
new animation.LinearAnimationSegment({ new animation.LinearAnimationSegment(move.beats,
beats: move.beats, startSetPosition,
startPosition: startSetPosition, {
endPosition: {
...endSetPosition, ...endSetPosition,
rotation: startSetPosition.rotation + rotation, rotation: startSetPosition.rotation + rotation,
} }),
}),
]; ];
case SemanticAnimationKind.Circle: case SemanticAnimationKind.Circle:
if (move.startPosition.kind !== PositionKind.Circle || move.endPosition.kind !== PositionKind.Circle) { if (move.startPosition.kind !== PositionKind.Circle || move.endPosition.kind !== PositionKind.Circle) {
@ -302,27 +300,22 @@ export function animateLowLevelMove(move: LowLevelMove): animation.AnimationSegm
} }
return [ return [
new animation.TransitionAnimationSegment({ new animation.RotationAnimationSegment(move.beats,
actualAnimation: new animation.RotationAnimationSegment({ startSetPosition,
beats: move.beats, endSetPosition,
startPosition: startSetPosition, move.movementPattern.places * 90,
endPosition: endSetPosition, {
rotation: move.movementPattern.places * 90, center: CenterOfSet(move.startPosition.setOffset, move.startPosition.lineOffset),
around: { width: setWidth,
center: CenterOfSet(move.startPosition.setOffset, move.startPosition.lineOffset), height: setHeight,
width: setWidth,
height: setHeight,
},
facing: animation.RotationAnimationFacing.Center, closer: undefined, hands: new Map<Hand, animation.HandAnimation>([
[Hand.Left, { kind: "End" }],
[Hand.Right, { kind: "End" }],
])
}),
flags: {
hands: true
}, },
startTransitionBeats: 1 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:
// TODO Rotation // TODO Rotation
@ -333,18 +326,17 @@ export function animateLowLevelMove(move: LowLevelMove): animation.AnimationSegm
} }
const center = CenterOf(move.startPosition.which.leftRightSide(), move.startPosition.setOffset, move.startPosition.lineOffset); const center = CenterOf(move.startPosition.which.leftRightSide(), move.startPosition.setOffset, move.startPosition.lineOffset);
return [ return [
new animation.RotationAnimationSegment({ new animation.RotationAnimationSegment(move.beats,
beats: move.beats, startSetPosition,
startPosition: startSetPosition, endSetPosition,
endPosition: endSetPosition, move.movementPattern.amount,
rotation: move.movementPattern.amount, {
around: {
center, center,
width: setWidth / 3, width: setWidth / 3,
height: setHeight, height: setHeight,
}, },
facing: animation.RotationAnimationFacing.Linear animation.RotationAnimationFacing.Linear,
}), ),
]; ];
case SemanticAnimationKind.RotateAround: case SemanticAnimationKind.RotateAround:
// TODO Rotation // TODO Rotation
@ -357,37 +349,31 @@ export function animateLowLevelMove(move: LowLevelMove): animation.AnimationSegm
const rotateCenter = CenterOf(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 const hands = move.movementPattern.byHand
? new Map<Hand, animation.HandAnimation>([ ? new Map<Hand, animation.HandAnimation>([
[move.movementPattern.byHand, { kind: "Center" }], [move.movementPattern.byHand, { kind: "Center", transitionBeats: 1 }],
[move.movementPattern.byHand.opposite(), { kind: "None" }], [move.movementPattern.byHand.opposite(), { kind: "None", transitionBeats: 1 }],
]) ])
: new Map<Hand, animation.HandAnimation>([ : new Map<Hand, animation.HandAnimation>([
[Hand.Left, { kind: "None" }], [Hand.Left, { kind: "None", transitionBeats: 1 }],
[Hand.Right, { kind: "None" }], [Hand.Right, { kind: "None", transitionBeats: 1 }],
]); ]);
return [ return [
new animation.TransitionAnimationSegment({ new animation.RotationAnimationSegment(move.beats,
actualAnimation: new animation.RotationAnimationSegment({ startSetPosition,
beats: move.beats, endSetPosition,
startPosition: startSetPosition, move.movementPattern.minAmount,
endPosition: endSetPosition, {
rotation: move.movementPattern.minAmount, center: rotateCenter,
around: { width: setWidth / 5,
center: rotateCenter, height: setHeight / 5,
width: setWidth / 5,
height: setHeight / 5,
},
facing: animation.RotationAnimationFacing.Center, closer: {
transitionBeats: 1,
minDistance: setWidth,
},
hands
}),
flags: {
hands: true,
rotation: true
}, },
startTransitionBeats: 1 animation.RotationAnimationFacing.Center,
}), {
transitionBeats: 1,
minDistance: setWidth,
},
hands,
1,
),
]; ];
case SemanticAnimationKind.Swing: case SemanticAnimationKind.Swing:
if (move.startPosition.kind !== PositionKind.Circle || move.endPosition.kind !== PositionKind.Circle) { if (move.startPosition.kind !== PositionKind.Circle || move.endPosition.kind !== PositionKind.Circle) {
@ -414,33 +400,29 @@ export function animateLowLevelMove(move: LowLevelMove): animation.AnimationSegm
}); });
return [ return [
new animation.LinearAnimationSegment({ new animation.LinearAnimationSegment(1,
beats: 1, startSetPosition,
startPosition: startSetPosition, swingStart,
endPosition: swingStart, ),
}), new animation.RotationAnimationSegment(move.beats - 3,
new animation.RotationAnimationSegment({ swingStart,
beats: move.beats - 3, beforeUnfold,
startPosition: swingStart, move.movementPattern.minAmount, // TODO base minAmount on number of beats?
endPosition: beforeUnfold, {
rotation: move.movementPattern.minAmount,
around: {
center: rotateSwingCenter, center: rotateSwingCenter,
width: setWidth / 5, width: setWidth / 5,
height: setHeight / 5, height: setHeight / 5,
}, },
facing: animation.RotationAnimationFacing.CenterRelative animation.RotationAnimationFacing.CenterRelative,
}), ),
new animation.LinearAnimationSegment({ new animation.LinearAnimationSegment(1,
beats: 1, beforeUnfold,
startPosition: beforeUnfold, unfolded,
endPosition: unfolded, ),
}), new animation.LinearAnimationSegment(1,
new animation.LinearAnimationSegment({ unfolded,
beats: 1, endSetPosition,
startPosition: unfolded, ),
endPosition: endSetPosition,
}),
]; ];
case SemanticAnimationKind.TwirlSwap: case SemanticAnimationKind.TwirlSwap:
const twirlCenter = const twirlCenter =
@ -453,26 +435,25 @@ export function animateLowLevelMove(move: LowLevelMove): animation.AnimationSegm
rightArmEnd: startSetPosition.rightArmEnd === undefined ? undefined : endSetPosition.rightArmEnd, rightArmEnd: startSetPosition.rightArmEnd === undefined ? undefined : endSetPosition.rightArmEnd,
}; };
return [ return [
new animation.RotationAnimationSegment({ new animation.RotationAnimationSegment(move.beats - 1,
beats: move.beats - 1, startSetPosition,
startPosition: startSetPosition, {
endPosition: {
...postTwirlPos, ...postTwirlPos,
rotation: endSetPosition.rotation - (move.startPosition.hands?.has(Hand.Left) ? 360 : 0) rotation: endSetPosition.rotation - (move.startPosition.hands?.has(Hand.Left) ? 360 : 0)
}, },
rotation: 180, 180,
around: { {
center: twirlCenter, center: twirlCenter,
width: aroundTopOrBottom ? setWidth : setWidth / 4, width: aroundTopOrBottom ? setWidth : setWidth / 4,
height: aroundTopOrBottom ? setHeight / 4 : setHeight, height: aroundTopOrBottom ? setHeight / 4 : setHeight,
}, },
facing: animation.RotationAnimationFacing.Linear, closer: undefined, hands: new Map<Hand, animation.HandAnimation>([[[...move.startPosition.hands!.keys()][0], { kind: "Center" }]]) animation.RotationAnimationFacing.Linear,
}), undefined,
new animation.LinearAnimationSegment({ new Map<Hand, animation.HandAnimation>([[[...move.startPosition.hands!.keys()][0], {kind: "Center"}]]),
beats: 1, ),
startPosition: postTwirlPos, new animation.LinearAnimationSegment(1,
endPosition: endSetPosition postTwirlPos,
}) endSetPosition)
]; ];
} }