@houstonp/rubiks-cube 2.1.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +304 -77
- package/package.json +18 -8
- package/src/{core.js → core/index.js} +72 -41
- package/src/rubiksCube/index.js +3 -0
- package/src/rubiksCube/rubiksCubeController.js +111 -0
- package/src/{three → rubiksCube3D}/centerPiece.js +37 -2
- package/src/{three → rubiksCube3D}/cornerPiece.js +56 -2
- package/src/rubiksCube3D/cubeConfig.js +87 -0
- package/src/rubiksCube3D/cubeSettings.js +30 -0
- package/src/{three → rubiksCube3D}/edgePiece.js +2 -1
- package/src/rubiksCube3D/index.js +3 -0
- package/src/rubiksCube3D/rubiksCube3D.js +383 -0
- package/src/{three → rubiksCube3D}/sticker.js +5 -4
- package/src/state/index.js +4 -0
- package/src/state/rubiksCubeState.js +471 -0
- package/src/state/slice.js +236 -0
- package/src/state/stickerState.js +185 -0
- package/src/{camera → webComponent}/cameraState.js +17 -25
- package/src/webComponent/constants.js +67 -0
- package/src/webComponent/index.js +7 -0
- package/src/webComponent/rubiksCubeElement.js +379 -0
- package/src/{settings.js → webComponent/settings.js} +36 -23
- package/tests/common.js +3 -20
- package/tests/core.test.js +56 -0
- package/tests/rubiksCube.solves.test.js +41 -0
- package/tests/rubiksCube3D.solves.test.js +185 -0
- package/tests/rubiksCubeState.solves.test.js +35 -0
- package/tests/testScrambles.js +194 -0
- package/types/{core.d.ts → core/index.d.ts} +45 -48
- package/types/rubiksCube/index.d.ts +3 -0
- package/types/rubiksCube/rubiksCubeController.d.ts +62 -0
- package/types/rubiksCube3D/centerPiece.d.ts +27 -0
- package/types/rubiksCube3D/cornerPiece.d.ts +38 -0
- package/types/rubiksCube3D/cubeConfig.d.ts +32 -0
- package/types/rubiksCube3D/cubeSettings.d.ts +33 -0
- package/types/{three → rubiksCube3D}/edgePiece.d.ts +5 -3
- package/types/rubiksCube3D/index.d.ts +3 -0
- package/types/rubiksCube3D/rubiksCube3D.d.ts +120 -0
- package/types/rubiksCube3D/sticker.d.ts +18 -0
- package/types/state/index.d.ts +5 -0
- package/types/state/rubiksCubeState.d.ts +108 -0
- package/types/state/slice.d.ts +46 -0
- package/types/state/stickerState.d.ts +34 -0
- package/types/webComponent/cameraState.d.ts +22 -0
- package/types/webComponent/constants.d.ts +57 -0
- package/types/webComponent/index.d.ts +6 -0
- package/types/webComponent/rubiksCubeElement.d.ts +89 -0
- package/types/{settings.d.ts → webComponent/settings.d.ts} +5 -8
- package/src/cube/animationSlice.js +0 -205
- package/src/cube/animationState.js +0 -96
- package/src/cube/cubeSettings.js +0 -19
- package/src/cube/cubeState.js +0 -337
- package/src/cube/stickerState.js +0 -188
- package/src/index.js +0 -621
- package/src/three/cube.js +0 -492
- package/tests/cube.five.test.js +0 -126
- package/tests/cube.four.test.js +0 -126
- package/tests/cube.seven.test.js +0 -126
- package/tests/cube.six.test.js +0 -126
- package/tests/cube.three.test.js +0 -151
- package/tests/cube.two.test.js +0 -125
- package/types/camera/cameraState.d.ts +0 -19
- package/types/cube/animationSlice.d.ts +0 -26
- package/types/cube/animationState.d.ts +0 -41
- package/types/cube/cubeSettings.d.ts +0 -17
- package/types/cube/cubeState.d.ts +0 -47
- package/types/cube/stickerState.d.ts +0 -21
- package/types/index.d.ts +0 -87
- package/types/three/centerPiece.d.ts +0 -15
- package/types/three/cornerPiece.d.ts +0 -24
- package/types/three/cube.d.ts +0 -130
- package/types/three/sticker.d.ts +0 -15
- /package/src/{debouncer.js → webComponent/debouncer.js} +0 -0
- /package/src/{globals.ts → webComponent/globals.ts} +0 -0
- /package/types/{debouncer.d.ts → webComponent/debouncer.d.ts} +0 -0
- /package/types/{globals.d.ts → webComponent/globals.d.ts} +0 -0
|
@@ -1,4 +1,36 @@
|
|
|
1
1
|
// @ts-check
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* reverses the direction of a movement or rotation
|
|
5
|
+
* @template {Movement | Rotation} T
|
|
6
|
+
* @param {T} action
|
|
7
|
+
* @returns {T}
|
|
8
|
+
* */
|
|
9
|
+
export function reverse(action) {
|
|
10
|
+
let reversedAction = action;
|
|
11
|
+
if (action.at(-1) === "'") {
|
|
12
|
+
reversedAction = /** @type {T} */ (action.slice(0, -1));
|
|
13
|
+
} else {
|
|
14
|
+
const newAction = /** @type {T} */ (action + "'");
|
|
15
|
+
reversedAction = newAction;
|
|
16
|
+
}
|
|
17
|
+
return reversedAction;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Translates notation meant for a 3x3 into notation a big cube. This is so that 3x3 algorithms can be used on a big cube if desired. eg. for a 6x6 r -> 5r
|
|
22
|
+
* @template {Movement | Rotation} T
|
|
23
|
+
* @param {T} action
|
|
24
|
+
* @param {CubeType} cubeType
|
|
25
|
+
* @returns {T}
|
|
26
|
+
* */
|
|
27
|
+
export function translate(action, cubeType) {
|
|
28
|
+
if (Object.values(Movements.Wide).includes(/** @type {WideMove} **/ (action))) {
|
|
29
|
+
return /** @type {T} */ (LayerCount[cubeType] - 1 + action);
|
|
30
|
+
}
|
|
31
|
+
return action;
|
|
32
|
+
}
|
|
33
|
+
|
|
2
34
|
/**
|
|
3
35
|
* @typedef {typeof Movements.Single[keyof typeof Movements.Single]} SingleMove
|
|
4
36
|
* @typedef {typeof Movements.Wide[keyof typeof Movements.Wide]} WideMove
|
|
@@ -71,7 +103,7 @@ export const Movements = Object.freeze({
|
|
|
71
103
|
u2: 'u2',
|
|
72
104
|
uP: "u'",
|
|
73
105
|
Dw: 'Dw',
|
|
74
|
-
Dw2: '
|
|
106
|
+
Dw2: 'Dw2',
|
|
75
107
|
DwP: "Dw'",
|
|
76
108
|
d: 'd',
|
|
77
109
|
d2: 'd2',
|
|
@@ -357,6 +389,27 @@ export const Movements = Object.freeze({
|
|
|
357
389
|
D2: '6D2',
|
|
358
390
|
DP: "6D'",
|
|
359
391
|
}),
|
|
392
|
+
/**
|
|
393
|
+
* Build a layer-range move for big-cube notation. e.g. Movements.Range(2, 4, Movements.Wide.Rw) returns '2-4Rw',
|
|
394
|
+
* meaning "rotate layers 2 through 4 from the right face." Accepts wide moves (`Movements.Wide.*`), face
|
|
395
|
+
* moves (`Movements.Single.{R,L,U,D,F,B}` and modifiers), and slice moves (`Movements.Single.{M,E,S}` and
|
|
396
|
+
* modifiers). Already-prefixed moves (`2R`, `2-4Rw`) are rejected.
|
|
397
|
+
* @param {number} lower
|
|
398
|
+
* @param {number} upper
|
|
399
|
+
* @param {WideMove | SingleMove} baseMove
|
|
400
|
+
* @returns {Movement}
|
|
401
|
+
*/
|
|
402
|
+
Range: (lower, upper, baseMove) => {
|
|
403
|
+
if (!Number.isInteger(lower) || !Number.isInteger(upper) || lower < 1 || lower >= upper || upper > 7) {
|
|
404
|
+
throw new Error(`Invalid layer range [${lower}-${upper}]: require integers with 1 <= lower < upper <= 7`);
|
|
405
|
+
}
|
|
406
|
+
const move = /** @type {Movement} */ (`${lower}-${upper}${baseMove}`);
|
|
407
|
+
if (!isMovement(move)) {
|
|
408
|
+
throw new Error(`Invalid range movement: ${move}`);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return move;
|
|
412
|
+
},
|
|
360
413
|
});
|
|
361
414
|
|
|
362
415
|
/**
|
|
@@ -398,50 +451,28 @@ export const CubeTypes = Object.freeze({
|
|
|
398
451
|
Seven: 'Seven',
|
|
399
452
|
});
|
|
400
453
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
Match: 'match',
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
/**
|
|
412
|
-
* @typedef {typeof FaceColours [keyof typeof FaceColours]} FaceColour
|
|
413
|
-
*/
|
|
414
|
-
export const FaceColours = Object.freeze({
|
|
415
|
-
U: 'white',
|
|
416
|
-
D: 'yellow',
|
|
417
|
-
L: '#fc9a05',
|
|
418
|
-
R: 'red',
|
|
419
|
-
F: '#2cbf13',
|
|
420
|
-
B: 'blue',
|
|
454
|
+
export const LayerCount = Object.freeze({
|
|
455
|
+
[CubeTypes.Two]: 2,
|
|
456
|
+
[CubeTypes.Three]: 3,
|
|
457
|
+
[CubeTypes.Four]: 4,
|
|
458
|
+
[CubeTypes.Five]: 5,
|
|
459
|
+
[CubeTypes.Six]: 6,
|
|
460
|
+
[CubeTypes.Seven]: 7,
|
|
421
461
|
});
|
|
422
462
|
|
|
423
463
|
/**
|
|
424
|
-
*
|
|
464
|
+
*
|
|
465
|
+
* @param {string} rotation
|
|
425
466
|
*/
|
|
426
|
-
export
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
Right: 'right',
|
|
430
|
-
Left: 'left',
|
|
431
|
-
Up: 'up',
|
|
432
|
-
Down: 'down',
|
|
433
|
-
RightUp: 'rightUp',
|
|
434
|
-
RightDown: 'rightDown',
|
|
435
|
-
LeftUp: 'leftUp',
|
|
436
|
-
LeftDown: 'leftDown',
|
|
437
|
-
});
|
|
467
|
+
export function IsRotation(rotation) {
|
|
468
|
+
return /^([xyz])(\d)?(\')?$/.test(rotation);
|
|
469
|
+
}
|
|
438
470
|
|
|
439
471
|
/**
|
|
440
|
-
*
|
|
472
|
+
*
|
|
473
|
+
* @param {string} movement
|
|
474
|
+
* @return {boolean}
|
|
441
475
|
*/
|
|
442
|
-
export
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
LeftUp: 'leftUp',
|
|
446
|
-
LeftDown: 'leftDown',
|
|
447
|
-
});
|
|
476
|
+
export function isMovement(movement) {
|
|
477
|
+
return /^([1234567]|[123456]-[1234567])?([RLUDFB]w|[RLUDFBMES]|[rludfbmes])([123])?(\')?$/.test(movement);
|
|
478
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/** @import {Slice} from '../state/slice' */
|
|
3
|
+
/** @import {StickerState} from '../state/stickerState' */
|
|
4
|
+
/** @import {CubeType, Movement, Rotation} from '../core' */
|
|
5
|
+
/**
|
|
6
|
+
* @typedef AnimationOptions
|
|
7
|
+
* @property {boolean} [translate]
|
|
8
|
+
* @property {number} [animationSpeedMs]
|
|
9
|
+
* @property {boolean} [reverse]
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {Object} RubiksCubeViewInterface
|
|
13
|
+
* @property {function(Slice, any=): Promise<void>} slice
|
|
14
|
+
* @property {function(StickerState): void} setState
|
|
15
|
+
* @property {function(): void} reset
|
|
16
|
+
* @property {function(CubeType): void} setType
|
|
17
|
+
**/
|
|
18
|
+
|
|
19
|
+
import { CubeTypes } from '../core';
|
|
20
|
+
import { RubiksCubeState } from '../state';
|
|
21
|
+
import { fromKociemba, toKociemba } from '../state/stickerState';
|
|
22
|
+
|
|
23
|
+
export default class RubiksCubeController {
|
|
24
|
+
/**
|
|
25
|
+
* @param {CubeType} cubeType
|
|
26
|
+
* @param {RubiksCubeViewInterface} view
|
|
27
|
+
* */
|
|
28
|
+
constructor(cubeType, view) {
|
|
29
|
+
this.state = new RubiksCubeState(cubeType);
|
|
30
|
+
this.view = view;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* @param {Movement} movement
|
|
34
|
+
* @param {AnimationOptions} [options]
|
|
35
|
+
* @returns {Promise<string>}
|
|
36
|
+
*/
|
|
37
|
+
movement(movement, options) {
|
|
38
|
+
const slice = this.state.move(movement, { reverse: options?.reverse, translate: options?.translate });
|
|
39
|
+
if (slice == null) {
|
|
40
|
+
return Promise.reject(new Error(`Invalid movement: ${movement}`));
|
|
41
|
+
}
|
|
42
|
+
return this.view.slice(slice, { animationSpeedMs: options?.animationSpeedMs }).then(() => toKociemba(this.state.getState()));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @param {Rotation} rotation
|
|
47
|
+
* @param {AnimationOptions} [options]
|
|
48
|
+
* @returns {Promise<string>}
|
|
49
|
+
*/
|
|
50
|
+
rotation(rotation, options) {
|
|
51
|
+
const slice = this.state.rotate(rotation, { reverse: options?.reverse });
|
|
52
|
+
if (slice == null) {
|
|
53
|
+
return Promise.reject(new Error(`Invalid rotation: ${rotation}`));
|
|
54
|
+
}
|
|
55
|
+
return this.view.slice(slice, { animationSpeedMs: options?.animationSpeedMs }).then(() => toKociemba(this.state.getState()));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @param {(Rotation | Movement)[]} actions
|
|
60
|
+
* @param {AnimationOptions} [options]
|
|
61
|
+
* @returns {string}
|
|
62
|
+
*/
|
|
63
|
+
do(actions, options) {
|
|
64
|
+
this.state.do(actions, { translate: options?.translate, reverse: options?.reverse });
|
|
65
|
+
this.view.setState(this.state.getState());
|
|
66
|
+
return toKociemba(this.state.getState());
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @returns {string}
|
|
71
|
+
*/
|
|
72
|
+
reset() {
|
|
73
|
+
this.state.reset();
|
|
74
|
+
this.view.reset();
|
|
75
|
+
return toKociemba(this.state.getState());
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @param {string} kociembaState
|
|
80
|
+
* @returns {boolean}
|
|
81
|
+
*/
|
|
82
|
+
setState(kociembaState) {
|
|
83
|
+
const state = fromKociemba(kociembaState);
|
|
84
|
+
if (state) {
|
|
85
|
+
this.state.setState(state);
|
|
86
|
+
this.view.setState(state);
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @returns {string}
|
|
94
|
+
*/
|
|
95
|
+
getState() {
|
|
96
|
+
return toKociemba(this.state.getState());
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @param {CubeType} cubeType
|
|
101
|
+
* @returns {string}
|
|
102
|
+
*/
|
|
103
|
+
setType(cubeType) {
|
|
104
|
+
if (!Object.values(CubeTypes).includes(cubeType)) {
|
|
105
|
+
throw new Error(`Invalid cube type: ${cubeType}`);
|
|
106
|
+
}
|
|
107
|
+
this.state = new RubiksCubeState(cubeType);
|
|
108
|
+
this.view.setType(cubeType);
|
|
109
|
+
return toKociemba(this.state.getState());
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// @ts-check
|
|
2
|
-
import { BoxGeometry, ExtrudeGeometry, Mesh, MeshBasicMaterial, Object3D } from 'three';
|
|
2
|
+
import { BoxGeometry, ExtrudeGeometry, Mesh, MeshBasicMaterial, MeshStandardMaterial, Object3D, PlaneGeometry, SRGBColorSpace, TextureLoader } from 'three';
|
|
3
3
|
import { SVGLoader } from 'three/examples/jsm/Addons.js';
|
|
4
4
|
import { Sticker } from './sticker';
|
|
5
|
+
/** @import {Vector3Like} from 'three' */
|
|
5
6
|
|
|
6
|
-
/** @typedef {{ positon:
|
|
7
|
+
/** @typedef {{ positon: Vector3Like, rotation: Vector3Like }} CenterPieceUserData */
|
|
7
8
|
|
|
8
9
|
export class CenterPiece extends Object3D {
|
|
9
10
|
constructor() {
|
|
@@ -18,11 +19,45 @@ export class CenterPiece extends Object3D {
|
|
|
18
19
|
this.frontSticker.position.set(0, 0, 0.5);
|
|
19
20
|
this.frontSticker.rotation.set(0, 0, 0);
|
|
20
21
|
this.add(this.frontSticker);
|
|
22
|
+
|
|
23
|
+
/** @type {Mesh<PlaneGeometry, MeshStandardMaterial> | null} */
|
|
24
|
+
this.logo = null;
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
get stickers() {
|
|
24
28
|
return [this.frontSticker];
|
|
25
29
|
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {string} logoPath
|
|
33
|
+
*/
|
|
34
|
+
addLogo(logoPath) {
|
|
35
|
+
this.removeLogo();
|
|
36
|
+
const material = new MeshStandardMaterial({
|
|
37
|
+
transparent: true,
|
|
38
|
+
color: 'white',
|
|
39
|
+
metalness: 0,
|
|
40
|
+
roughness: 0.4,
|
|
41
|
+
});
|
|
42
|
+
const mesh = new Mesh(new PlaneGeometry(0.9, 0.9), material);
|
|
43
|
+
mesh.position.set(0, 0, 0.54);
|
|
44
|
+
this.logo = mesh;
|
|
45
|
+
this.add(mesh);
|
|
46
|
+
const texture = new TextureLoader().load(logoPath, (texture) => {
|
|
47
|
+
texture.colorSpace = SRGBColorSpace;
|
|
48
|
+
material.map = texture;
|
|
49
|
+
material.needsUpdate = true;
|
|
50
|
+
texture.anisotropy = 16;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
removeLogo() {
|
|
55
|
+
if (!this.logo) return;
|
|
56
|
+
this.remove(this.logo);
|
|
57
|
+
this.logo.material.map?.dispose();
|
|
58
|
+
this.logo.material.dispose();
|
|
59
|
+
this.logo = null;
|
|
60
|
+
}
|
|
26
61
|
}
|
|
27
62
|
|
|
28
63
|
const loader = new SVGLoader();
|
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
/// @ts-check
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
BoxGeometry,
|
|
4
|
+
ExtrudeGeometry,
|
|
5
|
+
Material,
|
|
6
|
+
Mesh,
|
|
7
|
+
MeshBasicMaterial,
|
|
8
|
+
MeshStandardMaterial,
|
|
9
|
+
Object3D,
|
|
10
|
+
PlaneGeometry,
|
|
11
|
+
SRGBColorSpace,
|
|
12
|
+
TextureLoader,
|
|
13
|
+
} from 'three';
|
|
3
14
|
import { Sticker } from './sticker';
|
|
4
15
|
import { SVGLoader } from 'three/examples/jsm/Addons.js';
|
|
16
|
+
import { Faces } from '../core';
|
|
17
|
+
/** @import {Vector3Like} from 'three' */
|
|
18
|
+
/** @import {Face} from "../core" */
|
|
5
19
|
|
|
6
|
-
/** @typedef {{ positon:
|
|
20
|
+
/** @typedef {{ positon: Vector3Like, rotation: Vector3Like }} CornerPieceUserData */
|
|
7
21
|
/**
|
|
8
22
|
* @param {Material} frontMaterial
|
|
9
23
|
* @param {Material} rightMaterial
|
|
@@ -39,6 +53,46 @@ export class CornerPiece extends Object3D {
|
|
|
39
53
|
get stickers() {
|
|
40
54
|
return [this.frontSticker, this.rightSticker, this.topSticker];
|
|
41
55
|
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @param {Face} face
|
|
59
|
+
* @param {string} logoPath
|
|
60
|
+
*/
|
|
61
|
+
addLogo(face, logoPath) {
|
|
62
|
+
this.removeLogo();
|
|
63
|
+
const material = new MeshStandardMaterial({
|
|
64
|
+
transparent: true,
|
|
65
|
+
color: 'white',
|
|
66
|
+
metalness: 0,
|
|
67
|
+
roughness: 0.4,
|
|
68
|
+
});
|
|
69
|
+
const mesh = new Mesh(new PlaneGeometry(0.9, 0.9), material);
|
|
70
|
+
if (face === Faces.U) {
|
|
71
|
+
mesh.position.set(0, 0.54, 0);
|
|
72
|
+
mesh.rotation.set(-Math.PI / 2, 0, -Math.PI / 2);
|
|
73
|
+
} else if (face === Faces.F) {
|
|
74
|
+
mesh.position.set(0, 0, 0.54);
|
|
75
|
+
} else if (face === Faces.R) {
|
|
76
|
+
mesh.position.set(0.54, 0, 0);
|
|
77
|
+
mesh.rotation.set(Math.PI / 2, Math.PI / 2, 0);
|
|
78
|
+
}
|
|
79
|
+
this.logo = mesh;
|
|
80
|
+
this.add(mesh);
|
|
81
|
+
const texture = new TextureLoader().load(logoPath, (texture) => {
|
|
82
|
+
texture.colorSpace = SRGBColorSpace;
|
|
83
|
+
material.map = texture;
|
|
84
|
+
material.needsUpdate = true;
|
|
85
|
+
texture.anisotropy = 16;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
removeLogo() {
|
|
90
|
+
if (!this.logo) return;
|
|
91
|
+
this.remove(this.logo);
|
|
92
|
+
this.logo.material.map?.dispose();
|
|
93
|
+
this.logo.material.dispose();
|
|
94
|
+
this.logo = null;
|
|
95
|
+
}
|
|
42
96
|
}
|
|
43
97
|
|
|
44
98
|
const loader = new SVGLoader();
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { CubeTypes, Faces } from '../core';
|
|
3
|
+
/** @import {CubeType, Face} from '../core' */
|
|
4
|
+
/** @import {ColorRepresentation} from 'three' */
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef CubeConfig
|
|
8
|
+
* @property {number[]} layers
|
|
9
|
+
* @property {number} pieceSize
|
|
10
|
+
* @property {number} coreSize
|
|
11
|
+
* @property {number} outerLayerMultiplier
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {CubeType} cubeType
|
|
16
|
+
* @return {CubeConfig}
|
|
17
|
+
*/
|
|
18
|
+
export function getCubeConfig(cubeType) {
|
|
19
|
+
switch (cubeType) {
|
|
20
|
+
case CubeTypes.Two:
|
|
21
|
+
return {
|
|
22
|
+
layers: [-1, 1],
|
|
23
|
+
pieceSize: 2,
|
|
24
|
+
coreSize: 1.7,
|
|
25
|
+
outerLayerMultiplier: 1,
|
|
26
|
+
};
|
|
27
|
+
break;
|
|
28
|
+
case CubeTypes.Three:
|
|
29
|
+
return {
|
|
30
|
+
layers: [-1, 0, 1],
|
|
31
|
+
pieceSize: 1,
|
|
32
|
+
coreSize: 1.32,
|
|
33
|
+
outerLayerMultiplier: 1,
|
|
34
|
+
};
|
|
35
|
+
case CubeTypes.Four:
|
|
36
|
+
return {
|
|
37
|
+
layers: [-1, -1 / 3, 1 / 3, 1],
|
|
38
|
+
pieceSize: 2 / 3,
|
|
39
|
+
coreSize: 1.25,
|
|
40
|
+
outerLayerMultiplier: 1.1,
|
|
41
|
+
};
|
|
42
|
+
case CubeTypes.Five:
|
|
43
|
+
return {
|
|
44
|
+
layers: [-1, -1 / 2, 0, 1 / 2, 1],
|
|
45
|
+
pieceSize: 1 / 2,
|
|
46
|
+
coreSize: 1.2,
|
|
47
|
+
outerLayerMultiplier: 1.2,
|
|
48
|
+
};
|
|
49
|
+
case CubeTypes.Six:
|
|
50
|
+
return {
|
|
51
|
+
layers: [-1, -3 / 5, -1 / 5, 1 / 5, 3 / 5, 1],
|
|
52
|
+
pieceSize: 2 / 5,
|
|
53
|
+
coreSize: 1.18,
|
|
54
|
+
outerLayerMultiplier: 1.3,
|
|
55
|
+
};
|
|
56
|
+
case CubeTypes.Seven:
|
|
57
|
+
return {
|
|
58
|
+
layers: [-1, -2 / 3, -1 / 3, 0, 1 / 3, 2 / 3, 1],
|
|
59
|
+
pieceSize: 1 / 3,
|
|
60
|
+
coreSize: 1.16,
|
|
61
|
+
outerLayerMultiplier: 1.35,
|
|
62
|
+
};
|
|
63
|
+
default:
|
|
64
|
+
throw new Error(`Unsupported cube type: ${cubeType}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const FaceColors = {
|
|
69
|
+
[Faces.B]: 'blue',
|
|
70
|
+
[Faces.D]: 'yellow',
|
|
71
|
+
[Faces.F]: '#2cbf13',
|
|
72
|
+
[Faces.L]: '#fc9a05',
|
|
73
|
+
[Faces.R]: 'red',
|
|
74
|
+
[Faces.U]: 'white',
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @param {ColorRepresentation} color
|
|
79
|
+
* @return {Face}
|
|
80
|
+
* */
|
|
81
|
+
export const ColorToFace = (color) => {
|
|
82
|
+
const face = Object.values(Faces).find((face) => FaceColors[face] === color);
|
|
83
|
+
if (!face) {
|
|
84
|
+
throw new Error(`Invalid color: ${color}`);
|
|
85
|
+
}
|
|
86
|
+
return face;
|
|
87
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { CubeTypes } from '../core';
|
|
3
|
+
/** @import {CubeType} from '../core' */
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef RubiksCube3DSettingsOptions
|
|
7
|
+
* @property {CubeType} [cubeType]
|
|
8
|
+
* @property {number} [pieceGap]
|
|
9
|
+
* @property {number} [animationSpeedMs]
|
|
10
|
+
* @property {gsap.EaseString | gsap.EaseFunction} [animationStyle]
|
|
11
|
+
* @property {string | null} [logo]
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export default class RubiksCube3DSettings {
|
|
15
|
+
/**
|
|
16
|
+
* @param {RubiksCube3DSettingsOptions} [options]
|
|
17
|
+
*/
|
|
18
|
+
constructor(options = {}) {
|
|
19
|
+
/** @type {CubeType} */
|
|
20
|
+
this.cubeType = options.cubeType ?? CubeTypes.Three;
|
|
21
|
+
/** @type {number} */
|
|
22
|
+
this.pieceGap = options.pieceGap ?? 1.04;
|
|
23
|
+
/** @type {number} */
|
|
24
|
+
this.animationSpeedMs = options.animationSpeedMs ?? 150;
|
|
25
|
+
/** @type {gsap.EaseString | gsap.EaseFunction} */
|
|
26
|
+
this.animationStyle = options.animationStyle ?? 'sine';
|
|
27
|
+
/** @type {string | null} */
|
|
28
|
+
this.logo = options.logo ?? null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
import { BoxGeometry, ExtrudeGeometry, Mesh, MeshBasicMaterial, Object3D } from 'three';
|
|
3
3
|
import { SVGLoader } from 'three/examples/jsm/Addons.js';
|
|
4
4
|
import { Sticker } from './sticker';
|
|
5
|
+
/** @import {Vector3Like} from 'three' */
|
|
5
6
|
|
|
6
|
-
/** @typedef {{ positon:
|
|
7
|
+
/** @typedef {{ positon: Vector3Like, rotation: Vector3Like }} EdgePieceUserData*/
|
|
7
8
|
|
|
8
9
|
export class EdgePiece extends Object3D {
|
|
9
10
|
constructor() {
|