Skip to content

Commit

Permalink
added png visualization.
Browse files Browse the repository at this point in the history
updated readme
  • Loading branch information
tdecker91 committed Feb 22, 2021
1 parent 28af785 commit b726fbe
Show file tree
Hide file tree
Showing 35 changed files with 221 additions and 113 deletions.
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
14.15.4
84 changes: 58 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,61 @@
# Puzzle Visualizer
# PuzzleGen

PuzzleGen is a javascript library intended to render [WCA puzzles](https://www.worldcubeassociation.org/regulations/#article-9-events). Colors can be customized, or scrambles can be applied to make references for solve guides or scramble previews. It's heavily inspired by [visualcube](https://github.com/Cride5/visualcube) and [twistysim](http://cube.rider.biz/twistysim.html). My first go at this was porting visuacube to javascript [here](https://github.com/tdecker91/visualcube). But in order to get all the functionality desired, I had to completely rewrite from scratch.

PuzzleGen is built to be customizable, so if desired functionality can be extended to use cases other than embedding scramble previews in web pages.

See the [docs](https://tdecker91.github.io/puzzle-gen/) for more information

## Examples

Here are some examples of what the library is capable of.

<p float="left">
<img src="https://raw.githubusercontent.com/tdecker91/puzzle-gen/master/assets/cube.png" width="125" />
<img src="https://raw.githubusercontent.com/tdecker91/puzzle-gen/master/assets/cube-scrm.png" width="125" />
<img src="https://raw.githubusercontent.com/tdecker91/puzzle-gen/master/assets/cube-net.png" width="125" />
<img src="https://raw.githubusercontent.com/tdecker91/puzzle-gen/master/assets/cube-net-scrm.png" width="125" />
<img src="https://raw.githubusercontent.com/tdecker91/puzzle-gen/master/assets/cube-top.png" width="125" />
<img src="https://raw.githubusercontent.com/tdecker91/puzzle-gen/master/assets/cube-top-scrm.png" width="125" />
<img src="https://raw.githubusercontent.com/tdecker91/puzzle-gen/master/assets/megaminx.png" width="125" />
<img src="https://raw.githubusercontent.com/tdecker91/puzzle-gen/master/assets/megaminx-scrm.png" width="125" />
<img src="https://raw.githubusercontent.com/tdecker91/puzzle-gen/master/assets/megaminx-net.png" width="125" />
<img src="https://raw.githubusercontent.com/tdecker91/puzzle-gen/master/assets/megaminx-net-scrm.png" width="125" />
<img src="https://raw.githubusercontent.com/tdecker91/puzzle-gen/master/assets/pyraminx.png" width="125" />
<img src="https://raw.githubusercontent.com/tdecker91/puzzle-gen/master/assets/pyraminx-scrm.png" width="125" />
<img src="https://raw.githubusercontent.com/tdecker91/puzzle-gen/master/assets/pyraminx-net.png" width="125" />
<img src="https://raw.githubusercontent.com/tdecker91/puzzle-gen/master/assets/pyraminx-net-scrm.png" width="125" />
<img src="https://raw.githubusercontent.com/tdecker91/puzzle-gen/master/assets/skewb.png" width="125" />
<img src="https://raw.githubusercontent.com/tdecker91/puzzle-gen/master/assets/skewb-scrm.png" width="125" />
<img src="https://raw.githubusercontent.com/tdecker91/puzzle-gen/master/assets/skewb-net.png" width="125" />
<img src="https://raw.githubusercontent.com/tdecker91/puzzle-gen/master/assets/skewb-net-scrm.png" width="125" />
<img src="https://raw.githubusercontent.com/tdecker91/puzzle-gen/master/assets/sq1.png" width="125" />
<img src="https://raw.githubusercontent.com/tdecker91/puzzle-gen/master/assets/sq1-scrm.png" width="125" />
<img src="https://raw.githubusercontent.com/tdecker91/puzzle-gen/master/assets/sq1-net.png" width="125" />
<img src="https://raw.githubusercontent.com/tdecker91/puzzle-gen/master/assets/sq1-net-scrm.png" width="125" />
<img src="https://raw.githubusercontent.com/tdecker91/puzzle-gen/master/assets/megaminx-top.png" width="125" />
<img src="https://raw.githubusercontent.com/tdecker91/puzzle-gen/master/assets/megaminx-top-scrm.png" width="125" />
</p>

## Installation

Just a prototype

## Vision

This project aims to build on the idea of [visual cube](https://github.com/tdecker91/visualcube) and extend support to generate images for puzzles other than Rubik's cubes.

## Prototype

Really I just wanted an excuse to fiddle with very rudimentary 3d rendering. So I decided to make a basic "svg renderer" to take geometry and render images in a browser context. But the renderer can also be replaced with something custom to fit needs.

Sure, something like three.js SVGRenderer would probably be much better.

...But it works!
```bash
> # npm install instructions comming
```

![default cube](https://raw.githubusercontent.com/tdecker91/puzzle-visualizer/master/assets/svg-cube.gif)
## Usage

## Development

> Requirements
>
> - [Node.js](nodejs.org) - v14.15.4 but other versions may work
- [Node.js](nodejs.org)
> Tools Used
>
> - [nvm](https://github.com/nvm-sh/nvm)
> - [TypeScript](https://www.typescriptlang.org/) - v4.1.3
> - [WebPack](https://webpack.js.org/) - 5.15.0
Install dependencies

Expand All @@ -43,19 +78,16 @@ Run tests cases (Yes we have some tests!)
Build library. This will save build assets to `dist/`

```bash
> npm run build
> npm run build # build the bundled library with webpack to /dist/bundle
> tsc # build unbundled library to /dist/lib
```

Publish to registry

1. update version in `package.json`
1. build bundle `npm run build` and build library `tsc`
1. run npm publish command

```bash
> coming eventually
> npm publish
```

## Todo

- add support to rotate the visualizer puzzle in puzzle options

## Notes

using webpack dev server 4 beta version due to issue described here with `webpack serve` https://github.com/webpack/webpack-dev-server/issues/2484
Binary file modified assets/cube-net-scrm.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/cube-net.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/cube-scrm.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/cube-top-scrm.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/cube-top.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/cube.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/megaminx-net-scrm.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/megaminx-net.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/megaminx-scrm.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/megaminx-top-scrm.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/megaminx-top.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/megaminx.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/pyraminx-net-scrm.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/pyraminx-net.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/pyraminx-scrm.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/pyraminx.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/skewb-net-scrm.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/skewb-net.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/skewb-scrm.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/skewb.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/sq1-net-scrm.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/sq1-net.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/sq1-scrm.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/sq1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed assets/sq1net.png
Binary file not shown.
2 changes: 1 addition & 1 deletion dist/bundle/puzzleGen.min.js.map

Large diffs are not rendered by default.

50 changes: 25 additions & 25 deletions src/demos/puzzles/puzzles.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { CubeOptions } from './../../../dist/lib/visualizer/interface.d';
import { SVGVisualizerOptions } from './../../visualizer/svg';
import { VisualizerType } from './../../visualizer/enum';
import { SVG } from '../../visualizer/svg';
import { PNG } from '../../visualizer/png';

function renderDefault() {
const options: SVGVisualizerOptions<any> = {
Expand All @@ -12,18 +12,18 @@ function renderDefault() {
translation: { x: 0, y: 0, z: 0 }
}
}
SVG("#cube", VisualizerType.CUBE, options);
SVG("#megaminx", VisualizerType.MEGAMINX, options);
SVG("#pyraminx", VisualizerType.PYRAMINX, options);
SVG("#skewb", VisualizerType.SKEWB, options);
SVG("#square1", VisualizerType.SQUARE1, options);
SVG("#cube-net", VisualizerType.CUBE_NET, options);
SVG("#megaminx-net", VisualizerType.MEGAMINX_NET, options);
SVG("#pyraminx-net", VisualizerType.PYRAMINX_NET, options);
SVG("#skewb-net", VisualizerType.SKEWB_NET, options);
SVG("#square1-net", VisualizerType.SQUARE1_NET, options);
SVG("#cube-top", VisualizerType.CUBE_TOP, options);
SVG("#mega-top", VisualizerType.MEGAMINX_TOP, options);
PNG("#cube", VisualizerType.CUBE, options);
PNG("#megaminx", VisualizerType.MEGAMINX, options);
PNG("#pyraminx", VisualizerType.PYRAMINX, options);
PNG("#skewb", VisualizerType.SKEWB, options);
PNG("#square1", VisualizerType.SQUARE1, options);
PNG("#cube-net", VisualizerType.CUBE_NET, options);
PNG("#megaminx-net", VisualizerType.MEGAMINX_NET, options);
PNG("#pyraminx-net", VisualizerType.PYRAMINX_NET, options);
PNG("#skewb-net", VisualizerType.SKEWB_NET, options);
PNG("#square1-net", VisualizerType.SQUARE1_NET, options);
PNG("#cube-top", VisualizerType.CUBE_TOP, options);
PNG("#mega-top", VisualizerType.MEGAMINX_TOP, options);
}

function renderScrambled() {
Expand Down Expand Up @@ -54,18 +54,18 @@ function renderScrambled() {

const square1Alg = "(-2,3)/(3,-1)/(3,-3)/(6,6)/(6,0)/(-2,-1)/(-4,-2)/(0,-3)/(0,-4)/(-4,5)/(-5,-2)/(2,-5)/(6,-4)/(-3,6)/(-2,2)/(3,-5)/";

SVG("#cube-scrambled", VisualizerType.CUBE, {...options, puzzle: { alg: cubeAlg }});
SVG("#cube-net-scrambled", VisualizerType.CUBE_NET, {...options, puzzle: { alg: cubeAlg }});
SVG("#cube-top-scrambled", VisualizerType.CUBE_TOP, {...options, puzzle: { alg: cubeAlg }});
SVG("#megaminx-scrambled", VisualizerType.MEGAMINX, {...options, puzzle: { alg: megaminxAlg }});
SVG("#megaminx-net-scrambled", VisualizerType.MEGAMINX_NET, {...options, puzzle: { alg: megaminxAlg }});
SVG("#pyraminx-scrambled", VisualizerType.PYRAMINX, {...options, puzzle: { alg: pyraminxAlg }});
SVG("#pyraminx-net-scrambled", VisualizerType.PYRAMINX_NET, {...options, puzzle: { alg: pyraminxAlg }});
SVG("#skewb-scrambled", VisualizerType.SKEWB, {...options, puzzle: { alg: skewbAlg }});
SVG("#skewb-net-scrambled", VisualizerType.SKEWB_NET, {...options, puzzle: { alg: skewbAlg }});
SVG("#square1-scrambled", VisualizerType.SQUARE1, {...options, puzzle: { alg: square1Alg }});
SVG("#square1-net-scrambled", VisualizerType.SQUARE1_NET, {...options, puzzle: { alg: square1Alg }});
SVG("#mega-top-scrambled", VisualizerType.MEGAMINX_TOP, {...options, puzzle: { alg: megaminxAlg }});
PNG("#cube-scrambled", VisualizerType.CUBE, {...options, puzzle: { alg: cubeAlg }});
PNG("#cube-net-scrambled", VisualizerType.CUBE_NET, {...options, puzzle: { alg: cubeAlg }});
PNG("#cube-top-scrambled", VisualizerType.CUBE_TOP, {...options, puzzle: { alg: cubeAlg }});
PNG("#megaminx-scrambled", VisualizerType.MEGAMINX, {...options, puzzle: { alg: megaminxAlg }});
PNG("#megaminx-net-scrambled", VisualizerType.MEGAMINX_NET, {...options, puzzle: { alg: megaminxAlg }});
PNG("#pyraminx-scrambled", VisualizerType.PYRAMINX, {...options, puzzle: { alg: pyraminxAlg }});
PNG("#pyraminx-net-scrambled", VisualizerType.PYRAMINX_NET, {...options, puzzle: { alg: pyraminxAlg }});
PNG("#skewb-scrambled", VisualizerType.SKEWB, {...options, puzzle: { alg: skewbAlg }});
PNG("#skewb-net-scrambled", VisualizerType.SKEWB_NET, {...options, puzzle: { alg: skewbAlg }});
PNG("#square1-scrambled", VisualizerType.SQUARE1, {...options, puzzle: { alg: square1Alg }});
PNG("#square1-net-scrambled", VisualizerType.SQUARE1_NET, {...options, puzzle: { alg: square1Alg }});
PNG("#mega-top-scrambled", VisualizerType.MEGAMINX_TOP, {...options, puzzle: { alg: megaminxAlg }});
}

document.addEventListener('DOMContentLoaded', function (event) {
Expand Down
1 change: 0 additions & 1 deletion src/geometry/arrow.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { IColor } from "./../../dist/lib/geometry/color.d";
import { vec3 } from "gl-matrix";
import { calculateCentroid } from "../math/utils";
import { Object3D } from "./object3d";
Expand Down
5 changes: 2 additions & 3 deletions src/rendering/customSvgRenderer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { YELLOW, BLACK } from "./../puzzles/colors";
import { IColor } from "./../../dist/lib/geometry/color.d";
import { BLACK } from "./../puzzles/colors";
import { Arrow } from "./../geometry/arrow";
import { IFace, Face } from "./../geometry/face";
import { Camera } from "./camera";
Expand All @@ -10,14 +9,14 @@ import { Object3D } from "../geometry/object3d";
import { Group } from "../geometry/group";
import {
createSVGElement,
clearSVG,
createPolygonElement,
updatePolygonElement,
createArrowLineElement,
createMarkers,
} from "../svg/svg";
import { Renderer } from "./renderer";
import { applyTransformations } from "./utils";
import { IColor } from "../geometry/color";

/**
* A renderer that renders a scene viewed by a camera to an svg element.
Expand Down
11 changes: 9 additions & 2 deletions src/visualizer/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,11 @@ export function validatePuzzleOptions(options: PuzzleOptions) {
} else {
Object.keys(options.scheme).forEach((face) => {
const faceColor = options.scheme[face];
if (faceColor == null || typeof faceColor !== "object" || !faceColor.value) {
if (
faceColor == null ||
typeof faceColor !== "object" ||
!faceColor.value
) {
console.warn(
`Invalid scheme color ${faceColor}. must be an type IColor`
);
Expand Down Expand Up @@ -198,7 +202,10 @@ export function validatePuzzleOptions(options: PuzzleOptions) {
}

if (options.stickerColors) {
if (typeof options.stickerColors !== "object" || Array.isArray(options.stickerColors)) {
if (
typeof options.stickerColors !== "object" ||
Array.isArray(options.stickerColors)
) {
console.warn(
`Invalid stickerColors ${options.stickerColors}. stickerColors must be an object`
);
Expand Down
62 changes: 62 additions & 0 deletions src/visualizer/png.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { GREY } from "./../puzzles/colors";
import { VisualizerType } from "./enum";
import { PuzzleOptions } from "./interface";
import { SVG, SVGVisualizerOptions } from "./svg";

export interface PNGVisualizerOptions<T> extends SVGVisualizerOptions<T> {}

const defaultOptions: PNGVisualizerOptions<any> = {
width: 500,
height: 500,
minx: -0.9,
miny: -0.9,
svgWidth: 1.8,
svgHeight: 1.8,
strokeWidth: 0.02,
arrowColor: GREY,
arrowStrokeWidth: 0.03,
};

/**
* Creates PNG element
*/
export function PNG<T extends PuzzleOptions>(
container: Element | string,
type: VisualizerType,
options: PNGVisualizerOptions<T> = {}
) {

if (typeof container === "string") {
container = document.querySelector(container as string);
if (container === null) {
throw new Error(
`Could not find visuzlier element by query selector: ${container}`
);
}
}

let element = document.createElement("div");
options = { ...defaultOptions, ...options };

SVG(element, type, options);

setTimeout(() => {
let svgElement = element.querySelector("svg");
let targetImage = document.createElement("img");
(container as Element).appendChild(targetImage);

let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
let loader = new Image();

loader.width = canvas.width = targetImage.width = options.width;
loader.height = canvas.height = targetImage.height = options.height;
loader.onload = function() {
ctx.drawImage(loader, 0, 0, loader.width, loader.height);
targetImage.src = canvas.toDataURL();
}

var svgAsXML = new XMLSerializer().serializeToString(svgElement);
loader.src = `data:image/svg+xml,${encodeURIComponent(svgAsXML)}`;
});
}
10 changes: 6 additions & 4 deletions src/visualizer/svg.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { IColor } from "./../../dist/lib/geometry/color.d";
import { BLACK, GREY } from "./../puzzles/colors";
import { VisualizerType } from "./enum";
import { CustomSVGRenderer } from "./../rendering/customSvgRenderer";
import { Visualizer } from "./visualizer";
import { PuzzleOptions, validColor } from "./interface";
import { IColor } from "../geometry/color";

export interface SVGVisualizerOptions<T> {
/**
Expand Down Expand Up @@ -140,7 +140,7 @@ export class SvgVisualizer<T extends PuzzleOptions> extends Visualizer {
setSvgOptions(options: SVGVisualizerOptions<T>) {
this.svgOptions = { ...defaultOptions, ...options };
validateSvgOptions(this.svgOptions);

const renderer: CustomSVGRenderer = this.renderer as CustomSVGRenderer;
const svgElement: SVGElement = renderer.svgElement;

Expand All @@ -166,7 +166,9 @@ function validateSvgOptions(options: SVGVisualizerOptions<any>) {
}

if (!Number.isInteger(options.height)) {
console.warn(`invalid svg height ${options.height}. Must be a whole number`);
console.warn(
`invalid svg height ${options.height}. Must be a whole number`
);
options.width = defaultOptions.height;
}

Expand Down Expand Up @@ -203,4 +205,4 @@ function validateSvgOptions(options: SVGVisualizerOptions<any>) {
if (options.arrowColor && !validColor(options.arrowColor)) {
options.arrowColor = BLACK;
}
}
}
Loading

0 comments on commit b726fbe

Please sign in to comment.