Refactor: Implement SmartCube renderer, improve UI styling, and fix gaps
This commit is contained in:
129
node_modules/rubiks-js/README.md
generated
vendored
Normal file
129
node_modules/rubiks-js/README.md
generated
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
# Rubiks Cube for the web
|
||||
|
||||
This library can be used to embed a rubiks cube into any website. Supports mouse and touch, is pure js typed with jsdoc and does not use any libraries.
|
||||
|
||||
## Usage
|
||||
|
||||
First install
|
||||
```
|
||||
npm install rubiks
|
||||
```
|
||||
|
||||
then create a rubiks cube and start it
|
||||
```javascript
|
||||
import {RubiksCube, defaultTexture, defaultUVs, defaultHoveringColors} from 'rubiks-js'
|
||||
|
||||
const rubiksCube = new RubiksCube(
|
||||
attributeName,
|
||||
defaultTexture,
|
||||
defaultUVs,
|
||||
defaultHoveringColors,
|
||||
trackCenters
|
||||
)
|
||||
rubiksCube.start()
|
||||
```
|
||||
webgl is automaticly instanciated when first calling `start()`.
|
||||
|
||||
### `attributeName`
|
||||
Specifies the data attribute on the canvas
|
||||
```html
|
||||
<canvas data-rubiks-cube></canvas>
|
||||
<!-- attributeName = 'data-rubiks-cube' -->
|
||||
```
|
||||
|
||||
### `texture`
|
||||
Specifies how the sides of the cube look. Can be used to have pictures instead of plane colors.
|
||||
|
||||
### `uvs`
|
||||
Specifies which sticker (facelet) uses which part of the [texture](#texture) and is a 3 dimensional number array. The first dimension corresponds to a side. This means the indices should range from 0 to 5. Detailed description for side indices can be found [here](#side). The second dimension corresponds to one of the nine facelets on a side and should be between 0 and 8. A detailed description for facelet indices can be found [here](#facelet). The last dimenstion is the actual coordinate values and should consist of 4 consecutive x and y pairs. The order of these pairs is important and should either be clockwise or anticlockwise depending on the texture. You may have to experiment.
|
||||
|
||||
### `hoveringColors`
|
||||
Specifies a red, green and blue channel by which the colors of a side is multiplied when hovering over it.
|
||||
|
||||
### `trackCenters`
|
||||
This is only usefull when using images. It is a boolean and if set to `true` the rotation
|
||||
of all 6 centers is included in the [state](#state).
|
||||
|
||||
|
||||
## State
|
||||
This library has a simple way of tracking the current state of the cube.
|
||||
Below is an example of how to store the state inside the url.
|
||||
|
||||
### `rubiks.on()`
|
||||
```javascript
|
||||
const url = new URL(location.toString())
|
||||
const state = url.searchParams.get('state')
|
||||
if (state != null) {
|
||||
rubiksCube.setState(state)
|
||||
}
|
||||
|
||||
rubiksCube.on('change', event => {
|
||||
const url = new URL(location.toString())
|
||||
url.searchParams.set('state', event.state.toString())
|
||||
history.replaceState(null, '', url)
|
||||
})
|
||||
rubiksCube.start()
|
||||
```
|
||||
`rubiksCube.setState` returns `true` if the state was parsed correctly and `false`
|
||||
if there was an error.
|
||||
|
||||
### `rubiks.reset()`
|
||||
Resets the state of the cube.
|
||||
|
||||
|
||||
## Indices
|
||||
Indices have the following order: (brackets use default values)
|
||||
- First from right (blue) to left (green)
|
||||
- Then from bottom (yellow) to top (white)
|
||||
- Then from front (red) to back (orange)
|
||||
### Side
|
||||
| Side | Index |
|
||||
| --- | --- |
|
||||
| right | 0 |
|
||||
| left | 1 |
|
||||
| bottom | 2 |
|
||||
| top | 3 |
|
||||
| front | 4 |
|
||||
| back | 5 |
|
||||
|
||||
### Facelet
|
||||
This is a bit more complicated to explain. The indices follow the same [rule](#indices) as the [sides](#side). For example if you look at the front side without moving the cube index 0 would be the bottom right facelet and index 1 would be 1 to the left. Thats because the indices go first from left to right and the from bottom to top. This means index 0, 1 and 2 are the bottom row, 3, 4 and 5 the row above and so on. And remember always from left to right. In this example we were able to ignore the third [index rule](#indices) because these facelets where pointed at the front so it doesn't make sense to use this rule. Following the same logic we can always ignore on rule when figuring the facelet indices out.
|
||||
|
||||
If you want a visual way to see all indices use the following code and copy the [numbers.png](https://github.com/pedeEli/rubiks-cube-v2/blob/main/number.png) image to your source files
|
||||
```javascript
|
||||
import {RubiksCube, defaultHovorvingColors} from 'rubiks-js'
|
||||
|
||||
const image = new Image()
|
||||
image.src = 'number.png' // or 'https://raw.githubusercontent.com/pedeEli/rubiks-cube-v2/main/number.png'
|
||||
|
||||
const uvs = Array(6).fill(null).map((_, side) => {
|
||||
const bottom = 0.5 + Math.floor(side / 3) * 0.5
|
||||
const left = (side % 3) / 3
|
||||
return Array(9).fill(null).map((_, sideIndex) => {
|
||||
const b = bottom - (sideIndex % 3) / 6
|
||||
const t = b - 1 / 6
|
||||
const l = left + Math.floor(sideIndex / 3) / 9
|
||||
const r = l + 1 / 9
|
||||
|
||||
return [
|
||||
l, b,
|
||||
r, b,
|
||||
r, t,
|
||||
l, t
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
image.addEventListener('load', () => {
|
||||
const rubiksCube = new RubiksCube(
|
||||
'data-rubiks-cube',
|
||||
image,
|
||||
uvs,
|
||||
defaultHovorvingColors,
|
||||
true
|
||||
)
|
||||
|
||||
rubiksCube.start()
|
||||
})
|
||||
|
||||
```
|
||||
40
node_modules/rubiks-js/package.json
generated
vendored
Normal file
40
node_modules/rubiks-js/package.json
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "rubiks-js",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"description": "A rubiks cube with no dependencies. Can use custom images for faces. The state of the cube can be safed.",
|
||||
"keywords": [
|
||||
"rubiks",
|
||||
"cube",
|
||||
"rubiks cube",
|
||||
"3x3x3",
|
||||
"3x3",
|
||||
"state",
|
||||
"jsdoc",
|
||||
"image",
|
||||
"picture",
|
||||
"touch",
|
||||
"mobile",
|
||||
"phone"
|
||||
],
|
||||
"homepage": "https://github.com/pedeEli/rubiks/blob/main/packages/rubiks/README.md",
|
||||
"bugs": {
|
||||
"url": "https://github.com/pedeEli/rubiks/issues"
|
||||
},
|
||||
"license": "MIT",
|
||||
"author": {
|
||||
"name": "Elias Gerster"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/pedeEli/rubiks.git"
|
||||
},
|
||||
"main": "./src/index.js",
|
||||
"scripts": {
|
||||
"check": "tsc",
|
||||
"publish": "npm publish"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.4.3"
|
||||
}
|
||||
}
|
||||
91
node_modules/rubiks-js/src/converter.js
generated
vendored
Normal file
91
node_modules/rubiks-js/src/converter.js
generated
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* @typedef {import('./types').AIA} AIA
|
||||
* @typedef {import('./state/types').TurnBase} TurnBase
|
||||
* @typedef {import('./state/types').Turn} Turn
|
||||
*/
|
||||
|
||||
/** @type {[a: TurnBase, b: TurnBase, invert: boolean][]} */
|
||||
const axisToTurn = [
|
||||
['R', 'L', false],
|
||||
['D', 'U', true],
|
||||
['F', 'B', false]
|
||||
]
|
||||
|
||||
/**
|
||||
* @param {AIA} aia
|
||||
* @returns {Turn}
|
||||
*/
|
||||
export const convertAiaToTurn = (aia) => {
|
||||
if (aia.index === 1) {
|
||||
throw new Error('Cannot convert middle turns')
|
||||
}
|
||||
|
||||
let [a, b, invert] = axisToTurn[aia.axis]
|
||||
|
||||
const turn = aia.index === 0 ? a : b
|
||||
|
||||
if (aia.angle === 2) {
|
||||
return `${turn}2`
|
||||
}
|
||||
|
||||
if (aia.index === 2) {
|
||||
invert = !invert
|
||||
}
|
||||
|
||||
if (aia.angle === 3 && !invert || aia.angle === 1 && invert) {
|
||||
return `${turn}'`
|
||||
}
|
||||
|
||||
return turn
|
||||
}
|
||||
|
||||
/** @type {Record<TurnBase, [axis: number, index: number, invert: boolean]>} */
|
||||
const turnToAia = {
|
||||
R: [0, 0, false],
|
||||
L: [0, 2, false],
|
||||
D: [1, 0, true],
|
||||
U: [1, 2, true],
|
||||
F: [2, 0, false],
|
||||
B: [2, 2, false]
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Turn} turn
|
||||
* @returns {AIA}
|
||||
*/
|
||||
export const convertTurnToAia = (turn) => {
|
||||
const base = /** @type {TurnBase} */ (turn[0])
|
||||
|
||||
const [axis, index, invert] = turnToAia[base]
|
||||
|
||||
if (turn.endsWith('2')) {
|
||||
return {axis, index, angle: 2}
|
||||
}
|
||||
|
||||
let prime = turn.endsWith('\'')
|
||||
if (invert) {
|
||||
prime = !prime
|
||||
}
|
||||
|
||||
let angle = 3
|
||||
if (index === 0 && !prime || index === 2 && prime) {
|
||||
angle = 1
|
||||
}
|
||||
return {axis, index, angle}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {Turn} turn
|
||||
* @returns {TurnBase[]}
|
||||
*/
|
||||
export const convertTurnToTurnBase = turn => {
|
||||
const base = /** @type {TurnBase} */ (turn[0])
|
||||
if (turn.endsWith('2')) {
|
||||
return [base, base]
|
||||
}
|
||||
if (turn.endsWith('\'')) {
|
||||
return [base, base, base]
|
||||
}
|
||||
return [base]
|
||||
}
|
||||
56
node_modules/rubiks-js/src/events.js
generated
vendored
Normal file
56
node_modules/rubiks-js/src/events.js
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
export class ChangeEvent {
|
||||
/**
|
||||
* Axis that was turned around
|
||||
*
|
||||
* 0 => x-axis (from right to left)
|
||||
*
|
||||
* 1 => y-axis (from bottom to top)
|
||||
*
|
||||
* 2 => z-axis (from front to back)
|
||||
* @type {0 | 1 | 2}
|
||||
*/
|
||||
axis
|
||||
/**
|
||||
* The slice on the axis. Example using axis = 0:
|
||||
*
|
||||
* 0 => Left slice
|
||||
*
|
||||
* 2 => Right slice
|
||||
*
|
||||
* The middle layer is never turned. Instead both outer layers are turn
|
||||
* in the opposite direction. This has the same effect.
|
||||
* That way the centers always stay in the same position.
|
||||
* @type {0 | 2}
|
||||
*/
|
||||
index
|
||||
/**
|
||||
* Important: The angle is always clockwise around the axis and not
|
||||
* clockwise around the turning side. This means that R and L' both
|
||||
* have an angle of 1
|
||||
* @type {1 | 2 | 3}
|
||||
*/
|
||||
angle
|
||||
/**
|
||||
* @type {import('./state/types').Turn}
|
||||
*/
|
||||
turn
|
||||
/**
|
||||
* @type {import('./state').StateInfo}
|
||||
*/
|
||||
state
|
||||
|
||||
/**
|
||||
* @param {number} axis
|
||||
* @param {number} index
|
||||
* @param {number} angle
|
||||
* @param {import('./state/types').Turn} turn
|
||||
* @param {import('./state').StateInfo} state
|
||||
*/
|
||||
constructor(axis, index, angle, turn, state) {
|
||||
this.axis = /** @type {0 | 1 | 2} */ (axis)
|
||||
this.index = /** @type {0 | 2} */ (index)
|
||||
this.angle = /** @type {1 | 2 | 3} */ (angle)
|
||||
this.turn = turn
|
||||
this.state = state
|
||||
}
|
||||
}
|
||||
23
node_modules/rubiks-js/src/index.js
generated
vendored
Normal file
23
node_modules/rubiks-js/src/index.js
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
export {RubiksCube} from './rubiksCube'
|
||||
|
||||
export const defaultTexture = new ImageData(new Uint8ClampedArray([
|
||||
0, 0, 255, 255,
|
||||
0, 255, 0, 255,
|
||||
255, 255, 0, 255,
|
||||
255, 255, 255, 255,
|
||||
255, 0, 0, 255,
|
||||
255, 127, 0, 255
|
||||
]), 1, 6)
|
||||
|
||||
/** @type {number[][][]} */
|
||||
export const defaultUVs = Array(6).fill(null).map((_, index) => {
|
||||
return Array(9).fill([
|
||||
0, (index + 0) / 6,
|
||||
1, (index + 0) / 6,
|
||||
1, (index + 1) / 6,
|
||||
0, (index + 1) / 6
|
||||
])
|
||||
})
|
||||
|
||||
/** @type {number[][]} */
|
||||
export const defaultHovorvingColors = Array(6).fill(Array(3).fill(.7))
|
||||
13
node_modules/rubiks-js/src/math/abstractVector.d.ts
generated
vendored
Normal file
13
node_modules/rubiks-js/src/math/abstractVector.d.ts
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
export declare abstract class Vector<V extends Vector<any>> {
|
||||
abstract scale(a: number): V
|
||||
abstract add(v: V): V
|
||||
abstract sub(v: V): V
|
||||
abstract mult(v: V): V
|
||||
abstract dot(v: V): number
|
||||
abstract toArray(): number[]
|
||||
|
||||
get squareMag(): number
|
||||
get mag(): number
|
||||
get normalized(): V
|
||||
get negate(): V
|
||||
}
|
||||
67
node_modules/rubiks-js/src/math/abstractVector.js
generated
vendored
Normal file
67
node_modules/rubiks-js/src/math/abstractVector.js
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* @template {Vector<any>} V
|
||||
* @abstract
|
||||
*/
|
||||
export class Vector {
|
||||
/**
|
||||
* @abstract
|
||||
* @param {number} _a
|
||||
*/
|
||||
scale(_a) {
|
||||
throw new Error('must be implemented in subclass')
|
||||
}
|
||||
/**
|
||||
* @abstract
|
||||
* @param {V} _v
|
||||
* @returns {V}
|
||||
*/
|
||||
add(_v) {
|
||||
throw new Error('must be implemented in subclass')
|
||||
}
|
||||
/**
|
||||
* @abstract
|
||||
* @param {V} _v
|
||||
* @return {V}
|
||||
*/
|
||||
sub(_v) {
|
||||
throw new Error('must be implemented in subclass')
|
||||
}
|
||||
/**
|
||||
* @abstract
|
||||
* @param {V} _v
|
||||
* @return {V}
|
||||
*/
|
||||
mult(_v) {
|
||||
throw new Error('must be implemented in subclass')
|
||||
}
|
||||
/**
|
||||
* @abstract
|
||||
* @param {V} _v
|
||||
* @return {number}
|
||||
*/
|
||||
dot(_v) {
|
||||
throw new Error('must be implemented in subclass')
|
||||
}
|
||||
/**
|
||||
* @abstract
|
||||
* @return {number[]}
|
||||
*/
|
||||
toArray() {
|
||||
throw new Error('must be implemented in subclass')
|
||||
}
|
||||
|
||||
get squareMag() {
|
||||
/** @type {any} */
|
||||
const t = this
|
||||
return this.dot(t)
|
||||
}
|
||||
get mag() {
|
||||
return Math.sqrt(this.squareMag)
|
||||
}
|
||||
get normalized() {
|
||||
return this.scale(1 / this.mag)
|
||||
}
|
||||
get negate() {
|
||||
return this.scale(-1)
|
||||
}
|
||||
}
|
||||
194
node_modules/rubiks-js/src/math/matrix.js
generated
vendored
Normal file
194
node_modules/rubiks-js/src/math/matrix.js
generated
vendored
Normal file
@@ -0,0 +1,194 @@
|
||||
import {V4, V3} from './vector'
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {import('../types').Uniform} Uniform
|
||||
* @implements {Uniform}
|
||||
*/
|
||||
export class M44 {
|
||||
/**
|
||||
* @param {V4} r1
|
||||
* @param {V4} r2
|
||||
* @param {V4} r3
|
||||
* @param {V4} r4
|
||||
*/
|
||||
constructor(r1, r2, r3, r4) {
|
||||
this.r1 = r1
|
||||
this.r2 = r2
|
||||
this.r3 = r3
|
||||
this.r4 = r4
|
||||
}
|
||||
|
||||
/** @param {number} a */
|
||||
scale(a) {
|
||||
return new M44(this.r1.scale(a), this.r2.scale(a), this.r3.scale(a), this.r4.scale(a))
|
||||
}
|
||||
|
||||
/** @param {M44} m */
|
||||
add({r1, r2, r3, r4}) {
|
||||
return new M44(this.r1.add(r1), this.r2.add(r2), this.r3.add(r3), this.r4.add(r4))
|
||||
}
|
||||
|
||||
/** @param {M44} m */
|
||||
sub({r1, r2, r3, r4}) {
|
||||
return new M44(this.r1.sub(r1), this.r2.sub(r2), this.r3.sub(r3), this.r4.sub(r4))
|
||||
}
|
||||
|
||||
/**
|
||||
* @overload
|
||||
* @param {M44} m
|
||||
* @return {M44}
|
||||
*
|
||||
* @overload
|
||||
* @param {V4} m
|
||||
* @return {V4}
|
||||
*
|
||||
* @param {M44 | V4} m
|
||||
*/
|
||||
mult(m) {
|
||||
if ('x' in m) {
|
||||
return new V4(
|
||||
this.r1.dot(m),
|
||||
this.r2.dot(m),
|
||||
this.r3.dot(m),
|
||||
this.r4.dot(m)
|
||||
)
|
||||
}
|
||||
return new M44(
|
||||
new V4(this.r1.dot(m.c1), this.r1.dot(m.c2), this.r1.dot(m.c3), this.r1.dot(m.c4)),
|
||||
new V4(this.r2.dot(m.c1), this.r2.dot(m.c2), this.r2.dot(m.c3), this.r2.dot(m.c4)),
|
||||
new V4(this.r3.dot(m.c1), this.r3.dot(m.c2), this.r3.dot(m.c3), this.r3.dot(m.c4)),
|
||||
new V4(this.r4.dot(m.c1), this.r4.dot(m.c2), this.r4.dot(m.c3), this.r4.dot(m.c4))
|
||||
)
|
||||
}
|
||||
toArray() {
|
||||
return [...this.r1.toArray(), ...this.r2.toArray(), ...this.r3.toArray(), ...this.r4.toArray()]
|
||||
}
|
||||
static get identity() {
|
||||
return new M44(
|
||||
new V4(1, 0, 0, 0),
|
||||
new V4(0, 1, 0, 0),
|
||||
new V4(0, 0, 1, 0),
|
||||
new V4(0, 0, 0, 1)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
get c1() {
|
||||
return new V4(this.r1.x, this.r2.x, this.r3.x, this.r4.x)
|
||||
}
|
||||
get c2() {
|
||||
return new V4(this.r1.y, this.r2.y, this.r3.y, this.r4.y)
|
||||
}
|
||||
get c3() {
|
||||
return new V4(this.r1.z, this.r2.z, this.r3.z, this.r4.z)
|
||||
}
|
||||
get c4() {
|
||||
return new V4(this.r1.w, this.r2.w, this.r3.w, this.r4.w)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {WebGL2RenderingContext} gl
|
||||
* @param {WebGLUniformLocation} location
|
||||
*/
|
||||
setUniform(gl, location) {
|
||||
const data = new Float32Array(this.toArray())
|
||||
gl.uniformMatrix4fv(location, true, data)
|
||||
}
|
||||
|
||||
get transpose() {
|
||||
return new M44(this.c1, this.c2, this.c3, this.c4)
|
||||
}
|
||||
|
||||
get inverse() {
|
||||
const [i00, i01, i02, i03] = this.r1.toArray()
|
||||
const [i10, i11, i12, i13] = this.r2.toArray()
|
||||
const [i20, i21, i22, i23] = this.r3.toArray()
|
||||
const [i30, i31, i32, i33] = this.r4.toArray()
|
||||
|
||||
const s0 = i00 * i11 - i10 * i01
|
||||
const s1 = i00 * i12 - i10 * i02
|
||||
const s2 = i00 * i13 - i10 * i03
|
||||
const s3 = i01 * i12 - i11 * i02
|
||||
const s4 = i01 * i13 - i11 * i03
|
||||
const s5 = i02 * i13 - i12 * i03
|
||||
const c5 = i22 * i33 - i32 * i23
|
||||
const c4 = i21 * i33 - i31 * i23
|
||||
const c3 = i21 * i32 - i31 * i22
|
||||
const c2 = i20 * i33 - i30 * i23
|
||||
const c1 = i20 * i32 - i30 * i22
|
||||
const c0 = i20 * i31 - i30 * i21
|
||||
const det = s0 * c5 - s1 * c4 + s2 * c3 + s3 * c2 - s4 * c1 + s5 * c0
|
||||
const invDet = 1 / det
|
||||
return new M44(
|
||||
new V4(
|
||||
(i11 * c5 - i12 * c4 + i13 * c3),
|
||||
(-i01 * c5 + i02 * c4 - i03 * c3),
|
||||
(i31 * s5 - i32 * s4 + i33 * s3),
|
||||
(-i21 * s5 + i22 * s4 - i23 * s3)
|
||||
),
|
||||
new V4(
|
||||
(-i10 * c5 + i12 * c2 - i13 * c1),
|
||||
(i00 * c5 - i02 * c2 + i03 * c1),
|
||||
(-i30 * s5 + i32 * s2 - i33 * s1),
|
||||
(i20 * s5 - i22 * s2 + i23 * s1)
|
||||
),
|
||||
new V4(
|
||||
(i10 * c4 - i11 * c2 + i13 * c0),
|
||||
(-i00 * c4 + i01 * c2 - i03 * c0),
|
||||
(i30 * s4 - i31 * s2 + i33 * s0),
|
||||
(-i20 * s4 + i21 * s2 - i23 * s0)
|
||||
),
|
||||
new V4(
|
||||
(-i10 * c3 + i11 * c1 - i12 * c0),
|
||||
(i00 * c3 - i01 * c1 + i02 * c0),
|
||||
(-i30 * s3 + i31 * s1 - i32 * s0),
|
||||
(i20 * s3 - i21 * s1 + i22 * s0)
|
||||
)
|
||||
).scale(invDet)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} fovy
|
||||
* @param {number} aspect
|
||||
* @param {number} near
|
||||
* @param {number} far
|
||||
*/
|
||||
static perspective(fovy, aspect, near, far) {
|
||||
const tanHalfFovy = Math.tan(fovy / 2)
|
||||
const x = 1 / (aspect * tanHalfFovy)
|
||||
const y = 1 / tanHalfFovy
|
||||
const fpn = far + near
|
||||
const fmn = far - near
|
||||
const oon = .5 / near
|
||||
const oof = .5 / far
|
||||
const z = -fpn / fmn
|
||||
const w = 1 / (oof - oon)
|
||||
return new M44(
|
||||
new V4(x, 0, 0, 0),
|
||||
new V4(0, y, 0, 0),
|
||||
new V4(0, 0, z, w),
|
||||
new V4(0, 0, -1, 0)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {V3} eye
|
||||
* @param {V3} center
|
||||
* @param {V3} up
|
||||
*/
|
||||
static lookAt(eye, center, up) {
|
||||
const za = center.sub(eye).normalized
|
||||
const xa = za.cross(up).normalized
|
||||
const ya = xa.cross(za)
|
||||
const xd = -xa.dot(eye)
|
||||
const yd = -ya.dot(eye)
|
||||
const zd = za.dot(eye)
|
||||
return new M44(
|
||||
new V4( xa.x, xa.y, xa.z, xd),
|
||||
new V4( ya.x, ya.y, ya.z, yd),
|
||||
new V4(-za.x, -za.y, -za.z, zd),
|
||||
new V4( 0, 0, 0, 1)
|
||||
)
|
||||
}
|
||||
}
|
||||
84
node_modules/rubiks-js/src/math/quarternion.js
generated
vendored
Normal file
84
node_modules/rubiks-js/src/math/quarternion.js
generated
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
import {V3, V4} from './vector'
|
||||
import {M44} from './matrix'
|
||||
|
||||
export class Quaternion {
|
||||
/**
|
||||
* @param {number} real
|
||||
* @param {V3} im
|
||||
*/
|
||||
constructor(real, im) {
|
||||
this.real = real
|
||||
this.im = im
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {V3} axis
|
||||
* @param {number} angle
|
||||
* @param {boolean} [degree=true]
|
||||
*/
|
||||
static fromAngle(axis, angle, degree = true) {
|
||||
if (degree) {
|
||||
angle *= Math.PI / 180
|
||||
}
|
||||
const half = angle / 2;
|
||||
const real = Math.cos(half)
|
||||
const im = axis.normalized.scale(Math.sin(half))
|
||||
return new Quaternion(real, im)
|
||||
}
|
||||
get matrix() {
|
||||
const {x, y, z} = this.im
|
||||
const w = this.real
|
||||
const xx = x * x
|
||||
const yy = y * y
|
||||
const zz = z * z
|
||||
const xy = x * y
|
||||
const xz = x * z
|
||||
const xw = x * w
|
||||
const yz = y * z
|
||||
const yw = y * w
|
||||
const zw = z * w
|
||||
return new M44(
|
||||
new V4(1 - 2 * (yy + zz), 2 * (xy - zw), 2 * (xz + yw), 0),
|
||||
new V4(2 * (xy + zw), 1 - 2 * (xx + zz), 2 * (yz - xw), 0),
|
||||
new V4(2 * (xz - yw), 2 * (yz + xw), 1 - 2 * (xx + yy), 0),
|
||||
new V4( 0, 0, 0, 1)
|
||||
)
|
||||
}
|
||||
/** @param {Quaternion} q */
|
||||
mult({real, im}) {
|
||||
return new Quaternion(this.real * real - this.im.dot(im), this.im.cross(im).add(im.scale(this.real)).add(this.im.scale(real)))
|
||||
}
|
||||
/** @param {V3} v */
|
||||
rotate(v) {
|
||||
return new Quaternion(this.real, this.im.negate).mult(new Quaternion(0, v)).mult(this).im
|
||||
}
|
||||
|
||||
get conjugate() {
|
||||
return new Quaternion(this.real, this.im.negate)
|
||||
}
|
||||
get mag() {
|
||||
return Math.sqrt(this.real * this.real + this.im.squareMag)
|
||||
}
|
||||
|
||||
/** @param {number} n */
|
||||
power(n) {
|
||||
const {mag} = this
|
||||
const phi = Math.acos(this.real / mag)
|
||||
const unit = this.im.normalized
|
||||
const scalar = Math.pow(mag, n)
|
||||
return new Quaternion(scalar * Math.cos(phi * n), unit.scale(scalar * Math.sin(phi * n)))
|
||||
}
|
||||
|
||||
static get identity() {
|
||||
return new Quaternion(1, V3.zero)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Quaternion} q1
|
||||
* @param {Quaternion} q2
|
||||
* @param {number} t
|
||||
*/
|
||||
static slerp(q1, q2, t) {
|
||||
return q1.mult(q1.conjugate.mult(q2).power(t))
|
||||
}
|
||||
}
|
||||
28
node_modules/rubiks-js/src/math/utils.js
generated
vendored
Normal file
28
node_modules/rubiks-js/src/math/utils.js
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* @param {number} a
|
||||
* @param {number} b
|
||||
* @returns {number}
|
||||
*/
|
||||
export const mod = (a, b) => {
|
||||
return ((a % b) + b) % b
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} a
|
||||
* @param {number} b
|
||||
* @param {number} t
|
||||
* @returns {number}
|
||||
*/
|
||||
export const lerp = (a, b, t) => {
|
||||
return a + (b - a) * t
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} x
|
||||
* @param {number} min
|
||||
* @param {number} max
|
||||
* @returns {number}
|
||||
*/
|
||||
export const clamp = (x, min, max) => {
|
||||
return Math.min(Math.max(x, min), max)
|
||||
}
|
||||
217
node_modules/rubiks-js/src/math/vector.js
generated
vendored
Normal file
217
node_modules/rubiks-js/src/math/vector.js
generated
vendored
Normal file
@@ -0,0 +1,217 @@
|
||||
import {Vector} from './abstractVector'
|
||||
|
||||
/**
|
||||
* @typedef {import('../types').Uniform} Uniform
|
||||
*/
|
||||
|
||||
/**
|
||||
* @extends {Vector<V2>}
|
||||
* @implements {Uniform}
|
||||
*/
|
||||
export class V2 extends Vector {
|
||||
/**
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
*/
|
||||
constructor(x, y) {
|
||||
super()
|
||||
this.x = x
|
||||
this.y = y
|
||||
}
|
||||
/** @param {number} a */
|
||||
scale(a) {
|
||||
return new V2(a * this.x, a * this.y)
|
||||
}
|
||||
/** @param {V2} v */
|
||||
add({x, y}) {
|
||||
return new V2(this.x + x, this.y + y)
|
||||
}
|
||||
/** @param {V2} v */
|
||||
sub({x, y}) {
|
||||
return new V2(this.x - x, this.y - y)
|
||||
}
|
||||
/** @param {V2} v */
|
||||
mult({x, y}) {
|
||||
return new V2(this.x * x, this.y * y)
|
||||
}
|
||||
/** @param {V2} v */
|
||||
dot({x, y}) {
|
||||
return this.x * x + this.y * y
|
||||
}
|
||||
toArray() {
|
||||
return [this.x, this.y]
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {WebGL2RenderingContext} gl
|
||||
* @param {WebGLUniformLocation} location
|
||||
*/
|
||||
setUniform(gl, location) {
|
||||
gl.uniform2f(location, this.x, this.y)
|
||||
}
|
||||
|
||||
static get zero() {
|
||||
return new V2(0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @extends {Vector<V3>}
|
||||
* @implements {Uniform}
|
||||
*/
|
||||
export class V3 extends Vector {
|
||||
/**
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {number} z
|
||||
*/
|
||||
constructor(x, y, z) {
|
||||
super()
|
||||
this.x = x
|
||||
this.y = y
|
||||
this.z = z
|
||||
}
|
||||
/** @param {number} a */
|
||||
scale(a) {
|
||||
return new V3(a * this.x, a * this.y, a * this.z)
|
||||
}
|
||||
/** @param {V3} v */
|
||||
add({x, y, z}) {
|
||||
return new V3(this.x + x, this.y + y, this.z + z)
|
||||
}
|
||||
/** @param {V3} v */
|
||||
sub({x, y, z}) {
|
||||
return new V3(this.x - x, this.y - y, this.z - z)
|
||||
}
|
||||
/** @param {V3} v */
|
||||
mult({x, y, z}) {
|
||||
return new V3(this.x * x, this.y * y, this.z * z)
|
||||
}
|
||||
/** @param {V3} v */
|
||||
cross({x, y, z}) {
|
||||
return new V3(this.y * z - this.z * y, this.z * x - this.x * z, this.x * y - this.y * x)
|
||||
}
|
||||
/** @param {V3} v */
|
||||
dot({x, y, z}) {
|
||||
return this.x * x + this.y * y + this.z * z
|
||||
}
|
||||
toArray() {
|
||||
return [this.x, this.y, this.z]
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {WebGL2RenderingContext} gl
|
||||
* @param {WebGLUniformLocation} location
|
||||
*/
|
||||
setUniform(gl, location) {
|
||||
gl.uniform3f(location, this.x, this.y, this.z)
|
||||
}
|
||||
toV2() {
|
||||
return new V2(this.x, this.y)
|
||||
}
|
||||
|
||||
static get zero() {
|
||||
return new V3(0, 0, 0)
|
||||
}
|
||||
static get one() {
|
||||
return new V3(1, 1, 1)
|
||||
}
|
||||
static get up() {
|
||||
return new V3(0, 1, 0)
|
||||
}
|
||||
static get down() {
|
||||
return new V3(0, -1, 0)
|
||||
}
|
||||
static get left() {
|
||||
return new V3(1, 0, 0)
|
||||
}
|
||||
static get right() {
|
||||
return new V3(-1, 0, 0)
|
||||
}
|
||||
static get forward() {
|
||||
return new V3(0, 0, 1)
|
||||
}
|
||||
static get back() {
|
||||
return new V3(0, 0, -1)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {V3} v1
|
||||
* @param {V3} v2
|
||||
* @param {number} t
|
||||
*/
|
||||
static lerp(v1, v2, t) {
|
||||
return v1.add(v2.sub(v1).scale(t))
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {V3} v1
|
||||
* @param {V3} v2
|
||||
*/
|
||||
static angle(v1, v2) {
|
||||
return Math.acos(v1.dot(v2) / Math.sqrt(v1.squareMag * v2.squareMag))
|
||||
}
|
||||
|
||||
/** @param {number} axis */
|
||||
static getRotationAxis(axis) {
|
||||
if (axis === 0)
|
||||
return V3.right
|
||||
if (axis === 1)
|
||||
return V3.down
|
||||
return V3.back
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @extends {Vector<V4>}
|
||||
* @implements {Uniform}
|
||||
*/
|
||||
export class V4 extends Vector {
|
||||
/**
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {number} z
|
||||
* @param {number} w
|
||||
*/
|
||||
constructor(x, y, z, w) {
|
||||
super()
|
||||
this.x = x
|
||||
this.y = y
|
||||
this.z = z
|
||||
this.w = w
|
||||
}
|
||||
/** @param {number} a */
|
||||
scale(a) {
|
||||
return new V4(a * this.x, a * this.y, a * this.z, a * this.w)
|
||||
}
|
||||
/** @param {V4} v */
|
||||
add({x, y, z, w}) {
|
||||
return new V4(this.x + x, this.y + y, this.z + z, this.w + w)
|
||||
}
|
||||
/** @param {V4} v */
|
||||
sub({x, y, z, w}) {
|
||||
return new V4(this.x - x, this.y - y, this.z - z, this.w - w)
|
||||
}
|
||||
/** @param {V4} v */
|
||||
mult({x, y, z, w}) {
|
||||
return new V4(this.x * x, this.y * y, this.z * z, this.w * w)
|
||||
}
|
||||
/** @param {V4} v */
|
||||
dot({x, y, z, w}) {
|
||||
return this.x * x + this.y * y + this.z * z + this.w * w
|
||||
}
|
||||
toV3() {
|
||||
return new V3(this.x, this.y, this.z)
|
||||
}
|
||||
toArray() {
|
||||
return [this.x, this.y, this.z, this.w]
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {WebGL2RenderingContext} gl
|
||||
* @param {WebGLUniformLocation} location
|
||||
*/
|
||||
setUniform(gl, location) {
|
||||
gl.uniform4f(location, this.x, this.y, this.z, this.w)
|
||||
}
|
||||
}
|
||||
300
node_modules/rubiks-js/src/rubiksCube.js
generated
vendored
Normal file
300
node_modules/rubiks-js/src/rubiksCube.js
generated
vendored
Normal file
@@ -0,0 +1,300 @@
|
||||
import {V3} from './math/vector'
|
||||
import {Quaternion} from './math/quarternion'
|
||||
|
||||
import {Program} from './ui/program'
|
||||
import {Camera} from './ui/camera'
|
||||
import {Rubiks} from './ui/rubiks'
|
||||
import {InputHandler} from './ui/inputHandler'
|
||||
import debug from './ui/debugger'
|
||||
|
||||
import {vertex, fragment} from './shaders/facelet.glsl'
|
||||
|
||||
import {State} from './state'
|
||||
|
||||
import {convertAiaToTurn} from './converter'
|
||||
|
||||
import {ChangeEvent} from './events'
|
||||
|
||||
|
||||
/** @typedef {import('./types').Events} Events */
|
||||
|
||||
export class RubiksCube {
|
||||
#initialized = false
|
||||
|
||||
/** @type {WebGL2RenderingContext} */
|
||||
#gl
|
||||
/** @type {HTMLCanvasElement} */
|
||||
#canvas
|
||||
/** @type {Program} */
|
||||
#program
|
||||
/** @type {WebGLVertexArrayObject} */
|
||||
#vao
|
||||
/** @type {WebGLBuffer} */
|
||||
#uvsVbo
|
||||
/** @type {WebGLTexture} */
|
||||
#texture
|
||||
|
||||
/** @type {Camera} */
|
||||
#camera
|
||||
/** @type {Rubiks} */
|
||||
#rubiks
|
||||
/** @type {InputHandler} */
|
||||
#inputHandler
|
||||
/** @type {State} */
|
||||
#state
|
||||
/** @type {boolean} */
|
||||
#trackCenters
|
||||
|
||||
#frame = 0
|
||||
#resizeHandler = RubiksCube.#getResizeHandler(this)
|
||||
|
||||
/** @type {string} */
|
||||
#canvasData
|
||||
/** @type {ImageData | HTMLImageElement} */
|
||||
#image
|
||||
/** @type {number[][][]} */
|
||||
#uvs
|
||||
/** @type {number[][]} */
|
||||
#hoveringColors
|
||||
|
||||
/**
|
||||
* @param {string} canvasData
|
||||
* @param {ImageData | HTMLImageElement} image
|
||||
* @param {number[][][]} uvs
|
||||
* @param {number[][]} hoveringColors
|
||||
* @param {boolean} trackCenters
|
||||
*/
|
||||
constructor(canvasData, image, uvs, hoveringColors, trackCenters) {
|
||||
this.#canvasData = canvasData
|
||||
this.#image = image
|
||||
this.#uvs = uvs
|
||||
this.#hoveringColors = hoveringColors
|
||||
this.#trackCenters = trackCenters
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts rendering the cube and listening for user inputs.
|
||||
* Automaticly initializes webgl and pointer event listeners.
|
||||
*/
|
||||
start() {
|
||||
if (!this.#initialized) {
|
||||
this.#initialize()
|
||||
this.#initialized = true
|
||||
}
|
||||
|
||||
this.#inputHandler.addEventListeners()
|
||||
|
||||
window.addEventListener('resize', this.#resizeHandler)
|
||||
this.#resizeHandler()
|
||||
|
||||
this.#program.use()
|
||||
this.#gl.bindVertexArray(this.#vao)
|
||||
|
||||
this.#gl.activeTexture(this.#gl.TEXTURE0)
|
||||
this.#gl.bindTexture(this.#gl.TEXTURE_2D, this.#texture)
|
||||
this.#program.uniform('tex', {
|
||||
setUniform: (gl, location) => gl.uniform1i(location, 0)
|
||||
})
|
||||
|
||||
let lastTime = Date.now()
|
||||
const loop = () => {
|
||||
const currentTime = Date.now()
|
||||
const deltaTime = (currentTime - lastTime) / 1000
|
||||
lastTime = currentTime
|
||||
|
||||
this.#gl.clear(this.#gl.COLOR_BUFFER_BIT | this.#gl.DEPTH_BUFFER_BIT)
|
||||
|
||||
this.#program.uniform('view', this.#camera.worldToCameraMatrix)
|
||||
this.#program.uniform('projection', this.#camera.projectionMatrix)
|
||||
|
||||
this.#rubiks.render(this.#program, this.#gl, this.#uvsVbo)
|
||||
this.#rubiks.update(deltaTime)
|
||||
|
||||
this.#frame = requestAnimationFrame(loop)
|
||||
}
|
||||
this.#frame = requestAnimationFrame(loop)
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops rendering the cube and removes all listeners.
|
||||
*/
|
||||
stop() {
|
||||
window.removeEventListener('resize', this.#resizeHandler)
|
||||
cancelAnimationFrame(this.#frame)
|
||||
this.#inputHandler.removeEventListeners()
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the internal state and applys it to the cube.
|
||||
*/
|
||||
reset() {
|
||||
this.#state.reset()
|
||||
this.#state.applyState(this.#uvs, this.#rubiks)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} stateStr a base46 representation
|
||||
* @returns {boolean} false if `stateStr` was invalid
|
||||
*/
|
||||
setState(stateStr) {
|
||||
if (!this.#initialized) {
|
||||
this.#initialize()
|
||||
this.#initialized = true
|
||||
}
|
||||
|
||||
if (!this.#state.decode(stateStr)) {
|
||||
return false
|
||||
}
|
||||
|
||||
this.#state.applyState(this.#uvs, this.#rubiks)
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {RubiksCube} rubiksCube
|
||||
* @returns {() => void}
|
||||
*/
|
||||
static #getResizeHandler(rubiksCube) {
|
||||
return () => {
|
||||
const width = window.innerWidth
|
||||
const height = window.innerHeight
|
||||
|
||||
debug.setSize(width, height)
|
||||
|
||||
rubiksCube.#canvas.width = width
|
||||
rubiksCube.#canvas.height = height
|
||||
rubiksCube.#gl.viewport(0, 0, width, height)
|
||||
|
||||
rubiksCube.#camera.screenSize(width, height)
|
||||
}
|
||||
}
|
||||
|
||||
#initialize() {
|
||||
const canvas = document.querySelector(`[${this.#canvasData}]`)
|
||||
if (!canvas) {
|
||||
throw new Error(`<canvas ${this.#canvasData}> does not exist`)
|
||||
}
|
||||
if (!(canvas instanceof HTMLCanvasElement)) {
|
||||
throw new Error(`<canvas ${this.#canvasData}> is not a canvas, it is a <${canvas.tagName}>`)
|
||||
}
|
||||
this.#canvas = canvas
|
||||
|
||||
const gl = canvas.getContext('webgl2')
|
||||
if (!gl) {
|
||||
throw new Error(`cannot create webgl2 context`)
|
||||
}
|
||||
this.#gl = gl
|
||||
|
||||
gl.enable(gl.DEPTH_TEST)
|
||||
gl.depthFunc(gl.LESS)
|
||||
|
||||
this.#program = new Program('rubiksCube/shaders/facelet.glsl', vertex, fragment, gl)
|
||||
|
||||
const vao = gl.createVertexArray()
|
||||
if (!vao) {
|
||||
throw new Error('could not create a webgl vertex array object')
|
||||
}
|
||||
this.#vao = vao
|
||||
gl.bindVertexArray(vao)
|
||||
|
||||
const vertices = [
|
||||
0, -.5, -.5,
|
||||
0, -.5, .5,
|
||||
0, .5, .5,
|
||||
0, .5, -.5
|
||||
]
|
||||
const verticesBuffer = new Float32Array(vertices)
|
||||
const verticesVbo = gl.createBuffer()
|
||||
|
||||
const indices = [
|
||||
0, 1, 3,
|
||||
1, 3, 2
|
||||
]
|
||||
const indicesBuffer = new Int8Array(indices)
|
||||
const ebo = gl.createBuffer()
|
||||
|
||||
const uvsVbo = gl.createBuffer()
|
||||
|
||||
if (!verticesVbo || !ebo || !uvsVbo) {
|
||||
throw new Error('could not create a vertex buffer objects')
|
||||
}
|
||||
this.#uvsVbo = uvsVbo
|
||||
|
||||
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ebo)
|
||||
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indicesBuffer, gl.STATIC_DRAW)
|
||||
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, verticesVbo)
|
||||
gl.bufferData(gl.ARRAY_BUFFER, verticesBuffer, gl.STATIC_DRAW)
|
||||
|
||||
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 12, 0)
|
||||
gl.enableVertexAttribArray(0)
|
||||
gl.enableVertexAttribArray(1)
|
||||
|
||||
gl.bindVertexArray(null)
|
||||
|
||||
const texture = gl.createTexture()
|
||||
if (!texture) {
|
||||
throw new Error('could not create a texture')
|
||||
}
|
||||
this.#texture = texture
|
||||
gl.bindTexture(gl.TEXTURE_2D, texture)
|
||||
gl.texImage2D(
|
||||
gl.TEXTURE_2D,
|
||||
0,
|
||||
gl.RGBA,
|
||||
gl.RGBA,
|
||||
gl.UNSIGNED_BYTE,
|
||||
this.#image
|
||||
)
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
|
||||
gl.bindTexture(gl.TEXTURE_2D, null)
|
||||
|
||||
const hoveringColors = this.#hoveringColors.map(([r, g, b]) => new V3(r, g, b))
|
||||
this.#camera = new Camera(new V3(0, 0, -10), V3.zero, V3.up, 45, window.innerWidth, window.innerHeight, .1, 100)
|
||||
this.#rubiks = new Rubiks(Quaternion.identity, this.#uvs, hoveringColors, this.#turnHandler.bind(this))
|
||||
this.#inputHandler = new InputHandler(canvas, this.#rubiks, this.#camera)
|
||||
this.#state = new State(this.#trackCenters)
|
||||
}
|
||||
|
||||
get transform() {
|
||||
return this.#rubiks.transform
|
||||
}
|
||||
|
||||
/** @type {Map<keyof Events, Set<(event: any) => void>>} */
|
||||
#listeners = new Map()
|
||||
/**
|
||||
* @template {keyof Events} Name
|
||||
* @param {Name} name
|
||||
* @param {(event: Events[Name]) => void} callback
|
||||
*/
|
||||
on(name, callback) {
|
||||
const set = this.#listeners.get(name) ?? new Set()
|
||||
set.add(callback)
|
||||
this.#listeners.set(name, set)
|
||||
}
|
||||
/**
|
||||
* @param {import('./types').AIA} aia
|
||||
*/
|
||||
#turnHandler(aia) {
|
||||
const turn = convertAiaToTurn(aia)
|
||||
this.#state.applyTurn(turn)
|
||||
|
||||
/** @type {Set<(event: Events['change']) => void> | undefined} */
|
||||
const changeHandlers = this.#listeners.get('change')
|
||||
if (changeHandlers) {
|
||||
const event = new ChangeEvent(
|
||||
aia.axis,
|
||||
aia.index,
|
||||
aia.angle,
|
||||
turn,
|
||||
this.#state.stateInfo
|
||||
)
|
||||
for (const callback of changeHandlers) {
|
||||
callback(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
57
node_modules/rubiks-js/src/shaders/facelet.glsl.js
generated
vendored
Normal file
57
node_modules/rubiks-js/src/shaders/facelet.glsl.js
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
export const vertex = `#version 300 es
|
||||
|
||||
layout (location = 0) in vec3 aPos;
|
||||
layout (location = 1) in vec2 aUV;
|
||||
|
||||
out vec2 uv;
|
||||
out vec2 pos;
|
||||
|
||||
uniform mat4 model;
|
||||
uniform mat4 view;
|
||||
uniform mat4 projection;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = projection * view * model * vec4(aPos, 1.0);
|
||||
uv = aUV;
|
||||
pos = aPos.yz;
|
||||
}`
|
||||
|
||||
export const fragment = `#version 300 es
|
||||
|
||||
precision mediump float;
|
||||
|
||||
in vec2 uv;
|
||||
in vec2 pos;
|
||||
|
||||
out vec4 FragColor;
|
||||
|
||||
uniform sampler2D tex;
|
||||
uniform vec3 colorMult;
|
||||
|
||||
vec3 rime = vec3(0.07);
|
||||
float outer = 0.45;
|
||||
float inner = 0.44;
|
||||
|
||||
float map(float value, float min1, float max1, float min2, float max2)
|
||||
{
|
||||
return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
float x = abs(pos.x);
|
||||
float y = abs(pos.y);
|
||||
if (x > outer || y > outer) {
|
||||
FragColor = vec4(rime, 1.0);
|
||||
return;
|
||||
}
|
||||
vec3 color = texture(tex, uv).rgb * colorMult;
|
||||
if (x > inner || y > inner) {
|
||||
float t = smoothstep(0.0, 1.0, map(max(x, y), outer, inner, 0.0, 1.0));
|
||||
vec3 c = color * t + rime * (1.0 - t);
|
||||
FragColor = vec4(c, 1.0);
|
||||
return;
|
||||
}
|
||||
FragColor = vec4(color, 1.0);
|
||||
}`
|
||||
72
node_modules/rubiks-js/src/state/centers.js
generated
vendored
Normal file
72
node_modules/rubiks-js/src/state/centers.js
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
import {mod} from '../math/utils'
|
||||
|
||||
/**
|
||||
* @typedef {import('./types').TurnBase} TurnBase
|
||||
* @typedef {import('./types').Turn} Turn
|
||||
* @typedef {import('./types').Center} C
|
||||
* @typedef {import('./types').CenterOrientation} CO
|
||||
* @typedef {[C, C, C, C, C, C]} Permutation
|
||||
* @typedef {[CO, CO, CO, CO, CO, CO]} Orientation
|
||||
*/
|
||||
|
||||
/** @type {number[]} */
|
||||
const orderIndexToCubieIndex = [
|
||||
12, 14, 10, 16, 4, 22
|
||||
]
|
||||
|
||||
export class Centers {
|
||||
/** @type {Permutation} */
|
||||
static order = ['R', 'L', 'D', 'U', 'F', 'B']
|
||||
|
||||
/** @type {Orientation} */
|
||||
orientation = [0, 0, 0, 0, 0, 0]
|
||||
|
||||
/** @param {TurnBase} turn */
|
||||
applyTurn(turn) {
|
||||
const index = Centers.order.indexOf(turn)
|
||||
this.orientation[index] = /** @type {CO} */ (mod(this.orientation[index] + 1, 4))
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number[][][]} uvs
|
||||
* @param {import('../ui/rubiks').Rubiks} rubiks
|
||||
*/
|
||||
applyState(uvs, rubiks) {
|
||||
for (let side = 0; side < 6; side++) {
|
||||
const uv = uvs[side][4]
|
||||
const offset = this.orientation[side] * 2
|
||||
const cubieIndex = orderIndexToCubieIndex[side]
|
||||
const facelet = rubiks.cubies[cubieIndex].getFaceletOfSide(side)
|
||||
facelet.uvs = []
|
||||
for (let i = 0; i < 8; i++) {
|
||||
facelet.uvs[i] = uv[mod(offset + i, 8)]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.orientation = [0, 0, 0, 0, 0, 0]
|
||||
}
|
||||
|
||||
/** @returns {number[]} */
|
||||
encode() {
|
||||
let o = 0
|
||||
for (const orientation of this.orientation) {
|
||||
o = o << 2
|
||||
o += orientation
|
||||
}
|
||||
return [
|
||||
(o >> 0) & 0b11111111,
|
||||
(o >> 8) & 0b11111111
|
||||
]
|
||||
}
|
||||
|
||||
/** @param {number[]} code */
|
||||
decode(code) {
|
||||
let o = code[0] + (code[1] << 8)
|
||||
for (let i = 5; i >= 0; i--) {
|
||||
this.orientation[i] = /** @type {CO} */ (o & 0b11)
|
||||
o = o >> 2
|
||||
}
|
||||
}
|
||||
}
|
||||
481
node_modules/rubiks-js/src/state/corners.js
generated
vendored
Normal file
481
node_modules/rubiks-js/src/state/corners.js
generated
vendored
Normal file
@@ -0,0 +1,481 @@
|
||||
import {createSideToUvs, transformSidetoUvs, setUvs} from '.'
|
||||
|
||||
/**
|
||||
* @typedef {import('./types').TurnBase} TurnBase
|
||||
* @typedef {import('./types').Turn} Turn
|
||||
* @typedef {import('./types').Corner} C
|
||||
* @typedef {import('./types').CornerOrientation} CO
|
||||
* @typedef {[C, C, C, C, C, C, C, C]} Permutation
|
||||
* @typedef {[CO, CO, CO, CO, CO, CO, CO, CO]} Orientation
|
||||
*/
|
||||
|
||||
/** @satisfies {Record<TurnBase, Permutation>} */
|
||||
const permutations = {
|
||||
R: ['DRF', 'ULF', 'ULB', 'URF', 'DRB', 'DLF', 'DLB', 'URB'],
|
||||
L: ['URF', 'ULB', 'DLB', 'URB', 'DRF', 'ULF', 'DLF', 'DRB'],
|
||||
U: ['URB', 'URF', 'ULF', 'ULB', 'DRF', 'DLF', 'DLB', 'DRB'],
|
||||
D: ['URF', 'ULF', 'ULB', 'URB', 'DLF', 'DLB', 'DRB', 'DRF'],
|
||||
F: ['ULF', 'DLF', 'ULB', 'URB', 'URF', 'DRF', 'DLB', 'DRB'],
|
||||
B: ['URF', 'ULF', 'URB', 'DRB', 'DRF', 'DLF', 'ULB', 'DLB']
|
||||
}
|
||||
|
||||
/** @satisfies {Record<TurnBase, Orientation>} */
|
||||
const orientations = {
|
||||
R: [2, 0, 0, 1, 1, 0, 0, 2],
|
||||
L: [0, 1, 2, 0, 0, 2, 1, 0],
|
||||
U: [0, 0, 0, 0, 0, 0, 0, 0],
|
||||
D: [0, 0, 0, 0, 0, 0, 0, 0],
|
||||
F: [1, 2, 0, 0, 2, 1, 0, 0],
|
||||
B: [0, 0, 1, 2, 0, 0, 2, 1]
|
||||
}
|
||||
|
||||
/** @satisfies {Record<C, Record<C, Record<CO, Turn[]>>>} */
|
||||
const map = {
|
||||
URF: {
|
||||
URF: {
|
||||
0: [],
|
||||
1: ['R', 'U'],
|
||||
2: ['F\'', 'U\'']
|
||||
},
|
||||
ULF: {
|
||||
0: ['U'],
|
||||
1: ['R', 'U2'],
|
||||
2: ['F\'']
|
||||
},
|
||||
ULB: {
|
||||
0: ['U2'],
|
||||
1: ['R', 'U\''],
|
||||
2: ['F\'', 'U']
|
||||
},
|
||||
URB: {
|
||||
0: ['U\''],
|
||||
1: ['R'],
|
||||
2: ['F\'', 'U2']
|
||||
},
|
||||
DRF: {
|
||||
0: ['R2', 'D\''],
|
||||
1: ['R\''],
|
||||
2: ['F']
|
||||
},
|
||||
DLF: {
|
||||
0: ['F2'],
|
||||
1: ['R\'', 'D\''],
|
||||
2: ['F', 'D\'']
|
||||
},
|
||||
DLB: {
|
||||
0: ['R2', 'D'],
|
||||
1: ['R\'', 'D2'],
|
||||
2: ['F', 'D2']
|
||||
},
|
||||
DRB: {
|
||||
0: ['R2'],
|
||||
1: ['R\'', 'D'],
|
||||
2: ['F', 'D']
|
||||
}
|
||||
},
|
||||
ULF: {
|
||||
URF: {
|
||||
0: ['U\''],
|
||||
1: ['F'],
|
||||
2: ['L\'', 'U2']
|
||||
},
|
||||
ULF: {
|
||||
0: [],
|
||||
1: ['F', 'U'],
|
||||
2: ['L\'', 'U\'']
|
||||
},
|
||||
ULB: {
|
||||
0: ['U'],
|
||||
1: ['F', 'U2'],
|
||||
2: ['L\'']
|
||||
},
|
||||
URB: {
|
||||
0: ['U2'],
|
||||
1: ['F', 'U\''],
|
||||
2: ['L\'', 'U']
|
||||
},
|
||||
DRF: {
|
||||
0: ['F2'],
|
||||
1: ['F\'', 'D'],
|
||||
2: ['L', 'D']
|
||||
},
|
||||
DLF: {
|
||||
0: ['L2', 'D'],
|
||||
1: ['F\''],
|
||||
2: ['L']
|
||||
},
|
||||
DLB: {
|
||||
0: ['L2'],
|
||||
1: ['F\'', 'D\''],
|
||||
2: ['L', 'D\'']
|
||||
},
|
||||
DRB: {
|
||||
0: ['L2', 'D\''],
|
||||
1: ['F\'', 'D2'],
|
||||
2: ['L', 'D2']
|
||||
}
|
||||
},
|
||||
ULB: {
|
||||
URF: {
|
||||
0: ['U2'],
|
||||
1: ['L', 'U\''],
|
||||
2: ['B\'', 'U']
|
||||
},
|
||||
ULF: {
|
||||
0: ['U\''],
|
||||
1: ['L'],
|
||||
2: ['B\'', 'U2']
|
||||
},
|
||||
ULB: {
|
||||
0: [],
|
||||
1: ['L', 'U'],
|
||||
2: ['B\'', 'U\'']
|
||||
},
|
||||
URB: {
|
||||
0: ['U'],
|
||||
1: ['L', 'U2'],
|
||||
2: ['B\'']
|
||||
},
|
||||
DRF: {
|
||||
0: ['L2', 'D'],
|
||||
1: ['L\'', 'D2'],
|
||||
2: ['B', 'D2']
|
||||
},
|
||||
DLF: {
|
||||
0: ['L2'],
|
||||
1: ['L\'', 'D'],
|
||||
2: ['B', 'D']
|
||||
},
|
||||
DLB: {
|
||||
0: ['L2', 'D'],
|
||||
1: ['L\''],
|
||||
2: ['B']
|
||||
},
|
||||
DRB: {
|
||||
0: ['B2'],
|
||||
1: ['L\'', 'D\''],
|
||||
2: ['B', 'D\'']
|
||||
}
|
||||
},
|
||||
URB: {
|
||||
URF: {
|
||||
0: ['U'],
|
||||
1: ['B', 'U2'],
|
||||
2: ['R\'']
|
||||
},
|
||||
ULF: {
|
||||
0: ['U2'],
|
||||
1: ['B', 'U\''],
|
||||
2: ['R\'', 'U']
|
||||
},
|
||||
ULB: {
|
||||
0: ['U\''],
|
||||
1: ['B'],
|
||||
2: ['R\'', 'U2']
|
||||
},
|
||||
URB: {
|
||||
0: [],
|
||||
1: ['B', 'U'],
|
||||
2: ['R\'', 'U\'']
|
||||
},
|
||||
DRF: {
|
||||
0: ['R2'],
|
||||
1: ['B\'', 'D\''],
|
||||
2: ['R', 'D\'']
|
||||
},
|
||||
DLF: {
|
||||
0: ['R2', 'D\''],
|
||||
1: ['B\'', 'D2'],
|
||||
2: ['R', 'D2']
|
||||
},
|
||||
DLB: {
|
||||
0: ['B2'],
|
||||
1: ['B\'', 'D'],
|
||||
2: ['R', 'D']
|
||||
},
|
||||
DRB: {
|
||||
0: ['R2', 'D'],
|
||||
1: ['B\''],
|
||||
2: ['R']
|
||||
}
|
||||
},
|
||||
DRF: {
|
||||
URF: {
|
||||
0: ['R2', 'U'],
|
||||
1: ['F\''],
|
||||
2: ['R']
|
||||
},
|
||||
ULF: {
|
||||
0: ['F2'],
|
||||
1: ['F\'', 'U'],
|
||||
2: ['R', 'U']
|
||||
},
|
||||
ULB: {
|
||||
0: ['R2', 'U\''],
|
||||
1: ['F\'', 'U2'],
|
||||
2: ['R', 'U2']
|
||||
},
|
||||
URB: {
|
||||
0: ['R2'],
|
||||
1: ['F\'', 'U\''],
|
||||
2: ['R', 'U\'']
|
||||
},
|
||||
DRF: {
|
||||
0: [],
|
||||
1: ['F', 'D'],
|
||||
2: ['R\'', 'D\'']
|
||||
},
|
||||
DLF: {
|
||||
0: ['D\''],
|
||||
1: ['F'],
|
||||
2: ['R\'', 'D2']
|
||||
},
|
||||
DLB: {
|
||||
0: ['D2'],
|
||||
1: ['F', 'D\''],
|
||||
2: ['R\'', 'D']
|
||||
},
|
||||
DRB: {
|
||||
0: ['D'],
|
||||
1: ['F', 'D2'],
|
||||
2: ['R\'']
|
||||
}
|
||||
},
|
||||
DLF: {
|
||||
URF: {
|
||||
0: ['F2'],
|
||||
1: ['L\'', 'U\''],
|
||||
2: ['F', 'U\'']
|
||||
},
|
||||
ULF: {
|
||||
0: ['L2', 'U\''],
|
||||
1: ['L\''],
|
||||
2: ['F']
|
||||
},
|
||||
ULB: {
|
||||
0: ['L2'],
|
||||
1: ['L\'', 'U'],
|
||||
2: ['F', 'U']
|
||||
},
|
||||
URB: {
|
||||
0: ['L2', 'U'],
|
||||
1: ['L\'', 'U2'],
|
||||
2: ['F', 'U2']
|
||||
},
|
||||
DRF: {
|
||||
0: ['D'],
|
||||
1: ['L', 'D2'],
|
||||
2: ['F\'']
|
||||
},
|
||||
DLF: {
|
||||
0: [],
|
||||
1: ['L', 'D'],
|
||||
2: ['F\'', 'D\'']
|
||||
},
|
||||
DLB: {
|
||||
0: ['D\''],
|
||||
1: ['L'],
|
||||
2: ['F\'', 'D2']
|
||||
},
|
||||
DRB: {
|
||||
0: ['D2'],
|
||||
1: ['L', 'D\''],
|
||||
2: ['F\'', 'D']
|
||||
}
|
||||
},
|
||||
DLB: {
|
||||
URF: {
|
||||
0: ['L2', 'U\''],
|
||||
1: ['B\'', 'U2'],
|
||||
2: ['L', 'U2']
|
||||
},
|
||||
ULF: {
|
||||
0: ['L2'],
|
||||
1: ['B\'', 'U\''],
|
||||
2: ['L', 'U\'']
|
||||
},
|
||||
ULB: {
|
||||
0: ['L2', 'U'],
|
||||
1: ['B\''],
|
||||
2: ['L']
|
||||
},
|
||||
URB: {
|
||||
0: ['B2'],
|
||||
1: ['B\'', 'U'],
|
||||
2: ['L', 'U']
|
||||
},
|
||||
DRF: {
|
||||
0: ['D2'],
|
||||
1: ['B', 'D\''],
|
||||
2: ['L\'', 'D']
|
||||
},
|
||||
DLF: {
|
||||
0: ['D'],
|
||||
1: ['B', 'D2'],
|
||||
2: ['L\'']
|
||||
},
|
||||
DLB: {
|
||||
0: [],
|
||||
1: ['B', 'D'],
|
||||
2: ['L\'', 'D\'']
|
||||
},
|
||||
DRB: {
|
||||
0: ['D\''],
|
||||
1: ['B'],
|
||||
2: ['L\'', 'D2']
|
||||
}
|
||||
},
|
||||
DRB: {
|
||||
URF: {
|
||||
0: ['R2'],
|
||||
1: ['R\'', 'U'],
|
||||
2: ['B', 'U']
|
||||
},
|
||||
ULF: {
|
||||
0: ['R2', 'U'],
|
||||
1: ['R\'', 'U2'],
|
||||
2: ['B', 'U2']
|
||||
},
|
||||
ULB: {
|
||||
0: ['B2'],
|
||||
1: ['R\'', 'U\''],
|
||||
2: ['B', 'U\'']
|
||||
},
|
||||
URB: {
|
||||
0: ['R2', 'U\''],
|
||||
1: ['R\''],
|
||||
2: ['B']
|
||||
},
|
||||
DRF: {
|
||||
0: ['D\''],
|
||||
1: ['R'],
|
||||
2: ['B\'', 'D2']
|
||||
},
|
||||
DLF: {
|
||||
0: ['D2'],
|
||||
1: ['R', 'D\''],
|
||||
2: ['B\'', 'D']
|
||||
},
|
||||
DLB: {
|
||||
0: ['D'],
|
||||
1: ['R', 'D2'],
|
||||
2: ['B\'']
|
||||
},
|
||||
DRB: {
|
||||
0: [],
|
||||
1: ['R', 'D'],
|
||||
2: ['B\'', 'D\'']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {number[]} */
|
||||
const orderIndexToCubieIndex = [
|
||||
6, 8, 26, 24, 0, 2, 20, 18
|
||||
]
|
||||
|
||||
|
||||
export class Corners {
|
||||
/** @type {Permutation} */
|
||||
static order = ['URF', 'ULF', 'ULB', 'URB', 'DRF', 'DLF', 'DLB', 'DRB']
|
||||
|
||||
/** @type {Permutation} */
|
||||
permutation = [...Corners.order]
|
||||
/** @type {Orientation} */
|
||||
orientation = [0, 0, 0, 0, 0, 0, 0, 0]
|
||||
|
||||
/** @param {TurnBase} turn */
|
||||
applyTurn(turn) {
|
||||
const appliedPermutation = permutations[turn]
|
||||
const appliedOrientation = orientations[turn]
|
||||
|
||||
const permutation = [...this.permutation]
|
||||
const orientation = [...this.orientation]
|
||||
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const newPermutation = appliedPermutation[i]
|
||||
const orderIndex = Corners.order.indexOf(newPermutation)
|
||||
this.permutation[i] = permutation[orderIndex]
|
||||
const newOrientation = (appliedOrientation[i] + orientation[orderIndex]) % 3
|
||||
this.orientation[i] = /** @type {CO} */ (newOrientation)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number[][][]} uvs
|
||||
* @param {import('../ui/rubiks').Rubiks} rubiks
|
||||
*/
|
||||
applyState(uvs, rubiks) {
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const turns = this.#getTurns(i)
|
||||
|
||||
const originIndex = orderIndexToCubieIndex[Corners.order.indexOf(this.permutation[i])]
|
||||
let sideToUvs = createSideToUvs(originIndex, uvs)
|
||||
sideToUvs = transformSidetoUvs(originIndex, sideToUvs, turns)
|
||||
|
||||
const targetIndex = orderIndexToCubieIndex[i]
|
||||
setUvs(targetIndex, rubiks, sideToUvs)
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.permutation = [...Corners.order]
|
||||
this.orientation = [0, 0, 0, 0, 0, 0, 0, 0]
|
||||
}
|
||||
|
||||
/** @returns {number[]} */
|
||||
encode() {
|
||||
let p = 0
|
||||
for (const permutation of this.permutation) {
|
||||
p = p << 3
|
||||
p += Corners.order.indexOf(permutation)
|
||||
}
|
||||
let o = 0
|
||||
for (const orientation of this.orientation) {
|
||||
o = o << 2
|
||||
o += orientation
|
||||
}
|
||||
return [
|
||||
(p >> 0) & 0b11111111,
|
||||
(p >> 8) & 0b11111111,
|
||||
(p >> 16) & 0b11111111,
|
||||
(o >> 0) & 0b11111111,
|
||||
(o >> 8) & 0b11111111
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number[]} code
|
||||
* @returns {boolean}
|
||||
*/
|
||||
decode(code) {
|
||||
let p = code[0] + (code[1] << 8) + (code[2] << 16)
|
||||
let o = code[3] + (code[4] << 8)
|
||||
for (let i = 7; i >= 0; i--) {
|
||||
const p1 = p & 0b111
|
||||
if (p1 >= 8) {
|
||||
return false
|
||||
}
|
||||
this.permutation[i] = Corners.order[p1]
|
||||
p = p >> 3
|
||||
const o1 = o & 0b11
|
||||
if (o1 > 2) {
|
||||
return false
|
||||
}
|
||||
this.orientation[i] = /** @type {CO} */ (o1)
|
||||
o = o >> 2
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} index
|
||||
* @returns {Turn[]}
|
||||
*/
|
||||
#getTurns(index) {
|
||||
const start = Corners.order[index]
|
||||
const permutation = this.permutation[index]
|
||||
const orientation = this.orientation[index]
|
||||
return map[permutation][start][orientation]
|
||||
}
|
||||
}
|
||||
745
node_modules/rubiks-js/src/state/edges.js
generated
vendored
Normal file
745
node_modules/rubiks-js/src/state/edges.js
generated
vendored
Normal file
@@ -0,0 +1,745 @@
|
||||
import {createSideToUvs, transformSidetoUvs, setUvs} from '.'
|
||||
|
||||
/**
|
||||
* @typedef {import('./types').TurnBase} TurnBase
|
||||
* @typedef {import('./types').Turn} Turn
|
||||
* @typedef {import('./types').Edge} E
|
||||
* @typedef {import('./types').EdgeOrientation} EO
|
||||
* @typedef {[E, E, E, E, E, E, E, E, E, E, E, E]} Permutation
|
||||
* @typedef {[EO, EO, EO, EO, EO, EO, EO, EO, EO, EO, EO, EO]} Orientation
|
||||
*/
|
||||
|
||||
/** @satisfies {Record<TurnBase, Permutation>} */
|
||||
const permutations = {
|
||||
R: ['UF', 'UL', 'UB', 'FR', 'DR', 'FL', 'BL', 'UR', 'DF', 'DL', 'DB', 'BR'],
|
||||
L: ['UF', 'BL', 'UB', 'UR', 'FR', 'UL', 'DL', 'BR', 'DF', 'FL', 'DB', 'DR'],
|
||||
U: ['UR', 'UF', 'UL', 'UB', 'FR', 'FL', 'BL', 'BR', 'DF', 'DL', 'DB', 'DR'],
|
||||
D: ['UF', 'UL', 'UB', 'UR', 'FR', 'FL', 'BL', 'BR', 'DL', 'DB', 'DR', 'DF'],
|
||||
F: ['FL', 'UL', 'UB', 'UR', 'UF', 'DF', 'BL', 'BR', 'FR', 'DL', 'DB', 'DR'],
|
||||
B: ['UF', 'UL', 'BR', 'UR', 'FR', 'FL', 'UB', 'DB', 'DF', 'DL', 'BL', 'DR']
|
||||
}
|
||||
|
||||
/** @satisfies {Record<TurnBase, Orientation>} */
|
||||
const orientations = {
|
||||
R: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
L: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
U: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
D: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
F: [1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0],
|
||||
B: [0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0]
|
||||
}
|
||||
|
||||
/** @satisfies {Record<E, Record<E, Record<EO, Turn[]>>>} */
|
||||
const map = {
|
||||
UF: {
|
||||
UF: {
|
||||
0: [],
|
||||
1: ['F', 'R', 'U']
|
||||
},
|
||||
UL: {
|
||||
0: ['U'],
|
||||
1: ['F\'', 'L\'']
|
||||
},
|
||||
UB: {
|
||||
0: ['U2'],
|
||||
1: ['F', 'R', 'U\'']
|
||||
},
|
||||
UR: {
|
||||
0: ['U\''],
|
||||
1: ['F', 'R']
|
||||
},
|
||||
FR: {
|
||||
0: ['U\'', 'R\''],
|
||||
1: ['F']
|
||||
},
|
||||
FL: {
|
||||
0: ['U', 'L'],
|
||||
1: ['F\'']
|
||||
},
|
||||
BL: {
|
||||
0: ['U', 'L\''],
|
||||
1: ['U2', 'B']
|
||||
},
|
||||
BR: {
|
||||
0: ['U\'', 'R'],
|
||||
1: ['U2', 'B\'']
|
||||
},
|
||||
DF: {
|
||||
0: ['F2'],
|
||||
1: ['F', 'R\'', 'D\'']
|
||||
},
|
||||
DL: {
|
||||
0: ['F2', 'D\''],
|
||||
1: ['F\'', 'L']
|
||||
},
|
||||
DB: {
|
||||
0: ['F2', 'D2'],
|
||||
1: ['F', 'R\'', 'D']
|
||||
},
|
||||
DR: {
|
||||
0: ['F2', 'D'],
|
||||
1: ['F', 'R\'']
|
||||
}
|
||||
},
|
||||
UL: {
|
||||
UF: {
|
||||
0: ['U\''],
|
||||
1: ['L', 'F']
|
||||
},
|
||||
UL: {
|
||||
0: [],
|
||||
1: ['L', 'F', 'U']
|
||||
},
|
||||
UB: {
|
||||
0: ['U'],
|
||||
1: ['L\'', 'B\'']
|
||||
},
|
||||
UR: {
|
||||
0: ['U2'],
|
||||
1: ['L', 'F', 'U\'']
|
||||
},
|
||||
FR: {
|
||||
0: ['U2', 'R\''],
|
||||
1: ['U\'', 'F']
|
||||
},
|
||||
FL: {
|
||||
0: ['L'],
|
||||
1: ['U\'', 'F\'']
|
||||
},
|
||||
BL: {
|
||||
0: ['L\''],
|
||||
1: ['U', 'B']
|
||||
},
|
||||
BR: {
|
||||
0: ['U2', 'R'],
|
||||
1: ['U', 'B\'']
|
||||
},
|
||||
DF: {
|
||||
0: ['L2', 'D'],
|
||||
1: ['L', 'F\'']
|
||||
},
|
||||
DL: {
|
||||
0: ['L2'],
|
||||
1: ['L', 'F\'', 'D\'']
|
||||
},
|
||||
DB: {
|
||||
0: ['L2', 'D\''],
|
||||
1: ['L\'', 'B']
|
||||
},
|
||||
DR: {
|
||||
0: ['L2', 'D2'],
|
||||
1: ['L', 'F\'', 'D']
|
||||
}
|
||||
},
|
||||
UB: {
|
||||
UF: {
|
||||
0: ['U2'],
|
||||
1: ['B\'', 'R\'', 'U']
|
||||
},
|
||||
UL: {
|
||||
0: ['U\''],
|
||||
1: ['B', 'L']
|
||||
},
|
||||
UB: {
|
||||
0: [],
|
||||
1: ['B\'', 'R\'', 'U\'']
|
||||
},
|
||||
UR: {
|
||||
0: ['U'],
|
||||
1: ['B\'', 'R\'']
|
||||
},
|
||||
FR: {
|
||||
0: ['U', 'R\''],
|
||||
1: ['B\'', 'R2']
|
||||
},
|
||||
FL: {
|
||||
0: ['U\'', 'L'],
|
||||
1: ['B', 'L2']
|
||||
},
|
||||
BL: {
|
||||
0: ['U\'', 'L\''],
|
||||
1: ['B']
|
||||
},
|
||||
BR: {
|
||||
0: ['U', 'R'],
|
||||
1: ['B\'']
|
||||
},
|
||||
DF: {
|
||||
0: ['B2', 'D2'],
|
||||
1: ['B\'', 'R', 'D\'']
|
||||
},
|
||||
DL: {
|
||||
0: ['B2', 'D'],
|
||||
1: ['B', 'L\'']
|
||||
},
|
||||
DB: {
|
||||
0: ['B2'],
|
||||
1: ['B\'', 'R', 'D']
|
||||
},
|
||||
DR: {
|
||||
0: ['B2', 'D\''],
|
||||
1: ['B\'', 'R']
|
||||
}
|
||||
},
|
||||
UR: {
|
||||
UF: {
|
||||
0: ['U'],
|
||||
1: ['R\'', 'F\'']
|
||||
},
|
||||
UL: {
|
||||
0: ['U2'],
|
||||
1: ['R\'', 'F\'', 'U']
|
||||
},
|
||||
UB: {
|
||||
0: ['U\''],
|
||||
1: ['R', 'B']
|
||||
},
|
||||
UR: {
|
||||
0: [],
|
||||
1: ['R\'', 'F\'', 'U\'']
|
||||
},
|
||||
FR: {
|
||||
0: ['R\''],
|
||||
1: ['U', 'F']
|
||||
},
|
||||
FL: {
|
||||
0: ['U2', 'L'],
|
||||
1: ['U', 'F\'']
|
||||
},
|
||||
BL: {
|
||||
0: ['U2', 'L\''],
|
||||
1: ['U\'', 'B']
|
||||
},
|
||||
BR: {
|
||||
0: ['R'],
|
||||
1: ['U\'', 'B\'']
|
||||
},
|
||||
DF: {
|
||||
0: ['R2', 'D\''],
|
||||
1: ['R\'', 'F']
|
||||
},
|
||||
DL: {
|
||||
0: ['R2', 'D2'],
|
||||
1: ['R\'', 'F', 'D\'']
|
||||
},
|
||||
DB: {
|
||||
0: ['R2', 'D'],
|
||||
1: ['R', 'B\'']
|
||||
},
|
||||
DR: {
|
||||
0: ['R2'],
|
||||
1: ['R\'', 'F', 'D']
|
||||
}
|
||||
},
|
||||
FR: {
|
||||
UF: {
|
||||
0: ['R', 'U'],
|
||||
1: ['F\'']
|
||||
},
|
||||
UL: {
|
||||
0: ['R', 'U2'],
|
||||
1: ['F\'', 'U']
|
||||
},
|
||||
UB: {
|
||||
0: ['R', 'U\''],
|
||||
1: ['F\'', 'U2']
|
||||
},
|
||||
UR: {
|
||||
0: ['R'],
|
||||
1: ['F\'', 'U\'']
|
||||
},
|
||||
FR: {
|
||||
0: [],
|
||||
1: ['F\'', 'U\'', 'R\'']
|
||||
},
|
||||
FL: {
|
||||
0: ['F2'],
|
||||
1: ['F\'', 'U', 'L']
|
||||
},
|
||||
BL: {
|
||||
0: ['F2', 'L2'],
|
||||
1: ['F\'', 'U', 'L\'']
|
||||
},
|
||||
BR: {
|
||||
0: ['R2'],
|
||||
1: ['F\'', 'U\'', 'R']
|
||||
},
|
||||
DF: {
|
||||
0: ['R\'', 'D\''],
|
||||
1: ['F']
|
||||
},
|
||||
DL: {
|
||||
0: ['R\'', 'D2'],
|
||||
1: ['F', 'D\'']
|
||||
},
|
||||
DB: {
|
||||
0: ['R\'', 'D'],
|
||||
1: ['F', 'D2']
|
||||
},
|
||||
DR: {
|
||||
0: ['R\''],
|
||||
1: ['F', 'D']
|
||||
}
|
||||
},
|
||||
FL: {
|
||||
UF: {
|
||||
0: ['L\'', 'U\''],
|
||||
1: ['F']
|
||||
},
|
||||
UL: {
|
||||
0: ['L\''],
|
||||
1: ['F', 'U']
|
||||
},
|
||||
UB: {
|
||||
0: ['L\'', 'U'],
|
||||
1: ['F', 'U2']
|
||||
},
|
||||
UR: {
|
||||
0: ['L\'', 'U2'],
|
||||
1: ['F', 'U\'']
|
||||
},
|
||||
FR: {
|
||||
0: ['F2'],
|
||||
1: ['F', 'U\'', 'R\'']
|
||||
},
|
||||
FL: {
|
||||
0: [],
|
||||
1: ['F', 'U', 'L']
|
||||
},
|
||||
BL: {
|
||||
0: ['L2'],
|
||||
1: ['F', 'U', 'L\'']
|
||||
},
|
||||
BR: {
|
||||
0: ['F2', 'R2'],
|
||||
1: ['F', 'U\'', 'R']
|
||||
},
|
||||
DF: {
|
||||
0: ['L', 'D'],
|
||||
1: ['F\'']
|
||||
},
|
||||
DL: {
|
||||
0: ['L'],
|
||||
1: ['F\'', 'D\'']
|
||||
},
|
||||
DB: {
|
||||
0: ['L', 'D\''],
|
||||
1: ['F\'', 'D2']
|
||||
},
|
||||
DR: {
|
||||
0: ['L', 'D2'],
|
||||
1: ['F\'', 'D']
|
||||
}
|
||||
},
|
||||
BL: {
|
||||
UF: {
|
||||
0: ['L', 'U\''],
|
||||
1: ['B\'', 'U2']
|
||||
},
|
||||
UL: {
|
||||
0: ['L'],
|
||||
1: ['B\'', 'U\'']
|
||||
},
|
||||
UB: {
|
||||
0: ['L', 'U'],
|
||||
1: ['B\'']
|
||||
},
|
||||
UR: {
|
||||
0: ['L', 'U2'],
|
||||
1: ['B\'', 'U']
|
||||
},
|
||||
FR: {
|
||||
0: ['B2', 'R2'],
|
||||
1: ['B\'', 'U', 'R\'']
|
||||
},
|
||||
FL: {
|
||||
0: ['L2'],
|
||||
1: ['B\'', 'U\'', 'L']
|
||||
},
|
||||
BL: {
|
||||
0: [],
|
||||
1: ['B\'', 'U\'', 'L\'']
|
||||
},
|
||||
BR: {
|
||||
0: ['B2'],
|
||||
1: ['B\'', 'U', 'R']
|
||||
},
|
||||
DF: {
|
||||
0: ['L\'', 'D'],
|
||||
1: ['B', 'D2']
|
||||
},
|
||||
DL: {
|
||||
0: ['L\''],
|
||||
1: ['B', 'D']
|
||||
},
|
||||
DB: {
|
||||
0: ['L\'', 'D\''],
|
||||
1: ['B']
|
||||
},
|
||||
DR: {
|
||||
0: ['L\'', 'D2'],
|
||||
1: ['B', 'D\'']
|
||||
}
|
||||
},
|
||||
BR: {
|
||||
UF: {
|
||||
0: ['R\'', 'U'],
|
||||
1: ['B', 'U2']
|
||||
},
|
||||
UL: {
|
||||
0: ['R\'', 'U2'],
|
||||
1: ['B', 'U\'']
|
||||
},
|
||||
UB: {
|
||||
0: ['R\'', 'U\''],
|
||||
1: ['B']
|
||||
},
|
||||
UR: {
|
||||
0: ['R\''],
|
||||
1: ['B', 'U']
|
||||
},
|
||||
FR: {
|
||||
0: ['R2'],
|
||||
1: ['B', 'U', 'R\'']
|
||||
},
|
||||
FL: {
|
||||
0: ['B2', 'L2'],
|
||||
1: ['B', 'U\'', 'L']
|
||||
},
|
||||
BL: {
|
||||
0: ['B2'],
|
||||
1: ['B', 'U\'', 'L\'']
|
||||
},
|
||||
BR: {
|
||||
0: [],
|
||||
1: ['B', 'U', 'R']
|
||||
},
|
||||
DF: {
|
||||
0: ['R', 'D\''],
|
||||
1: ['B\'', 'D2']
|
||||
},
|
||||
DL: {
|
||||
0: ['R', 'D2'],
|
||||
1: ['B\'', 'D']
|
||||
},
|
||||
DB: {
|
||||
0: ['R', 'D'],
|
||||
1: ['B\'']
|
||||
},
|
||||
DR: {
|
||||
0: ['R'],
|
||||
1: ['B\'', 'D\'']
|
||||
}
|
||||
},
|
||||
DF: {
|
||||
UF: {
|
||||
0: ['F2'],
|
||||
1: ['F\'', 'R', 'U']
|
||||
},
|
||||
UL: {
|
||||
0: ['F2', 'U'],
|
||||
1: ['F', 'L\'']
|
||||
},
|
||||
UB: {
|
||||
0: ['F2', 'U2'],
|
||||
1: ['F\'', 'R', 'U\'']
|
||||
},
|
||||
UR: {
|
||||
0: ['F2', 'U\''],
|
||||
1: ['F\'', 'R']
|
||||
},
|
||||
FR: {
|
||||
0: ['D', 'R'],
|
||||
1: ['F\'']
|
||||
},
|
||||
FL: {
|
||||
0: ['D\'', 'L\''],
|
||||
1: ['F']
|
||||
},
|
||||
BL: {
|
||||
0: ['D\'', 'L'],
|
||||
1: ['D2', 'B\'']
|
||||
},
|
||||
BR: {
|
||||
0: ['D', 'R\''],
|
||||
1: ['D2', 'B']
|
||||
},
|
||||
DF: {
|
||||
0: [],
|
||||
1: ['F\'', 'R\'', 'D\'']
|
||||
},
|
||||
DL: {
|
||||
0: ['D\''],
|
||||
1: ['F', 'L']
|
||||
},
|
||||
DB: {
|
||||
0: ['D2'],
|
||||
1: ['F\'', 'R\'', 'D']
|
||||
},
|
||||
DR: {
|
||||
0: ['D'],
|
||||
1: ['F\'', 'R\'']
|
||||
}
|
||||
},
|
||||
DL: {
|
||||
UF: {
|
||||
0: ['L2', 'U\''],
|
||||
1: ['L\'', 'F']
|
||||
},
|
||||
UL: {
|
||||
0: ['L2'],
|
||||
1: ['L\'', 'F', 'U']
|
||||
},
|
||||
UB: {
|
||||
0: ['L2', 'U'],
|
||||
1: ['L', 'B\'']
|
||||
},
|
||||
UR: {
|
||||
0: ['L2', 'U2'],
|
||||
1: ['L\'', 'F', 'U\'']
|
||||
},
|
||||
FR: {
|
||||
0: ['D2', 'R'],
|
||||
1: ['D', 'F\'']
|
||||
},
|
||||
FL: {
|
||||
0: ['L\''],
|
||||
1: ['D', 'F']
|
||||
},
|
||||
BL: {
|
||||
0: ['L'],
|
||||
1: ['D\'', 'B\'']
|
||||
},
|
||||
BR: {
|
||||
0: ['D2', 'R\''],
|
||||
1: ['D\'', 'B']
|
||||
},
|
||||
DF: {
|
||||
0: ['D'],
|
||||
1: ['L\'', 'F\'']
|
||||
},
|
||||
DL: {
|
||||
0: [],
|
||||
1: ['L\'', 'F\'', 'D\'']
|
||||
},
|
||||
DB: {
|
||||
0: ['D\''],
|
||||
1: ['L', 'B']
|
||||
},
|
||||
DR: {
|
||||
0: ['D2'],
|
||||
1: ['L\'', 'F\'', 'D']
|
||||
}
|
||||
},
|
||||
DB: {
|
||||
UF: {
|
||||
0: ['B2', 'U2'],
|
||||
1: ['B', 'R\'', 'U']
|
||||
},
|
||||
UL: {
|
||||
0: ['B2', 'U\''],
|
||||
1: ['B\'', 'L']
|
||||
},
|
||||
UB: {
|
||||
0: ['B2'],
|
||||
1: ['B', 'R\'', 'U\'']
|
||||
},
|
||||
UR: {
|
||||
0: ['B2', 'U'],
|
||||
1: ['B', 'R\'']
|
||||
},
|
||||
FR: {
|
||||
0: ['D\'', 'R'],
|
||||
1: ['D2', 'F\'']
|
||||
},
|
||||
FL: {
|
||||
0: ['D', 'L\''],
|
||||
1: ['D2', 'F']
|
||||
},
|
||||
BL: {
|
||||
0: ['D', 'L'],
|
||||
1: ['B\'']
|
||||
},
|
||||
BR: {
|
||||
0: ['D\'', 'R\''],
|
||||
1: ['B']
|
||||
},
|
||||
DF: {
|
||||
0: ['D2'],
|
||||
1: ['B', 'R', 'D\'']
|
||||
},
|
||||
DL: {
|
||||
0: ['D'],
|
||||
1: ['B\'', 'L\'']
|
||||
},
|
||||
DB: {
|
||||
0: [],
|
||||
1: ['B', 'R', 'D']
|
||||
},
|
||||
DR: {
|
||||
0: ['D\''],
|
||||
1: ['B', 'R']
|
||||
}
|
||||
},
|
||||
DR: {
|
||||
UF: {
|
||||
0: ['R2', 'U'],
|
||||
1: ['R', 'F\'']
|
||||
},
|
||||
UL: {
|
||||
0: ['R2', 'U2'],
|
||||
1: ['R', 'F\'', 'U']
|
||||
},
|
||||
UB: {
|
||||
0: ['R2', 'U\''],
|
||||
1: ['R\'', 'B']
|
||||
},
|
||||
UR: {
|
||||
0: ['R2'],
|
||||
1: ['R', 'F\'', 'U\'']
|
||||
},
|
||||
FR: {
|
||||
0: ['R'],
|
||||
1: ['D\'', 'F\'']
|
||||
},
|
||||
FL: {
|
||||
0: ['D2', 'L\''],
|
||||
1: ['D\'', 'F']
|
||||
},
|
||||
BL: {
|
||||
0: ['D2', 'L'],
|
||||
1: ['D', 'B\'']
|
||||
},
|
||||
BR: {
|
||||
0: ['R\''],
|
||||
1: ['D', 'B']
|
||||
},
|
||||
DF: {
|
||||
0: ['D\''],
|
||||
1: ['R', 'F']
|
||||
},
|
||||
DL: {
|
||||
0: ['D2'],
|
||||
1: ['R', 'F', 'D\'']
|
||||
},
|
||||
DB: {
|
||||
0: ['D'],
|
||||
1: ['R\'', 'B\'']
|
||||
},
|
||||
DR: {
|
||||
0: [],
|
||||
1: ['R', 'F', 'D']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {number[]} */
|
||||
const orderIndexToCubieIndex = [
|
||||
7, 17, 25, 15, 3, 5, 23, 21, 1, 11, 19, 9
|
||||
]
|
||||
|
||||
export class Edges {
|
||||
/** @type {Permutation} */
|
||||
static order = ['UF', 'UL', 'UB', 'UR', 'FR', 'FL', 'BL', 'BR', 'DF', 'DL', 'DB', 'DR']
|
||||
|
||||
/** @type {Permutation} */
|
||||
permutation = [...Edges.order]
|
||||
/** @type {Orientation} */
|
||||
orientation = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
|
||||
/** @param {TurnBase} turn */
|
||||
applyTurn(turn) {
|
||||
const appliedPermutation = permutations[turn]
|
||||
const appliedOrientation = orientations[turn]
|
||||
|
||||
const permutation = [...this.permutation]
|
||||
const orientation = [...this.orientation]
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const newPermutation = appliedPermutation[i]
|
||||
const orderIndex = Edges.order.indexOf(newPermutation)
|
||||
this.permutation[i] = permutation[orderIndex]
|
||||
const newOrientation = (appliedOrientation[i] + orientation[orderIndex]) % 2
|
||||
this.orientation[i] = /** @type {EO} */ (newOrientation)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number[][][]} uvs
|
||||
* @param {import('../ui/rubiks').Rubiks} rubiks
|
||||
*/
|
||||
applyState(uvs, rubiks) {
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const turns = this.#getTurns(i)
|
||||
|
||||
const originIndex = orderIndexToCubieIndex[Edges.order.indexOf(this.permutation[i])]
|
||||
let sideToUvs = createSideToUvs(originIndex, uvs)
|
||||
sideToUvs = transformSidetoUvs(originIndex, sideToUvs, turns)
|
||||
|
||||
const targetIndex = orderIndexToCubieIndex[i]
|
||||
setUvs(targetIndex, rubiks, sideToUvs)
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.permutation = [...Edges.order]
|
||||
this.orientation = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
}
|
||||
|
||||
/** @returns {number[]} */
|
||||
encode() {
|
||||
/** @type {number[]} */
|
||||
let p = []
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const p1 = Edges.order.indexOf(this.permutation[i * 2])
|
||||
const p2 = Edges.order.indexOf(this.permutation[i * 2 + 1])
|
||||
p.push(
|
||||
(p2 << 4) + p1
|
||||
)
|
||||
}
|
||||
let o = 0
|
||||
for (const orientation of this.orientation) {
|
||||
o = o << 1
|
||||
o += orientation
|
||||
}
|
||||
return [
|
||||
...p,
|
||||
(o >> 0) & 0b11111111,
|
||||
(o >> 8) & 0b11111111
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number[]} code
|
||||
* @returns {boolean}
|
||||
*/
|
||||
decode(code) {
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const p1 = code[i] & 0b1111
|
||||
const p2 = (code[i] >> 4) & 0b1111
|
||||
if (p1 >= 12 || p2 >= 12) {
|
||||
return false
|
||||
}
|
||||
this.permutation[i * 2] = Edges.order[p1]
|
||||
this.permutation[i * 2 + 1] = Edges.order[p2]
|
||||
}
|
||||
|
||||
let o = code[6] + (code[7] << 8)
|
||||
for (let i = 11; i >= 0; i--) {
|
||||
this.orientation[i] = /** @type {EO} */ (o & 0b1)
|
||||
o = o >> 1
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} index
|
||||
* @returns {Turn[]}
|
||||
*/
|
||||
#getTurns(index) {
|
||||
const start = Edges.order[index]
|
||||
const permutation = this.permutation[index]
|
||||
const orientation = this.orientation[index]
|
||||
return map[permutation][start][orientation]
|
||||
}
|
||||
}
|
||||
238
node_modules/rubiks-js/src/state/index.js
generated
vendored
Normal file
238
node_modules/rubiks-js/src/state/index.js
generated
vendored
Normal file
@@ -0,0 +1,238 @@
|
||||
import {Corners} from './corners'
|
||||
import {Edges} from './edges'
|
||||
import {Centers} from './centers'
|
||||
import {convertTurnToTurnBase, convertTurnToAia} from '../converter'
|
||||
import {uvsTransformerPresets, cubiesShiftMapper, sidesShiftMapper} from '../ui/uvs'
|
||||
import {isInside, indexToPosition, positionToUvs} from '../ui/cubie'
|
||||
import {mod} from '../math/utils'
|
||||
|
||||
/**
|
||||
* @typedef {import('./types').Turn} Turn
|
||||
* @typedef {import('./types').TurnBase} TurnBase
|
||||
* @typedef {import('./types').Corner} Corner
|
||||
*/
|
||||
|
||||
export class State {
|
||||
#corners = new Corners()
|
||||
#edges = new Edges()
|
||||
#centers = new Centers()
|
||||
/** @type {boolean} */
|
||||
#trackCenters
|
||||
|
||||
stateInfo = new StateInfo(this)
|
||||
|
||||
/** @param {boolean} trackCenters */
|
||||
constructor(trackCenters) {
|
||||
this.#trackCenters = trackCenters
|
||||
}
|
||||
|
||||
/** @param {Turn} turn */
|
||||
applyTurn(turn) {
|
||||
const baseTurns = convertTurnToTurnBase(turn)
|
||||
for (const base of baseTurns) {
|
||||
this.#corners.applyTurn(base)
|
||||
this.#edges.applyTurn(base)
|
||||
if (this.#trackCenters) {
|
||||
this.#centers.applyTurn(base)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number[][][]} uvs
|
||||
* @param {import('../ui/rubiks').Rubiks} rubiks
|
||||
*/
|
||||
applyState(uvs, rubiks) {
|
||||
this.#corners.applyState(uvs, rubiks)
|
||||
this.#edges.applyState(uvs, rubiks)
|
||||
if (this.#trackCenters) {
|
||||
this.#centers.applyState(uvs, rubiks)
|
||||
}
|
||||
}
|
||||
|
||||
/** @returns {string} */
|
||||
encode() {
|
||||
/** @type {number[]} */
|
||||
const code = []
|
||||
code.push(...this.#corners.encode())
|
||||
code.push(...this.#edges.encode())
|
||||
if (this.#trackCenters) {
|
||||
code.push(...this.#centers.encode())
|
||||
}
|
||||
const array = new Uint8Array(code)
|
||||
return btoa(String.fromCharCode(...array))
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} str
|
||||
* @returns {boolean}
|
||||
*/
|
||||
decode(str) {
|
||||
const data = atob(str)
|
||||
if (data.length !== (this.#trackCenters ? 15 : 13)) {
|
||||
return false
|
||||
}
|
||||
|
||||
/** @type {number[]} */
|
||||
const code = []
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
code.push(data.charCodeAt(i))
|
||||
}
|
||||
|
||||
const corners = this.#corners
|
||||
/** @type {typeof corners['permutation']} */
|
||||
const cp = [...corners.permutation]
|
||||
/** @type {typeof corners['orientation']} */
|
||||
const co = [...corners.orientation]
|
||||
|
||||
if (!corners.decode(code.slice(0, 5))) {
|
||||
corners.permutation = cp
|
||||
corners.orientation = co
|
||||
return false
|
||||
}
|
||||
|
||||
const edges = this.#edges
|
||||
/** @type {typeof edges['permutation']} */
|
||||
const ep = [...edges.permutation]
|
||||
/** @type {typeof edges['orientation']} */
|
||||
const eo = [...edges.orientation]
|
||||
|
||||
if (!edges.decode(code.slice(5, 13))) {
|
||||
corners.permutation = cp
|
||||
corners.orientation = co
|
||||
edges.permutation = ep
|
||||
edges.orientation = eo
|
||||
return false
|
||||
}
|
||||
|
||||
if (this.#trackCenters) {
|
||||
this.#centers.decode(code.slice(13))
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.#corners.reset()
|
||||
this.#edges.reset()
|
||||
this.#centers.reset()
|
||||
}
|
||||
}
|
||||
|
||||
export class StateInfo {
|
||||
/** @type {State} */
|
||||
#state
|
||||
|
||||
/** @param {State} state */
|
||||
constructor(state) {
|
||||
this.#state = state
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string} base64 encoded, can be used in the setState method
|
||||
*/
|
||||
toString() {
|
||||
return this.#state.encode()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `uvsTransformers[axis][side][angle]`
|
||||
* @type {Record<number, Record<number, Record<number, (uvs: number[]) => number[]>>>}
|
||||
*/
|
||||
const uvsTransformers = {
|
||||
0: {
|
||||
0: uvsTransformerPresets.rcR2Rcc,
|
||||
1: uvsTransformerPresets.rcR2Rcc,
|
||||
2: uvsTransformerPresets.flipV12,
|
||||
3: uvsTransformerPresets.flipV12,
|
||||
4: uvsTransformerPresets.flipV23,
|
||||
5: uvsTransformerPresets.flipV23
|
||||
},
|
||||
1: {
|
||||
0: uvsTransformerPresets.rcFvRcfh,
|
||||
1: uvsTransformerPresets.rcFvRcfh,
|
||||
2: uvsTransformerPresets.rcR2Rcc,
|
||||
3: uvsTransformerPresets.rcR2Rcc,
|
||||
4: uvsTransformerPresets.rcfhFhRcc,
|
||||
5: uvsTransformerPresets.rcfhFhRcc
|
||||
},
|
||||
2: {
|
||||
0: uvsTransformerPresets.flipH12,
|
||||
1: uvsTransformerPresets.flipH12,
|
||||
2: uvsTransformerPresets.flipH23,
|
||||
3: uvsTransformerPresets.flipH23,
|
||||
4: uvsTransformerPresets.rcR2Rcc,
|
||||
5: uvsTransformerPresets.rcR2Rcc
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} originIndex
|
||||
* @param {number[][][]} uvs
|
||||
* @returns {Record<number, number[]>}
|
||||
*/
|
||||
export const createSideToUvs = (originIndex, uvs) => {
|
||||
const pos = indexToPosition(originIndex)
|
||||
|
||||
/** @type {Record<number, number[]>} */
|
||||
const sideToUvs = {}
|
||||
|
||||
// populate sideToUvs
|
||||
for (let side = 0; side < 6; side++) {
|
||||
const inside = isInside(side, originIndex)
|
||||
if (inside) {
|
||||
continue
|
||||
}
|
||||
sideToUvs[side] = positionToUvs(pos, side, uvs)
|
||||
}
|
||||
|
||||
return sideToUvs
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Turn[]} turns
|
||||
* @param {number} originIndex
|
||||
* @param {Record<number, number[]>} sideToUvs
|
||||
* @returns {Record<number, number[]>}
|
||||
*/
|
||||
export const transformSidetoUvs = (originIndex, sideToUvs, turns) => {
|
||||
for (const turn of turns) {
|
||||
let {axis, index, angle} = convertTurnToAia(turn)
|
||||
const innerSide = axis * 2 + Math.sign(index)
|
||||
|
||||
/** @type {Record<number, number[]>} */
|
||||
const newSideToUvs = {}
|
||||
for (const [sideStr, uvs] of Object.entries(sideToUvs)) {
|
||||
const side = parseInt(sideStr)
|
||||
const transformer = uvsTransformers[axis][side][angle]
|
||||
|
||||
if (side === innerSide) {
|
||||
newSideToUvs[innerSide] = transformer(uvs)
|
||||
continue
|
||||
}
|
||||
|
||||
const sideIndex = sidesShiftMapper[axis].indexOf(side)
|
||||
const newSide = sidesShiftMapper[axis][mod(sideIndex - angle, 4)]
|
||||
newSideToUvs[newSide] = transformer(uvs)
|
||||
}
|
||||
|
||||
const mapperIndex = cubiesShiftMapper[axis][index].indexOf(originIndex)
|
||||
originIndex = cubiesShiftMapper[axis][index][mod(mapperIndex + angle * 2, 8)]
|
||||
sideToUvs = newSideToUvs
|
||||
}
|
||||
|
||||
return sideToUvs
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} targetIndex
|
||||
* @param {import('../ui/rubiks').Rubiks} rubiks
|
||||
* @param {Record<number, number[]>} sideToUvs
|
||||
*/
|
||||
export const setUvs = (targetIndex, rubiks, sideToUvs) => {
|
||||
const cubie = rubiks.cubies[targetIndex]
|
||||
for (const facelet of cubie.facelets) {
|
||||
facelet.uvs = sideToUvs[facelet.side]
|
||||
}
|
||||
}
|
||||
19
node_modules/rubiks-js/src/state/types.d.ts
generated
vendored
Normal file
19
node_modules/rubiks-js/src/state/types.d.ts
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
export type TurnBase = UD | RL | FB
|
||||
export type Turn = TurnBase | `${TurnBase}2` | `${TurnBase}'`
|
||||
|
||||
export type UD = 'U' | 'D'
|
||||
export type RL = 'R' | 'L'
|
||||
export type FB = 'F' | 'B'
|
||||
|
||||
export type Corner = `${UD}${RL}${FB}`
|
||||
export type CornerOrientation = 0 | 1 | 2
|
||||
|
||||
export type Edge = `${UD}${FB | RL}` | `${FB}${RL}`
|
||||
export type EdgeOrientation = 0 | 1
|
||||
|
||||
export type Center = UD | RL | FB
|
||||
export type CenterOrientation = 0 | 1 | 2 | 3
|
||||
|
||||
export type Events = {
|
||||
change: import('.').State
|
||||
}
|
||||
59
node_modules/rubiks-js/src/types.d.ts
generated
vendored
Normal file
59
node_modules/rubiks-js/src/types.d.ts
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
import type {Facelet} from './ui/facelet'
|
||||
import type {Cubie} from './ui/cubie'
|
||||
|
||||
import type {V2, V3} from './math/vector'
|
||||
|
||||
import type {StateInfo} from './state'
|
||||
import type {Turn} from './state/types'
|
||||
|
||||
export type AxisInfo = {
|
||||
default: V3
|
||||
inverted: V3
|
||||
axis: number
|
||||
index: number
|
||||
}
|
||||
|
||||
export type SideInfo = {
|
||||
dir: V2
|
||||
axis: number
|
||||
rotationAxis: V3
|
||||
index: number
|
||||
angle: number
|
||||
cubies: Cubie[]
|
||||
}
|
||||
|
||||
export type Action = {
|
||||
type: 'none'
|
||||
} | {
|
||||
type: 'hovering',
|
||||
facelet: Facelet
|
||||
} | {
|
||||
type: 'rotatingCube',
|
||||
mouse: V2
|
||||
} | {
|
||||
type: 'rotatingSide',
|
||||
mouse: V2,
|
||||
right: SideInfo,
|
||||
down: SideInfo,
|
||||
side: 'right' | 'down' | null,
|
||||
facelet: Facelet
|
||||
} | {
|
||||
type: 'gesture',
|
||||
center: V2,
|
||||
distance: number
|
||||
}
|
||||
|
||||
|
||||
export interface Uniform {
|
||||
setUniform(gl: WebGL2RenderingContext, location: WebGLUniformLocation): void
|
||||
}
|
||||
|
||||
export type AIA = {
|
||||
axis: number,
|
||||
index: number,
|
||||
angle: number
|
||||
}
|
||||
|
||||
export type Events = {
|
||||
change: import('./events').ChangeEvent
|
||||
}
|
||||
194
node_modules/rubiks-js/src/ui/camera.js
generated
vendored
Normal file
194
node_modules/rubiks-js/src/ui/camera.js
generated
vendored
Normal file
@@ -0,0 +1,194 @@
|
||||
import {V2, V3, V4} from '../math/vector'
|
||||
import {M44} from '../math/matrix'
|
||||
|
||||
export class Camera {
|
||||
/** @type {V3} */
|
||||
#position
|
||||
/** @type {V3} */
|
||||
#lookAt
|
||||
/** @type {V3} */
|
||||
#forward
|
||||
/** @type {V3} */
|
||||
#right
|
||||
/** @type {V3} */
|
||||
#up
|
||||
|
||||
/** @type {number} */
|
||||
#fov
|
||||
/** @type {number} */
|
||||
#aspect
|
||||
/** @type {number} */
|
||||
#width
|
||||
/** @type {number} */
|
||||
#height
|
||||
/** @type {number} */
|
||||
#near
|
||||
/** @type {number} */
|
||||
#far
|
||||
|
||||
/** @type {M44} */
|
||||
#projectionMatrix
|
||||
/** @type {M44} */
|
||||
#worldToCameraMatrix
|
||||
/** @type {M44} */
|
||||
#projectionMatrixInverse
|
||||
/** @type {M44} */
|
||||
#cameraToWorldMatrix
|
||||
/** @type {M44} */
|
||||
#worldProjectionMatrix
|
||||
|
||||
/**
|
||||
* @param {V3} position
|
||||
* @param {V3} lookAt
|
||||
* @param {V3} up
|
||||
* @param {number} fov
|
||||
* @param {number} width
|
||||
* @param {number} height
|
||||
* @param {number} near
|
||||
* @param {number} far
|
||||
*/
|
||||
constructor(position, lookAt, up, fov, width, height, near, far) {
|
||||
this.#position = position
|
||||
this.#lookAt = lookAt
|
||||
this.#up = up
|
||||
|
||||
this.#fov = fov
|
||||
this.#width = width
|
||||
this.#height = height
|
||||
this.#aspect = width / height
|
||||
this.#near = near
|
||||
this.#far = far
|
||||
|
||||
this.calcCameraDirections()
|
||||
this.calcWorldToCameraMatrix()
|
||||
this.calcProjectionMatrix()
|
||||
}
|
||||
|
||||
/** @param {V3} value */
|
||||
set position(value) {
|
||||
this.#position = value
|
||||
this.calcCameraDirections()
|
||||
this.calcWorldToCameraMatrix()
|
||||
}
|
||||
/** @param {V3} value */
|
||||
set lookAt(value) {
|
||||
this.#lookAt = value
|
||||
this.calcCameraDirections()
|
||||
this.calcWorldToCameraMatrix()
|
||||
}
|
||||
/** @param {V3} value */
|
||||
set up(value) {
|
||||
this.#up = value
|
||||
this.calcCameraDirections()
|
||||
this.calcWorldToCameraMatrix()
|
||||
}
|
||||
get position() {
|
||||
return this.#position
|
||||
}
|
||||
get lookAt() {
|
||||
return this.#lookAt
|
||||
}
|
||||
get up() {
|
||||
return this.#up
|
||||
}
|
||||
get forward() {
|
||||
return this.#forward
|
||||
}
|
||||
get right() {
|
||||
return this.#right
|
||||
}
|
||||
|
||||
/** @param {number} value */
|
||||
set fov(value) {
|
||||
this.#fov = value
|
||||
this.calcProjectionMatrix()
|
||||
}
|
||||
/** @param {number} value */
|
||||
set near(value) {
|
||||
this.#near = value
|
||||
this.calcProjectionMatrix()
|
||||
}
|
||||
/** @param {number} value */
|
||||
set far(value) {
|
||||
this.#far = value
|
||||
this.calcProjectionMatrix()
|
||||
}
|
||||
get fov() {
|
||||
return this.#fov
|
||||
}
|
||||
get aspect() {
|
||||
return this.#aspect
|
||||
}
|
||||
get near() {
|
||||
return this.#near
|
||||
}
|
||||
get far() {
|
||||
return this.#far
|
||||
}
|
||||
get width() {
|
||||
return this.#width
|
||||
}
|
||||
get height() {
|
||||
return this.#height
|
||||
}
|
||||
/**
|
||||
* @param {number} width
|
||||
* @param {number} height
|
||||
*/
|
||||
screenSize(width, height) {
|
||||
this.#width = width
|
||||
this.#height = height
|
||||
this.#aspect = width / height
|
||||
this.calcProjectionMatrix()
|
||||
}
|
||||
|
||||
|
||||
get projectionMatrix() {
|
||||
return this.#projectionMatrix
|
||||
}
|
||||
get projectionMatrixInverse() {
|
||||
return this.#projectionMatrixInverse
|
||||
}
|
||||
get worldToCameraMatrix() {
|
||||
return this.#worldToCameraMatrix
|
||||
}
|
||||
get cameraToWorldMatrix() {
|
||||
return this.#cameraToWorldMatrix
|
||||
}
|
||||
get worldProjectionMatrix() {
|
||||
return this.#worldProjectionMatrix
|
||||
}
|
||||
|
||||
/** @param {V3} v */
|
||||
worldToScreen({x, y, z}) {
|
||||
const point4d = this.worldProjectionMatrix.mult(new V4(x, y, z, 1))
|
||||
const screenPoint = point4d.toV3().scale(1 / point4d.w).toV2().add(new V2(1, 1)).scale(.5).mult(new V2(this.width, this.height))
|
||||
return screenPoint
|
||||
}
|
||||
// worldDirectionToScreen({x, y, z}: V3) {
|
||||
// // const rotationTransform = this.cameraToWorldMatrix.transpose
|
||||
// const cameraPoint = this.worldToCameraMatrix.mult(new V4(x, y, z, 1))
|
||||
// const projectedPoint = this.projectionMatrix.mult(cameraPoint)
|
||||
// const viewportPoint = projectedPoint.toV3().scale(1 / projectedPoint.w).toV2()
|
||||
// const screenPoint = viewportPoint.add(new V2(1, 1)).scale(.5).mult(new V2(this.width, this.height))
|
||||
// // console.log(this.projectionMatrix)
|
||||
// console.table({cameraPoint, projectedPoint, viewportPoint, screenPoint})
|
||||
// return screenPoint
|
||||
// }
|
||||
|
||||
|
||||
calcProjectionMatrix() {
|
||||
this.#projectionMatrix = M44.perspective(this.#fov * Math.PI / 180, this.#aspect, this.#near, this.#far)
|
||||
this.#projectionMatrixInverse = this.#projectionMatrix.inverse
|
||||
this.#worldProjectionMatrix = this.#projectionMatrix?.mult(this.#worldToCameraMatrix)
|
||||
}
|
||||
calcWorldToCameraMatrix() {
|
||||
this.#worldToCameraMatrix = M44.lookAt(this.#position, this.#lookAt, this.#up)
|
||||
this.#cameraToWorldMatrix = this.#worldToCameraMatrix.inverse
|
||||
this.#worldProjectionMatrix = this.#projectionMatrix?.mult(this.#worldToCameraMatrix)
|
||||
}
|
||||
calcCameraDirections() {
|
||||
this.#forward = this.#lookAt.sub(this.#position).normalized
|
||||
this.#right = this.#up.cross(this.#forward).normalized
|
||||
}
|
||||
}
|
||||
125
node_modules/rubiks-js/src/ui/cubie.js
generated
vendored
Normal file
125
node_modules/rubiks-js/src/ui/cubie.js
generated
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
import {V3} from '../math/vector'
|
||||
import {Quaternion} from '../math/quarternion'
|
||||
|
||||
import {Facelet, FaceletTransform, InsideFacelet} from './facelet'
|
||||
import {Transform} from './transform'
|
||||
|
||||
|
||||
const positionForSide = [
|
||||
V3.right,
|
||||
V3.left,
|
||||
V3.down,
|
||||
V3.up,
|
||||
V3.back,
|
||||
V3.forward
|
||||
].map(v => v.scale(0.5))
|
||||
|
||||
const rotationForSide = [
|
||||
Quaternion.identity,
|
||||
Quaternion.identity,
|
||||
Quaternion.fromAngle(V3.back, 90),
|
||||
Quaternion.fromAngle(V3.back, 90),
|
||||
Quaternion.fromAngle(V3.back, 90).mult(Quaternion.fromAngle(V3.down, 90)),
|
||||
Quaternion.fromAngle(V3.back, 90).mult(Quaternion.fromAngle(V3.down, 90))
|
||||
]
|
||||
|
||||
/**
|
||||
* @param {number} side
|
||||
* @param {number} index
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const isInside = (side, index) => {
|
||||
const axis = Math.floor(side / 2)
|
||||
const invert = side % 2
|
||||
const coordinate = Math.floor(index / Math.pow(3, axis)) % 3
|
||||
return coordinate === 1
|
||||
|| coordinate === 0 && invert === 1
|
||||
|| coordinate === 2 && invert === 0
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} index
|
||||
* @returns {[x: number, y: number, z: number]}
|
||||
*/
|
||||
export const indexToPosition = index => {
|
||||
const x = Math.floor(index / 1) % 3
|
||||
const y = Math.floor(index / 3) % 3
|
||||
const z = Math.floor(index / 9) % 3
|
||||
return [x, y, z]
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {[x: number, y: number, z: number]} pos
|
||||
* @param {number} side
|
||||
* @param {number[][][]} uvs
|
||||
* @returns {number[]}
|
||||
*/
|
||||
export const positionToUvs = (pos, side, uvs) => {
|
||||
const axis = Math.floor(side / 2)
|
||||
/** @type {number[]} */
|
||||
const uvCoords = []
|
||||
for (let i = 0; i < 3; i++) {
|
||||
if (i !== axis) {
|
||||
uvCoords.push(pos[i])
|
||||
}
|
||||
}
|
||||
const sideIndex = uvCoords[0] + uvCoords[1] * 3
|
||||
return uvs[side][sideIndex]
|
||||
}
|
||||
|
||||
|
||||
export class Cubie {
|
||||
/** @type {Facelet[]} */
|
||||
facelets = []
|
||||
/** @type {Transform<Facelet | InsideFacelet, import('./rubiks').Rubiks>} */
|
||||
transform
|
||||
|
||||
/**
|
||||
* @param {number} index
|
||||
* @param {number[][][]} uvs
|
||||
* @param {V3[]} hoveringColors
|
||||
* @param {import('./rubiks').Rubiks} parent
|
||||
*/
|
||||
constructor(index, uvs, hoveringColors, parent) {
|
||||
this.index = index
|
||||
const pos = indexToPosition(index)
|
||||
const position = new V3(pos[0], pos[1], pos[2]).sub(V3.one)
|
||||
|
||||
this.transform = new Transform(position, Quaternion.identity, parent)
|
||||
for (let side = 0; side < 6; side++) {
|
||||
const inside = isInside(side, this.index)
|
||||
const position = positionForSide[side]
|
||||
const rotation = rotationForSide[side]
|
||||
|
||||
if (inside) {
|
||||
const transform = new Transform(position, rotation, this)
|
||||
const facelet = new InsideFacelet(transform)
|
||||
this.transform.children.push(facelet)
|
||||
continue
|
||||
}
|
||||
|
||||
const uv = positionToUvs(pos, side, uvs)
|
||||
const transform = new FaceletTransform(position, rotation, this)
|
||||
const facelet = new Facelet(transform, side, uv, hoveringColors[side])
|
||||
this.transform.children.push(facelet)
|
||||
this.facelets.push(facelet)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} side
|
||||
* @returns {Facelet}
|
||||
*/
|
||||
getFaceletOfSide(side) {
|
||||
return /** @type {Facelet} */ (this.facelets.find(facelet => facelet.side === side))
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('./program').Program} program
|
||||
* @param {WebGL2RenderingContext} gl
|
||||
* @param {WebGLBuffer} uvsVbo
|
||||
*/
|
||||
render(program, gl, uvsVbo) {
|
||||
this.transform.children.forEach(child => child.render.call(child, program, gl, uvsVbo))
|
||||
}
|
||||
}
|
||||
105
node_modules/rubiks-js/src/ui/debugger.js
generated
vendored
Normal file
105
node_modules/rubiks-js/src/ui/debugger.js
generated
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
import {V2} from '../math/vector'
|
||||
|
||||
class Debugger {
|
||||
/** @type {CanvasRenderingContext2D?} */
|
||||
#ctx
|
||||
/** @type {HTMLCanvasElement?} */
|
||||
#canvas
|
||||
|
||||
#strokeColor = 'white'
|
||||
#fillColor = 'white'
|
||||
|
||||
/**
|
||||
* @param {Element?} canvas
|
||||
*/
|
||||
constructor(canvas) {
|
||||
if (!canvas) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!(canvas instanceof HTMLCanvasElement)) {
|
||||
throw new Error(`rubiks cube debugger is not a canvas, it is a <${canvas.tagName}>`)
|
||||
}
|
||||
this.#canvas = canvas
|
||||
|
||||
const ctx = canvas.getContext('2d')
|
||||
if (!ctx)
|
||||
throw new Error('cannot create 2d context for rubiks cube debugger')
|
||||
this.#ctx = ctx
|
||||
}
|
||||
|
||||
clear() {
|
||||
if (this.#ctx && this.#canvas) {
|
||||
this.#ctx.clearRect(0, 0, this.#canvas.width, this.#canvas.height)
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {string} color */
|
||||
stroke(color) {
|
||||
this.#strokeColor = color
|
||||
}
|
||||
|
||||
/** @param {string} color */
|
||||
fill(color) {
|
||||
this.#fillColor = color
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {V2} from
|
||||
* @param {V2} to
|
||||
*/
|
||||
line(from, to) {
|
||||
if (this.#ctx && this.#canvas) {
|
||||
this.#ctx.strokeStyle = this.#strokeColor
|
||||
this.#ctx.lineWidth = 3
|
||||
const fromF = Debugger.#flipVector(from, this.#canvas)
|
||||
const toF = Debugger.#flipVector(to, this.#canvas)
|
||||
this.#ctx.beginPath()
|
||||
this.#ctx.moveTo(fromF.x, fromF.y)
|
||||
this.#ctx.lineTo(toF.x, toF.y)
|
||||
this.#ctx.stroke()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {V2} origin
|
||||
* @param {V2} direction
|
||||
* @param {number} [length=1]
|
||||
*/
|
||||
vector(origin, direction, length = 1) {
|
||||
this.line(origin, origin.add(direction.scale(length)))
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} width
|
||||
* @param {number} height
|
||||
*/
|
||||
setSize(width, height) {
|
||||
if (this.#canvas) {
|
||||
this.#canvas.width = width
|
||||
this.#canvas.height = height
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {V2} v
|
||||
* @param {HTMLCanvasElement} canvas
|
||||
*/
|
||||
static #flipVector({x, y}, canvas) {
|
||||
return new V2(x, canvas.height - y)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {V2} pos
|
||||
* @param {string} text
|
||||
*/
|
||||
text(pos, text) {
|
||||
if (this.#ctx) {
|
||||
this.#ctx.fillStyle = this.#fillColor
|
||||
this.#ctx.fillText(text, pos.x, pos.y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default new Debugger(document.querySelector('[data-rubiks-cube-debug]'))
|
||||
98
node_modules/rubiks-js/src/ui/facelet.js
generated
vendored
Normal file
98
node_modules/rubiks-js/src/ui/facelet.js
generated
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
import {V3, V4} from '../math/vector'
|
||||
import {Quaternion} from '../math/quarternion'
|
||||
|
||||
import {Program} from './program'
|
||||
import {Transform} from './transform'
|
||||
import {Cubie} from './cubie'
|
||||
|
||||
/**
|
||||
* @extends {Transform<any, Cubie>}
|
||||
*/
|
||||
export class FaceletTransform extends Transform {
|
||||
/** @type {V3} */
|
||||
left
|
||||
/** @type {V3} */
|
||||
top
|
||||
/** @type {V3} */
|
||||
normal
|
||||
/** @type {V3} */
|
||||
topLeft
|
||||
/** @type {V3} */
|
||||
bottomRight
|
||||
|
||||
/**
|
||||
* @param {V3} position
|
||||
* @param {Quaternion} rotation
|
||||
* @param {Cubie} parent
|
||||
*/
|
||||
constructor(position, rotation, parent) {
|
||||
super(position, rotation, parent)
|
||||
this.setTransforms()
|
||||
}
|
||||
|
||||
/** @protected */
|
||||
setTransforms() {
|
||||
super.setTransforms()
|
||||
|
||||
const rotationTransform = this.globalTransform.inverse.transpose
|
||||
|
||||
this.left = rotationTransform.mult(new V4(0, 0, -1, 1)).toV3().normalized
|
||||
this.top = rotationTransform.mult(new V4(0, -1, 0, 1)).toV3().normalized
|
||||
this.normal = rotationTransform.mult(new V4(-1, 0, 0, 1)).toV3().normalized
|
||||
|
||||
this.topLeft = this.globalTransform.mult(new V4(0, .5, .5, 1)).toV3()
|
||||
this.bottomRight = this.topLeft.add(this.left).add(this.top)
|
||||
}
|
||||
}
|
||||
|
||||
export class Facelet {
|
||||
hovering = false
|
||||
/** @type {V3} */
|
||||
#hoveringMult
|
||||
|
||||
/**
|
||||
* @param {FaceletTransform} transform
|
||||
* @param {number} side
|
||||
* @param {number[]} uvs
|
||||
* @param {V3} hoveringMult
|
||||
*/
|
||||
constructor(transform, side, uvs, hoveringMult) {
|
||||
this.transform = transform
|
||||
this.side = side
|
||||
this.uvs = uvs
|
||||
this.#hoveringMult = hoveringMult
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Program} program
|
||||
* @param {WebGL2RenderingContext} gl
|
||||
* @param {WebGLBuffer} uvsVbo
|
||||
*/
|
||||
render(program, gl, uvsVbo) {
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, uvsVbo)
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(this.uvs), gl.STATIC_DRAW)
|
||||
gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 8, 0)
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, null)
|
||||
|
||||
program.uniform('model', this.transform.globalTransform)
|
||||
program.uniform('colorMult', this.hovering ? this.#hoveringMult : V3.one)
|
||||
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0)
|
||||
}
|
||||
}
|
||||
|
||||
export class InsideFacelet {
|
||||
/** @param {Transform<any, Cubie>} transform */
|
||||
constructor(transform) {
|
||||
this.transform = transform
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Program} program
|
||||
* @param {WebGL2RenderingContext} gl
|
||||
*/
|
||||
render(program, gl) {
|
||||
program.uniform('model', this.transform.globalTransform)
|
||||
program.uniform('colorMult', V3.zero)
|
||||
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0)
|
||||
}
|
||||
}
|
||||
393
node_modules/rubiks-js/src/ui/inputHandler.js
generated
vendored
Normal file
393
node_modules/rubiks-js/src/ui/inputHandler.js
generated
vendored
Normal file
@@ -0,0 +1,393 @@
|
||||
import {Ray} from './ray'
|
||||
// import debug from './debugger'
|
||||
|
||||
import {V2, V3} from '../math/vector'
|
||||
import {clamp} from '../math/utils'
|
||||
|
||||
|
||||
export class InputHandler {
|
||||
/** @type {HTMLCanvasElement} */
|
||||
#canvas
|
||||
/** @type {import('./rubiks').Rubiks} */
|
||||
#rubiks
|
||||
/** @type {import('./camera').Camera} */
|
||||
#camera
|
||||
/** @readonly */
|
||||
#maxZoom = 40
|
||||
/** @readonly */
|
||||
#minZoom = 10
|
||||
|
||||
/**
|
||||
* @param {HTMLCanvasElement} canvas
|
||||
* @param {import('./rubiks').Rubiks} rubiks
|
||||
* @param {import('./camera').Camera} camera
|
||||
*/
|
||||
constructor(canvas, rubiks, camera) {
|
||||
this.#canvas = canvas
|
||||
this.#rubiks = rubiks
|
||||
this.#camera = camera
|
||||
}
|
||||
|
||||
addEventListeners() {
|
||||
this.#canvas.addEventListener('pointermove', this.#pointerMove)
|
||||
this.#canvas.addEventListener('pointerdown', this.#pointerDown)
|
||||
this.#canvas.addEventListener('pointerup', this.#pointerUp)
|
||||
this.#canvas.addEventListener('pointerleave', this.#pointerLeave)
|
||||
this.#canvas.addEventListener('wheel', this.#wheel)
|
||||
}
|
||||
|
||||
removeEventListeners() {
|
||||
this.#canvas.removeEventListener('pointermove', this.#pointerMove)
|
||||
this.#canvas.removeEventListener('pointerdown', this.#pointerDown)
|
||||
this.#canvas.removeEventListener('pointerup', this.#pointerUp)
|
||||
this.#canvas.removeEventListener('pointerleave', this.#pointerLeave)
|
||||
this.#canvas.removeEventListener('wheel', this.#wheel)
|
||||
}
|
||||
|
||||
|
||||
/** @type {Map<number, PointerEvent>} */
|
||||
#pointers = new Map()
|
||||
/** @type {import('../types').Action} */
|
||||
#action = {type: 'none'}
|
||||
|
||||
/** @param {import('../types').Action} action */
|
||||
#setAction(action) {
|
||||
if (this.#action.type === 'hovering') {
|
||||
this.#action.facelet.hovering = false
|
||||
}
|
||||
if (action.type === 'hovering') {
|
||||
action.facelet.hovering = true
|
||||
}
|
||||
if (this.#action.type === 'rotatingSide' && this.#action.side) {
|
||||
const info = this.#action[this.#action.side]
|
||||
const angle = Math.round(info.angle / 90)
|
||||
this.#rubiks.finishRotation(info.axis, info.index, angle, this.#action.facelet.side)
|
||||
}
|
||||
this.#action = action
|
||||
}
|
||||
|
||||
// event handlers
|
||||
#pointerDown = InputHandler.#getPointerDown(this)
|
||||
/** @param {InputHandler} inputHandler */
|
||||
static #getPointerDown(inputHandler) {
|
||||
return (/** @type {PointerEvent}*/ event) => {
|
||||
const {offsetX, offsetY, pointerId} = event
|
||||
if (event.button === 0) {
|
||||
inputHandler.#pointers.set(pointerId, event)
|
||||
} else if (event.button === 1) {
|
||||
inputHandler.#pointers.set(pointerId, event)
|
||||
inputHandler.#setAction({
|
||||
type: 'rotatingCube',
|
||||
mouse: new V2(offsetX, offsetY)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (inputHandler.#pointers.size === 1) {
|
||||
if (inputHandler.#rubiks.isTurning) {
|
||||
return
|
||||
}
|
||||
if (inputHandler.#action.type === 'none') {
|
||||
inputHandler.#actionHovering(offsetX, offsetY)
|
||||
}
|
||||
if (inputHandler.#action.type === 'hovering') {
|
||||
inputHandler.#startActionRotatingSide(offsetX, offsetY, inputHandler.#action.facelet)
|
||||
return
|
||||
}
|
||||
inputHandler.#startActionRotatingCube(offsetX, offsetY)
|
||||
} else if (inputHandler.#pointers.size === 2) {
|
||||
inputHandler.#startActionGesture()
|
||||
}
|
||||
}
|
||||
}
|
||||
#pointerMove = InputHandler.#getPointerMove(this)
|
||||
/** @param {InputHandler} inputHandler */
|
||||
static #getPointerMove(inputHandler) {
|
||||
return (/** @type {PointerEvent} */ event) => {
|
||||
const {offsetX, offsetY} = event
|
||||
|
||||
if (inputHandler.#pointers.size === 0) {
|
||||
inputHandler.#actionHovering(offsetX, offsetY)
|
||||
return
|
||||
}
|
||||
inputHandler.#pointers.set(event.pointerId, event)
|
||||
|
||||
inputHandler.#actionRotatingSide(offsetX, offsetY)
|
||||
inputHandler.#actionRotatingCube(offsetX, offsetY)
|
||||
inputHandler.#actionGesture()
|
||||
}
|
||||
}
|
||||
#pointerUp = InputHandler.#getPointerUp(this)
|
||||
/** @param {InputHandler} inputHandler */
|
||||
static #getPointerUp(inputHandler) {
|
||||
return () => {
|
||||
inputHandler.#pointers.clear()
|
||||
inputHandler.#setAction({
|
||||
type: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
#pointerLeave = InputHandler.#getPointerLeave(this)
|
||||
/** @param {InputHandler} inputHandler */
|
||||
static #getPointerLeave(inputHandler) {
|
||||
return () => {
|
||||
inputHandler.#pointers.clear()
|
||||
inputHandler.#setAction({
|
||||
type: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
#wheel = InputHandler.#getWheel(this)
|
||||
/** @param {InputHandler} inputHandler */
|
||||
static #getWheel(inputHandler) {
|
||||
return (/** @type {WheelEvent} */ {deltaY, deltaMode}) => {
|
||||
if (deltaMode !== WheelEvent.DOM_DELTA_PIXEL) {
|
||||
return
|
||||
}
|
||||
const d = clamp(deltaY / 102, -1, 1) * .1
|
||||
inputHandler.#zoomCube(d)
|
||||
}
|
||||
}
|
||||
|
||||
// actions
|
||||
// rotating side
|
||||
/**
|
||||
* @param {number} offsetX
|
||||
* @param {number} offsetY
|
||||
* @param {import('./facelet').Facelet} hovering
|
||||
*/
|
||||
#startActionRotatingSide(offsetX, offsetY, hovering) {
|
||||
const {left, top, topLeft} = hovering.transform
|
||||
|
||||
const screenTopLeft = this.#camera.worldToScreen(topLeft)
|
||||
const screenBottomLeft = this.#camera.worldToScreen(topLeft.add(left))
|
||||
const screenTopRight = this.#camera.worldToScreen(topLeft.add(top))
|
||||
const rightDir = screenTopRight.sub(screenTopLeft).normalized
|
||||
const downDir = screenBottomLeft.sub(screenTopLeft).normalized
|
||||
|
||||
const rubiksRotation = this.#rubiks.transform
|
||||
|
||||
const side = hovering.side
|
||||
const currentAxis = Math.floor(side / 2)
|
||||
const cubie = hovering.transform.parent
|
||||
const [axis1, axis2] = [0, 1, 2]
|
||||
.filter(axis => axis !== currentAxis)
|
||||
.map(axis => {
|
||||
const rotationAxis = V3.getRotationAxis(axis)
|
||||
const rubiksRotationAxis = rubiksRotation.apply(rotationAxis)
|
||||
const index = Math.floor(cubie.index / Math.pow(3, axis)) % 3
|
||||
/** @type {import('../types').AxisInfo} */
|
||||
const info = {
|
||||
default: rubiksRotationAxis,
|
||||
inverted: rubiksRotationAxis.negate,
|
||||
axis,
|
||||
index
|
||||
}
|
||||
return info
|
||||
})
|
||||
|
||||
const sideInvertMap = [
|
||||
false, true,
|
||||
true, false,
|
||||
false, true
|
||||
]
|
||||
const invert = sideInvertMap[hovering.side]
|
||||
|
||||
const mouse = new V2(offsetX, this.#canvas.height - offsetY)
|
||||
if (Math.abs(axis1.default.dot(top)) > .99) {
|
||||
this.#setAction({
|
||||
type: 'rotatingSide',
|
||||
mouse,
|
||||
down: this.#getTurnDirection(axis1, top, downDir, true !== invert),
|
||||
right: this.#getTurnDirection(axis2, left, rightDir, false !== invert),
|
||||
side: null,
|
||||
facelet: hovering
|
||||
})
|
||||
} else {
|
||||
this.#setAction({
|
||||
type: 'rotatingSide',
|
||||
mouse,
|
||||
down: this.#getTurnDirection(axis2, top, downDir, true !== invert),
|
||||
right: this.#getTurnDirection(axis1, left, rightDir, false !== invert),
|
||||
side: null,
|
||||
facelet: hovering
|
||||
})
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {number} offsetX
|
||||
* @param {number} offsetY
|
||||
*/
|
||||
#actionRotatingSide(offsetX, offsetY) {
|
||||
if (this.#action.type !== 'rotatingSide') {
|
||||
return
|
||||
}
|
||||
|
||||
const mouse = new V2(offsetX, this.#canvas.height - offsetY)
|
||||
if (this.#action.mouse.sub(mouse).mag < 10) {
|
||||
return
|
||||
}
|
||||
|
||||
const initialMouse = this.#action.mouse
|
||||
if (this.#action.side) {
|
||||
this.#rotateSide(mouse, this.#action[this.#action.side], initialMouse)
|
||||
return
|
||||
}
|
||||
|
||||
const {right, down} = this.#action
|
||||
const mouseDir = mouse.sub(initialMouse).normalized
|
||||
const rightDot = right.dir.dot(mouseDir)
|
||||
const downDot = down.dir.dot(mouseDir)
|
||||
if (Math.abs(rightDot) > Math.abs(downDot)) {
|
||||
this.#action.side = 'right'
|
||||
this.#rubiks.startRotation(right.cubies, right.rotationAxis)
|
||||
this.#rotateSide(mouse, right, initialMouse)
|
||||
} else {
|
||||
this.#action.side = 'down'
|
||||
this.#rubiks.startRotation(down.cubies, down.rotationAxis)
|
||||
this.#rotateSide(mouse, down, initialMouse)
|
||||
}
|
||||
}
|
||||
// rotating cube
|
||||
/**
|
||||
* @param {number} offsetX
|
||||
* @param {number} offsetY
|
||||
*/
|
||||
#startActionRotatingCube(offsetX, offsetY) {
|
||||
this.#setAction({
|
||||
type: 'rotatingCube',
|
||||
mouse: new V2(offsetX, offsetY)
|
||||
})
|
||||
}
|
||||
/**
|
||||
* @param {number} offsetX
|
||||
* @param {number} offsetY
|
||||
*/
|
||||
#actionRotatingCube(offsetX, offsetY) {
|
||||
if (this.#action.type !== 'rotatingCube') {
|
||||
return
|
||||
}
|
||||
|
||||
const mouse = new V2(offsetX, offsetY)
|
||||
const delta = mouse.sub(this.#action.mouse)
|
||||
this.#action.mouse = mouse
|
||||
this.#rotateCube(delta)
|
||||
}
|
||||
// two pointer gesture
|
||||
#startActionGesture() {
|
||||
const [e1, e2] = this.#pointers.values()
|
||||
const dx = e1.screenX - e2.screenX
|
||||
const dy = e1.screenY - e2.screenY
|
||||
this.#setAction({
|
||||
type: 'gesture',
|
||||
distance: dx * dx + dy * dy,
|
||||
center: new V2(e1.screenX + e2.screenX, e1.screenY + e2.screenY).scale(.5)
|
||||
})
|
||||
}
|
||||
#actionGesture() {
|
||||
if (this.#action.type !== 'gesture') {
|
||||
return
|
||||
}
|
||||
|
||||
const [e1, e2] = this.#pointers.values()
|
||||
|
||||
const dx = e1.screenX - e2.screenX
|
||||
const dy = e1.screenY - e2.screenY
|
||||
const distance = dx * dx + dy * dy
|
||||
const deltaDistance = distance - this.#action.distance
|
||||
this.#action.distance = distance
|
||||
const d = clamp(deltaDistance / 1000, -1, 1) * -.02
|
||||
this.#zoomCube(d)
|
||||
|
||||
const center = new V2(e1.screenX + e2.screenX, e1.screenY + e2.screenY).scale(.5)
|
||||
const deltaCenter = center.sub(this.#action.center)
|
||||
this.#action.center = center
|
||||
this.#rotateCube(deltaCenter)
|
||||
}
|
||||
// hovering
|
||||
/**
|
||||
* @param {number} offsetX
|
||||
* @param {number} offsetY
|
||||
*/
|
||||
#actionHovering(offsetX, offsetY) {
|
||||
const ray = new Ray(this.#camera, offsetX, offsetY, window.innerWidth, window.innerHeight)
|
||||
const facelets = ray.intersectRubiks(this.#rubiks)
|
||||
if (facelets.length) {
|
||||
facelets.sort((a, b) => a.d - b.d)
|
||||
this.#setAction({
|
||||
type: 'hovering',
|
||||
facelet: facelets[0].facelet
|
||||
})
|
||||
} else {
|
||||
this.#setAction({
|
||||
type: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// action helpers
|
||||
// rotating side
|
||||
/**
|
||||
* @param {import('../types').AxisInfo} axisInfo
|
||||
* @param {V3} vector
|
||||
* @param {V2} dir
|
||||
* @param {boolean} invert
|
||||
* @returns {import('../types').SideInfo}
|
||||
*/
|
||||
#getTurnDirection(axisInfo, vector, dir, invert) {
|
||||
if (axisInfo.default.dot(vector) > .99)
|
||||
return {
|
||||
dir,
|
||||
angle: 0,
|
||||
axis: axisInfo.axis,
|
||||
rotationAxis: V3.getRotationAxis(axisInfo.axis).scale(invert ? -1 : 1),
|
||||
index: axisInfo.index,
|
||||
cubies: this.#rubiks.getPlane(axisInfo.axis, axisInfo.index)
|
||||
}
|
||||
|
||||
return {
|
||||
dir,
|
||||
angle: 0,
|
||||
axis: axisInfo.axis,
|
||||
rotationAxis: V3.getRotationAxis(axisInfo.axis).scale(invert ? 1 : -1),
|
||||
index: axisInfo.index,
|
||||
cubies: this.#rubiks.getPlane(axisInfo.axis, axisInfo.index)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {V2} mouse
|
||||
* @param {import('../types').SideInfo} info
|
||||
* @param {V2} initialMouse
|
||||
*/
|
||||
#rotateSide(mouse, info, initialMouse) {
|
||||
const length = info.dir.dot(initialMouse.sub(mouse))
|
||||
const zoom = this.#getZoom()
|
||||
info.angle = length / (3 - zoom * 2)
|
||||
this.#rubiks.rotateManual(info.angle)
|
||||
}
|
||||
// rotating cube
|
||||
/** @param {V2} delta */
|
||||
#rotateCube(delta) {
|
||||
if (delta.x === 0 && delta.y === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const n = this.#camera.up.scale(delta.y).add(this.#camera.right.scale(delta.x))
|
||||
const axis = this.#camera.forward.cross(n)
|
||||
const zoom = this.#getZoom()
|
||||
const angle = Math.sqrt(delta.x * delta.x + delta.y * delta.y) * .3 + 2 * zoom
|
||||
this.#rubiks.transform.rotate(axis, angle)
|
||||
}
|
||||
// zooming camera
|
||||
/** @param {number} d */
|
||||
#zoomCube(d) {
|
||||
const {position} = this.#camera
|
||||
this.#camera.position = new V3(0, 0, clamp(position.z * (1 + d), -this.#maxZoom, -this.#minZoom))
|
||||
}
|
||||
#getZoom() {
|
||||
const zoom = -this.#camera.position.z
|
||||
const minZoom = this.#minZoom
|
||||
const maxZoom = this.#maxZoom
|
||||
return (zoom - minZoom) / (maxZoom - minZoom)
|
||||
}
|
||||
}
|
||||
96
node_modules/rubiks-js/src/ui/program.js
generated
vendored
Normal file
96
node_modules/rubiks-js/src/ui/program.js
generated
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
export class Program {
|
||||
/** @type {WebGLProgram} */
|
||||
#program
|
||||
/** @type {Map<string, WebGLUniformLocation>} */
|
||||
#uniformMap
|
||||
/** @type {string} */
|
||||
#path
|
||||
/** @type {WebGL2RenderingContext} */
|
||||
#gl
|
||||
|
||||
/**
|
||||
* @param {string} path
|
||||
* @param {string} vertexShaderSource
|
||||
* @param {string} fragmentShaderSource
|
||||
* @param {WebGL2RenderingContext} gl
|
||||
*/
|
||||
constructor(path, vertexShaderSource, fragmentShaderSource, gl) {
|
||||
this.#path = path
|
||||
this.#gl = gl
|
||||
const vertexShader = this.#createShader(vertexShaderSource, this.#gl.VERTEX_SHADER, 'vertex')
|
||||
const fragmentShader = this.#createShader(fragmentShaderSource, this.#gl.FRAGMENT_SHADER, 'fragment')
|
||||
|
||||
const p = this.#gl.createProgram()
|
||||
|
||||
if (!p)
|
||||
throw new Error('Fatal: webgl could not create program object!')
|
||||
|
||||
this.#program = p
|
||||
this.#gl.attachShader(this.#program, vertexShader)
|
||||
this.#gl.attachShader(this.#program, fragmentShader)
|
||||
this.#gl.linkProgram(this.#program)
|
||||
const success = this.#gl.getProgramParameter(this.#program, this.#gl.LINK_STATUS)
|
||||
if (!success) {
|
||||
const info = this.#gl.getProgramInfoLog(this.#program)
|
||||
this.#gl.deleteProgram(this.#program)
|
||||
throw new Error(`Link Program: ${info}`)
|
||||
}
|
||||
|
||||
const numUniforms = /** @type {number} */ (this.#gl.getProgramParameter(this.#program, this.#gl.ACTIVE_UNIFORMS))
|
||||
const uniformIndices = [...Array(numUniforms).keys()]
|
||||
const uniformNames = uniformIndices.map(index => {
|
||||
const info = this.#gl.getActiveUniform(this.#program, index)
|
||||
if (info == null) {
|
||||
throw new Error('failed to get active uniform')
|
||||
}
|
||||
const location = this.#gl.getUniformLocation(this.#program, info.name)
|
||||
if (location == null) {
|
||||
throw new Error('failed to get uniform location')
|
||||
}
|
||||
return /** @type {[string, WebGLUniformLocation]} */ ([info.name, location])
|
||||
})
|
||||
this.#uniformMap = new Map(uniformNames)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} source
|
||||
* @param {number} type
|
||||
* @param {string} typeStr
|
||||
* @returns {WebGLShader}
|
||||
*/
|
||||
#createShader(source, type, typeStr) {
|
||||
const shader = this.#gl.createShader(type)
|
||||
if (!shader)
|
||||
throw new Error('Fatal: webgl could not create shader object!')
|
||||
this.#gl.shaderSource(shader, source)
|
||||
this.#gl.compileShader(shader)
|
||||
|
||||
const success = /** @type {boolean} */ (this.#gl.getShaderParameter(shader, this.#gl.COMPILE_STATUS))
|
||||
if (success) {
|
||||
return shader
|
||||
}
|
||||
|
||||
const info = this.#gl.getShaderInfoLog(shader)
|
||||
this.#gl.deleteShader(shader)
|
||||
throw new Error(`Compile '${this.#path}': ${typeStr}: ${info}`)
|
||||
}
|
||||
|
||||
use() {
|
||||
if (!this.#program) {
|
||||
throw new Error('Fatal: program does not exists!')
|
||||
}
|
||||
this.#gl.useProgram(this.#program)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {import('../types').Uniform} u
|
||||
*/
|
||||
uniform(name, u) {
|
||||
const location = this.#uniformMap.get(name)
|
||||
if (location == undefined) {
|
||||
throw new Error(`Fatal: unkown name: ${name}`)
|
||||
}
|
||||
u.setUniform(this.#gl, location)
|
||||
}
|
||||
}
|
||||
87
node_modules/rubiks-js/src/ui/ray.js
generated
vendored
Normal file
87
node_modules/rubiks-js/src/ui/ray.js
generated
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
import {V3, V4} from '../math/vector'
|
||||
|
||||
export class Ray {
|
||||
/** @type {V3} */
|
||||
#origin
|
||||
/** @type {V3} */
|
||||
#direction
|
||||
|
||||
/**
|
||||
* @param {import('./camera').Camera} camera
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {number} width
|
||||
* @param {number} height
|
||||
*/
|
||||
constructor(camera, x, y, width, height) {
|
||||
const u = (x + .5) / width * 2 - 1
|
||||
const v = (height - y + .5) / height * 2 - 1
|
||||
this.#origin = camera.cameraToWorldMatrix.mult(new V4(0, 0, 0, 1)).toV3()
|
||||
|
||||
const d1 = camera.projectionMatrixInverse.mult(new V4(u, v, 0, 1))
|
||||
const d2 = camera.cameraToWorldMatrix.mult(new V4(d1.x, d1.y, d1.z, 0))
|
||||
this.#direction = d2.toV3().normalized
|
||||
}
|
||||
|
||||
/** @param {import('./rubiks').Rubiks} rubiks */
|
||||
intersectRubiks(rubiks) {
|
||||
return rubiks.cubies.map(cubie => this.#intersectCube(cubie)).flat(1)
|
||||
}
|
||||
|
||||
/** @param {import('./cubie').Cubie} cubie */
|
||||
#intersectCube(cubie) {
|
||||
return cubie.facelets.reduce((
|
||||
/** @type {{facelet: import('./facelet').Facelet, d: number}[]} */ acc,
|
||||
/** @type {import('./facelet').Facelet} */ facelet
|
||||
) => {
|
||||
const hit = this.intersectFacelet(facelet)
|
||||
if (hit.inside) {
|
||||
acc.push({facelet: hit.facelet, d: hit.d})
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('./facelet').Facelet} facelet
|
||||
* @returns {{
|
||||
* inside: false
|
||||
* } | {
|
||||
* inside: true,
|
||||
* facelet: import('./facelet').Facelet,
|
||||
* d: number
|
||||
* }}
|
||||
*/
|
||||
intersectFacelet(facelet) {
|
||||
const {normal, top, left, topLeft, bottomRight} = facelet.transform
|
||||
|
||||
const denom = this.#direction.dot(normal)
|
||||
if (denom === 0) {
|
||||
return {inside: false}
|
||||
}
|
||||
|
||||
const d = topLeft.sub(this.#origin).dot(normal) / denom
|
||||
const intersection = this.#origin.add(this.#direction.scale(d))
|
||||
const fromTopLeft = intersection.sub(topLeft).normalized
|
||||
const fromBottomRight = intersection.sub(bottomRight).normalized
|
||||
|
||||
const dot1 = fromTopLeft.dot(left)
|
||||
const dot2 = fromTopLeft.dot(top)
|
||||
const dot3 = fromBottomRight.dot(left.negate)
|
||||
const dot4 = fromBottomRight.dot(top.negate)
|
||||
const inside = dot1 <= 1 && dot1 >= 0
|
||||
&& dot2 <= 1 && dot2 >= 0
|
||||
&& dot3 <= 1 && dot3 >= 0
|
||||
&& dot4 <= 1 && dot4 >= 0
|
||||
|
||||
if (!inside) {
|
||||
return {inside: false}
|
||||
}
|
||||
|
||||
return {
|
||||
inside,
|
||||
facelet,
|
||||
d
|
||||
}
|
||||
}
|
||||
}
|
||||
386
node_modules/rubiks-js/src/ui/rubiks.js
generated
vendored
Normal file
386
node_modules/rubiks-js/src/ui/rubiks.js
generated
vendored
Normal file
@@ -0,0 +1,386 @@
|
||||
import {V3} from '../math/vector'
|
||||
import {Quaternion} from '../math/quarternion'
|
||||
import {lerp, mod} from '../math/utils'
|
||||
|
||||
import {Cubie} from './cubie'
|
||||
import {Transform} from './transform'
|
||||
import {uvsTransformerPresets, sidesShiftMapper} from './uvs'
|
||||
|
||||
// magic values
|
||||
const rotationAxis = [
|
||||
V3.getRotationAxis(0),
|
||||
V3.getRotationAxis(1),
|
||||
V3.getRotationAxis(2)
|
||||
]
|
||||
|
||||
/**
|
||||
* `cubeRotationOnMiddle[axis][side]`
|
||||
* @type {Record<number, Record<number, number>>}
|
||||
*/
|
||||
const cubeRotationOnMiddle = {
|
||||
0: {
|
||||
2: 90, 3: -90,
|
||||
4: -90, 5: 90
|
||||
},
|
||||
1: {
|
||||
0: -90, 1: 90,
|
||||
4: 90, 5: -90
|
||||
},
|
||||
2: {
|
||||
0: 90, 1: -90,
|
||||
2: -90, 3: 90
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `uvsTransformers[axis][side][angle]`
|
||||
* @type {Record<number, Record<number, Record<number, (uvs: number[]) => number[]>>>}
|
||||
*/
|
||||
const uvsTransformers = {
|
||||
0: {
|
||||
0: uvsTransformerPresets.rcR2Rcc,
|
||||
1: uvsTransformerPresets.rcR2Rcc,
|
||||
2: uvsTransformerPresets.flipV23,
|
||||
3: uvsTransformerPresets.flipV23,
|
||||
4: uvsTransformerPresets.flipV12,
|
||||
5: uvsTransformerPresets.flipV12
|
||||
},
|
||||
1: {
|
||||
0: uvsTransformerPresets.rcfhFvRcc,
|
||||
1: uvsTransformerPresets.rcfhFvRcc,
|
||||
2: uvsTransformerPresets.rcR2Rcc,
|
||||
3: uvsTransformerPresets.rcR2Rcc,
|
||||
4: uvsTransformerPresets.rcFhRcfh,
|
||||
5: uvsTransformerPresets.rcFhRcfh
|
||||
},
|
||||
2: {
|
||||
0: uvsTransformerPresets.flipH23,
|
||||
1: uvsTransformerPresets.flipH23,
|
||||
2: uvsTransformerPresets.flipH12,
|
||||
3: uvsTransformerPresets.flipH12,
|
||||
4: uvsTransformerPresets.rcR2Rcc,
|
||||
5: uvsTransformerPresets.rcR2Rcc
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {number} axis
|
||||
* @param {number} side
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const shouldInvertAngle = (axis, side) => {
|
||||
return (
|
||||
side === 0 ||
|
||||
side === 2 && axis === 0 ||
|
||||
side === 3 && axis === 2 ||
|
||||
side === 5
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @extends {Transform<Cubie, null>}
|
||||
*/
|
||||
export class RubiksTransform extends Transform {
|
||||
/** @type {[V3, V3, V3]} */
|
||||
rotationAxis
|
||||
|
||||
/**
|
||||
* @param {V3} position
|
||||
* @param {Quaternion} rotation
|
||||
*/
|
||||
constructor(position, rotation) {
|
||||
super(position, rotation, null)
|
||||
this.setTransforms()
|
||||
}
|
||||
|
||||
/** @property */
|
||||
setTransforms() {
|
||||
super.setTransforms()
|
||||
|
||||
this.rotationAxis = /** @type {[V3, V3, V3]} */ (rotationAxis
|
||||
.map(axis => {
|
||||
return this.apply(axis)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
/** @typedef {(event: import('../types').AIA) => void} TurnCallback */
|
||||
|
||||
export class Rubiks {
|
||||
/** @type {RubiksTransform} */
|
||||
transform
|
||||
/** @type {Cubie[]} */
|
||||
cubies
|
||||
/** @type {TurnCallback} */
|
||||
#turnCallback
|
||||
|
||||
/**
|
||||
* @param {Quaternion} rotation
|
||||
* @param {number[][][]} uvs
|
||||
* @param {V3[]} hoveringColors
|
||||
* @param {TurnCallback} turnCallback
|
||||
*/
|
||||
constructor(rotation, uvs, hoveringColors, turnCallback) {
|
||||
this.#turnCallback = turnCallback
|
||||
this.transform = new RubiksTransform(V3.zero, rotation)
|
||||
|
||||
this.cubies = []
|
||||
|
||||
for (let i = 0; i < 27; i++) {
|
||||
const cubie = new Cubie(
|
||||
i,
|
||||
uvs,
|
||||
hoveringColors,
|
||||
this
|
||||
)
|
||||
this.cubies.push(cubie)
|
||||
this.transform.children.push(cubie)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('./program').Program} program
|
||||
* @param {WebGL2RenderingContext} gl
|
||||
* @param {WebGLBuffer} uvsVbo
|
||||
*/
|
||||
render(program, gl, uvsVbo) {
|
||||
this.cubies.forEach(cubie => cubie.render(program, gl, uvsVbo))
|
||||
}
|
||||
|
||||
/** @type {[number, number][]} */
|
||||
static #axisDeltasMap = [
|
||||
[3, 9],
|
||||
[1, 9],
|
||||
[1, 3]
|
||||
]
|
||||
/**
|
||||
* @param {number} axis
|
||||
* @param {number} index
|
||||
* @returns {Cubie[]}
|
||||
*/
|
||||
getPlane(axis, index) {
|
||||
const [d1, d2] = Rubiks.#axisDeltasMap[axis]
|
||||
const initial = Math.pow(3, axis) * index
|
||||
|
||||
/** @type {Cubie[]} */
|
||||
const cubies = []
|
||||
for (let i = 0; i < 3; i++) {
|
||||
for (let j = 0; j < 3; j++) {
|
||||
const index = initial + i * d1 + j * d2
|
||||
const cubie = this.cubies[index]
|
||||
cubies.push(cubie)
|
||||
}
|
||||
}
|
||||
return cubies
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} axis
|
||||
* @param {number} index
|
||||
* @param {number} angle
|
||||
* @param {number} side
|
||||
* @param {Cubie[]} plane
|
||||
*/
|
||||
#turn(axis, index, angle, side, plane) {
|
||||
angle = mod(angle, 4)
|
||||
if (angle === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
if (index === 1) {
|
||||
this.#turn(axis, 0, -angle, side, this.getPlane(axis, 0))
|
||||
this.#turn(axis, 2, -angle, side, this.getPlane(axis, 2))
|
||||
this.transform.rotate(this.transform.rotationAxis[axis], angle * cubeRotationOnMiddle[axis][side])
|
||||
return
|
||||
}
|
||||
|
||||
if (shouldInvertAngle(axis, side)) {
|
||||
angle = mod(-angle, 4)
|
||||
}
|
||||
|
||||
this.#swapOuterFacelets(plane, axis, angle)
|
||||
this.#swapInnerFacelets(plane, axis, index, angle)
|
||||
|
||||
this.#turnCallback({axis, index, angle})
|
||||
}
|
||||
/**
|
||||
* @param {Cubie[]} plane
|
||||
* @param {number} axis
|
||||
* @param {number} angle
|
||||
*/
|
||||
#swapOuterFacelets(plane, axis, angle) {
|
||||
const [s0, s1, s2, s3] = sidesShiftMapper[axis]
|
||||
|
||||
const facelets = [
|
||||
plane[0].getFaceletOfSide(s0),
|
||||
plane[1].getFaceletOfSide(s0),
|
||||
plane[2].getFaceletOfSide(s0),
|
||||
plane[2].getFaceletOfSide(s1),
|
||||
plane[5].getFaceletOfSide(s1),
|
||||
plane[8].getFaceletOfSide(s1),
|
||||
plane[8].getFaceletOfSide(s2),
|
||||
plane[7].getFaceletOfSide(s2),
|
||||
plane[6].getFaceletOfSide(s2),
|
||||
plane[6].getFaceletOfSide(s3),
|
||||
plane[3].getFaceletOfSide(s3),
|
||||
plane[0].getFaceletOfSide(s3)
|
||||
]
|
||||
|
||||
/** @type {number[][]} */
|
||||
const shiftedUvs = []
|
||||
|
||||
for (let index = 0; index < 12; index++) {
|
||||
const shiftedIndex = mod(index + 3 * angle, 12)
|
||||
const {uvs} = facelets[shiftedIndex]
|
||||
|
||||
const facelet = facelets[index]
|
||||
const transformer = uvsTransformers[axis][facelet.side][angle]
|
||||
shiftedUvs[index] = transformer(uvs)
|
||||
}
|
||||
|
||||
for (let index = 0; index < 12; index++) {
|
||||
facelets[index].uvs = shiftedUvs[index]
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {Cubie[]} plane
|
||||
* @param {number} axis
|
||||
* @param {number} index
|
||||
* @param {number} angle
|
||||
*/
|
||||
#swapInnerFacelets(plane, axis, index, angle) {
|
||||
const side = axis * 2 + Math.sign(index)
|
||||
|
||||
const facelets = [
|
||||
plane[0].getFaceletOfSide(side),
|
||||
plane[1].getFaceletOfSide(side),
|
||||
plane[2].getFaceletOfSide(side),
|
||||
plane[5].getFaceletOfSide(side),
|
||||
plane[8].getFaceletOfSide(side),
|
||||
plane[7].getFaceletOfSide(side),
|
||||
plane[6].getFaceletOfSide(side),
|
||||
plane[3].getFaceletOfSide(side)
|
||||
]
|
||||
|
||||
/** @type {number[][]} */
|
||||
const shiftedUvs = []
|
||||
const transformer = uvsTransformers[axis][side][angle]
|
||||
|
||||
for (let index = 0; index < 8; index++) {
|
||||
const shiftedIndex = mod(index + 2 * angle, 8)
|
||||
const {uvs} = facelets[shiftedIndex]
|
||||
shiftedUvs[index] = transformer(uvs)
|
||||
}
|
||||
|
||||
for (let index = 0; index < 8; index++) {
|
||||
facelets[index].uvs = shiftedUvs[index]
|
||||
}
|
||||
|
||||
const center = plane[4].getFaceletOfSide(side)
|
||||
center.uvs = transformer(center.uvs)
|
||||
}
|
||||
|
||||
// manual rotation
|
||||
|
||||
/** @type {Array<{cubie: Cubie, backupPosition: V3, backupRotation: Quaternion, directionFromCenter: V3}>} */
|
||||
#rotatingCubies = []
|
||||
#rotationAxis = V3.zero
|
||||
#rotationCenter = V3.zero
|
||||
#currentAngle = 0
|
||||
|
||||
/**
|
||||
* @param {Cubie[]} cubies
|
||||
* @param {V3} axis
|
||||
*/
|
||||
startRotation(cubies, axis) {
|
||||
this.#rotationAxis = axis
|
||||
this.#rotationCenter = cubies[4].transform.position
|
||||
this.#rotatingCubies = cubies.map(cubie => {
|
||||
return {
|
||||
cubie,
|
||||
backupPosition: cubie.transform.position,
|
||||
backupRotation: cubie.transform.rotation,
|
||||
directionFromCenter: cubie.transform.position.sub(this.#rotationCenter)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** @param {number} angle */
|
||||
rotateManual(angle) {
|
||||
const rotation = Quaternion.fromAngle(this.#rotationAxis, -angle)
|
||||
this.#currentAngle = angle
|
||||
this.#rotatingCubies.forEach(({cubie, backupRotation, directionFromCenter}) => {
|
||||
const rotatedDirectionFromCenter = rotation.rotate(directionFromCenter)
|
||||
const newPosition = this.#rotationCenter.add(rotatedDirectionFromCenter)
|
||||
cubie.transform.position = newPosition
|
||||
cubie.transform.rotation = backupRotation
|
||||
cubie.transform.rotate(this.#rotationAxis, angle)
|
||||
})
|
||||
}
|
||||
|
||||
// automatic rotation
|
||||
#initialAngle = 0
|
||||
#rotatingAutomatic = false
|
||||
#turnProgress = 0
|
||||
#turnSpeed = 4
|
||||
#targetAngle = 0
|
||||
#rotationIndex = 0
|
||||
#rotationAxisIndex = 0
|
||||
#side = 0
|
||||
|
||||
/**
|
||||
* @param {number} axis
|
||||
* @param {number} index
|
||||
* @param {number} angle
|
||||
* @param {number} side
|
||||
*/
|
||||
finishRotation(axis, index, angle, side) {
|
||||
this.#rotatingAutomatic = true
|
||||
this.#turnProgress = 0
|
||||
this.#targetAngle = angle
|
||||
this.#initialAngle = this.#currentAngle
|
||||
this.#rotationIndex = index
|
||||
this.#rotationAxisIndex = axis
|
||||
this.#side = side
|
||||
}
|
||||
|
||||
/** @param {number} delta */
|
||||
update(delta) {
|
||||
if (!this.#rotatingAutomatic) {
|
||||
return
|
||||
}
|
||||
|
||||
this.#turnProgress += delta * this.#turnSpeed
|
||||
if (this.#turnProgress >= 1) {
|
||||
this.#rotatingAutomatic = false
|
||||
this.#rotatingCubies.forEach(({cubie, backupPosition, backupRotation}) => {
|
||||
cubie.transform.position = backupPosition
|
||||
cubie.transform.rotation = backupRotation
|
||||
})
|
||||
this.#turn(
|
||||
this.#rotationAxisIndex,
|
||||
this.#rotationIndex,
|
||||
this.#targetAngle,
|
||||
this.#side,
|
||||
this.#rotatingCubies.map(({cubie}) => cubie)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const currentAngle = lerp(this.#initialAngle, this.#targetAngle * 90, this.#turnProgress)
|
||||
const rotation = Quaternion.fromAngle(this.#rotationAxis, -currentAngle)
|
||||
this.#rotatingCubies.forEach(({cubie, backupRotation, directionFromCenter}) => {
|
||||
cubie.transform.rotation = backupRotation.mult(Quaternion.fromAngle(this.#rotationAxis, currentAngle))
|
||||
const rotatedDirectionFromCenter = rotation.rotate(directionFromCenter)
|
||||
const newPosition = this.#rotationCenter.add(rotatedDirectionFromCenter)
|
||||
cubie.transform.position = newPosition
|
||||
})
|
||||
}
|
||||
|
||||
get isTurning() {
|
||||
return this.#rotatingAutomatic
|
||||
}
|
||||
|
||||
}
|
||||
95
node_modules/rubiks-js/src/ui/transform.js
generated
vendored
Normal file
95
node_modules/rubiks-js/src/ui/transform.js
generated
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
import {Quaternion} from '../math/quarternion'
|
||||
import {V4} from '../math/vector'
|
||||
|
||||
/**
|
||||
* @typedef {{transform: Transform<any, any>}} WithTransform
|
||||
* @typedef {import('../math/matrix').M44} M44
|
||||
* @typedef {import('../math/vector').V3} V3
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {WithTransform} TChild
|
||||
* @template {WithTransform | null} TParent
|
||||
*/
|
||||
export class Transform {
|
||||
/** @type {M44} */
|
||||
localTransform
|
||||
/** @type {M44} */
|
||||
globalTransform
|
||||
|
||||
/** @type {TChild[]} */
|
||||
children = []
|
||||
|
||||
/**
|
||||
* @param {V3} position
|
||||
* @param {Quaternion} rotation
|
||||
* @param {TParent} parent
|
||||
*/
|
||||
constructor(position, rotation, parent) {
|
||||
/** @protected */
|
||||
this._position = position
|
||||
/** @protected */
|
||||
this._rotation = rotation
|
||||
this.parent = parent
|
||||
this.setTransforms()
|
||||
}
|
||||
|
||||
/** @protected */
|
||||
setTransforms() {
|
||||
const localTransform = Transform.getLocalTransform(this._position, this._rotation)
|
||||
this.localTransform = localTransform
|
||||
|
||||
if (!this.parent) {
|
||||
this.globalTransform = localTransform
|
||||
} else {
|
||||
const parentTransform = this.parent.transform.globalTransform
|
||||
this.globalTransform = parentTransform.mult(localTransform)
|
||||
}
|
||||
|
||||
this.children.forEach(child => child.transform.setTransforms())
|
||||
}
|
||||
|
||||
/** @param {V3} v */
|
||||
apply({x, y, z}) {
|
||||
return this.globalTransform.mult(new V4(x, y, z, 1)).toV3()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {V3} axis
|
||||
* @param {number} angle
|
||||
*/
|
||||
rotate(axis, angle) {
|
||||
this.rotation = this._rotation.mult(Quaternion.fromAngle(this._rotation.rotate(axis), angle))
|
||||
}
|
||||
|
||||
/** @param {V3} value */
|
||||
set position(value) {
|
||||
this._position = value
|
||||
this.setTransforms()
|
||||
}
|
||||
/** @param {Quaternion} value */
|
||||
set rotation(value) {
|
||||
this._rotation = value
|
||||
this.setTransforms()
|
||||
}
|
||||
|
||||
get position() {
|
||||
return this._position
|
||||
}
|
||||
get rotation() {
|
||||
return this._rotation
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {V3} position
|
||||
* @param {Quaternion} rotation
|
||||
* @returns {M44}
|
||||
*/
|
||||
static getLocalTransform({x, y, z}, rotation) {
|
||||
const transform = rotation.matrix
|
||||
transform.r1.w = x
|
||||
transform.r2.w = y
|
||||
transform.r3.w = z
|
||||
return transform
|
||||
}
|
||||
}
|
||||
94
node_modules/rubiks-js/src/ui/uvs.js
generated
vendored
Normal file
94
node_modules/rubiks-js/src/ui/uvs.js
generated
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
/** @satisfies {Record<string, (uvs: number[]) => number[]>} */
|
||||
const uvsTransformers = {
|
||||
identity: uvs => [...uvs],
|
||||
flipH: uvs => [uvs[6], uvs[7], uvs[4], uvs[5], uvs[2], uvs[3], uvs[0], uvs[1]],
|
||||
flipV: uvs => [uvs[2], uvs[3], uvs[0], uvs[1], uvs[6], uvs[7], uvs[4], uvs[5]],
|
||||
rotateCC: uvs => [uvs[6], uvs[7], uvs[0], uvs[1], uvs[2], uvs[3], uvs[4], uvs[5]],
|
||||
rotateC: uvs => [uvs[2], uvs[3], uvs[4], uvs[5], uvs[6], uvs[7], uvs[0], uvs[1]],
|
||||
rotate2: uvs => [uvs[4], uvs[5], uvs[6], uvs[7], uvs[0], uvs[1], uvs[2], uvs[3]],
|
||||
rotateCFlipH: uvs => [uvs[0], uvs[1], uvs[6], uvs[7], uvs[4], uvs[5], uvs[2], uvs[3]]
|
||||
}
|
||||
/** @satisfies {Record<string, Record<number, (uvs: number[]) => number[]>>} */
|
||||
export const uvsTransformerPresets = {
|
||||
flipV12: {
|
||||
1: uvsTransformers.flipV,
|
||||
2: uvsTransformers.flipV,
|
||||
3: uvsTransformers.identity
|
||||
},
|
||||
flipV23: {
|
||||
1: uvsTransformers.identity,
|
||||
2: uvsTransformers.flipV,
|
||||
3: uvsTransformers.flipV
|
||||
},
|
||||
flipH12: {
|
||||
1: uvsTransformers.flipH,
|
||||
2: uvsTransformers.flipH,
|
||||
3: uvsTransformers.identity
|
||||
},
|
||||
flipH23: {
|
||||
1: uvsTransformers.identity,
|
||||
2: uvsTransformers.flipH,
|
||||
3: uvsTransformers.flipH
|
||||
},
|
||||
rcfhFvRcc: {
|
||||
1: uvsTransformers.rotateCFlipH,
|
||||
2: uvsTransformers.flipV,
|
||||
3: uvsTransformers.rotateCC
|
||||
},
|
||||
rcFvRcfh: {
|
||||
1: uvsTransformers.rotateC,
|
||||
2: uvsTransformers.flipV,
|
||||
3: uvsTransformers.rotateCFlipH
|
||||
},
|
||||
rcFhRcfh: {
|
||||
1: uvsTransformers.rotateC,
|
||||
2: uvsTransformers.flipH,
|
||||
3: uvsTransformers.rotateCFlipH
|
||||
},
|
||||
rcfhFhRcc: {
|
||||
1: uvsTransformers.rotateCFlipH,
|
||||
2: uvsTransformers.flipH,
|
||||
3: uvsTransformers.rotateCC
|
||||
},
|
||||
rcR2Rcc: {
|
||||
1: uvsTransformers.rotateC,
|
||||
2: uvsTransformers.rotate2,
|
||||
3: uvsTransformers.rotateCC,
|
||||
},
|
||||
rccR2Rc: {
|
||||
1: uvsTransformers.rotateCC,
|
||||
2: uvsTransformers.rotate2,
|
||||
3: uvsTransformers.rotateC,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* `cubiesShiftMapper[axis][index]`
|
||||
* @type {Record<number, Record<number, number[]>>}
|
||||
*/
|
||||
export const cubiesShiftMapper = {
|
||||
0: {
|
||||
0: [0, 3, 6, 15, 24, 21, 18, 9],
|
||||
2: [2, 5, 8, 17, 26, 23, 20, 11]
|
||||
},
|
||||
1: {
|
||||
0: [0, 1, 2, 11, 20, 19, 18, 9],
|
||||
2: [6, 7, 8, 17, 26, 25, 24, 15]
|
||||
},
|
||||
2: {
|
||||
0: [0, 1, 2, 5, 8, 7, 6, 3],
|
||||
2: [18, 19, 20, 23, 26, 25, 24, 21]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `sidesShiftMapper[axis]`
|
||||
* @type {[number, number, number, number][]}
|
||||
*/
|
||||
export const sidesShiftMapper = [
|
||||
[2, 5, 3, 4],
|
||||
[0, 5, 1, 4],
|
||||
[0, 3, 1, 2]
|
||||
]
|
||||
16
node_modules/rubiks-js/tsconfig.json
generated
vendored
Normal file
16
node_modules/rubiks-js/tsconfig.json
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"moduleResolution": "Node",
|
||||
"strict": true,
|
||||
"resolveJsonModule": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"checkJs": true,
|
||||
"strictPropertyInitialization": false,
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
Reference in New Issue
Block a user