Compare commits
2 Commits
14c31583e5
...
b6f2451920
Author | SHA1 | Date | |
---|---|---|---|
b6f2451920 | |||
3f34de63e8 |
|
@ -1,5 +1,5 @@
|
|||
import { CoupleRole, DancerIdentity, Rotation, normalizeRotation } from "./danceCommon.js";
|
||||
import { DancerSetPosition, DancersSetPositions, Hand, OffsetEquals, OffsetMinus, OffsetPlus, OffsetRotate, OffsetTimes, OffsetTranspose, offsetZero, setHeight } from "./rendererConstants.js";
|
||||
import { DancerSetPosition, DancersSetPositions, Hand, OffsetDistance, OffsetEquals, OffsetMinus, OffsetPlus, OffsetRotate, OffsetTimes, OffsetTranspose, offsetZero, setHeight } from "./rendererConstants.js";
|
||||
import { Offset, leftShoulder, rightShoulder, degreesToRadians, radiansToDegrees } from "./rendererConstants.js";
|
||||
|
||||
export enum AnimationKind {
|
||||
|
@ -369,17 +369,16 @@ export class StepWideLinearAnimationSegment extends AnimationSegment {
|
|||
private readonly movementAngle: number;
|
||||
private readonly handTransitionProgress: number;
|
||||
private readonly progressCenter?: number;
|
||||
private readonly center?: Offset;
|
||||
private readonly actualCenter: Offset;
|
||||
private readonly hands: Map<Hand, HandAnimation>;
|
||||
private readonly facing: "Start" | "Forward";
|
||||
|
||||
constructor({ beats, startPosition, endPosition, distanceAtMidpoint, center, hands, facing }: {
|
||||
constructor({ beats, startPosition, endPosition, distanceAtMidpoint, otherPath, hands, facing }: {
|
||||
beats: number;
|
||||
startPosition: DancerSetPosition;
|
||||
endPosition: DancerSetPosition;
|
||||
distanceAtMidpoint: number;
|
||||
center?: Offset;
|
||||
otherPath?: { start: Offset, end: Offset };
|
||||
hands?: Map<Hand, HandAnimation>;
|
||||
facing?: "Start" | "Forward";
|
||||
}) {
|
||||
|
@ -403,13 +402,51 @@ export class StepWideLinearAnimationSegment extends AnimationSegment {
|
|||
// Center is the point where the dancer is closest to the other dancer.
|
||||
// If omitted, it's assumed the movement is symmetrical, so that happens halfway through the move.
|
||||
// Otherwise, find the point on the line between the start and end closest to the center.
|
||||
if (center) {
|
||||
this.actualCenter = this.center = center;
|
||||
this.progressCenter = ((center.x - this.startPosition.position.x) * vector.x
|
||||
+ (center.y - this.startPosition.position.y) * vector.y)
|
||||
/ norm;
|
||||
} else {
|
||||
this.actualCenter = OffsetPlus(this.startPosition.position, OffsetTimes(vector, 0.5));
|
||||
if (otherPath) {
|
||||
/*
|
||||
(xa(t), ya(t))
|
||||
(xb(t), yb(t))
|
||||
|
||||
(xa_0 + xa_m*t, ya_0, ya_m*t)
|
||||
(xb_0 + xb_m*t, yb_0, yb_m*t)
|
||||
|
||||
|
||||
// can omit the sqrt
|
||||
MIN[((xa_0 + xa_m*t) - (xb_0 + xb_m*t))**2 + ((ya_0, ya_m*t) - (yb_0, yb_m*t))**2]
|
||||
MIN[(xa_0-xb_0 + (xa_m-xb_m)*t)**2 + (ya_0-yb_0 + (ya_m-yb_m)*t)**2]
|
||||
MIN[(x_0 + x_m*t)**2 + (y_0 + y_m*t)**2]
|
||||
MIN[(x_0**2 + 2x_0*x_m*t + x_m**2*t**2) + (y_0**2 + 2y_0*y_m*t + y_m**2*t**2)]
|
||||
MIN[(x_0**2+y_0**2 + (2x_0*x_m+2y_0*y_m)*t + (x_m**2+y_m**2)*t**2)]
|
||||
|
||||
a = x_m**2+y_m**2 = (xa_m-xb_m)**2 + (ya_m-yb_m)**2
|
||||
b = (2x_0*x_m+2y_0*y_m) = 2*((xa_0-xb_0)*(xa_m-xb_m) + (ya_0-yb_0)*(ya_m-yb_m))
|
||||
|
||||
t = -b/2a
|
||||
*/
|
||||
const otherVector = OffsetMinus(otherPath.end, otherPath.start);
|
||||
const m = OffsetMinus(otherVector, vector);
|
||||
const i = OffsetMinus(otherPath.start, this.startPosition.position);
|
||||
|
||||
const a = m.x*m.x + m.y*m.y;
|
||||
const b = 2*(i.x*m.x + i.y*m.y);
|
||||
|
||||
const tMin = -b/(2*a);
|
||||
const t = tMin >= 0 && tMin <= 1 ? tMin : undefined;
|
||||
|
||||
if (t !== undefined) {
|
||||
this.progressCenter = t;
|
||||
this.actualCenter = interpolateLinearOffset(t, this.startPosition.position, this.endPosition.position);
|
||||
const otherCenter = interpolateLinearOffset(t, otherPath.start, otherPath.end);
|
||||
const otherSideways = OffsetMinus(this.actualCenter, otherCenter);
|
||||
const sidewaysLinedUp = Math.sign(otherSideways.x) === Math.sign(sideways.x) && Math.sign(otherSideways.y) === Math.sign(sideways.y);
|
||||
const distanceAdjustment = OffsetDistance(this.actualCenter, otherCenter) * (sidewaysLinedUp ? +1 : -1);
|
||||
if (this.distanceAtMidpoint > 0) {
|
||||
this.distanceAtMidpoint = Math.max(0, this.distanceAtMidpoint - distanceAdjustment);
|
||||
} else {
|
||||
this.distanceAtMidpoint = Math.min(0, this.distanceAtMidpoint + distanceAdjustment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.handTransitionProgress = 0.5 / beats;
|
||||
|
|
|
@ -1066,6 +1066,7 @@ function moveAsLowLevelMoves({ move, nextMove, startingPos, numProgessions }: {
|
|||
side: hand,
|
||||
withHands: true,
|
||||
facing: "Start",
|
||||
otherPath: "Swap",
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1149,6 +1150,7 @@ function moveAsLowLevelMoves({ move, nextMove, startingPos, numProgessions }: {
|
|||
side: hand,
|
||||
withHands: true,
|
||||
facing: "Forward",
|
||||
otherPath: "Swap",
|
||||
}
|
||||
},
|
||||
prevEnd => ({
|
||||
|
@ -1581,6 +1583,7 @@ function moveAsLowLevelMoves({ move, nextMove, startingPos, numProgessions }: {
|
|||
side: passShoulder,
|
||||
withHands: false,
|
||||
facing: "Start",
|
||||
otherPath: "Swap",
|
||||
},
|
||||
}], startPos);
|
||||
} else {
|
||||
|
@ -1608,16 +1611,18 @@ function moveAsLowLevelMoves({ move, nextMove, startingPos, numProgessions }: {
|
|||
|
||||
return handleCircleMove(({startPos}) => {
|
||||
const startingPos = { ...startPos, facing: startPos.which.facingAcross() };
|
||||
const swappedPos = { ...startingPos, which: startingPos.which.swapAcross() };
|
||||
return combine([
|
||||
{
|
||||
beats: move.beats / 2,
|
||||
endPosition: {...startingPos, which: startingPos.which.swapAcross()},
|
||||
endPosition: swappedPos,
|
||||
movementPattern: {
|
||||
kind: SemanticAnimationKind.PassBy,
|
||||
side: Hand.Right,
|
||||
withHands: true,
|
||||
facing: "Start",
|
||||
around: startingPos.which.topBottomSide(),
|
||||
otherPath: "Swap",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -1635,6 +1640,11 @@ function moveAsLowLevelMoves({ move, nextMove, startingPos, numProgessions }: {
|
|||
});
|
||||
|
||||
case "hey":
|
||||
type HeyStep = {
|
||||
kind: "StandStill" | "Loop" | "CenterPass" | "EndsPassIn" | "EndsPassOut",
|
||||
endPosition: SemanticPosition,
|
||||
}
|
||||
|
||||
if (move.parameters.dir !== "across") {
|
||||
throw new Error("Unsupported hey direction: " + move.parameters.dir);
|
||||
}
|
||||
|
@ -1657,12 +1667,77 @@ function moveAsLowLevelMoves({ move, nextMove, startingPos, numProgessions }: {
|
|||
const firstPassInCenter: boolean = dancerIsPair(move.parameters.who);
|
||||
const centerShoulder = firstPassInCenter === move.parameters.shoulder ? Hand.Right : Hand.Left;
|
||||
const endsShoulder = centerShoulder.opposite();
|
||||
return handleMove(({ id, startPos }) => {
|
||||
const endsInCircle = startPos.kind === PositionKind.Circle;
|
||||
type HeyStep = {
|
||||
kind: "StandStill" | "Loop" | "CenterPass" | "EndsPassIn" | "EndsPassOut",
|
||||
endPosition: SemanticPosition,
|
||||
|
||||
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(handleMove(({ id, startPos }) => {
|
||||
const endsInCircle = startPos.kind === PositionKind.Circle;
|
||||
function heyStepToPartialLowLevelMove(heyStep: HeyStep): PartialLowLevelMove & { heyStep: HeyStep } {
|
||||
return {
|
||||
beats: heyPartBeats,
|
||||
|
@ -1680,6 +1755,7 @@ function moveAsLowLevelMoves({ move, nextMove, startingPos, numProgessions }: {
|
|||
withHands: false,
|
||||
side: heyStep.kind === "CenterPass" ? centerShoulder : endsShoulder,
|
||||
facing: "Start",
|
||||
otherPath: undefined!, // Placeholder, fixup later.
|
||||
},
|
||||
heyStep,
|
||||
};
|
||||
|
@ -1819,7 +1895,7 @@ function moveAsLowLevelMoves({ move, nextMove, startingPos, numProgessions }: {
|
|||
heySteps.push(nextHeyStep);
|
||||
}
|
||||
return combine(heySteps.map(heyStepToPartialLowLevelMove), { ...startingPos, hands: undefined });
|
||||
});
|
||||
}));
|
||||
|
||||
case "turn alone":
|
||||
if (move.parameters.who !== "everyone" || move.beats !== 0) {
|
||||
|
|
|
@ -99,6 +99,11 @@ export type SemanticAnimation = {
|
|||
|
||||
around: CircleSideOrCenter,
|
||||
|
||||
otherPath: "Swap" | {
|
||||
start: SemanticPosition,
|
||||
end: SemanticPosition,
|
||||
}
|
||||
|
||||
side: Hand,
|
||||
|
||||
// If true, pull by the specified hand, if false, just pass by that side without hands.
|
||||
|
@ -729,9 +734,7 @@ function animateLowLevelMoveWithoutSlide(move: LowLevelMove): animation.Animatio
|
|||
})
|
||||
];
|
||||
case SemanticAnimationKind.PassBy:
|
||||
const passByCenter = CenterOf(move.movementPattern.around, move.startPosition.setOffset, move.startPosition.lineOffset);
|
||||
const wide = move.movementPattern.around === "Center";
|
||||
const width = wide ? dancerWidth : dancerWidth/2;
|
||||
const width = dancerWidth/2;
|
||||
const distanceAtMidpoint = move.movementPattern.side == Hand.Left ? +width : -width;
|
||||
// "Pull By" is just "Pass By" with hands.
|
||||
const passByHands = move.movementPattern.withHands
|
||||
|
@ -759,7 +762,10 @@ function animateLowLevelMoveWithoutSlide(move: LowLevelMove): animation.Animatio
|
|||
startPosition: startSetPosition,
|
||||
endPosition: { ...endSetPosition, rotation: endRotation },
|
||||
distanceAtMidpoint,
|
||||
center: move.movementPattern.around === "Center" ? passByCenter : undefined, // TODO Better center?
|
||||
otherPath: move.movementPattern.otherPath === "Swap" ? undefined : {
|
||||
start: SemanticToSetPosition(move.movementPattern.otherPath.start).position,
|
||||
end: SemanticToSetPosition(move.movementPattern.otherPath.end).position,
|
||||
},
|
||||
hands: passByHands,
|
||||
facing: move.movementPattern.facing,
|
||||
}),
|
||||
|
|
|
@ -48,6 +48,12 @@ export function OffsetEquals(a: Offset, b: Offset): boolean {
|
|||
return a.x === b.x && a.y === b.y;
|
||||
}
|
||||
|
||||
export function OffsetDistance(a: Offset, b: Offset): number {
|
||||
const dx = a.x - b.x;
|
||||
const dy = a.y - b.y;
|
||||
return Math.sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
export interface DancerSetPosition {
|
||||
// Position of the dancer relative to the center of their set.
|
||||
position: Offset;
|
||||
|
|
Loading…
Reference in New Issue
Block a user