Compare commits

..

No commits in common. "b6f2451920328a3b7c01388bee8d60cac868d183" and "14c31583e52abc6519f9214925e61812ae4e3ce9" have entirely different histories.

4 changed files with 23 additions and 148 deletions

View File

@ -1,5 +1,5 @@
import { CoupleRole, DancerIdentity, Rotation, normalizeRotation } from "./danceCommon.js"; import { CoupleRole, DancerIdentity, Rotation, normalizeRotation } from "./danceCommon.js";
import { DancerSetPosition, DancersSetPositions, Hand, OffsetDistance, OffsetEquals, OffsetMinus, OffsetPlus, OffsetRotate, OffsetTimes, OffsetTranspose, offsetZero, setHeight } from "./rendererConstants.js"; import { DancerSetPosition, DancersSetPositions, Hand, OffsetEquals, OffsetMinus, OffsetPlus, OffsetRotate, OffsetTimes, OffsetTranspose, offsetZero, setHeight } from "./rendererConstants.js";
import { Offset, leftShoulder, rightShoulder, degreesToRadians, radiansToDegrees } from "./rendererConstants.js"; import { Offset, leftShoulder, rightShoulder, degreesToRadians, radiansToDegrees } from "./rendererConstants.js";
export enum AnimationKind { export enum AnimationKind {
@ -369,16 +369,17 @@ export class StepWideLinearAnimationSegment extends AnimationSegment {
private readonly movementAngle: number; private readonly movementAngle: number;
private readonly handTransitionProgress: number; private readonly handTransitionProgress: number;
private readonly progressCenter?: number; private readonly progressCenter?: number;
private readonly center?: Offset;
private readonly actualCenter: Offset; private readonly actualCenter: Offset;
private readonly hands: Map<Hand, HandAnimation>; private readonly hands: Map<Hand, HandAnimation>;
private readonly facing: "Start" | "Forward"; private readonly facing: "Start" | "Forward";
constructor({ beats, startPosition, endPosition, distanceAtMidpoint, otherPath, hands, facing }: { constructor({ beats, startPosition, endPosition, distanceAtMidpoint, center, hands, facing }: {
beats: number; beats: number;
startPosition: DancerSetPosition; startPosition: DancerSetPosition;
endPosition: DancerSetPosition; endPosition: DancerSetPosition;
distanceAtMidpoint: number; distanceAtMidpoint: number;
otherPath?: { start: Offset, end: Offset }; center?: Offset;
hands?: Map<Hand, HandAnimation>; hands?: Map<Hand, HandAnimation>;
facing?: "Start" | "Forward"; facing?: "Start" | "Forward";
}) { }) {
@ -402,51 +403,13 @@ export class StepWideLinearAnimationSegment extends AnimationSegment {
// Center is the point where the dancer is closest to the other dancer. // 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. // 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. // Otherwise, find the point on the line between the start and end closest to the center.
this.actualCenter = OffsetPlus(this.startPosition.position, OffsetTimes(vector, 0.5)); if (center) {
if (otherPath) { this.actualCenter = this.center = center;
/* this.progressCenter = ((center.x - this.startPosition.position.x) * vector.x
(xa(t), ya(t)) + (center.y - this.startPosition.position.y) * vector.y)
(xb(t), yb(t)) / norm;
} else {
(xa_0 + xa_m*t, ya_0, ya_m*t) this.actualCenter = OffsetPlus(this.startPosition.position, OffsetTimes(vector, 0.5));
(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; this.handTransitionProgress = 0.5 / beats;

View File

@ -1066,7 +1066,6 @@ function moveAsLowLevelMoves({ move, nextMove, startingPos, numProgessions }: {
side: hand, side: hand,
withHands: true, withHands: true,
facing: "Start", facing: "Start",
otherPath: "Swap",
} }
}; };
@ -1150,7 +1149,6 @@ function moveAsLowLevelMoves({ move, nextMove, startingPos, numProgessions }: {
side: hand, side: hand,
withHands: true, withHands: true,
facing: "Forward", facing: "Forward",
otherPath: "Swap",
} }
}, },
prevEnd => ({ prevEnd => ({
@ -1583,7 +1581,6 @@ function moveAsLowLevelMoves({ move, nextMove, startingPos, numProgessions }: {
side: passShoulder, side: passShoulder,
withHands: false, withHands: false,
facing: "Start", facing: "Start",
otherPath: "Swap",
}, },
}], startPos); }], startPos);
} else { } else {
@ -1611,18 +1608,16 @@ function moveAsLowLevelMoves({ move, nextMove, startingPos, numProgessions }: {
return handleCircleMove(({startPos}) => { return handleCircleMove(({startPos}) => {
const startingPos = { ...startPos, facing: startPos.which.facingAcross() }; const startingPos = { ...startPos, facing: startPos.which.facingAcross() };
const swappedPos = { ...startingPos, which: startingPos.which.swapAcross() };
return combine([ return combine([
{ {
beats: move.beats / 2, beats: move.beats / 2,
endPosition: swappedPos, endPosition: {...startingPos, which: startingPos.which.swapAcross()},
movementPattern: { movementPattern: {
kind: SemanticAnimationKind.PassBy, kind: SemanticAnimationKind.PassBy,
side: Hand.Right, side: Hand.Right,
withHands: true, withHands: true,
facing: "Start", facing: "Start",
around: startingPos.which.topBottomSide(), around: startingPos.which.topBottomSide(),
otherPath: "Swap",
}, },
}, },
{ {
@ -1640,11 +1635,6 @@ function moveAsLowLevelMoves({ move, nextMove, startingPos, numProgessions }: {
}); });
case "hey": case "hey":
type HeyStep = {
kind: "StandStill" | "Loop" | "CenterPass" | "EndsPassIn" | "EndsPassOut",
endPosition: SemanticPosition,
}
if (move.parameters.dir !== "across") { if (move.parameters.dir !== "across") {
throw new Error("Unsupported hey direction: " + move.parameters.dir); throw new Error("Unsupported hey direction: " + move.parameters.dir);
} }
@ -1667,77 +1657,12 @@ function moveAsLowLevelMoves({ move, nextMove, startingPos, numProgessions }: {
const firstPassInCenter: boolean = dancerIsPair(move.parameters.who); const firstPassInCenter: boolean = dancerIsPair(move.parameters.who);
const centerShoulder = firstPassInCenter === move.parameters.shoulder ? Hand.Right : Hand.Left; const centerShoulder = firstPassInCenter === move.parameters.shoulder ? Hand.Right : Hand.Left;
const endsShoulder = centerShoulder.opposite(); const endsShoulder = centerShoulder.opposite();
return handleMove(({ id, startPos }) => {
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; const endsInCircle = startPos.kind === PositionKind.Circle;
type HeyStep = {
kind: "StandStill" | "Loop" | "CenterPass" | "EndsPassIn" | "EndsPassOut",
endPosition: SemanticPosition,
}
function heyStepToPartialLowLevelMove(heyStep: HeyStep): PartialLowLevelMove & { heyStep: HeyStep } { function heyStepToPartialLowLevelMove(heyStep: HeyStep): PartialLowLevelMove & { heyStep: HeyStep } {
return { return {
beats: heyPartBeats, beats: heyPartBeats,
@ -1755,7 +1680,6 @@ function moveAsLowLevelMoves({ move, nextMove, startingPos, numProgessions }: {
withHands: false, withHands: false,
side: heyStep.kind === "CenterPass" ? centerShoulder : endsShoulder, side: heyStep.kind === "CenterPass" ? centerShoulder : endsShoulder,
facing: "Start", facing: "Start",
otherPath: undefined!, // Placeholder, fixup later.
}, },
heyStep, heyStep,
}; };
@ -1895,7 +1819,7 @@ function moveAsLowLevelMoves({ move, nextMove, startingPos, numProgessions }: {
heySteps.push(nextHeyStep); heySteps.push(nextHeyStep);
} }
return combine(heySteps.map(heyStepToPartialLowLevelMove), { ...startingPos, hands: undefined }); return combine(heySteps.map(heyStepToPartialLowLevelMove), { ...startingPos, hands: undefined });
})); });
case "turn alone": case "turn alone":
if (move.parameters.who !== "everyone" || move.beats !== 0) { if (move.parameters.who !== "everyone" || move.beats !== 0) {

View File

@ -99,11 +99,6 @@ export type SemanticAnimation = {
around: CircleSideOrCenter, around: CircleSideOrCenter,
otherPath: "Swap" | {
start: SemanticPosition,
end: SemanticPosition,
}
side: Hand, side: Hand,
// If true, pull by the specified hand, if false, just pass by that side without hands. // If true, pull by the specified hand, if false, just pass by that side without hands.
@ -734,7 +729,9 @@ function animateLowLevelMoveWithoutSlide(move: LowLevelMove): animation.Animatio
}) })
]; ];
case SemanticAnimationKind.PassBy: case SemanticAnimationKind.PassBy:
const width = dancerWidth/2; 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 distanceAtMidpoint = move.movementPattern.side == Hand.Left ? +width : -width; const distanceAtMidpoint = move.movementPattern.side == Hand.Left ? +width : -width;
// "Pull By" is just "Pass By" with hands. // "Pull By" is just "Pass By" with hands.
const passByHands = move.movementPattern.withHands const passByHands = move.movementPattern.withHands
@ -762,10 +759,7 @@ function animateLowLevelMoveWithoutSlide(move: LowLevelMove): animation.Animatio
startPosition: startSetPosition, startPosition: startSetPosition,
endPosition: { ...endSetPosition, rotation: endRotation }, endPosition: { ...endSetPosition, rotation: endRotation },
distanceAtMidpoint, distanceAtMidpoint,
otherPath: move.movementPattern.otherPath === "Swap" ? undefined : { center: move.movementPattern.around === "Center" ? passByCenter : undefined, // TODO Better center?
start: SemanticToSetPosition(move.movementPattern.otherPath.start).position,
end: SemanticToSetPosition(move.movementPattern.otherPath.end).position,
},
hands: passByHands, hands: passByHands,
facing: move.movementPattern.facing, facing: move.movementPattern.facing,
}), }),

View File

@ -48,12 +48,6 @@ export function OffsetEquals(a: Offset, b: Offset): boolean {
return a.x === b.x && a.y === b.y; 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 { export interface DancerSetPosition {
// Position of the dancer relative to the center of their set. // Position of the dancer relative to the center of their set.
position: Offset; position: Offset;