Compare commits
10 Commits
7f63a9866b
...
d2f07b203b
Author | SHA1 | Date | |
---|---|---|---|
d2f07b203b | |||
192edd4356 | |||
c0abd76a0c | |||
f971e52736 | |||
b16267b9dc | |||
b683803ab7 | |||
15937c55f7 | |||
6f2259faf2 | |||
82bc463859 | |||
6ace619ec1 |
|
@ -58,29 +58,58 @@
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
th.Ones.Lark {
|
|
||||||
background-color: hsl(0, 80%, 50%);
|
th.Ones {
|
||||||
|
background-color: hsl(27, 99%, 59%);
|
||||||
}
|
}
|
||||||
td.Ones.Lark {
|
td.Ones {
|
||||||
background-color: hsl(0, 90%, 70%);
|
background-color: hsl(27, 99%, 85%);
|
||||||
}
|
}
|
||||||
th.Ones.Robin {
|
th.Twos {
|
||||||
background-color: hsl(39, 80%, 50%);
|
background-color: hsl(249, 42%, 57%);
|
||||||
}
|
}
|
||||||
td.Ones.Robin {
|
td.Twos {
|
||||||
background-color: hsl(39, 90%, 70%);
|
background-color: hsl(249, 52%, 85%);
|
||||||
}
|
}
|
||||||
th.Twos.Lark {
|
|
||||||
background-color: hsl(240, 70%, 65%);
|
th.Lark::before {
|
||||||
|
content: "◠";
|
||||||
|
text-decoration: line-through;
|
||||||
|
padding-right: 1ex;
|
||||||
}
|
}
|
||||||
td.Twos.Lark {
|
th.Robin::before {
|
||||||
background-color: hsl(240, 90%, 80%);
|
content: "△";
|
||||||
|
padding-right: 1ex;
|
||||||
}
|
}
|
||||||
th.Twos.Robin {
|
|
||||||
background-color: hsl(180, 80%, 50%);
|
td.Lark.Ones {
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at bottom left, hsl(27, 99%, 75%) 15%, transparent 16%),
|
||||||
|
radial-gradient(circle at bottom right, hsl(27, 99%, 75%) 15%, transparent 16%),
|
||||||
|
hsl(27, 99%, 85%);
|
||||||
|
background-size: 6em 3em;
|
||||||
}
|
}
|
||||||
td.Twos.Robin {
|
td.Lark.Twos {
|
||||||
background-color: hsl(180, 90%, 70%);
|
background:
|
||||||
|
radial-gradient(circle at bottom left, hsl(249, 42%, 75%) 15%, transparent 16%),
|
||||||
|
radial-gradient(circle at bottom right,hsl(249, 42%, 75%) 15%, transparent 16%),
|
||||||
|
hsl(249, 52%, 85%);
|
||||||
|
background-size: 6em 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.Robin.Ones {
|
||||||
|
background:
|
||||||
|
linear-gradient(45deg,hsl(27, 99%, 75%) 10%, transparent 10%),
|
||||||
|
linear-gradient(135deg, transparent 90%,hsl(27, 99%, 75%) 90%),
|
||||||
|
hsl(27, 99%, 85%);
|
||||||
|
background-size: 6em 3em;
|
||||||
|
}
|
||||||
|
td.Robin.Twos {
|
||||||
|
background:
|
||||||
|
linear-gradient(45deg,hsl(249, 42%, 75%) 10%, transparent 10%),
|
||||||
|
linear-gradient(135deg, transparent 90%,hsl(249, 42%, 75%) 90%),
|
||||||
|
hsl(249, 52%, 85%);
|
||||||
|
background-size: 6em 3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.move {
|
.move {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { nameLibFigureParameters, Move, chooser_pairz } from "./libfigureMapper.
|
||||||
import { LowLevelMove, SemanticAnimation, SemanticAnimationKind, animateFromLowLevelMoves } from "./lowLevelMove.js";
|
import { LowLevelMove, SemanticAnimation, SemanticAnimationKind, animateFromLowLevelMoves } from "./lowLevelMove.js";
|
||||||
import { BalanceWeight, CirclePosition, CircleSide, CircleSideOrCenter, DancerDistance, Facing, HandConnection, HandTo, LongLines, PositionKind, SemanticPosition, ShortLinesPosition, StarGrip, handToDancerToSideInCircleFacingAcross, handsFourImproper, handsInCircle, handsInLine, handsInShortLine, oppositeFacing } from "./interpreterCommon.js";
|
import { BalanceWeight, CirclePosition, CircleSide, CircleSideOrCenter, DancerDistance, Facing, HandConnection, HandTo, LongLines, PositionKind, SemanticPosition, ShortLinesPosition, StarGrip, handToDancerToSideInCircleFacingAcross, handsFourImproper, handsInCircle, handsInLine, handsInShortLine, oppositeFacing } from "./interpreterCommon.js";
|
||||||
import { ContraDBDance } from "./danceLibrary.js";
|
import { ContraDBDance } from "./danceLibrary.js";
|
||||||
import { errorMoveInterpreterCtor, moveInterpreters } from "./moves/_moveInterpreter.js";
|
import { AllVariantsForMoveArgs, ErrorMoveInterpreter, LowLevelMovesForAllDancers, MoveAsLowLevelMovesArgs, SemanticPositionsForAllDancers, Variant, VariantCollection, errorMoveInterpreterCtor, moveInterpreters } from "./moves/_moveInterpreter.js";
|
||||||
|
|
||||||
// Import all individual move files.
|
// Import all individual move files.
|
||||||
// ls [a-z]*.js | sed 's@.*@import "./moves/\0";@'
|
// ls [a-z]*.js | sed 's@.*@import "./moves/\0";@'
|
||||||
|
@ -46,62 +46,111 @@ import "./moves/turnAlone.js";
|
||||||
import "./moves/upTheHall.js";
|
import "./moves/upTheHall.js";
|
||||||
|
|
||||||
|
|
||||||
function moveAsLowLevelMoves({ move, nextMove, startingPos, numProgessions }: { move: Move; nextMove: Move; startingPos: Map<DancerIdentity, SemanticPosition>; numProgessions: number; }): Map<DancerIdentity, LowLevelMove[]> {
|
// TODO Get rid of nextMove here once variants are actually supported by Swing.
|
||||||
|
function allVariantsForMove({ move, nextMove, startingVariants, numProgessions }: { move: Move; nextMove: Move; startingVariants: AllVariantsForMoveArgs; numProgessions: number; }): VariantCollection {
|
||||||
const moveInterpreter = moveInterpreters.get(move.move) ?? errorMoveInterpreterCtor;
|
const moveInterpreter = moveInterpreters.get(move.move) ?? errorMoveInterpreterCtor;
|
||||||
return new moveInterpreter({ move, nextMove, numProgessions }).moveAsLowLevelMoves({ startingPos });
|
return new moveInterpreter({ move, nextMove, numProgessions }).allVariantsForMove(startingVariants);
|
||||||
}
|
}
|
||||||
|
|
||||||
function danceAsLowLevelMoves(moves: Move[], startingPos: Map<DancerIdentity, SemanticPosition>): Map<DancerIdentity, LowLevelMove[]> {
|
|
||||||
const res = new Map<DancerIdentity, LowLevelMove[]>([...startingPos.keys()].map(id => [id, []]));
|
function danceAsLowLevelMoves(moves: Move[], startingVariants: AllVariantsForMoveArgs): Map<DancerIdentity, LowLevelMove[]> {
|
||||||
let currentPos = new Map<DancerIdentity, SemanticPosition>(startingPos);
|
let currentVariants = new Map<string, MoveAsLowLevelMovesArgs>(startingVariants);
|
||||||
|
let currentVariantMoves: Map<string, LowLevelMovesForAllDancers> = new Map<string, LowLevelMovesForAllDancers>(
|
||||||
|
[...startingVariants.keys()].map(name => [name, new Map<DancerIdentity, LowLevelMove[]>(DancerIdentity.all().map(id => [id, []]))]));
|
||||||
let numProgessions = 0;
|
let numProgessions = 0;
|
||||||
for (let i = 0; i < moves.length; i++) {
|
for (let i = 0; i < moves.length; i++) {
|
||||||
const move = moves[i];
|
const move = moves[i];
|
||||||
const nextMove = i === moves.length - 1 ? moves[0] : moves[i + 1];
|
const nextMove = i === moves.length - 1 ? moves[0] : moves[i + 1];
|
||||||
|
|
||||||
|
function updateVariants(newVariants: VariantCollection) {
|
||||||
|
let previousVariants = currentVariants;
|
||||||
|
let previousVariantMoves = currentVariantMoves;
|
||||||
|
try {
|
||||||
|
currentVariants = new Map<string, MoveAsLowLevelMovesArgs>();
|
||||||
|
currentVariantMoves = new Map<string, LowLevelMovesForAllDancers>();
|
||||||
|
|
||||||
|
variant: for (const [name, variant] of newVariants) {
|
||||||
|
const oldMoves = previousVariantMoves.get(variant.previousMoveVariant);
|
||||||
|
const oldPos = previousVariants.get(variant.previousMoveVariant);
|
||||||
|
if (!oldMoves || !oldPos) {
|
||||||
|
throw new Error("Referenced unknown previous variant: " + variant.previousMoveVariant);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newMoves: LowLevelMovesForAllDancers = new Map<DancerIdentity, LowLevelMove[]>();
|
||||||
|
const newPositions = new Map<DancerIdentity, SemanticPosition>();
|
||||||
|
for (const [id, newMoveList] of variant.lowLevelMoves.entries()) {
|
||||||
|
const prevEnd = oldPos.startingPos.get(id)!;
|
||||||
|
const newStart = newMoveList[0].startPosition;
|
||||||
|
|
||||||
|
if ((prevEnd.setOffset ?? 0) != (newStart.setOffset ?? 0)
|
||||||
|
|| (prevEnd.lineOffset ?? 0) != (newStart.lineOffset ?? 0)
|
||||||
|
|| prevEnd.kind != newStart.kind
|
||||||
|
|| prevEnd.which != newStart.which
|
||||||
|
|| prevEnd.balance != newStart.balance
|
||||||
|
|| prevEnd.dancerDistance != newStart.dancerDistance) {
|
||||||
|
// TODO Should this be considered a bug?
|
||||||
|
// TODO Require more properties to match? facing? probably not hands.
|
||||||
|
continue variant;
|
||||||
|
}
|
||||||
|
|
||||||
|
newMoves.set(id, [...oldMoves.get(id)!, ...newMoveList]);
|
||||||
|
newPositions.set(id, newMoveList.at(-1)!.endPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentVariants.set(name, { startingPos: newPositions });
|
||||||
|
currentVariantMoves.set(name, newMoves);
|
||||||
|
}
|
||||||
|
} catch (ex) {
|
||||||
|
currentVariants = previousVariants;
|
||||||
|
currentVariantMoves = previousVariantMoves;
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (i > 0 && move.beats === 0 && move.move === "slide along set") {
|
if (i > 0 && move.beats === 0 && move.move === "slide along set") {
|
||||||
const slideLeft: boolean = move.parameters.slide;
|
const slideLeft: boolean = move.parameters.slide;
|
||||||
for (const [id, currPos] of currentPos.entries()) {
|
for (const [name, currentPos] of currentVariants.entries()) {
|
||||||
const slideAmount = (currPos.which.leftRightSide() === CircleSide.Left) === slideLeft ? +0.5 : -0.5;
|
for (const [id, currPos] of currentPos.startingPos.entries()) {
|
||||||
const setOffset = (currPos.setOffset ?? 0) + slideAmount;
|
const slideAmount = (currPos.which.leftRightSide() === CircleSide.Left) === slideLeft ? +0.5 : -0.5;
|
||||||
currentPos.set(id, { ...currPos, setOffset });
|
const setOffset = (currPos.setOffset ?? 0) + slideAmount;
|
||||||
|
currentPos.startingPos.set(id, { ...currPos, setOffset });
|
||||||
|
|
||||||
const prevMove = res.get(id)!.at(-1)!;
|
const prevMove = currentVariantMoves.get(name)!.get(id)!.at(-1)!;
|
||||||
prevMove.movementPattern.setSlideAmount = slideAmount;
|
prevMove.movementPattern.setSlideAmount = slideAmount;
|
||||||
prevMove.endPosition.setOffset = setOffset;
|
prevMove.endPosition.setOffset = setOffset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
const newMoves = moveAsLowLevelMoves({ move, nextMove, startingPos: currentPos, numProgessions });
|
const newMoves = allVariantsForMove({ move, nextMove, startingVariants: currentVariants, numProgessions });
|
||||||
for (const [id, newMoveList] of newMoves.entries()) {
|
if (newMoves.size === 0) {
|
||||||
res.get(id)!.push(...newMoveList);
|
updateVariants(new ErrorMoveInterpreter({ move, nextMove, numProgessions },
|
||||||
currentPos.set(id, newMoveList.at(-1)!.endPosition);
|
"ERROR: " + move.move + " has no valid starting variants.").allVariantsForMove(currentVariants));
|
||||||
|
} else {
|
||||||
|
updateVariants(newMoves);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (ex) {
|
catch (ex) {
|
||||||
// catch exception so something can be displayed
|
// catch exception so something can be displayed
|
||||||
for (const [id, pos] of currentPos.entries()) {
|
const errorMessage: string = ex instanceof Error ? ex.message : ex;
|
||||||
res.get(id)!.push({
|
const errorVariants = new ErrorMoveInterpreter({ move, nextMove, numProgessions }, errorMessage).allVariantsForMove(currentVariants);
|
||||||
beats: move.beats,
|
updateVariants(errorVariants);
|
||||||
startPosition: pos,
|
|
||||||
endPosition: pos,
|
|
||||||
movementPattern: { kind: SemanticAnimationKind.StandStill },
|
|
||||||
move,
|
|
||||||
startBeat: 0,
|
|
||||||
interpreterError: ex instanceof Error ? ex.message : ex,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (move.progression) numProgessions++;
|
if (move.progression) numProgessions++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const res = [...currentVariantMoves.values()].at(-1)!;
|
||||||
|
if (currentVariantMoves.size !== 1) {
|
||||||
|
res.get(DancerIdentity.OnesRobin)![0].interpreterError = "Expected exactly one variant. Found "
|
||||||
|
+ currentVariantMoves.size + ": " + [...currentVariantMoves.keys()].join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const progression = animateFromLowLevelMoves(res).progression;
|
const progression = animateFromLowLevelMoves(res).progression;
|
||||||
const progressionInSets = progression.y / setDistance;
|
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.
|
|
||||||
for (const [id, lowLevelMoves] of res.entries()) {
|
for (const [id, lowLevelMoves] of res.entries()) {
|
||||||
for (let i = 0; i < lowLevelMoves.length - 1; i++) {
|
for (let i = 0; i < lowLevelMoves.length - 1; i++) {
|
||||||
if (!lowLevelMoves[i].endPosition) throw "endPosition is undefined";
|
if (!lowLevelMoves[i].endPosition) throw "endPosition is undefined";
|
||||||
|
@ -198,6 +247,15 @@ function StartingPosForFormation(formation: common.StartFormation, dance?: Contr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function startingVariantsForFormation(formation: common.StartFormation, dance?: ContraDBDance) : AllVariantsForMoveArgs {
|
||||||
|
// TODO Handle various different starting position that all count as "improper" like short/long waves, etc.
|
||||||
|
const pos = StartingPosForFormation(formation, dance);
|
||||||
|
|
||||||
|
return new Map<string, MoveAsLowLevelMovesArgs>([
|
||||||
|
["Initial", { startingPos: pos }]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
export let mappedDance: Move[];
|
export let mappedDance: Move[];
|
||||||
|
|
||||||
export let interpretedDance: Map<DancerIdentity, LowLevelMove[]>;
|
export let interpretedDance: Map<DancerIdentity, LowLevelMove[]>;
|
||||||
|
@ -205,7 +263,7 @@ export let interpretedAnimation: animation.Animation;
|
||||||
|
|
||||||
export function loadDance(dance: ContraDBDance): animation.Animation {
|
export function loadDance(dance: ContraDBDance): animation.Animation {
|
||||||
mappedDance = dance.figures.map(nameLibFigureParameters);
|
mappedDance = dance.figures.map(nameLibFigureParameters);
|
||||||
interpretedDance = danceAsLowLevelMoves(mappedDance, StartingPosForFormation(dance.start_type, dance));
|
interpretedDance = danceAsLowLevelMoves(mappedDance, startingVariantsForFormation(dance.start_type, dance));
|
||||||
interpretedAnimation = animateFromLowLevelMoves(interpretedDance);
|
interpretedAnimation = animateFromLowLevelMoves(interpretedDance);
|
||||||
|
|
||||||
return interpretedAnimation;
|
return interpretedAnimation;
|
||||||
|
|
|
@ -9,7 +9,7 @@ export const moveInterpreters: Map<MoveName, MoveInterpreterCtor<MoveName>> = ne
|
||||||
|
|
||||||
export interface MoveInterpreterCtorArgs<N extends MoveName> {
|
export interface MoveInterpreterCtorArgs<N extends MoveName> {
|
||||||
move: Move & { move: N };
|
move: Move & { move: N };
|
||||||
nextMove: Move;
|
nextMove?: Move;
|
||||||
numProgessions: number;
|
numProgessions: number;
|
||||||
}
|
}
|
||||||
export type SemanticPositionsForAllDancers = Map<DancerIdentity, SemanticPosition>;
|
export type SemanticPositionsForAllDancers = Map<DancerIdentity, SemanticPosition>;
|
||||||
|
@ -18,11 +18,13 @@ export interface MoveAsLowLevelMovesArgs {
|
||||||
}
|
}
|
||||||
export type LowLevelMovesForAllDancers = Map<DancerIdentity, LowLevelMove[]>;
|
export type LowLevelMovesForAllDancers = Map<DancerIdentity, LowLevelMove[]>;
|
||||||
export interface Variant {
|
export interface Variant {
|
||||||
previousMoveVariant?: string,
|
previousMoveVariant: string,
|
||||||
lowLevelMoves: LowLevelMovesForAllDancers,
|
lowLevelMoves: LowLevelMovesForAllDancers,
|
||||||
};
|
};
|
||||||
export type VariantCollection = Map<string, Variant>;
|
export type VariantCollection = Map<string, Variant>;
|
||||||
|
|
||||||
|
export type AllVariantsForMoveArgs = Map<string, MoveAsLowLevelMovesArgs>;
|
||||||
|
|
||||||
export type PartialLowLevelMove = {
|
export type PartialLowLevelMove = {
|
||||||
remarks?: string,
|
remarks?: string,
|
||||||
beats: number,
|
beats: number,
|
||||||
|
@ -32,8 +34,7 @@ export type PartialLowLevelMove = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ISingleVariantMoveInterpreter {
|
export interface ISingleVariantMoveInterpreter {
|
||||||
moveAsLowLevelMoves: () => LowLevelMovesForAllDancers;
|
moveAsVariants: (previousMoveVariant: string | undefined) => VariantCollection;
|
||||||
moveAsVariants: () => VariantCollection;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export abstract class SingleVariantMoveInterpreter<T extends MoveInterpreter<N>, N extends MoveName> implements ISingleVariantMoveInterpreter {
|
export abstract class SingleVariantMoveInterpreter<T extends MoveInterpreter<N>, N extends MoveName> implements ISingleVariantMoveInterpreter {
|
||||||
|
@ -49,11 +50,15 @@ export abstract class SingleVariantMoveInterpreter<T extends MoveInterpreter<N>,
|
||||||
return this.moveInterpreter.move;
|
return this.moveInterpreter.move;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract moveAsLowLevelMoves(): LowLevelMovesForAllDancers;
|
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
||||||
|
throw new Error("You must implement either moveAsLowLevelMoves() or moveAsVariants().");
|
||||||
|
}
|
||||||
|
|
||||||
moveAsVariants(): VariantCollection {
|
// TODO It would make more sense for previousMoveVariant to get passed into the constructor...
|
||||||
|
// ... but that requires touching every subclass, so it's an annoying refactor.
|
||||||
|
moveAsVariants(previousMoveVariant: string): VariantCollection {
|
||||||
return new Map<string, Variant>([
|
return new Map<string, Variant>([
|
||||||
["default", { lowLevelMoves: this.moveAsLowLevelMoves() }]
|
["default", { lowLevelMoves: this.moveAsLowLevelMoves(), previousMoveVariant }]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -293,10 +298,10 @@ export abstract class SingleVariantMoveInterpreter<T extends MoveInterpreter<N>,
|
||||||
} : undefined);
|
} : undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
errorStandStill() {
|
errorStandStill(error?: string) {
|
||||||
return this.handleMove(({ startPos }) => {
|
return this.handleMove(({ startPos }) => {
|
||||||
return [{
|
return [{
|
||||||
interpreterError: "UNKNOWN MOVE '" + this.move.move + "': standing still",
|
interpreterError: error ?? ("UNKNOWN MOVE '" + this.move.move + "': standing still"),
|
||||||
move: this.move,
|
move: this.move,
|
||||||
startBeat: 0,
|
startBeat: 0,
|
||||||
beats: this.move.beats,
|
beats: this.move.beats,
|
||||||
|
@ -312,7 +317,7 @@ export abstract class SingleVariantMoveInterpreter<T extends MoveInterpreter<N>,
|
||||||
|
|
||||||
export abstract class MoveInterpreter<N extends MoveName> {
|
export abstract class MoveInterpreter<N extends MoveName> {
|
||||||
public readonly move: Move & { move: N };
|
public readonly move: Move & { move: N };
|
||||||
public readonly nextMove: Move;
|
public readonly nextMove?: Move;
|
||||||
public readonly numProgressions: number;
|
public readonly numProgressions: number;
|
||||||
|
|
||||||
constructor({ move, nextMove, numProgessions }: MoveInterpreterCtorArgs<N>) {
|
constructor({ move, nextMove, numProgessions }: MoveInterpreterCtorArgs<N>) {
|
||||||
|
@ -323,19 +328,61 @@ export abstract class MoveInterpreter<N extends MoveName> {
|
||||||
|
|
||||||
abstract buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter;
|
abstract buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter;
|
||||||
|
|
||||||
moveAsLowLevelMoves({ startingPos }: MoveAsLowLevelMovesArgs): LowLevelMovesForAllDancers {
|
// TODO Better name?
|
||||||
return this.buildSingleVariantMoveInterpreter(startingPos).moveAsLowLevelMoves();
|
moveAsVariants({ startingPos }: MoveAsLowLevelMovesArgs, previousMoveVariant: string): VariantCollection {
|
||||||
|
return this.buildSingleVariantMoveInterpreter(startingPos).moveAsVariants(previousMoveVariant);
|
||||||
|
}
|
||||||
|
|
||||||
|
allVariantsForMove(args: AllVariantsForMoveArgs): VariantCollection {
|
||||||
|
const res = new Map<string, Variant>();
|
||||||
|
let error;
|
||||||
|
|
||||||
|
for (const [variantName, variantArgs] of args.entries()) {
|
||||||
|
let newVariants: VariantCollection;
|
||||||
|
try {
|
||||||
|
newVariants = this.moveAsVariants(variantArgs, variantName);
|
||||||
|
} catch (ex) {
|
||||||
|
// TODO Maybe have a way to distinguish invalid start from error processing move?
|
||||||
|
error = ex;
|
||||||
|
// If this variant can't be processed, just continue.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [newVariantName, variant] of newVariants) {
|
||||||
|
let combinedVariantName: string;
|
||||||
|
if (args.size === 1) {
|
||||||
|
combinedVariantName = newVariantName;
|
||||||
|
} else if (newVariants.size === 1) {
|
||||||
|
combinedVariantName = variantName;
|
||||||
|
} else {
|
||||||
|
combinedVariantName = newVariantName + "_from_" + variantName;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.set(combinedVariantName, variant);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.size === 0) throw error;
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DefaultSingleVariantMoveInterpreter extends SingleVariantMoveInterpreter<DefaultMoveInterpreter, MoveName> {
|
class ErrorSingleVariantMoveInterpreter extends SingleVariantMoveInterpreter<ErrorMoveInterpreter, MoveName> {
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
||||||
return this.errorStandStill();
|
return this.errorStandStill(this.moveInterpreter.error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
class DefaultMoveInterpreter extends MoveInterpreter<MoveName> {
|
export class ErrorMoveInterpreter extends MoveInterpreter<MoveName> {
|
||||||
|
public readonly error?: string;
|
||||||
|
|
||||||
|
constructor(args: MoveInterpreterCtorArgs<MoveName>, error?: string) {
|
||||||
|
super(args);
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
|
||||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
||||||
throw new Error("Method not implemented.");
|
//throw new Error("Method not implemented.");
|
||||||
|
return new ErrorSingleVariantMoveInterpreter(this, startingPos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export const errorMoveInterpreterCtor: MoveInterpreterCtor<MoveName> = DefaultMoveInterpreter;
|
export const errorMoveInterpreterCtor: MoveInterpreterCtor<MoveName> = ErrorMoveInterpreter;Error
|
|
@ -1,4 +1,4 @@
|
||||||
import { SemanticPosition, PositionKind, CircleSide, Facing, CirclePosition, LongLines, HandConnection } from "../interpreterCommon.js";
|
import { SemanticPosition, PositionKind, CircleSide, Facing, CirclePosition, LongLines, HandConnection, DancerDistance } from "../interpreterCommon.js";
|
||||||
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
||||||
import { Hand } from "../rendererConstants.js";
|
import { Hand } from "../rendererConstants.js";
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
||||||
|
@ -24,6 +24,15 @@ class AllemandeSingleVariant extends SingleVariantMoveInterpreter<Allemande, all
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.handlePairedMove(move.parameters.who, ({ startPos, around, withId, withPos }) => {
|
return this.handlePairedMove(move.parameters.who, ({ startPos, around, withId, withPos }) => {
|
||||||
|
if (startPos.kind === PositionKind.ShortLines && withPos.kind === PositionKind.ShortLines
|
||||||
|
&& startPos.which.leftRightSide() != withPos.which.leftRightSide()
|
||||||
|
&& (!startPos.which.isMiddle() || !withPos.which.isMiddle())) {
|
||||||
|
throw new Error("Allemande in short lines must either be on same side of set or in the middle.");
|
||||||
|
}
|
||||||
|
if (startPos.dancerDistance && startPos.dancerDistance !== DancerDistance.Normal) {
|
||||||
|
throw new Error(this.move.move + " can only start in normal DancerDistance.");
|
||||||
|
}
|
||||||
|
|
||||||
let endPosition: SemanticPosition = startPos;
|
let endPosition: SemanticPosition = startPos;
|
||||||
let startingPos = startPos;
|
let startingPos = startPos;
|
||||||
if (swap) {
|
if (swap) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { SemanticPosition, PositionKind } from "../interpreterCommon.js";
|
import { SemanticPosition, PositionKind, DancerDistance } from "../interpreterCommon.js";
|
||||||
import { Move } from "../libfigureMapper.js";
|
import { Move } from "../libfigureMapper.js";
|
||||||
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
||||||
|
@ -12,6 +12,10 @@ class MadRobinSingleVariant extends SingleVariantMoveInterpreter<MadRobin, typeo
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.handleCircleMove(({ id, startPos }) => {
|
return this.handleCircleMove(({ id, startPos }) => {
|
||||||
|
if (startPos.dancerDistance && startPos.dancerDistance !== DancerDistance.Normal) {
|
||||||
|
throw new Error(this.move.move + " can only start in normal DancerDistance.");
|
||||||
|
}
|
||||||
|
|
||||||
// Read who of mad robin to decide direction.
|
// Read who of mad robin to decide direction.
|
||||||
const madRobinClockwise: boolean = (this.findPairOpposite(this.move.parameters.who, id) !== null) === startPos.which.isOnLeftLookingAcross();
|
const madRobinClockwise: boolean = (this.findPairOpposite(this.move.parameters.who, id) !== null) === startPos.which.isOnLeftLookingAcross();
|
||||||
|
|
||||||
|
|
|
@ -3,26 +3,52 @@ import { LongLines, CircleSide, SemanticPosition, CirclePosition, Facing, Positi
|
||||||
import { Move } from "../libfigureMapper.js";
|
import { Move } from "../libfigureMapper.js";
|
||||||
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
||||||
import { Hand } from "../rendererConstants.js";
|
import { Hand } from "../rendererConstants.js";
|
||||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, PartialLowLevelMove, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveAsLowLevelMovesArgs, MoveInterpreter, PartialLowLevelMove, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, Variant, VariantCollection, moveInterpreters } from "./_moveInterpreter.js";
|
||||||
|
|
||||||
const moveName: Move["move"] = "swing";
|
const moveName: Move["move"] = "swing";
|
||||||
|
|
||||||
|
interface SwingEnd {
|
||||||
|
facing: "Across" | Facing.Up | Facing.Down,
|
||||||
|
close: boolean,
|
||||||
|
}
|
||||||
|
function swingEndString(swingEnd: SwingEnd): string {
|
||||||
|
return swingEnd.facing + (swingEnd.close ? "Close" : "");
|
||||||
|
}
|
||||||
|
const swingEndValues: SwingEnd[] = [
|
||||||
|
{ facing: "Across", close: false},
|
||||||
|
{ facing: "Across", close: true},
|
||||||
|
{ facing: Facing.Up, close: false},
|
||||||
|
{ facing: Facing.Up, close: true},
|
||||||
|
{ facing: Facing.Down, close: false},
|
||||||
|
{ facing: Facing.Down, close: true},
|
||||||
|
];
|
||||||
|
|
||||||
class SwingSingleVariant extends SingleVariantMoveInterpreter<Swing, typeof moveName> {
|
class SwingSingleVariant extends SingleVariantMoveInterpreter<Swing, typeof moveName> {
|
||||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
moveAsVariants(previousMoveVariant: string): VariantCollection {
|
||||||
// TODO Use variants instead of nextMove
|
const res = new Map<string, Variant>();
|
||||||
const nextMove = this.moveInterpreter.nextMove;
|
|
||||||
|
|
||||||
|
for (const swingEnd of swingEndValues) {
|
||||||
|
try {
|
||||||
|
res.set(swingEndString(swingEnd), {
|
||||||
|
lowLevelMoves: this.moveAsLowLevelMovesFacing(swingEnd),
|
||||||
|
previousMoveVariant
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
moveAsLowLevelMovesFacing(swingEnd: SwingEnd): LowLevelMovesForAllDancers {
|
||||||
return this.handlePairedMove(this.move.parameters.who, ({ id, startPos, around, withId, withPos }) => {
|
return this.handlePairedMove(this.move.parameters.who, ({ id, startPos, around, withId, withPos }) => {
|
||||||
// TODO swing can start from non-circle positions.
|
|
||||||
// TODO swing end is only in notes / looking at next move.
|
|
||||||
|
|
||||||
// TODO better way to detect swing end?
|
|
||||||
// TODO more structured way to do this than enumerating next moves here?
|
|
||||||
// maybe instead of nextMove an optional endPosition for fixing up positions?
|
|
||||||
// ... but then every move would have to handle that...
|
|
||||||
const afterTake = startPos.longLines === LongLines.Near || withPos.longLines === LongLines.Near;
|
const afterTake = startPos.longLines === LongLines.Near || withPos.longLines === LongLines.Near;
|
||||||
const toShortLines = nextMove.move === "down the hall" || nextMove.move === "up the hall";
|
// Swing in center ends facing up/down but not in short lines.
|
||||||
|
const toShortLines = around !== "Center" && (swingEnd.facing === Facing.Down || swingEnd.facing === Facing.Up);
|
||||||
const endFacingAcross = (around === CircleSide.Left || around === CircleSide.Right) && !toShortLines;
|
const endFacingAcross = (around === CircleSide.Left || around === CircleSide.Right) && !toShortLines;
|
||||||
|
if ((swingEnd.facing === "Across") !== endFacingAcross) {
|
||||||
|
throw new Error("Expected to end facing across.");
|
||||||
|
}
|
||||||
|
|
||||||
const startWhich = startPos.which;
|
const startWhich = startPos.which;
|
||||||
const startPosition: SemanticPosition = {
|
const startPosition: SemanticPosition = {
|
||||||
|
@ -49,7 +75,7 @@ class SwingSingleVariant extends SingleVariantMoveInterpreter<Swing, typeof move
|
||||||
|
|
||||||
// TODO This assumes swing around right/left, not center or top/bottom.
|
// TODO This assumes swing around right/left, not center or top/bottom.
|
||||||
let endPosition: SemanticPosition;
|
let endPosition: SemanticPosition;
|
||||||
if (endFacingAcross) {
|
if (swingEnd.facing === "Across") {
|
||||||
endPosition = {
|
endPosition = {
|
||||||
...startPos,
|
...startPos,
|
||||||
kind: PositionKind.Circle,
|
kind: PositionKind.Circle,
|
||||||
|
@ -62,16 +88,14 @@ class SwingSingleVariant extends SingleVariantMoveInterpreter<Swing, typeof move
|
||||||
: (swingRole === DanceRole.Lark ? CirclePosition.TopRight : CirclePosition.BottomRight)),
|
: (swingRole === DanceRole.Lark ? CirclePosition.TopRight : CirclePosition.BottomRight)),
|
||||||
facing: startWhich.leftRightSide() === CircleSide.Left ? Facing.Right : Facing.Left,
|
facing: startWhich.leftRightSide() === CircleSide.Left ? Facing.Right : Facing.Left,
|
||||||
balance: undefined,
|
balance: undefined,
|
||||||
dancerDistance: undefined,
|
dancerDistance: swingEnd.close ? DancerDistance.Compact : undefined,
|
||||||
longLines: undefined,
|
longLines: undefined,
|
||||||
hands: new Map<Hand, HandConnection>([swingRole === DanceRole.Lark
|
hands: new Map<Hand, HandConnection>([swingRole === DanceRole.Lark
|
||||||
? [Hand.Right, { to: HandTo.DancerRight, hand: Hand.Left }]
|
? [Hand.Right, { to: HandTo.DancerRight, hand: Hand.Left }]
|
||||||
: [Hand.Left, { to: HandTo.DancerLeft, hand: Hand.Right }]]),
|
: [Hand.Left, { to: HandTo.DancerLeft, hand: Hand.Right }]]),
|
||||||
};
|
};
|
||||||
} else if (toShortLines) {
|
} else if (toShortLines) {
|
||||||
const endFacing = nextMove.move === "down the hall" !== (nextMove.parameters.facing === "backward")
|
const endFacing = swingEnd.facing;
|
||||||
? Facing.Down
|
|
||||||
: Facing.Up;
|
|
||||||
const endWhich = startWhich.isLeft()
|
const endWhich = startWhich.isLeft()
|
||||||
? ((endFacing === Facing.Down) === (swingRole === DanceRole.Lark) ? ShortLinesPosition.FarLeft : ShortLinesPosition.MiddleLeft)
|
? ((endFacing === Facing.Down) === (swingRole === DanceRole.Lark) ? ShortLinesPosition.FarLeft : ShortLinesPosition.MiddleLeft)
|
||||||
: ((endFacing === Facing.Down) === (swingRole === DanceRole.Lark) ? ShortLinesPosition.MiddleRight : ShortLinesPosition.FarRight)
|
: ((endFacing === Facing.Down) === (swingRole === DanceRole.Lark) ? ShortLinesPosition.MiddleRight : ShortLinesPosition.FarRight)
|
||||||
|
@ -81,12 +105,11 @@ class SwingSingleVariant extends SingleVariantMoveInterpreter<Swing, typeof move
|
||||||
which: endWhich,
|
which: endWhich,
|
||||||
facing: endFacing,
|
facing: endFacing,
|
||||||
balance: undefined,
|
balance: undefined,
|
||||||
dancerDistance: undefined,
|
dancerDistance: swingEnd.close ? DancerDistance.Compact : undefined,
|
||||||
longLines: undefined,
|
longLines: undefined,
|
||||||
hands: handsInLine({ wavy: false, which: endWhich, facing: endFacing }),
|
hands: handsInLine({ wavy: false, which: endWhich, facing: endFacing }),
|
||||||
};
|
};
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
// TODO Need to figure out the logic of knowing if this should be facing up or down.
|
// TODO Need to figure out the logic of knowing if this should be facing up or down.
|
||||||
// Probably based on knowing Ones vs. Twos? Also then the not-participating-dancers need their
|
// Probably based on knowing Ones vs. Twos? Also then the not-participating-dancers need their
|
||||||
// "standing still" to update that they are in a new set...
|
// "standing still" to update that they are in a new set...
|
||||||
|
|
|
@ -1,40 +1,48 @@
|
||||||
import { Animation } 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 { 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";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function hueForDancer(identity: DancerIdentity): number {
|
function baseColorForDancer(identity: ExtendedDancerIdentity): {hue: number, sat: number, lum: number} {
|
||||||
if (identity.coupleRole == CoupleRole.Ones) {
|
if (identity.setIdentity.coupleRole == CoupleRole.Ones) {
|
||||||
if (identity.danceRole == DanceRole.Lark) {
|
if (identity.relativeSet < 0)
|
||||||
return 0; //red
|
return { hue: 340, sat: 67, lum: 56 };
|
||||||
} else {
|
if (identity.relativeSet === 0)
|
||||||
return 39; //orange
|
return { hue: 27, sat: 99, lum: 59 };
|
||||||
}
|
if (identity.relativeSet > 0)
|
||||||
|
return { hue: 54, sat: 97, lum: 49 };
|
||||||
} else {
|
} else {
|
||||||
if (identity.danceRole == DanceRole.Lark) {
|
if (identity.relativeSet < 0)
|
||||||
return 240; //blue
|
return { hue: 183, sat: 88, lum: 23 };
|
||||||
} else {
|
if (identity.relativeSet === 0)
|
||||||
return 180; //teal
|
return { hue: 249, sat: 42, lum: 37 };
|
||||||
}
|
if (identity.relativeSet > 0)
|
||||||
|
return { hue: 13, sat: 33, lum: 29 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw new Error("Unreachable: relativeSet must be one of <, ===, or > 0.");
|
||||||
}
|
}
|
||||||
|
|
||||||
function colorForDancer(identity: ExtendedDancerIdentity) : string {
|
function colorForDancer(identity: ExtendedDancerIdentity) : string {
|
||||||
const hue = hueForDancer(identity.setIdentity);
|
const baseColor = baseColorForDancer(identity);
|
||||||
const sat = 100 - Math.abs(identity.relativeLine * 40);
|
|
||||||
const unclampedLum = 50 + identity.relativeSet * 20;
|
const hue = baseColor.hue;
|
||||||
|
const sat = baseColor.sat - Math.abs(identity.relativeLine * 40);
|
||||||
|
const unclampedLum = baseColor.lum + (Math.abs(identity.relativeSet) <= 1 ? 0 : identity.relativeSet * 20);
|
||||||
const lum = unclampedLum < 10 ? 10 : unclampedLum > 90 ? 90 : unclampedLum;
|
const lum = unclampedLum < 10 ? 10 : unclampedLum > 90 ? 90 : unclampedLum;
|
||||||
return `hsl(${hue}, ${sat}%, ${lum}%)`;
|
return `hsl(${hue}, ${sat}%, ${lum}%)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function colorForDancerLabel(identity: ExtendedDancerIdentity) : string {
|
function colorForDancerLabel(identity: ExtendedDancerIdentity) : string {
|
||||||
const hue = (hueForDancer(identity.setIdentity) + 180) % 360;
|
const dancerColor = baseColorForDancer(identity);
|
||||||
|
const hue = (dancerColor.hue + 180) % 360;
|
||||||
const sat = 100;
|
const sat = 100;
|
||||||
const lum = hueForDancer(identity.setIdentity) === 240 && identity.relativeSet < 2 || identity.relativeSet < 0
|
const unclampedLum = ((dancerColor.hue >= 215 && dancerColor.hue <= 285) || (identity.relativeSet < 0)
|
||||||
|
|| dancerColor.lum < 40) && identity.relativeSet < 2
|
||||||
? 100
|
? 100
|
||||||
: 20 - identity.relativeSet * 40;
|
: 20 - identity.relativeSet * 40;
|
||||||
|
const lum = unclampedLum < 0 ? 0 : unclampedLum > 100 ? 100 : unclampedLum;
|
||||||
return `hsl(${hue}, ${sat}%, ${lum}%)`;
|
return `hsl(${hue}, ${sat}%, ${lum}%)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,13 +62,21 @@ export class Renderer {
|
||||||
drawDancerBody(identity: ExtendedDancerIdentity, drawText: boolean) {
|
drawDancerBody(identity: ExtendedDancerIdentity, drawText: boolean) {
|
||||||
this.ctx.beginPath();
|
this.ctx.beginPath();
|
||||||
this.ctx.moveTo(leftShoulder.x, leftShoulder.y);
|
this.ctx.moveTo(leftShoulder.x, leftShoulder.y);
|
||||||
this.ctx.lineTo(rightShoulder.x, rightShoulder.y);
|
if (identity.setIdentity.danceRole === DanceRole.Robin) {
|
||||||
this.ctx.lineTo(0, dancerHeight-dancerHeightOffset);
|
// Draw triangle for robin.
|
||||||
|
this.ctx.lineTo(rightShoulder.x, rightShoulder.y);
|
||||||
|
this.ctx.lineTo(0, dancerHeight-dancerHeightOffset);
|
||||||
|
} else {
|
||||||
|
// Draw dome for lark.
|
||||||
|
this.ctx.arcTo(0, dancerHeight*2-dancerHeightOffset, rightShoulder.x, rightShoulder.y, dancerHeight * 1.5);
|
||||||
|
this.ctx.lineTo(rightShoulder.x, rightShoulder.y);
|
||||||
|
}
|
||||||
this.ctx.fill();
|
this.ctx.fill();
|
||||||
|
|
||||||
// Draw dot at origin to identify "center" point of dancer.
|
// Draw dot at origin to identify "center" point of dancer.
|
||||||
const backupFillStyle = this.ctx.fillStyle;
|
const backupFillStyle = this.ctx.fillStyle;
|
||||||
this.ctx.fillStyle = 'black';
|
this.ctx.fillStyle = 'black';
|
||||||
|
|
||||||
const pointSize = 0.05;
|
const pointSize = 0.05;
|
||||||
this.ctx.fillRect(-pointSize/2, -pointSize/2, pointSize, pointSize);
|
this.ctx.fillRect(-pointSize/2, -pointSize/2, pointSize, pointSize);
|
||||||
|
|
||||||
|
@ -199,9 +215,9 @@ export class Renderer {
|
||||||
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.ctx.save();
|
this.ctx.save();
|
||||||
const hue = (relativeLine + relativeSet) % 2 === 0 ? 60 : 170;
|
const hue = 0;
|
||||||
const sat = 100 - Math.abs(relativeLine * 40);
|
const sat = 0;
|
||||||
const lum = Math.min(98, 90 + Math.abs(relativeSet) * 5);
|
const lum = Math.min(98, 90 + Math.abs(Math.abs(relativeSet) + Math.abs(relativeLine)) * 5);
|
||||||
this.ctx.fillStyle = `hsl(${hue}, ${sat}%, ${lum}%)`;
|
this.ctx.fillStyle = `hsl(${hue}, ${sat}%, ${lum}%)`;
|
||||||
this.ctx.translate(relativeLine * lineDistance,
|
this.ctx.translate(relativeLine * lineDistance,
|
||||||
relativeSet * setDistance);
|
relativeSet * setDistance);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user