Compare commits
2 Commits
14c31583e5
...
b6f2451920
Author | SHA1 | Date | |
---|---|---|---|
b6f2451920 | |||
3f34de63e8 |
|
@ -1,5 +1,5 @@
|
||||||
import { CoupleRole, DancerIdentity, Rotation, normalizeRotation } from "./danceCommon.js";
|
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";
|
import { Offset, leftShoulder, rightShoulder, degreesToRadians, radiansToDegrees } from "./rendererConstants.js";
|
||||||
|
|
||||||
export enum AnimationKind {
|
export enum AnimationKind {
|
||||||
|
@ -369,17 +369,16 @@ 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, center, hands, facing }: {
|
constructor({ beats, startPosition, endPosition, distanceAtMidpoint, otherPath, hands, facing }: {
|
||||||
beats: number;
|
beats: number;
|
||||||
startPosition: DancerSetPosition;
|
startPosition: DancerSetPosition;
|
||||||
endPosition: DancerSetPosition;
|
endPosition: DancerSetPosition;
|
||||||
distanceAtMidpoint: number;
|
distanceAtMidpoint: number;
|
||||||
center?: Offset;
|
otherPath?: { start: Offset, end: Offset };
|
||||||
hands?: Map<Hand, HandAnimation>;
|
hands?: Map<Hand, HandAnimation>;
|
||||||
facing?: "Start" | "Forward";
|
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.
|
// 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.
|
||||||
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));
|
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;
|
this.handTransitionProgress = 0.5 / beats;
|
||||||
|
|
|
@ -1066,6 +1066,7 @@ function moveAsLowLevelMoves({ move, nextMove, startingPos, numProgessions }: {
|
||||||
side: hand,
|
side: hand,
|
||||||
withHands: true,
|
withHands: true,
|
||||||
facing: "Start",
|
facing: "Start",
|
||||||
|
otherPath: "Swap",
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1149,6 +1150,7 @@ function moveAsLowLevelMoves({ move, nextMove, startingPos, numProgessions }: {
|
||||||
side: hand,
|
side: hand,
|
||||||
withHands: true,
|
withHands: true,
|
||||||
facing: "Forward",
|
facing: "Forward",
|
||||||
|
otherPath: "Swap",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
prevEnd => ({
|
prevEnd => ({
|
||||||
|
@ -1581,6 +1583,7 @@ function moveAsLowLevelMoves({ move, nextMove, startingPos, numProgessions }: {
|
||||||
side: passShoulder,
|
side: passShoulder,
|
||||||
withHands: false,
|
withHands: false,
|
||||||
facing: "Start",
|
facing: "Start",
|
||||||
|
otherPath: "Swap",
|
||||||
},
|
},
|
||||||
}], startPos);
|
}], startPos);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1608,16 +1611,18 @@ 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: {...startingPos, which: startingPos.which.swapAcross()},
|
endPosition: swappedPos,
|
||||||
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",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1635,6 +1640,11 @@ 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);
|
||||||
}
|
}
|
||||||
|
@ -1657,12 +1667,77 @@ 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 }) => {
|
|
||||||
const endsInCircle = startPos.kind === PositionKind.Circle;
|
function fixupHeyOtherPath(withoutOtherPath: Map<DancerIdentity, (LowLevelMove & { heyStep?: HeyStep })[]>): Map<DancerIdentity, LowLevelMove[]> {
|
||||||
type HeyStep = {
|
const numSteps = withoutOtherPath.get(DancerIdentity.OnesLark)!.length;
|
||||||
kind: "StandStill" | "Loop" | "CenterPass" | "EndsPassIn" | "EndsPassOut",
|
for (let i = 0; i < numSteps; i++) {
|
||||||
endPosition: SemanticPosition,
|
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 } {
|
function heyStepToPartialLowLevelMove(heyStep: HeyStep): PartialLowLevelMove & { heyStep: HeyStep } {
|
||||||
return {
|
return {
|
||||||
beats: heyPartBeats,
|
beats: heyPartBeats,
|
||||||
|
@ -1680,6 +1755,7 @@ 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,
|
||||||
};
|
};
|
||||||
|
@ -1819,7 +1895,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) {
|
||||||
|
|
|
@ -99,6 +99,11 @@ 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.
|
||||||
|
@ -729,9 +734,7 @@ function animateLowLevelMoveWithoutSlide(move: LowLevelMove): animation.Animatio
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
case SemanticAnimationKind.PassBy:
|
case SemanticAnimationKind.PassBy:
|
||||||
const passByCenter = CenterOf(move.movementPattern.around, move.startPosition.setOffset, move.startPosition.lineOffset);
|
const width = dancerWidth/2;
|
||||||
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
|
||||||
|
@ -759,7 +762,10 @@ function animateLowLevelMoveWithoutSlide(move: LowLevelMove): animation.Animatio
|
||||||
startPosition: startSetPosition,
|
startPosition: startSetPosition,
|
||||||
endPosition: { ...endSetPosition, rotation: endRotation },
|
endPosition: { ...endSetPosition, rotation: endRotation },
|
||||||
distanceAtMidpoint,
|
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,
|
hands: passByHands,
|
||||||
facing: move.movementPattern.facing,
|
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;
|
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;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user