@houstonp/rubiks-cube 1.1.1 → 1.2.1
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/.prettierrc +7 -0
- package/README.md +35 -27
- package/index.js +201 -208
- package/package.json +20 -20
- package/src/cube/cube.js +194 -0
- package/src/cube/cubeRotation.js +60 -0
- package/src/cube/cubeState.js +191 -0
- package/src/{materials.js → threejs/materials.js} +8 -8
- package/src/threejs/pieces.js +103 -0
- package/src/{stickers.js → threejs/stickers.js} +4 -4
- package/src/utils/debouncer.js +7 -0
- package/src/utils/rotation.js +53 -0
- package/src/animation.js +0 -168
- package/src/center.js +0 -23
- package/src/corner.js +0 -45
- package/src/cube.js +0 -333
- package/src/cubeState.js +0 -128
- package/src/edge.js +0 -36
- package/src/rotation.js +0 -52
package/src/cube/cube.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { Group, Vector3 } from 'three';
|
|
2
|
+
import { createCoreMesh } from '../threejs/pieces';
|
|
3
|
+
import { createCubeState } from './cubeState';
|
|
4
|
+
import { CubeRotation } from './cubeRotation';
|
|
5
|
+
|
|
6
|
+
const minimumGap = 1;
|
|
7
|
+
|
|
8
|
+
export default class Cube {
|
|
9
|
+
/**
|
|
10
|
+
* @param {{style: "exponential" | "next" | "fixed", speed: number, gap: number}} params
|
|
11
|
+
*/
|
|
12
|
+
constructor({ gap, speed, style }) {
|
|
13
|
+
/** @type {number} */
|
|
14
|
+
this.gap = gap < minimumGap ? minimumGap : gap;
|
|
15
|
+
/** @type {Group} */
|
|
16
|
+
this.group = new Group();
|
|
17
|
+
/** @type {Group} */
|
|
18
|
+
this.rotationGroup = new Group();
|
|
19
|
+
/** @type {CubeRotation[]} */
|
|
20
|
+
this.rotationQueue = [];
|
|
21
|
+
/** @type {CubeRotation | undefined} */
|
|
22
|
+
this.currentRotation = undefined;
|
|
23
|
+
/** @type {number} */
|
|
24
|
+
this.animationSpeed = speed;
|
|
25
|
+
/** @type {"exponential" | "next" | "fixed"} */
|
|
26
|
+
this.animationStyle = style;
|
|
27
|
+
|
|
28
|
+
const core = createCoreMesh();
|
|
29
|
+
core.userData = {
|
|
30
|
+
position: { x: 0, y: 0, z: 0 },
|
|
31
|
+
rotation: { x: 0, y: 0, z: 0 },
|
|
32
|
+
initialPosition: { x: 0, y: 0, z: 0 },
|
|
33
|
+
initialRotation: { x: 0, y: 0, z: 0 },
|
|
34
|
+
type: 'core',
|
|
35
|
+
};
|
|
36
|
+
this.group.add(core);
|
|
37
|
+
|
|
38
|
+
for (const piece of createCubeState()) {
|
|
39
|
+
var pieceGroup = piece.group;
|
|
40
|
+
pieceGroup.position.set(piece.position.x * this.gap, piece.position.y * this.gap, piece.position.z * this.gap);
|
|
41
|
+
pieceGroup.rotation.set(piece.rotation.x, piece.rotation.y, piece.rotation.z);
|
|
42
|
+
pieceGroup.userData = {
|
|
43
|
+
position: Object.assign({}, piece.position),
|
|
44
|
+
rotation: Object.assign({}, piece.rotation),
|
|
45
|
+
initialPosition: Object.assign({}, piece.position),
|
|
46
|
+
initialRotation: Object.assign({}, piece.rotation),
|
|
47
|
+
type: piece.type,
|
|
48
|
+
};
|
|
49
|
+
this.group.add(pieceGroup);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
update() {
|
|
54
|
+
if (this.currentRotation === undefined) {
|
|
55
|
+
this.currentRotation = this.rotationQueue.shift();
|
|
56
|
+
if (this.currentRotation === undefined) return;
|
|
57
|
+
this.rotationGroup.add(...this.getRotationLayer(this.currentRotation.rotation));
|
|
58
|
+
this.currentRotation.initialise();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (this.currentRotation.status === 'complete') {
|
|
62
|
+
this.clearRotationGroup();
|
|
63
|
+
this.currentRotation.dispose();
|
|
64
|
+
this.currentRotation = this.rotationQueue.shift();
|
|
65
|
+
if (this.currentRotation === undefined) return;
|
|
66
|
+
this.rotationGroup.add(...this.getRotationLayer(this.currentRotation.rotation));
|
|
67
|
+
this.currentRotation.initialise();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
this.currentRotation.update(this.rotationGroup, this.getRotationSpeed());
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
getRotationSpeed() {
|
|
74
|
+
if (this.animationStyle == 'exponential') {
|
|
75
|
+
return this.animationSpeed / 2 ** this.rotationQueue.length;
|
|
76
|
+
}
|
|
77
|
+
if (this.animationStyle == 'next') {
|
|
78
|
+
return this.rotationQueue.length > 0 ? 0 : this.animationSpeed;
|
|
79
|
+
}
|
|
80
|
+
return this.animationSpeed;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
reset() {
|
|
84
|
+
this.rotationQueue = [];
|
|
85
|
+
if (this.currentRotation) {
|
|
86
|
+
this.currentRotation.update(this.rotationGroup, 0);
|
|
87
|
+
this.clearRotationGroup();
|
|
88
|
+
this.currentRotation.dispose();
|
|
89
|
+
this.currentRotation = undefined;
|
|
90
|
+
}
|
|
91
|
+
this.group.children.forEach((piece) => {
|
|
92
|
+
const { x, y, z } = piece.userData.initialPosition;
|
|
93
|
+
const { x: u, y: v, z: w } = piece.userData.initialRotation;
|
|
94
|
+
piece.position.set(x * this.gap, y * this.gap, z * this.gap);
|
|
95
|
+
piece.rotation.set(u, v, w);
|
|
96
|
+
piece.userData.position.x = x;
|
|
97
|
+
piece.userData.position.y = y;
|
|
98
|
+
piece.userData.position.z = z;
|
|
99
|
+
piece.userData.rotation.x = u;
|
|
100
|
+
piece.userData.rotation.y = v;
|
|
101
|
+
piece.userData.rotation.z = w;
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
clearRotationGroup() {
|
|
106
|
+
if (this.currentRotation.status != 'complete') {
|
|
107
|
+
throw Error('cannot clear rotation group while rotating');
|
|
108
|
+
}
|
|
109
|
+
this.rotationGroup.children.forEach((piece) => {
|
|
110
|
+
piece.getWorldPosition(piece.position);
|
|
111
|
+
piece.getWorldQuaternion(piece.quaternion);
|
|
112
|
+
var x = Math.round(piece.position.x);
|
|
113
|
+
var y = Math.round(piece.position.y);
|
|
114
|
+
var z = Math.round(piece.position.z);
|
|
115
|
+
piece.userData.position.x = Math.abs(x) > 1 ? Math.sign(x) : x;
|
|
116
|
+
piece.userData.position.y = Math.abs(y) > 1 ? Math.sign(y) : y;
|
|
117
|
+
piece.userData.position.z = Math.abs(z) > 1 ? Math.sign(z) : z;
|
|
118
|
+
piece.userData.rotation.x = piece.rotation.x;
|
|
119
|
+
piece.userData.rotation.y = piece.rotation.y;
|
|
120
|
+
piece.userData.rotation.z = piece.rotation.z;
|
|
121
|
+
});
|
|
122
|
+
this.group.add(...this.rotationGroup.children);
|
|
123
|
+
this.rotationGroup.rotation.set(0, 0, 0);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @param {{axis: "x"|"y"|"z", layers: (-1|0|1)[], direction: 1|-1|2|-2}} input
|
|
128
|
+
*/
|
|
129
|
+
rotate(input) {
|
|
130
|
+
this.rotationQueue.push(new CubeRotation(input));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* @param {{axis: "x"|"y"|"z", layers: (-1|0|1)[], direction: 1|-1|2|-2}}
|
|
135
|
+
* @returns {Object3D[]}
|
|
136
|
+
*/
|
|
137
|
+
getRotationLayer({ axis, layers, direction }) {
|
|
138
|
+
if (layers.length === 0) {
|
|
139
|
+
return [...this.group.children];
|
|
140
|
+
}
|
|
141
|
+
return this.group.children.filter((piece) => {
|
|
142
|
+
if (axis === 'x') {
|
|
143
|
+
return layers.includes(Math.round(piece.userData.position.x));
|
|
144
|
+
} else if (axis === 'y') {
|
|
145
|
+
return layers.includes(Math.round(piece.userData.position.y));
|
|
146
|
+
} else if (axis === 'z') {
|
|
147
|
+
return layers.includes(Math.round(piece.userData.position.z));
|
|
148
|
+
}
|
|
149
|
+
return false;
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
getStickerState() {
|
|
154
|
+
const state = {
|
|
155
|
+
up: [[], [], []],
|
|
156
|
+
down: [[], [], []],
|
|
157
|
+
front: [[], [], []],
|
|
158
|
+
back: [[], [], []],
|
|
159
|
+
left: [[], [], []],
|
|
160
|
+
right: [[], [], []],
|
|
161
|
+
};
|
|
162
|
+
this.group.children.forEach((piece) => {
|
|
163
|
+
if (piece.userData.type === 'core') {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
piece.children.forEach((mesh) => {
|
|
167
|
+
if (mesh.userData.type === 'sticker') {
|
|
168
|
+
const piecepos = new Vector3();
|
|
169
|
+
piecepos.copy(piece.userData.position);
|
|
170
|
+
piecepos.round();
|
|
171
|
+
const stickerpos = new Vector3();
|
|
172
|
+
mesh.getWorldPosition(stickerpos);
|
|
173
|
+
stickerpos.sub(piecepos);
|
|
174
|
+
stickerpos.multiplyScalar(2);
|
|
175
|
+
stickerpos.round();
|
|
176
|
+
if (stickerpos.x === 1) {
|
|
177
|
+
state.right[1 - Math.round(piecepos.y)][1 - Math.round(piecepos.z)] = mesh.material.userData.face;
|
|
178
|
+
} else if (stickerpos.x === -1) {
|
|
179
|
+
state.left[1 - Math.round(piecepos.y)][1 + Math.round(piecepos.z)] = mesh.material.userData.face;
|
|
180
|
+
} else if (stickerpos.y === 1) {
|
|
181
|
+
state.up[1 + Math.round(piecepos.z)][1 + Math.round(piecepos.x)] = mesh.material.userData.face;
|
|
182
|
+
} else if (stickerpos.y === -1) {
|
|
183
|
+
state.down[1 - Math.round(piecepos.z)][1 + Math.round(piecepos.x)] = mesh.material.userData.face;
|
|
184
|
+
} else if (stickerpos.z === 1) {
|
|
185
|
+
state.front[1 - Math.round(piecepos.y)][1 + Math.round(piecepos.x)] = mesh.material.userData.face;
|
|
186
|
+
} else if (stickerpos.z === -1) {
|
|
187
|
+
state.back[1 - Math.round(piecepos.y)][1 - Math.round(piecepos.x)] = mesh.material.userData.face;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
return state;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Vector3, Group } from 'three';
|
|
2
|
+
|
|
3
|
+
export class CubeRotation {
|
|
4
|
+
/**
|
|
5
|
+
* @param {{axis: "x"|"y"|"z", layers: (-1|0|1)[], direction: 1|-1|2|-2}} input
|
|
6
|
+
*/
|
|
7
|
+
constructor(rotation) {
|
|
8
|
+
/** @type {{axis: "x"|"y"|"z", layers: (-1|0|1)[], direction: 1|-1|2|-2}} */
|
|
9
|
+
this.rotation = rotation;
|
|
10
|
+
/** @type {"pending" | "initialised" | "complete" | "disposed"} */
|
|
11
|
+
this.status = 'pending';
|
|
12
|
+
/** @type {number} */
|
|
13
|
+
this._lastUpdatedTimeMs = undefined;
|
|
14
|
+
this._startTimeMs = undefined;
|
|
15
|
+
/** @type {number} */
|
|
16
|
+
this._rotationPercentage = 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
initialise() {
|
|
20
|
+
this._lastUpdatedTimeMs = performance.now();
|
|
21
|
+
this._startTimeMs = this._lastUpdatedTimeMs;
|
|
22
|
+
this.status = 'initialised';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
dispose() {
|
|
26
|
+
this.status = 'disposed';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
*
|
|
31
|
+
* @param {Group} rotationGroup
|
|
32
|
+
* @param {number} speedMs
|
|
33
|
+
*/
|
|
34
|
+
update(rotationGroup, speedMs) {
|
|
35
|
+
var intervalMs = performance.now() - this._lastUpdatedTimeMs;
|
|
36
|
+
this._lastUpdatedTimeMs = performance.now();
|
|
37
|
+
|
|
38
|
+
var increment = 100 - this._rotationPercentage;
|
|
39
|
+
if (speedMs != 0) {
|
|
40
|
+
var potentialIncrement = (intervalMs / speedMs) * 100;
|
|
41
|
+
if (potentialIncrement + this._rotationPercentage < 100) {
|
|
42
|
+
increment = potentialIncrement;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const rotationIncrement = (Math.abs(this.rotation.direction) * ((increment / 100) * Math.PI)) / 2;
|
|
46
|
+
this._rotationPercentage += increment;
|
|
47
|
+
rotationGroup.rotateOnWorldAxis(
|
|
48
|
+
new Vector3(
|
|
49
|
+
this.rotation.axis === 'x' ? this.rotation.direction : 0,
|
|
50
|
+
this.rotation.axis === 'y' ? this.rotation.direction : 0,
|
|
51
|
+
this.rotation.axis === 'z' ? this.rotation.direction : 0,
|
|
52
|
+
).normalize(),
|
|
53
|
+
rotationIncrement,
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
if (this._rotationPercentage >= 100) {
|
|
57
|
+
this.status = 'complete';
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { Group } from 'three';
|
|
2
|
+
import Materials from '../threejs/materials';
|
|
3
|
+
import { createCornerGroup, createEdgeGroup, createCenterGroup } from '../threejs/pieces';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {{x: number,y: number,z: number}} vector
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {{position: vector, rotation: vector, type: "corner" | "edge" | "center", group: Group}} state
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @return {state[]}
|
|
15
|
+
*/
|
|
16
|
+
const corners = () => [
|
|
17
|
+
{
|
|
18
|
+
position: { x: 1, y: 1, z: 1 },
|
|
19
|
+
rotation: { x: 0, y: 0, z: 0 },
|
|
20
|
+
type: 'corner',
|
|
21
|
+
group: createCornerGroup(Materials.front, Materials.right, Materials.up, Materials.core),
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
position: { x: 1, y: 1, z: -1 },
|
|
25
|
+
rotation: { x: 0, y: Math.PI / 2, z: 0 },
|
|
26
|
+
type: 'corner',
|
|
27
|
+
group: createCornerGroup(Materials.right, Materials.back, Materials.up, Materials.core),
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
position: { x: 1, y: -1, z: 1 },
|
|
31
|
+
rotation: { x: 0, y: Math.PI / 2, z: Math.PI },
|
|
32
|
+
type: 'corner',
|
|
33
|
+
group: createCornerGroup(Materials.right, Materials.front, Materials.down, Materials.core),
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
position: { x: 1, y: -1, z: -1 },
|
|
37
|
+
rotation: { x: 0, y: Math.PI, z: Math.PI },
|
|
38
|
+
type: 'corner',
|
|
39
|
+
group: createCornerGroup(Materials.back, Materials.right, Materials.down, Materials.core),
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
position: { x: -1, y: 1, z: 1 },
|
|
43
|
+
rotation: { x: 0, y: -Math.PI / 2, z: 0 },
|
|
44
|
+
type: 'corner',
|
|
45
|
+
group: createCornerGroup(Materials.left, Materials.front, Materials.up, Materials.core),
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
position: { x: -1, y: 1, z: -1 },
|
|
49
|
+
rotation: { x: 0, y: Math.PI, z: 0 },
|
|
50
|
+
type: 'corner',
|
|
51
|
+
group: createCornerGroup(Materials.back, Materials.left, Materials.up, Materials.core),
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
position: { x: -1, y: -1, z: 1 },
|
|
55
|
+
rotation: { x: 0, y: 0, z: Math.PI },
|
|
56
|
+
type: 'corner',
|
|
57
|
+
group: createCornerGroup(Materials.front, Materials.left, Materials.down, Materials.core),
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
position: { x: -1, y: -1, z: -1 },
|
|
61
|
+
rotation: { x: 0, y: -Math.PI / 2, z: Math.PI },
|
|
62
|
+
type: 'corner',
|
|
63
|
+
group: createCornerGroup(Materials.left, Materials.back, Materials.down, Materials.core),
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @return {state[]}
|
|
69
|
+
*/
|
|
70
|
+
const edges = () => [
|
|
71
|
+
{
|
|
72
|
+
position: { x: 1, y: 1, z: 0 },
|
|
73
|
+
rotation: { x: 0, y: Math.PI / 2, z: 0 },
|
|
74
|
+
type: 'edge',
|
|
75
|
+
group: createEdgeGroup(Materials.right, Materials.up, Materials.core),
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
position: { x: 1, y: 0, z: 1 },
|
|
79
|
+
rotation: { x: 0, y: 0, z: -Math.PI / 2 },
|
|
80
|
+
type: 'edge',
|
|
81
|
+
group: createEdgeGroup(Materials.front, Materials.right, Materials.core),
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
position: { x: 1, y: 0, z: -1 },
|
|
85
|
+
rotation: { x: 0, y: Math.PI / 2, z: -Math.PI / 2 },
|
|
86
|
+
type: 'edge',
|
|
87
|
+
group: createEdgeGroup(Materials.right, Materials.back, Materials.core),
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
position: { x: 1, y: -1, z: 0 },
|
|
91
|
+
rotation: { x: Math.PI, y: Math.PI / 2, z: 0 },
|
|
92
|
+
type: 'edge',
|
|
93
|
+
group: createEdgeGroup(Materials.right, Materials.down, Materials.core),
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
position: { x: 0, y: 1, z: 1 },
|
|
97
|
+
rotation: { x: 0, y: 0, z: 0 },
|
|
98
|
+
type: 'edge',
|
|
99
|
+
group: createEdgeGroup(Materials.front, Materials.up, Materials.core),
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
position: { x: 0, y: 1, z: -1 },
|
|
103
|
+
rotation: { x: -Math.PI / 2, y: 0, z: 0 },
|
|
104
|
+
type: 'edge',
|
|
105
|
+
group: createEdgeGroup(Materials.up, Materials.back, Materials.core),
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
position: { x: 0, y: -1, z: 1 },
|
|
109
|
+
rotation: { x: Math.PI / 2, y: 0, z: 0 },
|
|
110
|
+
type: 'edge',
|
|
111
|
+
group: createEdgeGroup(Materials.down, Materials.front, Materials.core),
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
position: { x: 0, y: -1, z: -1 },
|
|
115
|
+
rotation: { x: Math.PI, y: 0, z: 0 },
|
|
116
|
+
type: 'edge',
|
|
117
|
+
group: createEdgeGroup(Materials.back, Materials.down, Materials.core),
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
position: { x: -1, y: 1, z: 0 },
|
|
121
|
+
rotation: { x: 0, y: -Math.PI / 2, z: 0 },
|
|
122
|
+
type: 'edge',
|
|
123
|
+
group: createEdgeGroup(Materials.left, Materials.up, Materials.core),
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
position: { x: -1, y: 0, z: 1 },
|
|
127
|
+
rotation: { x: 0, y: 0, z: Math.PI / 2 },
|
|
128
|
+
type: 'edge',
|
|
129
|
+
group: createEdgeGroup(Materials.front, Materials.left, Materials.core),
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
position: { x: -1, y: 0, z: -1 },
|
|
133
|
+
rotation: { x: 0, y: -Math.PI / 2, z: Math.PI / 2 },
|
|
134
|
+
type: 'edge',
|
|
135
|
+
group: createEdgeGroup(Materials.left, Materials.back, Materials.core),
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
position: { x: -1, y: -1, z: 0 },
|
|
139
|
+
rotation: { x: 0, y: -Math.PI / 2, z: Math.PI },
|
|
140
|
+
type: 'edge',
|
|
141
|
+
group: createEdgeGroup(Materials.left, Materials.down, Materials.core),
|
|
142
|
+
},
|
|
143
|
+
];
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* @return {state[]}
|
|
147
|
+
*/
|
|
148
|
+
const centers = () => [
|
|
149
|
+
{
|
|
150
|
+
position: { x: 1, y: 0, z: 0 },
|
|
151
|
+
rotation: { x: 0, y: Math.PI / 2, z: 0 },
|
|
152
|
+
type: 'center',
|
|
153
|
+
group: createCenterGroup(Materials.right, Materials.core),
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
position: { x: 0, y: 1, z: 0 },
|
|
157
|
+
rotation: { x: -Math.PI / 2, y: 0, z: 0 },
|
|
158
|
+
type: 'center',
|
|
159
|
+
group: createCenterGroup(Materials.up, Materials.core),
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
position: { x: 0, y: 0, z: 1 },
|
|
163
|
+
rotation: { x: 0, y: 0, z: 0 },
|
|
164
|
+
type: 'center',
|
|
165
|
+
group: createCenterGroup(Materials.front, Materials.core),
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
position: { x: 0, y: 0, z: -1 },
|
|
169
|
+
rotation: { x: 0, y: Math.PI, z: 0 },
|
|
170
|
+
type: 'center',
|
|
171
|
+
group: createCenterGroup(Materials.back, Materials.core),
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
position: { x: 0, y: -1, z: 0 },
|
|
175
|
+
rotation: { x: Math.PI / 2, y: 0, z: 0 },
|
|
176
|
+
type: 'center',
|
|
177
|
+
group: createCenterGroup(Materials.down, Materials.core),
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
position: { x: -1, y: 0, z: 0 },
|
|
181
|
+
rotation: { x: 0, y: -Math.PI / 2, z: 0 },
|
|
182
|
+
type: 'center',
|
|
183
|
+
group: createCenterGroup(Materials.left, Materials.core),
|
|
184
|
+
},
|
|
185
|
+
];
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* @return {state[]}
|
|
189
|
+
*/
|
|
190
|
+
const createCubeState = () => [...corners(), ...edges(), ...centers()];
|
|
191
|
+
export { createCubeState };
|
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { MeshStandardMaterial, MeshBasicMaterial } from "three";
|
|
2
2
|
export default class Materials {
|
|
3
|
-
static front = new
|
|
3
|
+
static front = new MeshStandardMaterial({
|
|
4
4
|
color: "#2cbf13",
|
|
5
5
|
metalness: 0,
|
|
6
6
|
roughness: 0.4,
|
|
7
7
|
userData: { face: "front", color: "green" },
|
|
8
8
|
});
|
|
9
|
-
static back = new
|
|
9
|
+
static back = new MeshStandardMaterial({
|
|
10
10
|
color: "blue",
|
|
11
11
|
metalness: 0,
|
|
12
12
|
roughness: 0.4,
|
|
13
13
|
userData: { face: "back", color: "blue" },
|
|
14
14
|
});
|
|
15
|
-
static up = new
|
|
15
|
+
static up = new MeshStandardMaterial({
|
|
16
16
|
color: "white",
|
|
17
17
|
metalness: 0,
|
|
18
18
|
roughness: 0.4,
|
|
19
19
|
userData: { face: "up", color: "white" },
|
|
20
20
|
});
|
|
21
|
-
static down = new
|
|
21
|
+
static down = new MeshStandardMaterial({
|
|
22
22
|
color: "yellow",
|
|
23
23
|
metalness: 0,
|
|
24
24
|
roughness: 0.4,
|
|
25
25
|
userData: { face: "down", color: "yellow" },
|
|
26
26
|
});
|
|
27
|
-
static left = new
|
|
27
|
+
static left = new MeshStandardMaterial({
|
|
28
28
|
color: "#fc9a05",
|
|
29
29
|
metalness: 0,
|
|
30
30
|
roughness: 0.4,
|
|
31
31
|
userData: { face: "left", color: "orange" },
|
|
32
32
|
});
|
|
33
|
-
static right = new
|
|
33
|
+
static right = new MeshStandardMaterial({
|
|
34
34
|
color: "red",
|
|
35
35
|
metalness: 0,
|
|
36
36
|
roughness: 0.4,
|
|
37
37
|
userData: { face: "right", color: "red" },
|
|
38
38
|
});
|
|
39
|
-
static core = new
|
|
39
|
+
static core = new MeshBasicMaterial({
|
|
40
40
|
color: "black",
|
|
41
41
|
});
|
|
42
42
|
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { Group, BoxGeometry, Mesh, SphereGeometry, Material } from 'three';
|
|
2
|
+
import Stickers from './stickers';
|
|
3
|
+
import Materials from './materials';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {Geometry} sticker
|
|
7
|
+
* @param {Material} frontMaterial
|
|
8
|
+
* @param {Material} rightMaterial
|
|
9
|
+
* @param {Material} topMaterial
|
|
10
|
+
* @param {Material} coreMaterial
|
|
11
|
+
* @returns {Group}
|
|
12
|
+
*/
|
|
13
|
+
export function createCornerGroup(frontMaterial, rightMaterial, topMaterial, coreMaterial) {
|
|
14
|
+
const group = new Group();
|
|
15
|
+
const boxGeom = new BoxGeometry(1, 1, 1);
|
|
16
|
+
const boxMesh = new Mesh(boxGeom, coreMaterial);
|
|
17
|
+
boxMesh.userData = { type: 'piece' };
|
|
18
|
+
group.add(boxMesh);
|
|
19
|
+
|
|
20
|
+
// front
|
|
21
|
+
const frontSticker = new Mesh(Stickers.corner, frontMaterial);
|
|
22
|
+
frontSticker.userData = { type: 'sticker' };
|
|
23
|
+
frontSticker.position.set(0, 0, 0.5);
|
|
24
|
+
frontSticker.rotation.set(0, 0, 0);
|
|
25
|
+
group.add(frontSticker);
|
|
26
|
+
|
|
27
|
+
//right
|
|
28
|
+
const rightSticker = new Mesh(Stickers.corner, rightMaterial);
|
|
29
|
+
rightSticker.userData = { type: 'sticker' };
|
|
30
|
+
rightSticker.position.set(0.5, 0, 0);
|
|
31
|
+
rightSticker.rotation.set(Math.PI / 2, Math.PI / 2, 0);
|
|
32
|
+
group.add(rightSticker);
|
|
33
|
+
|
|
34
|
+
//white/yellow
|
|
35
|
+
const topSticker = new Mesh(Stickers.corner, topMaterial);
|
|
36
|
+
topSticker.userData = { type: 'sticker' };
|
|
37
|
+
topSticker.position.set(0, 0.5, 0);
|
|
38
|
+
topSticker.rotation.set(-Math.PI / 2, 0, -Math.PI / 2);
|
|
39
|
+
group.add(topSticker);
|
|
40
|
+
|
|
41
|
+
return group;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param {Geometry} sticker
|
|
46
|
+
* @param {Material} frontMaterial
|
|
47
|
+
* @param {Material} topMaterial
|
|
48
|
+
* @param {Material} coreMaterial
|
|
49
|
+
* @returns {Group}
|
|
50
|
+
*/
|
|
51
|
+
export function createEdgeGroup(frontMaterial, topMaterial, coreMaterial) {
|
|
52
|
+
const group = new Group();
|
|
53
|
+
const boxGeom = new BoxGeometry(1, 1, 1);
|
|
54
|
+
const boxMesh = new Mesh(boxGeom, coreMaterial);
|
|
55
|
+
boxMesh.userData = { type: 'piece' };
|
|
56
|
+
group.add(boxMesh);
|
|
57
|
+
|
|
58
|
+
// front
|
|
59
|
+
const frontSticker = new Mesh(Stickers.edge, frontMaterial);
|
|
60
|
+
frontSticker.userData = { type: 'sticker' };
|
|
61
|
+
frontSticker.position.set(0, 0, 0.5);
|
|
62
|
+
frontSticker.rotation.set(0, 0, 0);
|
|
63
|
+
group.add(frontSticker);
|
|
64
|
+
|
|
65
|
+
// top
|
|
66
|
+
const topSticker = new Mesh(Stickers.edge, topMaterial);
|
|
67
|
+
topSticker.userData = { type: 'sticker' };
|
|
68
|
+
topSticker.position.set(0, 0.5, 0);
|
|
69
|
+
topSticker.rotation.set(-Math.PI / 2, 0, Math.PI);
|
|
70
|
+
group.add(topSticker);
|
|
71
|
+
|
|
72
|
+
return group;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @param {Geometry} sticker
|
|
77
|
+
* @param {Material} frontMaterial
|
|
78
|
+
* @param {Material} topMaterial
|
|
79
|
+
* @param {Material} coreMaterial
|
|
80
|
+
* @returns {Group}
|
|
81
|
+
*/
|
|
82
|
+
export function createCenterGroup(frontMaterial, coreMaterial) {
|
|
83
|
+
const group = new Group();
|
|
84
|
+
const boxGeom = new BoxGeometry(1, 1, 1);
|
|
85
|
+
const boxMesh = new Mesh(boxGeom, coreMaterial);
|
|
86
|
+
boxMesh.userData = { type: 'piece' };
|
|
87
|
+
group.add(boxMesh);
|
|
88
|
+
|
|
89
|
+
const frontSticker = new Mesh(Stickers.center, frontMaterial);
|
|
90
|
+
frontSticker.userData = { type: 'sticker' };
|
|
91
|
+
frontSticker.position.set(0, 0, 0.5);
|
|
92
|
+
frontSticker.rotation.set(0, 0, 0);
|
|
93
|
+
group.add(frontSticker);
|
|
94
|
+
|
|
95
|
+
return group;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @returns {Mesh}
|
|
100
|
+
*/
|
|
101
|
+
export function createCoreMesh() {
|
|
102
|
+
return new Mesh(new SphereGeometry(1.53), Materials.core);
|
|
103
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import * as THREE from "three";
|
|
2
1
|
import { SVGLoader } from "three/examples/jsm/Addons.js";
|
|
2
|
+
import { ExtrudeGeometry } from "three";
|
|
3
3
|
|
|
4
4
|
const loader = new SVGLoader();
|
|
5
5
|
const cornerSVG = loader.parse(`
|
|
@@ -19,7 +19,7 @@ const centerSVG = loader.parse(`
|
|
|
19
19
|
`);
|
|
20
20
|
|
|
21
21
|
export default class Stickers {
|
|
22
|
-
static center = new
|
|
22
|
+
static center = new ExtrudeGeometry(
|
|
23
23
|
SVGLoader.createShapes(centerSVG.paths[0])[0],
|
|
24
24
|
{
|
|
25
25
|
depth: 15,
|
|
@@ -28,7 +28,7 @@ export default class Stickers {
|
|
|
28
28
|
.scale(0.002, 0.002, 0.002)
|
|
29
29
|
.translate(-0.5, -0.5, 0);
|
|
30
30
|
|
|
31
|
-
static edge = new
|
|
31
|
+
static edge = new ExtrudeGeometry(
|
|
32
32
|
SVGLoader.createShapes(edgeSVG.paths[0])[0],
|
|
33
33
|
{
|
|
34
34
|
depth: 15,
|
|
@@ -37,7 +37,7 @@ export default class Stickers {
|
|
|
37
37
|
.scale(0.002, 0.002, 0.002)
|
|
38
38
|
.translate(-0.5, -0.5, 0);
|
|
39
39
|
|
|
40
|
-
static corner = new
|
|
40
|
+
static corner = new ExtrudeGeometry(
|
|
41
41
|
SVGLoader.createShapes(cornerSVG.paths[0])[0],
|
|
42
42
|
{
|
|
43
43
|
depth: 15,
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {string} action
|
|
3
|
+
* @returns {{axis: "x"|"y"|"z", layers: (0|1|-1)[], direction: (1|-1|2|-2)}}
|
|
4
|
+
*/
|
|
5
|
+
export default function getRotationDetailsFromNotation(action) {
|
|
6
|
+
if (!action) return;
|
|
7
|
+
const reverse = action.includes("'") ? -1 : 1;
|
|
8
|
+
action = action.replace("'", '');
|
|
9
|
+
const multiplier = action.includes('2') ? 2 : 1;
|
|
10
|
+
action = action.replace('2', '');
|
|
11
|
+
if (!action) return;
|
|
12
|
+
const move = action[0];
|
|
13
|
+
|
|
14
|
+
if (move === 'x') {
|
|
15
|
+
return { axis: 'x', layers: [], direction: -reverse * multiplier };
|
|
16
|
+
} else if (move === 'y') {
|
|
17
|
+
return { axis: 'y', layers: [], direction: -reverse * multiplier };
|
|
18
|
+
} else if (move === 'z') {
|
|
19
|
+
return { axis: 'z', layers: [], direction: -reverse * multiplier };
|
|
20
|
+
} else if (move === 'U') {
|
|
21
|
+
return { axis: 'y', layers: [1], direction: -reverse * multiplier };
|
|
22
|
+
} else if (move === 'u') {
|
|
23
|
+
return { axis: 'y', layers: [1, 0], direction: -reverse * multiplier };
|
|
24
|
+
} else if (move === 'R') {
|
|
25
|
+
return { axis: 'x', layers: [1], direction: -reverse * multiplier };
|
|
26
|
+
} else if (move === 'r') {
|
|
27
|
+
return { axis: 'x', layers: [1, 0], direction: -reverse * multiplier };
|
|
28
|
+
} else if (move === 'L') {
|
|
29
|
+
return { axis: 'x', layers: [-1], direction: -reverse * multiplier };
|
|
30
|
+
} else if (move == 'l') {
|
|
31
|
+
return { axis: 'x', layers: [-1, 0], direction: -reverse * multiplier };
|
|
32
|
+
} else if (move === 'D') {
|
|
33
|
+
return { axis: 'y', layers: [-1], direction: -reverse * multiplier };
|
|
34
|
+
} else if (move === 'd') {
|
|
35
|
+
return { axis: 'y', layers: [-1, 0], direction: -reverse * multiplier };
|
|
36
|
+
} else if (move === 'F') {
|
|
37
|
+
return { axis: 'z', layers: [1], direction: -reverse * multiplier };
|
|
38
|
+
} else if (move === 'f') {
|
|
39
|
+
return { axis: 'z', layers: [1, 0], direction: -reverse * multiplier };
|
|
40
|
+
} else if (move === 'B') {
|
|
41
|
+
return { axis: 'z', layers: [-1], direction: -reverse * multiplier };
|
|
42
|
+
} else if (move === 'b') {
|
|
43
|
+
return { axis: 'z', layers: [-1, 0], direction: -reverse * multiplier };
|
|
44
|
+
} else if (move === 'M') {
|
|
45
|
+
return { axis: 'x', layers: [0], direction: -reverse * multiplier };
|
|
46
|
+
} else if (move === 'E') {
|
|
47
|
+
return { axis: 'y', layers: [0], direction: -reverse * multiplier };
|
|
48
|
+
} else if (move === 'S') {
|
|
49
|
+
return { axis: 'z', layers: [0], direction: -reverse * multiplier };
|
|
50
|
+
}
|
|
51
|
+
console.log(`rubiks-cube invalid Action: ${action}`);
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|