diff --git a/src/cubing/bluetooth/gan.ts b/src/cubing/bluetooth/gan.ts index c8e7919a8..291a0b03b 100644 --- a/src/cubing/bluetooth/gan.ts +++ b/src/cubing/bluetooth/gan.ts @@ -4,7 +4,7 @@ import { Quaternion } from "three"; import { BareBlockMove, BlockMove } from "../alg"; import { KPuzzle } from "../kpuzzle"; import { KPuzzleDefinition } from "../puzzle-geometry/interfaces"; -import { puzzles } from "../puzzles"; +import * as puzzles from "../puzzles"; import { BluetoothConfig, BluetoothPuzzle, @@ -330,7 +330,7 @@ export class GanCube extends BluetoothPuzzle { ).moveCounter(); debugLog("Initial Move Counter:", initialMoveCounter); const cube = new GanCube( - await puzzles["3x3x3"].def(), + await puzzles.koob333.def(), ganCubeService, server, physicalStateCharacteristic, diff --git a/src/cubing/kpuzzle/svg.ts b/src/cubing/kpuzzle/svg.ts index 9d9022c77..ade4890fd 100644 --- a/src/cubing/kpuzzle/svg.ts +++ b/src/cubing/kpuzzle/svg.ts @@ -1,3 +1,7 @@ +import type { + FaceletMeshAppearance, + PuzzleAppearance, +} from "../twisty/3D/puzzles/appearance"; // TODO import { KPuzzleDefinition, Transformation } from "./definition_types"; import { KPuzzle } from "./kpuzzle"; @@ -12,13 +16,56 @@ function nextSVGID(): string { return "svg" + svgCounter.toString(); } +// TODO: This is hardcoded to 3x3x3 SVGs +const colorMaps: Partial +>> = { + dim: { + "white": "#dddddd", + "orange": "#884400", + "limegreen": "#008800", + "red": "#660000", + "rgb(34, 102, 255)": "#000088", // TODO + "yellow": "#888800", + }, + oriented: { + "white": "#ff88ff", + "orange": "#ff88ff", + "limegreen": "#ff88ff", + "red": "#ff88ff", + "rgb(34, 102, 255)": "#ff88ff", // TODO + "yellow": "#ff88ff", + }, + ignored: { + "white": "#444444", + "orange": "#444444", + "limegreen": "#444444", + "red": "#444444", + "rgb(34, 102, 255)": "#444444", // TODO + "yellow": "#444444", + }, + invisible: { + "white": "#00000000", + "orange": "#00000000", + "limegreen": "#00000000", + "red": "#00000000", + "rgb(34, 102, 255)": "#00000000", // TODO + "yellow": "#00000000", + }, +}; + export class SVG { public element: HTMLElement; public gradientDefs: SVGDefsElement; private originalColors: { [type: string]: string } = {}; private gradients: { [type: string]: SVGGradientElement } = {}; private svgID: string; - constructor(public kPuzzleDefinition: KPuzzleDefinition, svgSource: string) { + constructor( + public kPuzzleDefinition: KPuzzleDefinition, + svgSource: string, + experimentalAppearance?: PuzzleAppearance, + ) { if (!svgSource) { throw new Error( `No SVG definition for puzzle type: ${kPuzzleDefinition.name}`, @@ -58,7 +105,39 @@ export class SVG { ) { const id = this.elementID(orbitName, idx, orientation); const elem = this.elementByID(id); - const originalColor = elem.style.fill as string; + let originalColor: string = elem.style.fill; + /// TODO: Allow setting appearance dynamically. + if (experimentalAppearance) { + (() => { + // TODO: dedup with Cube3D,,factor out fallback calculations + const a = experimentalAppearance.orbits; + if (!a) { + return; + } + const orbitAppearance = a[orbitName]; + if (!orbitAppearance) { + return; + } + const pieceAppearance = orbitAppearance.pieces[idx]; + if (!pieceAppearance) { + return; + } + const faceletAppearance = pieceAppearance.facelets[orientation]; + if (!faceletAppearance) { + return; + } + const appearance = + typeof faceletAppearance === "string" + ? faceletAppearance + : faceletAppearance?.appearance; + const colorMap = colorMaps[appearance]; + if (colorMap) { + originalColor = colorMap[originalColor]; + } + })(); + } else { + originalColor = elem.style.fill as string; + } this.originalColors[id] = originalColor; this.gradients[id] = this.newGradient(id, originalColor); this.gradientDefs.appendChild(this.gradients[id]); diff --git a/src/cubing/puzzles/implementations/3x3x3/3x3x3-ll.kpuzzle.svg.ts b/src/cubing/puzzles/implementations/3x3x3/3x3x3-ll.kpuzzle.svg.ts index bc9f3f566..fd7559407 100644 --- a/src/cubing/puzzles/implementations/3x3x3/3x3x3-ll.kpuzzle.svg.ts +++ b/src/cubing/puzzles/implementations/3x3x3/3x3x3-ll.kpuzzle.svg.ts @@ -1,5 +1,5 @@ export default ` - + 3x3x3 LL @@ -7,32 +7,32 @@ export default ` - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/cubing/puzzles/implementations/3x3x3/src/3x3x3-ll.kpuzzle.svg b/src/cubing/puzzles/implementations/3x3x3/src/3x3x3-ll.kpuzzle.svg index 6b0a053a7..9c63681ad 100644 --- a/src/cubing/puzzles/implementations/3x3x3/src/3x3x3-ll.kpuzzle.svg +++ b/src/cubing/puzzles/implementations/3x3x3/src/3x3x3-ll.kpuzzle.svg @@ -1,5 +1,5 @@ - + 3x3x3 LL @@ -7,32 +7,32 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -105,4 +105,4 @@ - +` diff --git a/src/cubing/puzzles/index.ts b/src/cubing/puzzles/index.ts index 3dcd9a49d..01a55ae72 100644 --- a/src/cubing/puzzles/index.ts +++ b/src/cubing/puzzles/index.ts @@ -9,6 +9,13 @@ import { skewb } from "./implementations/skewb"; import { square1 } from "./implementations/square1"; import { PuzzleManager } from "./PuzzleManager"; +module.exports["222"] = cube2x2x2; +module.exports["cube333"] = cube3x3x3; + +export const koob333 = cube3x3x3; + +export { cube2x2x2 as ads }; + export const puzzles: Record = { /******** Start of WCA Puzzles *******/ "3x3x3": cube3x3x3, diff --git a/src/cubing/twisty/3D/puzzles/stickerings.ts b/src/cubing/twisty/3D/puzzles/stickerings.ts index c6c5d9ca6..a672a14d8 100644 --- a/src/cubing/twisty/3D/puzzles/stickerings.ts +++ b/src/cubing/twisty/3D/puzzles/stickerings.ts @@ -37,7 +37,7 @@ const oi: PieceAppearance = { }; const invis: PieceAppearance = { - facelets: ["invisible", "invisible", "invisible"], + facelets: ["invisible", "invisible", "invisible", "invisible"], // TODO: 4th entry is for void cube. Should be handled properly for all stickerings. }; const c: PieceAppearance = { @@ -132,6 +132,34 @@ export const stickerings: Record = { }, }, + "OCLL": { + orbits: { + EDGES: { + pieces: [di, di, di, di, d, d, d, d, d, d, d, d], + }, + CORNERS: { + pieces: [o, o, o, o, d, d, d, d], + }, + CENTERS: { + pieces: [p, d, d, d, d, d], + }, + }, + }, + + "ELL": { + orbits: { + EDGES: { + pieces: [r, r, r, r, d, d, d, d, d, d, d, d], + }, + CORNERS: { + pieces: [d, d, d, d, d, d, d, d], + }, + CENTERS: { + pieces: [p, d, d, d, d, d], + }, + }, + }, + "ELS": { orbits: { EDGES: { diff --git a/src/cubing/twisty/dom/TwistyPlayer.css.ts b/src/cubing/twisty/dom/TwistyPlayer.css.ts index af24d7c7e..3ddfa96ab 100644 --- a/src/cubing/twisty/dom/TwistyPlayer.css.ts +++ b/src/cubing/twisty/dom/TwistyPlayer.css.ts @@ -8,7 +8,6 @@ export const twistyPlayerCSS = new CSSSource(` contain: content; display: grid; box-sizing: border-box; - border: 1px solid rgba(0, 0, 0, 0.1); } .wrapper { diff --git a/src/cubing/twisty/dom/TwistyPlayer.ts b/src/cubing/twisty/dom/TwistyPlayer.ts index 97f642862..922628fd2 100644 --- a/src/cubing/twisty/dom/TwistyPlayer.ts +++ b/src/cubing/twisty/dom/TwistyPlayer.ts @@ -39,7 +39,7 @@ import { TwistyPlayerInitialConfig, VisualizationFormat, } from "./TwistyPlayerConfig"; -import { Twisty2DSVG } from "./viewers/Twisty2DSVG"; +import { Twisty2DSVG, Twisty2DSVGOptions } from "./viewers/Twisty2DSVG"; import { Twisty3DCanvas } from "./viewers/Twisty3DCanvas"; import { TwistyViewerElement } from "./viewers/TwistyViewerElement"; import { @@ -206,6 +206,11 @@ export class TwistyPlayer extends ManagedCustomElement { experimentalStickering, }); } + if (this.viewerElems[0] instanceof Twisty2DSVG) { + (this.viewerElems[0] as Twisty2DSVG).experimentalSetStickering( + this.experimentalStickering, + ); + } } } @@ -333,7 +338,8 @@ export class TwistyPlayer extends ManagedCustomElement { // TODO: specify exactly when back views are possible. // TODO: Are there any SVGs where we'd want a separate back view? - const setBackView: boolean = this.backView && this.visualization !== "2D"; + const setBackView: boolean = + this.backView && is3DVisualization(this.visualization); const backView: BackViewLayout = setBackView ? (this.backView as BackViewLayout) : "none"; @@ -382,8 +388,14 @@ export class TwistyPlayer extends ManagedCustomElement { } private setTwisty2DSVG(twisty2DSVG: Twisty2DSVG): void { + if (this.#renderMode !== "2D") { + return; + } + this.clearRenderMode(); + this.#viewerWrapper.clear(); this.#viewerWrapper.addElement(twisty2DSVG); + this.viewerElems.push(twisty2DSVG); } private setRenderMode3D(): void { @@ -482,12 +494,23 @@ export class TwistyPlayer extends ManagedCustomElement { } switch (this.visualization) { case "2D": + case "experimental-2D-LL": { + const options: Twisty2DSVGOptions = {}; + if (this.experimentalStickering) { + options.experimentalStickering = this.experimentalStickering; + } + this.setRenderMode2D(); + const svgPromiseFn = + this.visualization === "2D" + ? puzzleManager.svg + : puzzleManager.llSVG ?? puzzleManager.svg; const mainViewer = new Twisty2DSVG( cursor, def, - await puzzleManager.svg(), + await svgPromiseFn(), + options, ); if (!pendingPuzzleUpdate.cancelled) { this.setTwisty2DSVG(mainViewer); diff --git a/src/cubing/twisty/dom/TwistyPlayerConfig.ts b/src/cubing/twisty/dom/TwistyPlayerConfig.ts index ccf0822d7..92a6b2634 100644 --- a/src/cubing/twisty/dom/TwistyPlayerConfig.ts +++ b/src/cubing/twisty/dom/TwistyPlayerConfig.ts @@ -25,6 +25,7 @@ export const cubeCameraPosition = new Vector3(3, 4, 5); export const visualizationFormats = { "3D": true, // default "2D": true, + "experimental-2D-LL": true, // TODO "PG3D": true, }; export type VisualizationFormat = keyof typeof visualizationFormats; @@ -51,6 +52,8 @@ export const experimentalStickerings = { "CLS": true, "OLL": true, "COLL": true, + "OCLL": true, + "ELL": true, "ELS": true, "LL": true, "F2L": true, diff --git a/src/cubing/twisty/dom/viewers/Twisty2DSVG.ts b/src/cubing/twisty/dom/viewers/Twisty2DSVG.ts index 8c6b169f8..369de783c 100644 --- a/src/cubing/twisty/dom/viewers/Twisty2DSVG.ts +++ b/src/cubing/twisty/dom/viewers/Twisty2DSVG.ts @@ -6,6 +6,8 @@ import { SVG, Transformation, } from "../../../kpuzzle"; +import { PuzzleAppearance } from "../../3D/puzzles/appearance"; +import { stickerings } from "../../3D/puzzles/stickerings"; import { PositionDispatcher, PositionListener, @@ -14,9 +16,14 @@ import { PuzzlePosition } from "../../animation/cursor/CursorTypes"; import { RenderScheduler } from "../../animation/RenderScheduler"; import { ManagedCustomElement } from "../element/ManagedCustomElement"; import { customElementsShim } from "../element/node-custom-element-shims"; +import { ExperimentalStickering } from "../TwistyPlayerConfig"; import { twisty2DSVGCSS } from "./Twisty2DSVGView.css"; import { TwistyViewerElement } from "./TwistyViewerElement"; +export interface Twisty2DSVGOptions { + experimentalStickering?: ExperimentalStickering; +} + // export class Twisty2DSVG extends ManagedCustomElement @@ -24,18 +31,23 @@ export class Twisty2DSVG private definition: KPuzzleDefinition; private svg: SVG; private scheduler = new RenderScheduler(this.render.bind(this)); + #cachedPosition: PuzzlePosition | null = null; // TODO: pull when needed. constructor( cursor?: PositionDispatcher, def?: KPuzzleDefinition, - svgSource?: string, + private svgSource?: string, + private options?: Twisty2DSVGOptions, ) { super(); this.addCSS(twisty2DSVGCSS); this.definition = def!; - this.svg = new SVG(this.definition, svgSource!); // TODO - this.addElement(this.svg.element); + this.resetSVG(); cursor!.addPositionListener(this); + + if (this.options?.experimentalStickering) { + this.experimentalSetStickering(this.options.experimentalStickering); + } } // eslint-disable-next-line @typescript-eslint/no-unused-vars-experimental @@ -64,6 +76,7 @@ export class Twisty2DSVG ); } else { this.svg.draw(this.definition, position.state as Transformation); + this.#cachedPosition = position; } } @@ -71,6 +84,23 @@ export class Twisty2DSVG this.scheduler.requestAnimFrame(); } + experimentalSetStickering(stickering: ExperimentalStickering): void { + const appearance = stickerings[stickering]; + this.resetSVG(appearance); + } + + // TODO: do this without constructing a new SVG. + private resetSVG(appearance?: PuzzleAppearance): void { + if (this.svg) { + this.removeElement(this.svg.element); + } + this.svg = new SVG(this.definition, this.svgSource!, appearance); // TODO + this.addElement(this.svg.element); + if (this.#cachedPosition) { + this.onPositionChange(this.#cachedPosition); + } + } + private render(): void { /*...*/ } diff --git a/src/demo/index.html b/src/demo/index.html index 5ceb60ce7..e96b6b580 100644 --- a/src/demo/index.html +++ b/src/demo/index.html @@ -43,6 +43,11 @@

cubing.js
Dev Apps

🔄 twisty/cube3d-stickering +
+ 🔄 + twisty/2d-stickering +
+
🔄 twisty/2d-ll-stickering
🔄 twisty/pg3d
🔄 twisty/pyraminx
🔄 twisty/ll
diff --git a/src/demo/twisty/2d-ll-stickering.html b/src/demo/twisty/2d-ll-stickering.html new file mode 100644 index 000000000..75a7056b4 --- /dev/null +++ b/src/demo/twisty/2d-ll-stickering.html @@ -0,0 +1,63 @@ + + + + + 2D LL Stickering | twisty.js + + + + + +
+

2D LL Stickering

+

OLL

+ +

PLL

+ +

COLL

+ +

OCLL

+ +

ELL

+ +

CLS

+ +
+ + diff --git a/src/demo/twisty/2d-stickering.html b/src/demo/twisty/2d-stickering.html new file mode 100644 index 000000000..314258cce --- /dev/null +++ b/src/demo/twisty/2d-stickering.html @@ -0,0 +1,13 @@ + + + + + 2D Stickering | twisty.js + + + + + +
+ + diff --git a/src/demo/twisty/2d-stickering.ts b/src/demo/twisty/2d-stickering.ts new file mode 100644 index 000000000..6879bb3f3 --- /dev/null +++ b/src/demo/twisty/2d-stickering.ts @@ -0,0 +1,3 @@ +import { demo } from "./stickering-common"; + +demo("2D"); diff --git a/src/demo/twisty/cube3d-stickering.ts b/src/demo/twisty/cube3d-stickering.ts index 4f2ad6cb3..7eca074ab 100644 --- a/src/demo/twisty/cube3d-stickering.ts +++ b/src/demo/twisty/cube3d-stickering.ts @@ -1,49 +1,3 @@ -import { invert, parseAlg } from "../../cubing/alg"; -import { - Cube3D, - experimentalSetShareAllNewRenderers, - ExperimentalStickering, - TwistyPlayer, -} from "../../cubing/twisty"; +import { demo } from "./stickering-common"; -experimentalSetShareAllNewRenderers(true); - -const content = document.querySelector(".content")!; - -function addAlg(stickering: ExperimentalStickering, s: string): Cube3D { - const div = content.appendChild(document.createElement("div")); - div.classList.add("case"); - const twistyPlayer = new TwistyPlayer({ - experimentalStartSetup: invert(parseAlg(s)), - alg: parseAlg(s), - experimentalStickering: stickering, - }); - div.appendChild(document.createElement("h1")).textContent = stickering; - div.appendChild(twistyPlayer); - return twistyPlayer.twisty3D as Cube3D; -} - -addAlg("full", "y L' U R' F' U L2 U2' L' U' L U2' D R' D' F2 R2 U'"); -addAlg("centers-only", "(x y)3"); -addAlg("PLL", "R U R' U' R' F R2 U' R' U' R U R' F'"); -addAlg("CLS", "R U R' U' R U R' U R U' R'"); -addAlg("OLL", "r U R' U R U2 r'"); -addAlg("COLL", "L R' U' R U L' U2 R' U2 R"); -addAlg("ELS", "[r U' r': U]"); -addAlg("LL", "R' F R F2' U F R U R' F' U' F"); -addAlg("F2L", "R2' u R2 u' R2'"); -addAlg("ZBLL", "R' F R U' R' U' R U R' F' R U R' U' R' F R F' R"); -addAlg("ZBLS", "x' R2 U' R' U l'"); -addAlg("WVLS", "R U R' U R U' R'"); -addAlg("VLS", "R' F2 R F2' L' U2 L"); -addAlg("LS", "U' R U' R' U R U R'"); -addAlg("EO", "R' F R"); -addAlg("CMLL", "F R U R' U' F'"); -addAlg("L6E", "U M2' U' M2'"); -addAlg("L6EO", "(U' M U' M')2"); -addAlg("Daisy", "S2 R2 L2"); -addAlg("Cross", "(y' R)5 D"); -addAlg("2x2x2", "y2"); -addAlg("2x2x3", "y"); -addAlg("Void Cube", ""); -addAlg("invisible", ""); +demo("3D"); diff --git a/src/demo/twisty/stickering-common.ts b/src/demo/twisty/stickering-common.ts new file mode 100644 index 000000000..f7beeb326 --- /dev/null +++ b/src/demo/twisty/stickering-common.ts @@ -0,0 +1,55 @@ +import { invert, parseAlg } from "../../cubing/alg"; +import { + Cube3D, + experimentalSetShareAllNewRenderers, + ExperimentalStickering, + TwistyPlayer, +} from "../../cubing/twisty"; +import { VisualizationFormat } from "../../cubing/twisty/dom/TwistyPlayerConfig"; + +export function demo(visualization: VisualizationFormat): void { + experimentalSetShareAllNewRenderers(true); + + const content = document.querySelector(".content")!; + + function addAlg(stickering: ExperimentalStickering, s: string): Cube3D { + const div = content.appendChild(document.createElement("div")); + div.classList.add("case"); + const twistyPlayer = new TwistyPlayer({ + experimentalStartSetup: invert(parseAlg(s)), + alg: parseAlg(s), + visualization, + experimentalStickering: stickering, + }); + div.appendChild(document.createElement("h1")).textContent = stickering; + div.appendChild(twistyPlayer); + return twistyPlayer.twisty3D as Cube3D; + } + + addAlg("full", "y L' U R' F' U L2 U2' L' U' L U2' D R' D' F2 R2 U'"); + addAlg("centers-only", "(x y)3"); + addAlg("PLL", "R U R' U' R' F R2 U' R' U' R U R' F'"); + addAlg("CLS", "R U R' U' R U R' U R U' R'"); + addAlg("OLL", "r U R' U R U2 r'"); + addAlg("COLL", "L R' U' R U L' U2 R' U2 R"); + addAlg("OCLL", "R U R' U R U2 R'"); + addAlg("ELL", "M U' M' U2 M U' M'"); + addAlg("ELS", "[r U' r': U]"); + addAlg("LL", "R' F R F2' U F R U R' F' U' F"); + addAlg("F2L", "R2' u R2 u' R2'"); + addAlg("ZBLL", "R' F R U' R' U' R U R' F' R U R' U' R' F R F' R"); + addAlg("ZBLS", "x' R2 U' R' U l'"); + addAlg("WVLS", "R U R' U R U' R'"); + addAlg("VLS", "R' F2 R F2' L' U2 L"); + addAlg("LS", "U' R U' R' U R U R'"); + addAlg("EO", "R' F R"); + addAlg("CMLL", "F R U R' U' F'"); + addAlg("L6E", "U M2' U' M2'"); + addAlg("L6EO", "(U' M U' M')2"); + addAlg("Daisy", "S2 R2 L2"); + addAlg("Cross", "(y' R)5 D"); + addAlg("2x2x2", "y2"); + addAlg("2x2x3", "y"); + addAlg("Void Cube", "M' U M' U M' U' M' U' M' U2' M' U' M' U'"); + addAlg("invisible", ""); +}