Compare commits

...

2 Commits

5 changed files with 115 additions and 27 deletions

View File

@ -43,12 +43,15 @@ export abstract class AnimationSegment {
return interpolateLinear(progress, this.startPosition.rotation, this.endPosition.rotation);
}
abstract drawDebug(ctx: CanvasRenderingContext2D, progress: number);
positionAtFraction(progress: number) : DancerSetPosition {
return {
position: this.interpolateOffset(progress),
rotation: this.interpolateRotation(progress),
leftArmEnd: this.interpolateHandOffset(progress, Hand.Left),
rightArmEnd: this.interpolateHandOffset(progress, Hand.Right),
drawDebug: (ctx) => this.drawDebug(ctx, progress),
}
}
}
@ -84,6 +87,13 @@ export class LinearAnimationSegment extends AnimationSegment {
override interpolateOffset(progress: number): Offset {
return interpolateLinearOffset(progress, this.startPosition.position, this.endPosition.position);
}
override drawDebug(ctx: CanvasRenderingContext2D, progress: number) {
ctx.beginPath();
ctx.moveTo(this.startPosition.position.x, this.startPosition.position.y);
ctx.lineTo(this.endPosition.position.x, this.endPosition.position.y);
ctx.stroke();
}
}
export interface AnimationTransitionFlags {
@ -206,6 +216,11 @@ export class TransitionAnimationSegment extends AnimationSegment {
return actualHandOffset;
}
override drawDebug(ctx: CanvasRenderingContext2D, progress: number) {
// TODO display transition somehow?
this.actualAnimation.drawDebug(ctx, progress);
}
}
// TODO Not sure this really belongs here instead of on some Semantic* type.
@ -359,6 +374,25 @@ export class RotationAnimationSegment extends AnimationSegment {
return hand === Hand.Left ? leftShoulder : rightShoulder;
}
}
override drawDebug(ctx: CanvasRenderingContext2D, progress: number) {
ctx.beginPath();
const distance = this.closer?.middleDistance ?? this.endDistance;
let start: number, end: number;
if (Math.abs(this.endRotation - this.startRotation) >= 360) {
start = 0;
end = 2 * Math.PI;
} else {
start = degreesToRadians(this.startRotation);
end = degreesToRadians(this.endRotation);
}
ctx.ellipse(this.center.x, this.center.y,
this.xRadius * distance, this.yRadius * distance,
0,
start, end,
this.endRotation < this.startRotation);
ctx.stroke();
}
}
// For "PassBy" and related moves where stepping straight forward would walk into the
@ -372,6 +406,7 @@ export class StepWideLinearAnimationSegment extends AnimationSegment {
private readonly actualCenter: Offset;
private readonly hands: Map<Hand, HandAnimation>;
private readonly facing: "Start" | "Forward";
private readonly midPoint: Offset;
constructor({ beats, startPosition, endPosition, distanceAtMidpoint, otherPath, hands, facing }: {
beats: number;
@ -450,6 +485,8 @@ t = -b/2a
}
this.handTransitionProgress = 0.5 / beats;
this.midPoint = this.interpolateOffset(this.progressCenter ?? 0.5);
}
interpolateOffset(progress: number): Offset {
@ -512,6 +549,14 @@ t = -b/2a
return hand.shoulderPosition();
}
}
override drawDebug(ctx: CanvasRenderingContext2D, progress: number) {
ctx.beginPath();
ctx.moveTo(this.startPosition.position.x, this.startPosition.position.y);
ctx.lineTo(this.midPoint.x, this.midPoint.y);
ctx.lineTo(this.endPosition.position.x, this.endPosition.position.y);
ctx.stroke();
}
}
export class SlideAnimationSegment extends AnimationSegment {
@ -558,6 +603,15 @@ export class SlideAnimationSegment extends AnimationSegment {
const { segment, segmentProgress } = this.selectSegment(progress);
return segment.interpolateRotation(segmentProgress);
}
override drawDebug(ctx: CanvasRenderingContext2D, progress: number) {
// TODO Right way to handle the slide part?
const slide = OffsetTimes(this.slideAmount, progress);
ctx.translate(slide.x, slide.y);
const { segment, segmentProgress } = this.selectSegment(progress);
segment.drawDebug(ctx, segmentProgress);
}
}
export class Animation {
@ -633,13 +687,17 @@ export class Animation {
for (const [id, animationSegments] of this.segments.entries()) {
const basePosition = positionAtBeat(animationSegments, beat);
if (progression !== 0) {
const progressionOffset = OffsetTimes(
this.progression,
progression * (id.coupleRole === CoupleRole.Ones ? 1 : -1));
res.set(id, {
...basePosition,
position: OffsetPlus(
basePosition.position,
OffsetTimes(
this.progression,
progression * (id.coupleRole === CoupleRole.Ones ? 1 : -1)))
progressionOffset),
drawDebug: basePosition.drawDebug ?
(ctx) => { ctx.translate(progressionOffset.x, progressionOffset.y); basePosition.drawDebug!(ctx); }
: undefined
});
} else {
res.set(id, basePosition);

View File

@ -1157,7 +1157,7 @@ function moveAsLowLevelMoves({ move, nextMove, startingPos, numProgessions }: {
beats: turnBeats,
endPosition: {
kind: PositionKind.Circle,
which: endWhich.swapUpAndDown(),
which: endWhich,
facing: endWhich.facingAcross(),
hands: prevEnd.hands,
setOffset: prevEnd.setOffset,
@ -1183,7 +1183,7 @@ function moveAsLowLevelMoves({ move, nextMove, startingPos, numProgessions }: {
endPosition: {
...startingPos,
// TODO Does CourtesyTurn always end in same position?
which: startPos.which.swapUpAndDown(),
which: startPos.which,
facing: startPos.which.facingAcross(),
},
movementPattern: {

View File

@ -89,6 +89,13 @@ function updateCanvasSettings(arg : ResetCanvasSetting) {
updateCanvasSettings({});
const debugRender = document.createElement('input');
debugRender.type = 'checkbox';
debugRender.checked = r.drawDebug;
const debugRenderLabel = document.createElement('label');
debugRenderLabel.htmlFor = debugRender.id = 'debugRender';
debugRenderLabel.innerText = 'Debug display';
const bpmSelector = document.createElement('input');
bpmSelector.type = 'number';
bpmSelector.value = '180';
@ -147,6 +154,12 @@ function drawAtCurrentBeat() {
.classList.add('currentMove');
}
debugRender.addEventListener('change', (ev) => {
r.drawDebug = debugRender.checked;
drawAtCurrentBeat();
restartAnimation(false);
})
beatSlider.addEventListener('input', (ev) => {
drawAtCurrentBeat();
beatDisplay.innerText = beatSlider.valueAsNumber.toFixed(1);
@ -400,6 +413,10 @@ displaySettingsDiv.appendChild(document.createElement('br'));
displaySettingsDiv.appendChild(extraLinesLabel);
displaySettingsDiv.appendChild(extraLinesSelector);
displaySettingsDiv.appendChild(document.createElement('br'));
displaySettingsDiv.appendChild(debugRender);
displaySettingsDiv.appendChild(debugRenderLabel);
wrapperDiv.appendChild(displaySettingsDiv);
// Default dance is Two Hearts in Time by Isaac Banner. Selected arbitrarily.

View File

@ -44,6 +44,7 @@ export class Renderer {
animation?: Animation;
extraSets?: number;
extraLines?: number;
drawDebug: boolean = false;
constructor(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) {
this.canvas = canvas;
@ -83,7 +84,7 @@ export class Renderer {
this.ctx.fillStyle = backupFillStyle;
}
drawDancer(position: DancerSetPosition, identity: ExtendedDancerIdentity, offsetSets: number) {
drawDancer(position: DancerSetPosition, identity: ExtendedDancerIdentity, offsetSets: number, drawDebug: boolean) {
this.ctx.save();
this.ctx.translate(identity.relativeLine * lineDistance,
@ -94,41 +95,50 @@ export class Renderer {
};
this.ctx.fillStyle = this.ctx.strokeStyle = colorForDancer(realIdentity);
this.ctx.translate(position.position.x, position.position.y);
this.ctx.rotate(-degreesToRadians(position.rotation));
if (drawDebug) {
if (this.drawDebug && position.drawDebug) {
this.ctx.save();
this.ctx.lineWidth = 0.05;
position.drawDebug(this.ctx);
this.ctx.restore();
}
} else {
this.ctx.translate(position.position.x, position.position.y);
this.ctx.rotate(-degreesToRadians(position.rotation));
this.drawDancerBody(realIdentity);
this.drawDancerBody(realIdentity);
// Draw arms.
this.ctx.lineWidth = 0.03;
if (position.leftArmEnd) {
this.ctx.beginPath();
this.ctx.moveTo(leftShoulder.x, leftShoulder.y);
this.ctx.lineTo(position.leftArmEnd.x, position.leftArmEnd.y);
this.ctx.stroke();
}
if (position.rightArmEnd) {
this.ctx.beginPath();
this.ctx.moveTo(rightShoulder.x, rightShoulder.y);
this.ctx.lineTo(position.rightArmEnd.x, position.rightArmEnd.y);
this.ctx.stroke();
// Draw arms.
this.ctx.lineWidth = 0.03;
if (position.leftArmEnd) {
this.ctx.beginPath();
this.ctx.moveTo(leftShoulder.x, leftShoulder.y);
this.ctx.lineTo(position.leftArmEnd.x, position.leftArmEnd.y);
this.ctx.stroke();
}
if (position.rightArmEnd) {
this.ctx.beginPath();
this.ctx.moveTo(rightShoulder.x, rightShoulder.y);
this.ctx.lineTo(position.rightArmEnd.x, position.rightArmEnd.y);
this.ctx.stroke();
}
}
this.ctx.restore();
}
drawSet(positions: DancersSetPositions, relativeSet: number, relativeLine: number, offsetSets: number) {
drawSet(positions: DancersSetPositions, relativeSet: number, relativeLine: number, offsetSets: number, drawDebug: boolean) {
for (const entry of positions.entries()) {
this.drawDancer(entry[1], { setIdentity: entry[0], relativeLine, relativeSet }, offsetSets);
this.drawDancer(entry[1], { setIdentity: entry[0], relativeLine, relativeSet }, offsetSets, drawDebug);
}
}
drawSets(positions: DancersSetPositions, offsetSets?: number) {
drawSets(positions: DancersSetPositions, offsetSets?: number, drawDebug?: boolean) {
const extraSets = this.extraSets ?? 0;
const extraLines = this.extraLines ?? 0;
for (var relativeLine = -extraLines; relativeLine <= extraLines; relativeLine++) {
for (var relativeSet = -extraSets; relativeSet <= extraSets; relativeSet++) {
this.drawSet(positions, relativeSet, relativeLine, offsetSets ?? 0);
this.drawSet(positions, relativeSet, relativeLine, offsetSets ?? 0, !!drawDebug);
}
}
}
@ -153,7 +163,9 @@ export class Renderer {
for (var i = increments; i >= 0; i--) {
const beatToDraw = beat - i*incrementLength;
this.ctx.globalAlpha = i == 0 ? 1 : (1 - i/increments)*0.3;
this.drawSets(this.animation.positionsAtBeat(beatToDraw, progression % 2), offsetSets);
const positions = this.animation.positionsAtBeat(beatToDraw, progression % 2);
if (this.drawDebug) this.drawSets(positions, offsetSets, true);
this.drawSets(positions, offsetSets, false);
}
}

View File

@ -63,6 +63,7 @@ export interface DancerSetPosition {
rotation: number;
leftArmEnd?: Offset;
rightArmEnd?: Offset;
drawDebug?: (CanvasRenderingContext2D) => void;
}
export type DancersSetPositions = Map<DancerIdentity, DancerSetPosition>;