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); return interpolateLinear(progress, this.startPosition.rotation, this.endPosition.rotation);
} }
abstract drawDebug(ctx: CanvasRenderingContext2D, progress: number);
positionAtFraction(progress: number) : DancerSetPosition { positionAtFraction(progress: number) : DancerSetPosition {
return { return {
position: this.interpolateOffset(progress), position: this.interpolateOffset(progress),
rotation: this.interpolateRotation(progress), rotation: this.interpolateRotation(progress),
leftArmEnd: this.interpolateHandOffset(progress, Hand.Left), leftArmEnd: this.interpolateHandOffset(progress, Hand.Left),
rightArmEnd: this.interpolateHandOffset(progress, Hand.Right), 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 { override interpolateOffset(progress: number): Offset {
return interpolateLinearOffset(progress, this.startPosition.position, this.endPosition.position); 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 { export interface AnimationTransitionFlags {
@ -206,6 +216,11 @@ export class TransitionAnimationSegment extends AnimationSegment {
return actualHandOffset; 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. // 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; 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 // 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 actualCenter: Offset;
private readonly hands: Map<Hand, HandAnimation>; private readonly hands: Map<Hand, HandAnimation>;
private readonly facing: "Start" | "Forward"; private readonly facing: "Start" | "Forward";
private readonly midPoint: Offset;
constructor({ beats, startPosition, endPosition, distanceAtMidpoint, otherPath, hands, facing }: { constructor({ beats, startPosition, endPosition, distanceAtMidpoint, otherPath, hands, facing }: {
beats: number; beats: number;
@ -450,6 +485,8 @@ t = -b/2a
} }
this.handTransitionProgress = 0.5 / beats; this.handTransitionProgress = 0.5 / beats;
this.midPoint = this.interpolateOffset(this.progressCenter ?? 0.5);
} }
interpolateOffset(progress: number): Offset { interpolateOffset(progress: number): Offset {
@ -512,6 +549,14 @@ t = -b/2a
return hand.shoulderPosition(); 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 { export class SlideAnimationSegment extends AnimationSegment {
@ -558,6 +603,15 @@ export class SlideAnimationSegment extends AnimationSegment {
const { segment, segmentProgress } = this.selectSegment(progress); const { segment, segmentProgress } = this.selectSegment(progress);
return segment.interpolateRotation(segmentProgress); 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 { export class Animation {
@ -633,13 +687,17 @@ export class Animation {
for (const [id, animationSegments] of this.segments.entries()) { for (const [id, animationSegments] of this.segments.entries()) {
const basePosition = positionAtBeat(animationSegments, beat); const basePosition = positionAtBeat(animationSegments, beat);
if (progression !== 0) { if (progression !== 0) {
const progressionOffset = OffsetTimes(
this.progression,
progression * (id.coupleRole === CoupleRole.Ones ? 1 : -1));
res.set(id, { res.set(id, {
...basePosition, ...basePosition,
position: OffsetPlus( position: OffsetPlus(
basePosition.position, basePosition.position,
OffsetTimes( progressionOffset),
this.progression, drawDebug: basePosition.drawDebug ?
progression * (id.coupleRole === CoupleRole.Ones ? 1 : -1))) (ctx) => { ctx.translate(progressionOffset.x, progressionOffset.y); basePosition.drawDebug!(ctx); }
: undefined
}); });
} else { } else {
res.set(id, basePosition); res.set(id, basePosition);

View File

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

View File

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

View File

@ -44,6 +44,7 @@ export class Renderer {
animation?: Animation; animation?: Animation;
extraSets?: number; extraSets?: number;
extraLines?: number; extraLines?: number;
drawDebug: boolean = false;
constructor(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) { constructor(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) {
this.canvas = canvas; this.canvas = canvas;
@ -83,7 +84,7 @@ export class Renderer {
this.ctx.fillStyle = backupFillStyle; 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.save();
this.ctx.translate(identity.relativeLine * lineDistance, this.ctx.translate(identity.relativeLine * lineDistance,
@ -94,6 +95,14 @@ export class Renderer {
}; };
this.ctx.fillStyle = this.ctx.strokeStyle = colorForDancer(realIdentity); this.ctx.fillStyle = this.ctx.strokeStyle = colorForDancer(realIdentity);
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.translate(position.position.x, position.position.y);
this.ctx.rotate(-degreesToRadians(position.rotation)); this.ctx.rotate(-degreesToRadians(position.rotation));
@ -113,22 +122,23 @@ export class Renderer {
this.ctx.lineTo(position.rightArmEnd.x, position.rightArmEnd.y); this.ctx.lineTo(position.rightArmEnd.x, position.rightArmEnd.y);
this.ctx.stroke(); this.ctx.stroke();
} }
}
this.ctx.restore(); 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()) { 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 extraSets = this.extraSets ?? 0;
const extraLines = this.extraLines ?? 0; const extraLines = this.extraLines ?? 0;
for (var relativeLine = -extraLines; relativeLine <= extraLines; relativeLine++) { for (var relativeLine = -extraLines; relativeLine <= extraLines; relativeLine++) {
for (var relativeSet = -extraSets; relativeSet <= extraSets; relativeSet++) { 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--) { for (var i = increments; i >= 0; i--) {
const beatToDraw = beat - i*incrementLength; const beatToDraw = beat - i*incrementLength;
this.ctx.globalAlpha = i == 0 ? 1 : (1 - i/increments)*0.3; 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; rotation: number;
leftArmEnd?: Offset; leftArmEnd?: Offset;
rightArmEnd?: Offset; rightArmEnd?: Offset;
drawDebug?: (CanvasRenderingContext2D) => void;
} }
export type DancersSetPositions = Map<DancerIdentity, DancerSetPosition>; export type DancersSetPositions = Map<DancerIdentity, DancerSetPosition>;