Compare commits

...

2 Commits

Author SHA1 Message Date
f533e3d202 Improved swing animation. 2023-08-23 17:43:25 -07:00
ea9871d545 Make debug output slightly easier to read. 2023-08-23 16:32:53 -07:00
4 changed files with 132 additions and 20 deletions

View File

@ -40,6 +40,11 @@
}
table, tr, th, td {
border: solid black 1px;
vertical-align: top;
}
table pre {
position: sticky;
top: 0;
}
th.Ones.Lark {
background-color: hsl(0, 80%, 50%);

View File

@ -1,4 +1,4 @@
import { DancerIdentity, normalizeRotation } from "./danceCommon.js";
import { DancerIdentity, Rotation, normalizeRotation } from "./danceCommon.js";
import { DancerSetPosition, DancersSetPositions, Hand } from "./rendererConstants.js";
import { Offset, leftShoulder, rightShoulder, degreesToRadians, radiansToDegrees } from "./rendererConstants.js";
@ -82,6 +82,7 @@ export class LinearAnimationSegment extends AnimationSegment {
export enum RotationAnimationFacing {
Linear = "Linear", // Default, linearly interpolate.
Center = "Center", // Always face the center.
CenterRelative = "CenterRelative", // Always face the center.
Forward = "Forward", // Always face the direction of the rotation.
Backward = "Backward", // Opposite of forward.
}
@ -95,6 +96,7 @@ export class RotationAnimationSegment extends AnimationSegment {
private readonly endRotation: number;
private readonly center: Offset;
private readonly facing: RotationAnimationFacing;
private readonly startFacing: Rotation;
constructor(beats: number, startPosition: DancerSetPosition, endPosition: DancerSetPosition,
rotation: number, around: RotationAround, facing: RotationAnimationFacing) {
@ -113,6 +115,7 @@ export class RotationAnimationSegment extends AnimationSegment {
return -radiansToDegrees(radians);
}
this.startRotation = positionToDegrees(startPosition.position);
this.startFacing = startPosition.rotation;
const actualRotation = normalizeRotation(positionToDegrees(endPosition.position) - this.startRotation,
rotation);
this.endRotation = this.startRotation + actualRotation;
@ -142,6 +145,8 @@ export class RotationAnimationSegment extends AnimationSegment {
switch (this.facing) {
case RotationAnimationFacing.Center:
return degrees - 90;
case RotationAnimationFacing.CenterRelative:
return degrees - this.startRotation + this.startFacing;
case RotationAnimationFacing.Forward:
return degrees + 180;
case RotationAnimationFacing.Backward:

View File

@ -160,6 +160,14 @@ enum BalanceWeight {
Left = "Left",
Right = "Right",
}
enum DancerDistance {
Normal = "Normal",
Compact = "Compact", // allemande, etc.
// Swings asymmetrical, but some dances may have the Lark do a swing as a Robin or vice versa,
// especially any dance with Larks Swing or Robins Swing.
SwingLark = "SwingLark",
SwingRobin = "SwingRobin",
}
type SemanticPosition = {
kind: PositionKind.Circle,
setOffset?: number,
@ -168,7 +176,7 @@ type SemanticPosition = {
facing: Facing,
hands?: Map<Hand, HandConnection>,
balance?: BalanceWeight,
sideCompact?: boolean, // if true, have moved in for swing/allemande/etc.
dancerDistance?: DancerDistance,
} | {
kind: PositionKind.ShortLines,
setOffset?: number,
@ -205,8 +213,10 @@ enum SemanticAnimationKind {
// California twirl, box the gnat, etc.
TwirlSwap = "TwirlSwap",
// Rotate as a pair around a point. Allemande, right/left shoulder round, maybe swing?
// Rotate as a pair around a point. Allemande, right/left shoulder round
RotateAround = "RotateAround",
Swing = "Swing",
}
type SemanticAnimation = {
kind: SemanticAnimationKind.StandStill,
@ -235,6 +245,14 @@ type SemanticAnimation = {
minAmount: number,
around: CircleSide | "Center",
} | {
kind: SemanticAnimationKind.Swing,
minAmount: number,
around: CircleSide | "Center",
endFacing: Facing,
} | {
kind: SemanticAnimationKind.TwirlSwap,
}
@ -556,23 +574,25 @@ function moveAsLowLevelMoves(move: Move, startingPos: Map<DancerIdentity, Semant
: startPos.which.swapUpAndDown(),
facing: startPos.which.leftRightSide() === CircleSide.Left ? Facing.Right : Facing.Left,
balance: undefined,
sideCompact: undefined,
dancerDistance: undefined,
hands: new Map<Hand, HandConnection>([id.danceRole === DanceRole.Lark
? [Hand.Right, { to: HandTo.DancerRight, hand: Hand.Left }]
: [Hand.Left, { to: HandTo.DancerLeft, hand: Hand.Right }]]),
};
// TODO same role swing currently unsupported.
const swingRole = id.danceRole === DanceRole.Lark ? DancerDistance.SwingLark : DancerDistance.SwingRobin;
switch (move.parameters.prefix) {
case "none":
res.set(id, combine([
{
beats: 1,
endPosition: { ...startPosition, sideCompact: true },
endPosition: { ...startPosition, dancerDistance: DancerDistance.Compact },
movementPattern: { kind: SemanticAnimationKind.Linear, },
},
{
beats: move.beats - 2,
endPosition: { ...endPosition, sideCompact: true },
endPosition: { ...endPosition, dancerDistance: DancerDistance.Compact },
movementPattern: {
kind: SemanticAnimationKind.RotateAround,
minAmount: 360,
@ -603,7 +623,7 @@ function moveAsLowLevelMoves(move: Move, startingPos: Map<DancerIdentity, Semant
endPosition: {
...startWithBalHands,
balance: BalanceWeight.Forward,
sideCompact: true,
dancerDistance: DancerDistance.Compact,
},
movementPattern: { kind: SemanticAnimationKind.Linear, },
},
@ -620,16 +640,18 @@ function moveAsLowLevelMoves(move: Move, startingPos: Map<DancerIdentity, Semant
endPosition: {
...prevEnd,
balance: undefined,
dancerDistance: swingRole,
},
movementPattern: { kind: SemanticAnimationKind.Linear, },
}),
{
beats: move.beats - 2 * balancePartBeats - 1,
endPosition: { ...endPosition, sideCompact: true },
endPosition: { ...endPosition, dancerDistance: DancerDistance.Compact },
movementPattern: {
kind: SemanticAnimationKind.RotateAround,
kind: SemanticAnimationKind.Swing,
minAmount: 360,
around: startPos.which.leftRightSide(),
endFacing: startPos.which.leftRightSide() === CircleSide.Left ? Facing.Right : Facing.Left,
},
},
{
@ -648,7 +670,7 @@ function moveAsLowLevelMoves(move: Move, startingPos: Map<DancerIdentity, Semant
beats: meltdownBeats,
endPosition: {
...prevEnd,
sideCompact: true,
dancerDistance: swingRole,
},
movementPattern: {
kind: SemanticAnimationKind.RotateAround,
@ -660,9 +682,10 @@ function moveAsLowLevelMoves(move: Move, startingPos: Map<DancerIdentity, Semant
beats: move.beats - meltdownBeats,
endPosition: endPosition,
movementPattern: {
kind: SemanticAnimationKind.RotateAround,
kind: SemanticAnimationKind.Swing,
minAmount: 360,
around: startPos.which.leftRightSide(),
endFacing: startPos.which.leftRightSide() === CircleSide.Left ? Facing.Right : Facing.Left,
},
},
], startPos));
@ -1025,7 +1048,22 @@ function SemanticToSetPosition(semantic: SemanticPosition): DancerSetPosition {
default:
throw "Unexpected facing: " + semantic.facing;
}
const yAmount = semantic.sideCompact ? 0.75 : 1;
let yAmount : number;
switch (semantic.dancerDistance) {
case DancerDistance.Normal:
case undefined:
yAmount = 1;
break;
case DancerDistance.Compact:
yAmount = 0.75;
break;
case DancerDistance.SwingLark:
case DancerDistance.SwingRobin:
yAmount = 0.5;
break;
default:
throw "Unknown DancerDistance: " + semantic.dancerDistance;
}
let position: Offset;
switch (semantic.which) {
case CirclePosition.TopLeft:
@ -1066,6 +1104,35 @@ function SemanticToSetPosition(semantic: SemanticPosition): DancerSetPosition {
position.x += setOffset.x;
position.y += setOffset.y;
const isFacingUpOrDown = semantic.facing === Facing.Up || semantic.facing === Facing.Down;
if (semantic.dancerDistance === DancerDistance.SwingLark) {
if (isFacingUpOrDown) {
rotation -= 45;
} else {
rotation += 45;
}
return {
position,
rotation,
leftArmEnd: { x: 0, y: Math.sqrt(1/2)},
rightArmEnd: { x: 0.8, y: 0.5 },
};
} else if (semantic.dancerDistance === DancerDistance.SwingRobin) {
if (isFacingUpOrDown) {
rotation += 45;
} else {
rotation -= 45;
}
return {
position,
rotation,
leftArmEnd: { x: -0.8, y: 0.5 },
rightArmEnd: { x: 0, y: Math.sqrt(1/2) },
};
}
function processHand(hand: Hand, connection: HandConnection | undefined): Offset | undefined {
// TODO Hands. Might need more info? Or need context of nearby dancer SemanticPositions?
if (!connection) return undefined;
@ -1095,8 +1162,8 @@ function SemanticToSetPosition(semantic: SemanticPosition): DancerSetPosition {
return {
position,
rotation,
leftArmEnd: processHand(Hand.Left, semantic.hands?.get(Hand.Left))
rightArmEnd: processHand(Hand.Right, semantic.hands?.get(Hand.Right))
leftArmEnd: processHand(Hand.Left, semantic.hands?.get(Hand.Left)),
rightArmEnd: processHand(Hand.Right, semantic.hands?.get(Hand.Right)),
};
default:
throw "Unsupported PositionKind: " + semantic.kind;
@ -1174,7 +1241,7 @@ export function animateLowLevelMove(move: LowLevelMove): animation.AnimationSegm
// TODO Parameters: rotation, amount, direction, diagonal?
if (move.startPosition.kind !== PositionKind.Circle) {
throw "DoSoDo must start in a circle: " + JSON.stringify(move);
throw "DoSiDo must start in a circle: " + JSON.stringify(move);
}
const center = CenterOfSideOfSet(move.startPosition.which.leftRightSide(), move.startPosition.setOffset, move.startPosition.lineOffset);
return [
@ -1195,7 +1262,7 @@ export function animateLowLevelMove(move: LowLevelMove): animation.AnimationSegm
// TODO Parameters: rotation, amount, direction, diagonal?
if (move.startPosition.kind !== PositionKind.Circle) {
throw "DoSoDo must start in a circle: " + JSON.stringify(move);
throw "Rotate must start in a circle: " + JSON.stringify(move);
}
// TODO Could rotate around other things.
const rotateCenter = move.movementPattern.around === "Center"
@ -1214,6 +1281,36 @@ export function animateLowLevelMove(move: LowLevelMove): animation.AnimationSegm
animation.RotationAnimationFacing.Center,
),
];
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 });
return [
new animation.RotationAnimationSegment(move.beats,
startSetPosition,
beforeUnfold,
move.movementPattern.minAmount,
{
center: rotateSwingCenter,
width: setWidth / 5,
height: setHeight / 5,
},
animation.RotationAnimationFacing.CenterRelative,
),
new animation.LinearAnimationSegment(move.beats,
beforeUnfold,
endSetPosition,
),
];
case SemanticAnimationKind.TwirlSwap:
// TODO
const twirlRotation : number = 180;

View File

@ -79,10 +79,15 @@ function createJsonCell(content: any, rowSpan?: number, id?: DancerIdentity) {
// from https://stackoverflow.com/a/56150320
function replacer(key, value) {
if (value instanceof Map) {
return {
dataType: 'Map',
value: Array.from(value.entries()), // or with spread: value: [...value]
};
let res = {};
for (var entry of value.entries()) {
res[entry[0].toString()] = entry[1];
}
return res;
} else if (!value) {
return value;
} else if (value.enumValue) {
return value.enumValue;
} else {
return value;
}