Compare commits
4 Commits
abecb02112
...
17a49d8c45
Author | SHA1 | Date | |
---|---|---|---|
17a49d8c45 | |||
3d073efce8 | |||
d3e8cedef2 | |||
d7e1af26bf |
|
@ -1,5 +1,5 @@
|
||||||
import { DancerIdentity, Rotation, normalizeRotation } from "./danceCommon.js";
|
import { CoupleRole, DancerIdentity, Rotation, normalizeRotation } from "./danceCommon.js";
|
||||||
import { DancerSetPosition, DancersSetPositions, Hand } from "./rendererConstants.js";
|
import { DancerSetPosition, DancersSetPositions, Hand, OffsetEquals, OffsetMinus, OffsetPlus, OffsetTimes, offsetZero } 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 {
|
||||||
|
@ -355,34 +355,87 @@ export class RotationAnimationSegment extends AnimationSegment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Animation = Map<DancerIdentity, AnimationSegment[]>;
|
export class Animation {
|
||||||
export function newAnimation() : Animation { return new Map<DancerIdentity, AnimationSegment[]>() };
|
private readonly segments : Map<DancerIdentity, AnimationSegment[]>;
|
||||||
|
public readonly progression : Offset;
|
||||||
|
public readonly numBeats : number;
|
||||||
|
|
||||||
function positionAtBeat(animation: AnimationSegment[], beat: number): DancerSetPosition {
|
constructor(segments: Map<DancerIdentity, AnimationSegment[]>) {
|
||||||
let lastFrame : AnimationSegment = animation[0];
|
this.segments = segments;
|
||||||
let lastFrameEndBeat = 0;
|
this.numBeats = Math.max(...[...this.segments.values()].map(el => el.reduce((a, s) => a + s.beats, 0)));
|
||||||
for (const frame of animation) {
|
|
||||||
let currentFrameEndBeat = lastFrameEndBeat + frame.beats;
|
const progressions = new Map<DancerIdentity, Offset>(
|
||||||
if (currentFrameEndBeat < beat) {
|
[...this.segments.entries()]
|
||||||
lastFrame = frame;
|
.map(([id, a]) => [
|
||||||
lastFrameEndBeat = currentFrameEndBeat;
|
id,
|
||||||
continue;
|
OffsetMinus(
|
||||||
} else if (currentFrameEndBeat === beat) {
|
a.at(-1)!.endPosition.position,
|
||||||
return frame.endPosition;
|
a.at(0)!.startPosition.position)]));
|
||||||
} else {
|
this.progression = progressions.get(DancerIdentity.OnesLark)!;
|
||||||
const progress = (beat - lastFrameEndBeat) / (currentFrameEndBeat - lastFrameEndBeat);
|
if (this.progression.x !== 0) {
|
||||||
return frame.positionAtFraction(progress);
|
throw "Progressing to different line is unsupported.";
|
||||||
|
}
|
||||||
|
if (this.progression.y === 0) {
|
||||||
|
throw "Dance does not progress.";
|
||||||
|
}
|
||||||
|
if (!OffsetEquals(progressions.get(DancerIdentity.OnesLark)!, progressions.get(DancerIdentity.OnesRobin)!)) {
|
||||||
|
throw "Ones are not progressing the same as each other.";
|
||||||
|
}
|
||||||
|
if (!OffsetEquals(progressions.get(DancerIdentity.TwosLark)!, progressions.get(DancerIdentity.TwosRobin)!)) {
|
||||||
|
throw "Twos are not progressing the same as each other.";
|
||||||
|
}
|
||||||
|
if (!OffsetEquals(progressions.get(DancerIdentity.OnesLark)!, OffsetMinus(offsetZero, progressions.get(DancerIdentity.TwosLark)!))) {
|
||||||
|
throw "Ones and Twos are not progressing opposite each other.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fallthrough
|
positionsAtBeat(beat: number, progression?: number): DancersSetPositions {
|
||||||
return lastFrame.endPosition;
|
function positionAtBeat(animation: AnimationSegment[], beat: number): DancerSetPosition {
|
||||||
}
|
let lastFrame: AnimationSegment = animation[0];
|
||||||
|
let lastFrameEndBeat = 0;
|
||||||
|
for (const frame of animation) {
|
||||||
|
let currentFrameEndBeat = lastFrameEndBeat + frame.beats;
|
||||||
|
if (currentFrameEndBeat < beat) {
|
||||||
|
lastFrame = frame;
|
||||||
|
lastFrameEndBeat = currentFrameEndBeat;
|
||||||
|
continue;
|
||||||
|
} else if (currentFrameEndBeat === beat) {
|
||||||
|
return frame.endPosition;
|
||||||
|
} else {
|
||||||
|
const progress = (beat - lastFrameEndBeat) / (currentFrameEndBeat - lastFrameEndBeat);
|
||||||
|
return frame.positionAtFraction(progress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function positionsAtBeat(animation: Animation, beat: number): DancersSetPositions {
|
// fallthrough
|
||||||
|
return lastFrame.endPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
progression ??= 0;
|
||||||
|
while (beat < 0) {
|
||||||
|
beat += this.numBeats;
|
||||||
|
progression -= 1;
|
||||||
|
}
|
||||||
|
while (beat > this.numBeats) {
|
||||||
|
beat -= this.numBeats;
|
||||||
|
progression += 1;
|
||||||
|
}
|
||||||
const res = new Map<DancerIdentity, DancerSetPosition>();
|
const res = new Map<DancerIdentity, DancerSetPosition>();
|
||||||
for (const [id, animationSegments] of animation.entries()) {
|
for (const [id, animationSegments] of this.segments.entries()) {
|
||||||
res.set(id, positionAtBeat(animationSegments, beat));
|
const basePosition = positionAtBeat(animationSegments, beat);
|
||||||
|
if (progression !== 0) {
|
||||||
|
res.set(id, {
|
||||||
|
...basePosition,
|
||||||
|
position: OffsetPlus(
|
||||||
|
basePosition.position,
|
||||||
|
OffsetTimes(
|
||||||
|
this.progression,
|
||||||
|
progression * (id.coupleRole === CoupleRole.Ones ? 1 : -1)))
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.set(id, basePosition);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -125,6 +125,15 @@ export class DancerIdentity {
|
||||||
public toString() : string {
|
public toString() : string {
|
||||||
return this.danceRole.toString() + "!" + this.coupleRole.toString();
|
return this.danceRole.toString() + "!" + this.coupleRole.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static all() : DancerIdentity[] {
|
||||||
|
return [
|
||||||
|
DancerIdentity.OnesLark,
|
||||||
|
DancerIdentity.OnesRobin,
|
||||||
|
DancerIdentity.TwosLark,
|
||||||
|
DancerIdentity.TwosRobin,
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExtendedDancerIdentity {
|
export interface ExtendedDancerIdentity {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import * as animation from "./animation.js";
|
import * as animation from "./animation.js";
|
||||||
import { CoupleRole, DanceRole, DancerIdentity, Rotation } from "./danceCommon.js";
|
import { CoupleRole, DanceRole, DancerIdentity, Rotation } from "./danceCommon.js";
|
||||||
import * as common from "./danceCommon.js";
|
import * as common from "./danceCommon.js";
|
||||||
import { Hand } from "./rendererConstants.js";
|
import { Hand, setDistance, setHeight } from "./rendererConstants.js";
|
||||||
import { nameLibFigureParameters, Move, LibFigureDance, chooser_pairz } from "./libfigureMapper.js";
|
import { nameLibFigureParameters, Move, LibFigureDance, chooser_pairz } from "./libfigureMapper.js";
|
||||||
import { LowLevelMove, SemanticAnimation, SemanticAnimationKind, animateFromLowLevelMoves } from "./lowLevelMove.js";
|
import { LowLevelMove, SemanticAnimation, SemanticAnimationKind, animateFromLowLevelMoves } from "./lowLevelMove.js";
|
||||||
import { BalanceWeight, CirclePosition, CircleSide, DancerDistance, Facing, HandConnection, HandTo, PositionKind, SemanticPosition } from "./interpreterCommon.js";
|
import { BalanceWeight, CirclePosition, CircleSide, DancerDistance, Facing, HandConnection, HandTo, PositionKind, SemanticPosition } from "./interpreterCommon.js";
|
||||||
|
@ -544,6 +544,12 @@ function danceAsLowLevelMoves(moves: Move[], startingPos: Map<DancerIdentity, Se
|
||||||
currentPos.set(id, newMoveList.at(-1)!.endPosition);
|
currentPos.set(id, newMoveList.at(-1)!.endPosition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const progression = animateFromLowLevelMoves(res).progression;
|
||||||
|
if (progression.x !== 0) throw "Progressing to different line is unsupported.";
|
||||||
|
if (progression.y % setHeight / 2 !== 0) throw "Progression is not an integer number of places.";
|
||||||
|
const progressionInSets = progression.y / setDistance;
|
||||||
|
|
||||||
// fixup end positions to match start of next move
|
// fixup end positions to match start of next move
|
||||||
// TODO Handle progression.
|
// TODO Handle progression.
|
||||||
for (const [id, lowLevelMoves] of res.entries()) {
|
for (const [id, lowLevelMoves] of res.entries()) {
|
||||||
|
@ -556,8 +562,7 @@ function danceAsLowLevelMoves(moves: Move[], startingPos: Map<DancerIdentity, Se
|
||||||
lowLevelMoves[lowLevelMoves.length - 1].endPosition = {
|
lowLevelMoves[lowLevelMoves.length - 1].endPosition = {
|
||||||
...lowLevelMoves[0].startPosition,
|
...lowLevelMoves[0].startPosition,
|
||||||
// progressed
|
// progressed
|
||||||
// TODO progression kind? This assumes single progression. Maybe read from endPosition?
|
setOffset: (startPos.setOffset ?? 0) + (id.coupleRole == CoupleRole.Ones ? 1 : -1) * progressionInSets,
|
||||||
setOffset: (startPos.setOffset ?? 0) + (id.coupleRole == CoupleRole.Ones ? 0.5 : -0.5),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
|
|
|
@ -512,9 +512,9 @@ export function animateLowLevelMove(move: LowLevelMove): animation.AnimationSegm
|
||||||
}
|
}
|
||||||
|
|
||||||
export function animateFromLowLevelMoves(moves: Map<DancerIdentity, LowLevelMove[]>): animation.Animation {
|
export function animateFromLowLevelMoves(moves: Map<DancerIdentity, LowLevelMove[]>): animation.Animation {
|
||||||
const res = animation.newAnimation();
|
const res = new Map<DancerIdentity, animation.AnimationSegment[]>();
|
||||||
for (const [id, moveList] of moves.entries()) {
|
for (const [id, moveList] of moves.entries()) {
|
||||||
res.set(id, moveList.flatMap(move => animateLowLevelMove(move)))
|
res.set(id, moveList.flatMap(move => animateLowLevelMove(move)))
|
||||||
}
|
}
|
||||||
return res;
|
return new animation.Animation(res);
|
||||||
}
|
}
|
|
@ -35,7 +35,7 @@ beatSliderLabel.appendChild(beatSlider);
|
||||||
|
|
||||||
const beatDisplay = document.createElement('span');
|
const beatDisplay = document.createElement('span');
|
||||||
beatDisplay.className = 'beatDisplay';
|
beatDisplay.className = 'beatDisplay';
|
||||||
beatDisplay.innerText = '0';
|
beatDisplay.innerText = '0.0';
|
||||||
|
|
||||||
const ctx = canvas.getContext('2d')!;
|
const ctx = canvas.getContext('2d')!;
|
||||||
|
|
||||||
|
@ -55,6 +55,7 @@ r.extraLines = 3;
|
||||||
r.extraSets = 3;
|
r.extraSets = 3;
|
||||||
r.clear();
|
r.clear();
|
||||||
r.animation = interpreter.interpretedAnimation;
|
r.animation = interpreter.interpretedAnimation;
|
||||||
|
beatSlider.max = r.animation.numBeats.toString();
|
||||||
r.drawSetsWithTrails(0);
|
r.drawSetsWithTrails(0);
|
||||||
|
|
||||||
const bpmSelector = document.createElement('input');
|
const bpmSelector = document.createElement('input');
|
||||||
|
@ -65,20 +66,37 @@ bpmSelector.style.width = '4em';
|
||||||
const bpmLabel = document.createElement('label');
|
const bpmLabel = document.createElement('label');
|
||||||
bpmLabel.innerText = 'BPM';
|
bpmLabel.innerText = 'BPM';
|
||||||
bpmLabel.htmlFor = 'bpm';
|
bpmLabel.htmlFor = 'bpm';
|
||||||
|
|
||||||
|
const progressionSelector = document.createElement('input');
|
||||||
|
progressionSelector.type = 'number';
|
||||||
|
progressionSelector.value = '0';
|
||||||
|
progressionSelector.id = 'progression';
|
||||||
|
progressionSelector.style.width = '4em';
|
||||||
|
const progressionLabel = document.createElement('label');
|
||||||
|
progressionLabel.innerText = 'Progression';
|
||||||
|
progressionLabel.htmlFor = 'progression';
|
||||||
|
|
||||||
const playButton = document.createElement('button');
|
const playButton = document.createElement('button');
|
||||||
playButton.innerText = "Play";
|
playButton.innerText = "Play";
|
||||||
const numBeats = Math.max(...[...r.animation.values()].map(el => el.reduce((a, s) => a + s.beats, 0)));
|
|
||||||
beatSlider.max = numBeats.toString();
|
|
||||||
wrapperDiv.appendChild(bpmSelector);
|
wrapperDiv.appendChild(bpmSelector);
|
||||||
wrapperDiv.appendChild(bpmLabel);
|
wrapperDiv.appendChild(bpmLabel);
|
||||||
wrapperDiv.appendChild(playButton);
|
wrapperDiv.appendChild(playButton);
|
||||||
wrapperDiv.appendChild(document.createElement('br'));
|
wrapperDiv.appendChild(document.createElement('br'));
|
||||||
|
wrapperDiv.appendChild(progressionSelector);
|
||||||
|
wrapperDiv.appendChild(progressionLabel);
|
||||||
|
wrapperDiv.appendChild(document.createElement('br'));
|
||||||
wrapperDiv.appendChild(beatSliderLabel);
|
wrapperDiv.appendChild(beatSliderLabel);
|
||||||
wrapperDiv.appendChild(beatDisplay);
|
wrapperDiv.appendChild(beatDisplay);
|
||||||
|
|
||||||
beatSlider.addEventListener('input', (ev) => {
|
beatSlider.addEventListener('input', (ev) => {
|
||||||
r.drawSetsWithTrails(parseFloat(beatSlider.value));
|
r.drawSetsWithTrails(beatSlider.valueAsNumber, progressionSelector.valueAsNumber);
|
||||||
beatDisplay.innerText = beatSlider.value;
|
beatDisplay.innerText = beatSlider.valueAsNumber.toFixed(1);
|
||||||
|
restartAnimation(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
progressionSelector.addEventListener('input', (ev) => {
|
||||||
|
r.drawSetsWithTrails(beatSlider.valueAsNumber, progressionSelector.valueAsNumber);
|
||||||
restartAnimation(false);
|
restartAnimation(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -92,14 +110,14 @@ function restartAnimation(startIfPaused: boolean) {
|
||||||
const bpm = parseFloat(bpmSelector.value);
|
const bpm = parseFloat(bpmSelector.value);
|
||||||
if (bpm < 0) {
|
if (bpm < 0) {
|
||||||
playAnimation(
|
playAnimation(
|
||||||
parseFloat(bpmSelector.value),
|
bpmSelector.valueAsNumber,
|
||||||
beatSlider.value === '0' ? numBeats : parseFloat(beatSlider.value),
|
beatSlider.value === '0' ? r.animation.numBeats : beatSlider.valueAsNumber,
|
||||||
0);
|
0);
|
||||||
} else if (bpm > 0) {
|
} else if (bpm > 0) {
|
||||||
playAnimation(
|
playAnimation(
|
||||||
parseFloat(bpmSelector.value),
|
bpmSelector.valueAsNumber,
|
||||||
beatSlider.value === beatSlider.max ? 0 : parseFloat(beatSlider.value),
|
beatSlider.value === beatSlider.max ? 0 : beatSlider.valueAsNumber,
|
||||||
numBeats);
|
r.animation.numBeats);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
playButton.addEventListener('click', (ev) => {
|
playButton.addEventListener('click', (ev) => {
|
||||||
|
@ -121,20 +139,30 @@ function playAnimation(bpm: number, start: number, end: number) {
|
||||||
function anim() {
|
function anim() {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const msElapsed = now - startTime;
|
const msElapsed = now - startTime;
|
||||||
const beat = start + msElapsed / msPerBeat;
|
|
||||||
if (bpm > 0 && beat > end || bpm < 0 && beat < end) {
|
let beat = start + msElapsed / msPerBeat;
|
||||||
beatSlider.value = end.toString();
|
let changedProgression = false;
|
||||||
beatDisplay.innerText = end.toString();
|
if (bpm > 0 && beat > end) {
|
||||||
r.drawSetsWithTrails(end);
|
beat -= r.animation.numBeats;
|
||||||
cancelAnim = undefined;
|
progressionSelector.valueAsNumber++;
|
||||||
playButton.innerText = 'Play';
|
changedProgression = true;
|
||||||
} else {
|
|
||||||
beatSlider.value = beat.toString();
|
|
||||||
beatDisplay.innerText = beat.toString();
|
|
||||||
r.drawSetsWithTrails(beat);
|
|
||||||
cancelAnim = requestAnimationFrame(anim);
|
|
||||||
playButton.innerText = 'Pause';
|
|
||||||
}
|
}
|
||||||
|
if (bpm < 0 && beat < end) {
|
||||||
|
beat += r.animation.numBeats;
|
||||||
|
progressionSelector.valueAsNumber--;
|
||||||
|
changedProgression = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
beatSlider.value = beat.toString();
|
||||||
|
beatDisplay.innerText = beat.toFixed(1);
|
||||||
|
|
||||||
|
r.drawSetsWithTrails(beat, progressionSelector.valueAsNumber);
|
||||||
|
if (changedProgression) {
|
||||||
|
restartAnimation(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cancelAnim = requestAnimationFrame(anim);
|
||||||
|
playButton.innerText = 'Pause';
|
||||||
}
|
}
|
||||||
anim();
|
anim();
|
||||||
}
|
}
|
||||||
|
@ -160,7 +188,8 @@ loadDanceButton.addEventListener('click', (ev) => {
|
||||||
playButton.innerText = 'Play';
|
playButton.innerText = 'Play';
|
||||||
}
|
}
|
||||||
beatSlider.value = '0';
|
beatSlider.value = '0';
|
||||||
beatDisplay.innerText = '0';
|
beatSlider.max = r.animation.numBeats.toString();
|
||||||
|
beatDisplay.innerText = '0.0';
|
||||||
r.drawSetsWithTrails(0);
|
r.drawSetsWithTrails(0);
|
||||||
buildDebugTable();
|
buildDebugTable();
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Animation, positionsAtBeat } from "./animation.js";
|
import { Animation } from "./animation.js";
|
||||||
import { CoupleRole, DanceRole, DancerIdentity, ExtendedDancerIdentity } from "./danceCommon.js";
|
import { CoupleRole, DanceRole, DancerIdentity, ExtendedDancerIdentity } from "./danceCommon.js";
|
||||||
import * as exampleAnimations from "./exampleAnimations.js";
|
import * as exampleAnimations from "./exampleAnimations.js";
|
||||||
import { DancerSetPosition, DancersSetPositions, dancerHeight, dancerHeightOffset, leftShoulder, lineDistance, rightShoulder, setDistance, degreesToRadians } from "./rendererConstants.js";
|
import { DancerSetPosition, DancersSetPositions, dancerHeight, dancerHeightOffset, leftShoulder, lineDistance, rightShoulder, setDistance, degreesToRadians } from "./rendererConstants.js";
|
||||||
|
@ -31,7 +31,7 @@ function colorForDancer(identity: ExtendedDancerIdentity) : string {
|
||||||
function colorForDancerLabel(identity: ExtendedDancerIdentity) : string {
|
function colorForDancerLabel(identity: ExtendedDancerIdentity) : string {
|
||||||
const hue = (hueForDancer(identity.setIdentity) + 180) % 360;
|
const hue = (hueForDancer(identity.setIdentity) + 180) % 360;
|
||||||
const sat = 100;
|
const sat = 100;
|
||||||
const lum = hueForDancer(identity.setIdentity) === 240 || identity.relativeSet < 0
|
const lum = hueForDancer(identity.setIdentity) === 240 && identity.relativeSet < 2 || identity.relativeSet < 0
|
||||||
? 100
|
? 100
|
||||||
: 20 - identity.relativeSet * 40;
|
: 20 - identity.relativeSet * 40;
|
||||||
return `hsl(${hue}, ${sat}%, ${lum}%)`;
|
return `hsl(${hue}, ${sat}%, ${lum}%)`;
|
||||||
|
@ -83,17 +83,21 @@ export class Renderer {
|
||||||
this.ctx.fillStyle = backupFillStyle;
|
this.ctx.fillStyle = backupFillStyle;
|
||||||
}
|
}
|
||||||
|
|
||||||
drawDancer(position: DancerSetPosition, identity: ExtendedDancerIdentity) {
|
drawDancer(position: DancerSetPosition, identity: ExtendedDancerIdentity, offsetSets: number) {
|
||||||
this.ctx.save();
|
this.ctx.save();
|
||||||
|
|
||||||
this.ctx.translate(identity.relativeLine * lineDistance,
|
this.ctx.translate(identity.relativeLine * lineDistance,
|
||||||
identity.relativeSet * setDistance);
|
identity.relativeSet * setDistance);
|
||||||
this.ctx.fillStyle = this.ctx.strokeStyle = colorForDancer(identity);
|
const realIdentity = {
|
||||||
|
...identity,
|
||||||
|
relativeSet: identity.relativeSet + (offsetSets * (identity.setIdentity.coupleRole === CoupleRole.Ones ? 1 : -1))
|
||||||
|
};
|
||||||
|
this.ctx.fillStyle = this.ctx.strokeStyle = colorForDancer(realIdentity);
|
||||||
|
|
||||||
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));
|
||||||
|
|
||||||
this.drawDancerBody(identity);
|
this.drawDancerBody(realIdentity);
|
||||||
|
|
||||||
// Draw arms.
|
// Draw arms.
|
||||||
this.ctx.lineWidth = 0.03;
|
this.ctx.lineWidth = 0.03;
|
||||||
|
@ -113,36 +117,37 @@ export class Renderer {
|
||||||
this.ctx.restore();
|
this.ctx.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
drawSet(positions: DancersSetPositions, relativeSet: number, relativeLine: number) {
|
drawSet(positions: DancersSetPositions, relativeSet: number, relativeLine: number, offsetSets: number) {
|
||||||
for (const entry of positions.entries()) {
|
for (const entry of positions.entries()) {
|
||||||
this.drawDancer(entry[1], { setIdentity: entry[0], relativeLine, relativeSet });
|
this.drawDancer(entry[1], { setIdentity: entry[0], relativeLine, relativeSet }, offsetSets);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
drawSets(positions: DancersSetPositions) {
|
drawSets(positions: DancersSetPositions, offsetSets?: number) {
|
||||||
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);
|
this.drawSet(positions, relativeSet, relativeLine, offsetSets ?? 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
drawSetsAtBeat(beat: number) {
|
drawSetsAtBeat(beat: number) {
|
||||||
this.drawSets(positionsAtBeat(this.animation, beat));
|
this.drawSets(this.animation.positionsAtBeat(beat));
|
||||||
}
|
}
|
||||||
|
|
||||||
drawSetsWithTrails(beat: number) {
|
drawSetsWithTrails(beat: number, progression?: number) {
|
||||||
this.clear();
|
this.clear();
|
||||||
const increments = 10;
|
const increments = 10;
|
||||||
const trailLengthInBeats = 1;
|
const trailLengthInBeats = 1;
|
||||||
const incrementLength = trailLengthInBeats / increments;
|
const incrementLength = trailLengthInBeats / increments;
|
||||||
|
progression ??= 0;
|
||||||
|
const offsetSets = -((progression - (progression % 2)) / 2) / ((this.animation.progression.y * 2) / setDistance);
|
||||||
for (var i = increments; i >= 0; i--) {
|
for (var i = increments; i >= 0; i--) {
|
||||||
const beatToDraw = beat - i*incrementLength;
|
const beatToDraw = beat - i*incrementLength;
|
||||||
if (beatToDraw < 0) continue;
|
|
||||||
this.ctx.globalAlpha = i == 0 ? 1 : (1 - i/increments)*0.3;
|
this.ctx.globalAlpha = i == 0 ? 1 : (1 - i/increments)*0.3;
|
||||||
this.drawSets(positionsAtBeat(this.animation, beatToDraw));
|
this.drawSets(this.animation.positionsAtBeat(beatToDraw, progression % 2), offsetSets);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,31 @@ export interface Offset {
|
||||||
y: number;
|
y: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function OffsetPlus(a: Offset, b: Offset) {
|
||||||
|
return {
|
||||||
|
x: a.x + b.x,
|
||||||
|
y: a.y + b.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OffsetTimes(a: Offset, b: number) {
|
||||||
|
return {
|
||||||
|
x: a.x * b,
|
||||||
|
y: a.y * b,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OffsetMinus(a: Offset, b: Offset) {
|
||||||
|
return {
|
||||||
|
x: a.x - b.x,
|
||||||
|
y: a.y - b.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OffsetEquals(a: Offset, b: Offset) {
|
||||||
|
return a.x === b.x && a.y === b.y;
|
||||||
|
}
|
||||||
|
|
||||||
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