@houstonp/rubiks-cube 1.5.1 → 2.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 +100 -194
- package/package.json +34 -4
- package/src/cameraState.js +81 -0
- package/src/core.js +127 -0
- package/src/cube/cube.js +123 -75
- package/src/cube/cubeRotation.js +26 -10
- package/src/cube/cubeSettings.js +18 -0
- package/src/cube/cubeState.js +1 -0
- package/src/cube/slice.js +143 -0
- package/src/debouncer.js +16 -0
- package/src/globals.ts +9 -0
- package/src/index.js +496 -0
- package/src/schema.js +22 -0
- package/src/settings.js +126 -0
- package/src/threejs/materials.js +52 -40
- package/src/threejs/pieces.js +1 -4
- package/src/threejs/stickers.js +18 -26
- package/types/cameraState.d.ts +19 -0
- package/types/core.d.ts +125 -0
- package/types/cube/cube.d.ts +102 -0
- package/types/cube/cubeRotation.d.ts +33 -0
- package/types/cube/cubeSettings.d.ts +17 -0
- package/types/cube/cubeState.d.ts +16 -0
- package/types/cube/slice.d.ts +15 -0
- package/types/debouncer.d.ts +13 -0
- package/types/globals.d.ts +7 -0
- package/types/index.d.ts +65 -0
- package/types/schema.d.ts +11 -0
- package/types/settings.d.ts +34 -0
- package/types/threejs/materials.d.ts +21 -0
- package/types/threejs/pieces.d.ts +28 -0
- package/types/threejs/stickers.d.ts +6 -0
- package/.prettierrc +0 -7
- package/index.js +0 -274
- package/src/utils/debouncer.js +0 -7
- package/src/utils/rotation.js +0 -53
package/src/cube/cube.js
CHANGED
|
@@ -1,38 +1,45 @@
|
|
|
1
|
-
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { Group, Material, Mesh, Object3D, Vector3 } from 'three';
|
|
2
3
|
import { createCoreMesh } from '../threejs/pieces';
|
|
3
4
|
import { createCubeState } from './cubeState';
|
|
4
5
|
import { CubeRotation } from './cubeRotation';
|
|
6
|
+
import CubeSettings from './cubeSettings';
|
|
7
|
+
import GetMovementSlice, { GetRotationSlice } from './slice';
|
|
8
|
+
import { Axi } from '../core';
|
|
5
9
|
|
|
10
|
+
/** @typedef {{ up: import('../core').Face[][], down: import('../core').Face[][], front: import('../core').Face[][], back: import('../core').Face[][], left: import('../core').Face[][], right: import('../core').Face[][] }} StickerState*/
|
|
6
11
|
export default class Cube {
|
|
7
12
|
/**
|
|
8
|
-
* @param {
|
|
13
|
+
* @param {CubeSettings} cubeSettings
|
|
9
14
|
*/
|
|
10
|
-
constructor(
|
|
11
|
-
/** @type {
|
|
12
|
-
this.
|
|
15
|
+
constructor(cubeSettings) {
|
|
16
|
+
/** @type {CubeSettings} */
|
|
17
|
+
this.cubeSettings = cubeSettings;
|
|
13
18
|
/** @type {Group} */
|
|
14
|
-
this.group =
|
|
19
|
+
this.group = new Group();
|
|
15
20
|
/** @type {Group} */
|
|
16
21
|
this.rotationGroup = new Group();
|
|
17
22
|
/** @type {CubeRotation[]} */
|
|
18
23
|
this.rotationQueue = [];
|
|
19
24
|
/** @type {CubeRotation | undefined} */
|
|
20
25
|
this.currentRotation = undefined;
|
|
21
|
-
|
|
22
|
-
this.currentState = this.getStickerState();
|
|
26
|
+
|
|
23
27
|
/** @type {number | undefined} */
|
|
24
28
|
this._matchSpeed = undefined;
|
|
25
29
|
/** @type {number} */
|
|
26
|
-
this._lastGap =
|
|
30
|
+
this._lastGap = cubeSettings.pieceGap;
|
|
31
|
+
|
|
32
|
+
// initialise threejs Objects
|
|
33
|
+
this.init();
|
|
34
|
+
|
|
35
|
+
/** @type {StickerState} */
|
|
36
|
+
this.currentState = this.getStickerState();
|
|
27
37
|
}
|
|
28
38
|
|
|
29
39
|
/**
|
|
30
|
-
*
|
|
31
|
-
* @param {Group} group
|
|
32
|
-
* @returns {Group}
|
|
40
|
+
* adds threejs objects to group
|
|
33
41
|
*/
|
|
34
|
-
|
|
35
|
-
var group = new Group();
|
|
42
|
+
init() {
|
|
36
43
|
const core = createCoreMesh();
|
|
37
44
|
core.userData = {
|
|
38
45
|
position: { x: 0, y: 0, z: 0 },
|
|
@@ -41,14 +48,14 @@ export default class Cube {
|
|
|
41
48
|
initialRotation: { x: 0, y: 0, z: 0 },
|
|
42
49
|
type: 'core',
|
|
43
50
|
};
|
|
44
|
-
group.add(core);
|
|
51
|
+
this.group.add(core);
|
|
45
52
|
|
|
46
53
|
for (const piece of createCubeState()) {
|
|
47
54
|
var pieceGroup = piece.group;
|
|
48
55
|
pieceGroup.position.set(
|
|
49
|
-
piece.position.x * this.
|
|
50
|
-
piece.position.y * this.
|
|
51
|
-
piece.position.z * this.
|
|
56
|
+
piece.position.x * this.cubeSettings.pieceGap,
|
|
57
|
+
piece.position.y * this.cubeSettings.pieceGap,
|
|
58
|
+
piece.position.z * this.cubeSettings.pieceGap,
|
|
52
59
|
);
|
|
53
60
|
pieceGroup.rotation.set(piece.rotation.x, piece.rotation.y, piece.rotation.z);
|
|
54
61
|
pieceGroup.userData = {
|
|
@@ -58,42 +65,40 @@ export default class Cube {
|
|
|
58
65
|
initialRotation: Object.assign({}, piece.rotation),
|
|
59
66
|
type: piece.type,
|
|
60
67
|
};
|
|
61
|
-
group.add(pieceGroup);
|
|
68
|
+
this.group.add(pieceGroup);
|
|
62
69
|
}
|
|
63
|
-
return group;
|
|
70
|
+
return this.group;
|
|
64
71
|
}
|
|
65
72
|
|
|
66
73
|
/**
|
|
67
74
|
* update the cube and continue any rotations
|
|
68
|
-
* @returns {string}
|
|
69
75
|
*/
|
|
70
76
|
update() {
|
|
71
77
|
if (this.currentRotation === undefined) {
|
|
72
|
-
if (this._lastGap !== this.
|
|
78
|
+
if (this._lastGap !== this.cubeSettings.pieceGap) {
|
|
73
79
|
this.updateGap();
|
|
74
80
|
}
|
|
75
81
|
this.currentRotation = this.rotationQueue.shift();
|
|
76
82
|
if (this.currentRotation === undefined) {
|
|
77
83
|
this._matchSpeed = undefined; // reset speed for the match animation options
|
|
78
|
-
return
|
|
84
|
+
return;
|
|
79
85
|
}
|
|
80
86
|
}
|
|
81
87
|
if (this.currentRotation.status === 'pending') {
|
|
82
|
-
this.rotationGroup.add(...this.getRotationLayer(this.currentRotation.
|
|
88
|
+
this.rotationGroup.add(...this.getRotationLayer(this.currentRotation.slice));
|
|
83
89
|
this.currentRotation.initialise();
|
|
84
90
|
}
|
|
85
|
-
if (this.currentRotation.status === 'initialised') {
|
|
91
|
+
if (this.currentRotation.status === 'initialised' || this.currentRotation.status === 'inProgress') {
|
|
86
92
|
var speed = this.getRotationSpeed();
|
|
87
93
|
this.currentRotation.update(this.rotationGroup, speed);
|
|
88
94
|
}
|
|
89
95
|
if (this.currentRotation.status === 'complete') {
|
|
90
96
|
this.clearRotationGroup();
|
|
91
|
-
var eventId = this.currentRotation.eventId;
|
|
92
|
-
this.currentRotation = undefined;
|
|
93
97
|
this.currentState = this.getStickerState();
|
|
94
|
-
|
|
98
|
+
this.currentRotation.completedCallback(this.kociembaState);
|
|
99
|
+
this.currentRotation = undefined;
|
|
95
100
|
}
|
|
96
|
-
return
|
|
101
|
+
return;
|
|
97
102
|
}
|
|
98
103
|
|
|
99
104
|
/**
|
|
@@ -104,9 +109,9 @@ export default class Cube {
|
|
|
104
109
|
if (this.currentRotation === undefined) {
|
|
105
110
|
this.group.children.forEach((piece) => {
|
|
106
111
|
var { x, y, z } = piece.userData.position;
|
|
107
|
-
piece.position.set(x * this.
|
|
112
|
+
piece.position.set(x * this.cubeSettings.pieceGap, y * this.cubeSettings.pieceGap, z * this.cubeSettings.pieceGap);
|
|
108
113
|
});
|
|
109
|
-
this._lastGap = this.
|
|
114
|
+
this._lastGap = this.cubeSettings.pieceGap;
|
|
110
115
|
}
|
|
111
116
|
}
|
|
112
117
|
|
|
@@ -121,16 +126,17 @@ export default class Cube {
|
|
|
121
126
|
* @returns {number}
|
|
122
127
|
*/
|
|
123
128
|
getRotationSpeed() {
|
|
124
|
-
if (this.
|
|
125
|
-
return this.
|
|
129
|
+
if (this.cubeSettings.animationStyle === 'exponential') {
|
|
130
|
+
return this.cubeSettings.animationSpeedMs / 2 ** this.rotationQueue.length;
|
|
126
131
|
}
|
|
127
|
-
if (this.
|
|
128
|
-
return this.rotationQueue.length > 0 ? 0 : this.
|
|
132
|
+
if (this.cubeSettings.animationStyle === 'next') {
|
|
133
|
+
return this.rotationQueue.length > 0 ? 0 : this.cubeSettings.animationSpeedMs;
|
|
129
134
|
}
|
|
130
|
-
if (this.
|
|
135
|
+
if (this.cubeSettings.animationStyle === 'match') {
|
|
131
136
|
if (this.rotationQueue.length > 0) {
|
|
132
|
-
|
|
133
|
-
|
|
137
|
+
const rotation = /** @type {CubeRotation} */ (this.currentRotation);
|
|
138
|
+
const lastTimeStamp = rotation.timestampMs;
|
|
139
|
+
let minGap = this._matchSpeed ?? this.cubeSettings.animationSpeedMs;
|
|
134
140
|
for (var i = 0; i < this.rotationQueue.length; i++) {
|
|
135
141
|
var gap = this.rotationQueue[i].timestampMs - lastTimeStamp;
|
|
136
142
|
if (gap < minGap) {
|
|
@@ -142,29 +148,32 @@ export default class Cube {
|
|
|
142
148
|
if (this._matchSpeed !== undefined) {
|
|
143
149
|
return this._matchSpeed;
|
|
144
150
|
}
|
|
145
|
-
return this.
|
|
151
|
+
return this.cubeSettings.animationSpeedMs;
|
|
146
152
|
}
|
|
147
|
-
if (this.
|
|
148
|
-
return this.
|
|
153
|
+
if (this.cubeSettings.animationStyle === 'fixed') {
|
|
154
|
+
return this.cubeSettings.animationSpeedMs;
|
|
149
155
|
}
|
|
150
|
-
return this.
|
|
156
|
+
return this.cubeSettings.animationSpeedMs;
|
|
151
157
|
}
|
|
152
158
|
|
|
153
159
|
/**
|
|
154
160
|
* Complete the current rotation and reset the cube
|
|
161
|
+
* @param {(state:string) => boolean} completedCallback
|
|
155
162
|
* @returns {void}
|
|
156
163
|
*/
|
|
157
|
-
reset() {
|
|
164
|
+
reset(completedCallback) {
|
|
165
|
+
this.rotationQueue.forEach((cubeRotation) => cubeRotation.failedCallback('State reset during action'));
|
|
158
166
|
this.rotationQueue = [];
|
|
159
167
|
if (this.currentRotation) {
|
|
160
168
|
this.currentRotation.update(this.rotationGroup, 0);
|
|
161
169
|
this.clearRotationGroup();
|
|
170
|
+
this.currentRotation.failedCallback('State reset during action');
|
|
162
171
|
this.currentRotation = undefined;
|
|
163
172
|
}
|
|
164
173
|
this.group.children.forEach((piece) => {
|
|
165
174
|
const { x, y, z } = piece.userData.initialPosition;
|
|
166
175
|
const { x: u, y: v, z: w } = piece.userData.initialRotation;
|
|
167
|
-
piece.position.set(x * this.
|
|
176
|
+
piece.position.set(x * this.cubeSettings.pieceGap, y * this.cubeSettings.pieceGap, z * this.cubeSettings.pieceGap);
|
|
168
177
|
piece.rotation.set(u, v, w);
|
|
169
178
|
piece.userData.position.x = x;
|
|
170
179
|
piece.userData.position.y = y;
|
|
@@ -173,6 +182,11 @@ export default class Cube {
|
|
|
173
182
|
piece.userData.rotation.y = v;
|
|
174
183
|
piece.userData.rotation.z = w;
|
|
175
184
|
});
|
|
185
|
+
|
|
186
|
+
this.currentState = this.getStickerState();
|
|
187
|
+
if (!completedCallback(this.kociembaState)) {
|
|
188
|
+
console.error('Failed to invoke reset completedCallback');
|
|
189
|
+
}
|
|
176
190
|
}
|
|
177
191
|
|
|
178
192
|
/**
|
|
@@ -180,8 +194,13 @@ export default class Cube {
|
|
|
180
194
|
* @returns {void}
|
|
181
195
|
*/
|
|
182
196
|
clearRotationGroup() {
|
|
197
|
+
if (this.currentRotation == null) {
|
|
198
|
+
console.error('cannot clear rotation when rotation is null');
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
183
201
|
if (this.currentRotation.status != 'complete') {
|
|
184
|
-
|
|
202
|
+
console.error('cannot clear rotation group while rotating');
|
|
203
|
+
return;
|
|
185
204
|
}
|
|
186
205
|
this.rotationGroup.children.forEach((piece) => {
|
|
187
206
|
piece.getWorldPosition(piece.position);
|
|
@@ -202,51 +221,78 @@ export default class Cube {
|
|
|
202
221
|
}
|
|
203
222
|
|
|
204
223
|
/**
|
|
205
|
-
*
|
|
206
|
-
*
|
|
224
|
+
* @param {string} eventId
|
|
225
|
+
* @param {import('../core').Rotation} rotation
|
|
226
|
+
* @param {((state: string) => void )} completedCallback
|
|
227
|
+
* @param {((reason: string) => void )} failedCallback
|
|
228
|
+
*/
|
|
229
|
+
rotation(eventId, rotation, completedCallback, failedCallback) {
|
|
230
|
+
const slice = GetRotationSlice(rotation);
|
|
231
|
+
this.rotationQueue.push(new CubeRotation(eventId, slice, completedCallback, failedCallback));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* @param {string} eventId
|
|
236
|
+
* @param {import('../core').Movement} movement
|
|
237
|
+
* @param {((state: string) => void )} completedCallback
|
|
238
|
+
* @param {((reason: string) => void )} failedCallback
|
|
207
239
|
*/
|
|
208
|
-
|
|
209
|
-
|
|
240
|
+
movement(eventId, movement, completedCallback, failedCallback) {
|
|
241
|
+
const slice = GetMovementSlice(movement);
|
|
242
|
+
this.rotationQueue.push(new CubeRotation(eventId, slice, completedCallback, failedCallback));
|
|
210
243
|
}
|
|
211
244
|
|
|
212
245
|
/**
|
|
213
|
-
* @param {
|
|
246
|
+
* @param {import('./slice').Slice} slice
|
|
214
247
|
* @returns {Object3D[]}
|
|
215
248
|
*/
|
|
216
|
-
getRotationLayer(
|
|
217
|
-
if (layers.length === 0) {
|
|
249
|
+
getRotationLayer(slice) {
|
|
250
|
+
if (slice.layers.length === 0) {
|
|
218
251
|
return [...this.group.children];
|
|
219
252
|
}
|
|
220
253
|
return this.group.children.filter((piece) => {
|
|
221
|
-
if (axis ===
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
return layers.includes(
|
|
254
|
+
if (slice.axis === Axi.x) {
|
|
255
|
+
const roundedPos = /** @type {(-1|0|1)} */ (Math.round(piece.userData.position.x));
|
|
256
|
+
return slice.layers.includes(roundedPos);
|
|
257
|
+
} else if (slice.axis === Axi.y) {
|
|
258
|
+
const roundedPos = /** @type {(-1|0|1)} */ (Math.round(piece.userData.position.y));
|
|
259
|
+
return slice.layers.includes(roundedPos);
|
|
260
|
+
} else if (slice.axis === Axi.z) {
|
|
261
|
+
const roundedPos = /** @type {(-1|0|1)} */ (Math.round(piece.userData.position.z));
|
|
262
|
+
return slice.layers.includes(roundedPos);
|
|
227
263
|
}
|
|
228
264
|
return false;
|
|
229
265
|
});
|
|
230
266
|
}
|
|
231
267
|
|
|
232
268
|
/**
|
|
233
|
-
* @returns {
|
|
269
|
+
* @returns {string}
|
|
270
|
+
*/
|
|
271
|
+
get kociembaState() {
|
|
272
|
+
return this.toKociemba(this.currentState);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* @param {StickerState} stickerState
|
|
277
|
+
* @returns {string}
|
|
278
|
+
*/
|
|
279
|
+
toKociemba(stickerState) {
|
|
280
|
+
return `${stickerState.up.flat().join('')}${stickerState.right.flat().join('')}${stickerState.front.flat().join('')}${stickerState.down.flat().join('')}${stickerState.left.flat().join('')}${stickerState.back.flat().join('')}`;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* @returns {StickerState}
|
|
234
285
|
*/
|
|
235
286
|
getStickerState() {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
down: [[], [], []],
|
|
239
|
-
front: [[], [], []],
|
|
240
|
-
back: [[], [], []],
|
|
241
|
-
left: [[], [], []],
|
|
242
|
-
right: [[], [], []],
|
|
243
|
-
};
|
|
287
|
+
/** @type {StickerState} */
|
|
288
|
+
let state = { up: [[], [], []], down: [[], [], []], front: [[], [], []], back: [[], [], []], left: [[], [], []], right: [[], [], []] };
|
|
244
289
|
this.group.children.forEach((piece) => {
|
|
245
290
|
if (piece.userData.type === 'core') {
|
|
246
291
|
return;
|
|
247
292
|
}
|
|
248
|
-
piece.children.forEach((
|
|
249
|
-
if (
|
|
293
|
+
piece.children.forEach((object3D) => {
|
|
294
|
+
if (object3D.userData.type === 'sticker') {
|
|
295
|
+
const mesh = /** @type {Mesh} */ (object3D);
|
|
250
296
|
const piecepos = new Vector3();
|
|
251
297
|
piecepos.copy(piece.userData.position);
|
|
252
298
|
piecepos.round();
|
|
@@ -255,18 +301,20 @@ export default class Cube {
|
|
|
255
301
|
stickerpos.sub(piecepos);
|
|
256
302
|
stickerpos.multiplyScalar(2);
|
|
257
303
|
stickerpos.round();
|
|
304
|
+
const material = /** @type {Material} */ (mesh.material);
|
|
305
|
+
const materialUserData = /** @type {import('../threejs/materials').StickerUserData} */ (material.userData);
|
|
258
306
|
if (stickerpos.x === 1) {
|
|
259
|
-
state.right[1 - Math.round(piecepos.y)][1 - Math.round(piecepos.z)] =
|
|
307
|
+
state.right[1 - Math.round(piecepos.y)][1 - Math.round(piecepos.z)] = materialUserData.face;
|
|
260
308
|
} else if (stickerpos.x === -1) {
|
|
261
|
-
state.left[1 - Math.round(piecepos.y)][1 + Math.round(piecepos.z)] =
|
|
309
|
+
state.left[1 - Math.round(piecepos.y)][1 + Math.round(piecepos.z)] = materialUserData.face;
|
|
262
310
|
} else if (stickerpos.y === 1) {
|
|
263
|
-
state.up[1 + Math.round(piecepos.z)][1 + Math.round(piecepos.x)] =
|
|
311
|
+
state.up[1 + Math.round(piecepos.z)][1 + Math.round(piecepos.x)] = materialUserData.face;
|
|
264
312
|
} else if (stickerpos.y === -1) {
|
|
265
|
-
state.down[1 - Math.round(piecepos.z)][1 + Math.round(piecepos.x)] =
|
|
313
|
+
state.down[1 - Math.round(piecepos.z)][1 + Math.round(piecepos.x)] = materialUserData.face;
|
|
266
314
|
} else if (stickerpos.z === 1) {
|
|
267
|
-
state.front[1 - Math.round(piecepos.y)][1 + Math.round(piecepos.x)] =
|
|
315
|
+
state.front[1 - Math.round(piecepos.y)][1 + Math.round(piecepos.x)] = materialUserData.face;
|
|
268
316
|
} else if (stickerpos.z === -1) {
|
|
269
|
-
state.back[1 - Math.round(piecepos.y)][1 - Math.round(piecepos.x)] =
|
|
317
|
+
state.back[1 - Math.round(piecepos.y)][1 - Math.round(piecepos.x)] = materialUserData.face;
|
|
270
318
|
}
|
|
271
319
|
}
|
|
272
320
|
});
|
package/src/cube/cubeRotation.js
CHANGED
|
@@ -1,20 +1,27 @@
|
|
|
1
|
+
// @ts-check
|
|
1
2
|
import { Vector3, Group } from 'three';
|
|
2
3
|
|
|
3
4
|
export class CubeRotation {
|
|
4
5
|
/**
|
|
5
6
|
* @param {string} eventId
|
|
6
|
-
* @param {
|
|
7
|
+
* @param {import('./slice').Slice} slice
|
|
8
|
+
* @param {((state: string) => void )} completedCallback
|
|
9
|
+
* @param {((reason: string) => void )} failedCallback
|
|
7
10
|
*/
|
|
8
|
-
constructor(eventId,
|
|
11
|
+
constructor(eventId, slice, completedCallback, failedCallback) {
|
|
12
|
+
/** @type {((state: string) => void )} */
|
|
13
|
+
this.completedCallback = completedCallback;
|
|
14
|
+
/** @type {((reason: string) => void )} */
|
|
15
|
+
this.failedCallback = failedCallback;
|
|
9
16
|
/** @type {string} */
|
|
10
17
|
this.eventId = eventId;
|
|
11
|
-
/** @type {
|
|
12
|
-
this.
|
|
13
|
-
/** @type {"pending" | "initialised" | "complete" | "disposed"} */
|
|
18
|
+
/** @type {import('./slice').Slice} */
|
|
19
|
+
this.slice = slice;
|
|
20
|
+
/** @type {"pending" | "initialised" | "inProgress" | "complete" | "disposed"} */
|
|
14
21
|
this.status = 'pending';
|
|
15
22
|
/** @type {number} */
|
|
16
23
|
this.timestampMs = performance.now();
|
|
17
|
-
/** @type {number} */
|
|
24
|
+
/** @type {number | undefined} */
|
|
18
25
|
this._lastUpdatedTimeMs = undefined;
|
|
19
26
|
/** @type {number} */
|
|
20
27
|
this._rotationPercentage = 0;
|
|
@@ -31,6 +38,15 @@ export class CubeRotation {
|
|
|
31
38
|
* @param {number} speedMs
|
|
32
39
|
*/
|
|
33
40
|
update(rotationGroup, speedMs) {
|
|
41
|
+
if (this.status === 'initialised') {
|
|
42
|
+
this.status = 'inProgress';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (this.status !== 'inProgress' || this._lastUpdatedTimeMs == null) {
|
|
46
|
+
console.error(`Cannot update cubeRotation. Status - [${this.status}]. LastUpdated - [${this._lastUpdatedTimeMs}].`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
34
50
|
var intervalMs = performance.now() - this._lastUpdatedTimeMs;
|
|
35
51
|
this._lastUpdatedTimeMs = performance.now();
|
|
36
52
|
|
|
@@ -41,13 +57,13 @@ export class CubeRotation {
|
|
|
41
57
|
increment = potentialIncrement;
|
|
42
58
|
}
|
|
43
59
|
}
|
|
44
|
-
const rotationIncrement = (Math.abs(this.
|
|
60
|
+
const rotationIncrement = (Math.abs(this.slice.direction) * ((increment / 100) * Math.PI)) / 2;
|
|
45
61
|
this._rotationPercentage += increment;
|
|
46
62
|
rotationGroup.rotateOnWorldAxis(
|
|
47
63
|
new Vector3(
|
|
48
|
-
this.
|
|
49
|
-
this.
|
|
50
|
-
this.
|
|
64
|
+
this.slice.axis === 'x' ? this.slice.direction : 0,
|
|
65
|
+
this.slice.axis === 'y' ? this.slice.direction : 0,
|
|
66
|
+
this.slice.axis === 'z' ? this.slice.direction : 0,
|
|
51
67
|
).normalize(),
|
|
52
68
|
rotationIncrement,
|
|
53
69
|
);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/** @typedef {"exponential" | "next" | "fixed" | "match"} AnimationStyle */
|
|
3
|
+
export default class CubeSettings {
|
|
4
|
+
/**
|
|
5
|
+
*
|
|
6
|
+
* @param {number} pieceGap
|
|
7
|
+
* @param {number} animationSpeed
|
|
8
|
+
* @param {AnimationStyle} animationStyle
|
|
9
|
+
*/
|
|
10
|
+
constructor(pieceGap, animationSpeed, animationStyle) {
|
|
11
|
+
/** @type {number} pieceGap */
|
|
12
|
+
this.pieceGap = pieceGap;
|
|
13
|
+
/** @type {number} pieceGap */
|
|
14
|
+
this.animationSpeedMs = animationSpeed;
|
|
15
|
+
/** @type {AnimationStyle} pieceGap */
|
|
16
|
+
this.animationStyle = animationStyle;
|
|
17
|
+
}
|
|
18
|
+
}
|
package/src/cube/cubeState.js
CHANGED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/** @typedef {{axis: import('../core').Axis, layers: (-1|0|1)[], direction: 1|-1|2|-2}} Slice */
|
|
3
|
+
|
|
4
|
+
import { Axi, Movements, Rotations } from '../core';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param {import("../core").Movement} movement
|
|
8
|
+
* @returns {Slice}
|
|
9
|
+
*/
|
|
10
|
+
export default function GetMovementSlice(movement) {
|
|
11
|
+
switch (movement) {
|
|
12
|
+
// Up
|
|
13
|
+
case Movements.U:
|
|
14
|
+
return { axis: Axi.y, layers: [1], direction: -1 };
|
|
15
|
+
case Movements.U2:
|
|
16
|
+
return { axis: Axi.y, layers: [1], direction: -2 };
|
|
17
|
+
case Movements.UP:
|
|
18
|
+
return { axis: Axi.y, layers: [1], direction: 1 };
|
|
19
|
+
case Movements.u:
|
|
20
|
+
return { axis: Axi.y, layers: [1, 0], direction: -1 };
|
|
21
|
+
case Movements.u2:
|
|
22
|
+
return { axis: Axi.y, layers: [1, 0], direction: -2 };
|
|
23
|
+
case Movements.uP:
|
|
24
|
+
return { axis: Axi.y, layers: [1, 0], direction: 1 };
|
|
25
|
+
// Down
|
|
26
|
+
case Movements.D:
|
|
27
|
+
return { axis: Axi.y, layers: [-1], direction: -1 };
|
|
28
|
+
case Movements.D2:
|
|
29
|
+
return { axis: Axi.y, layers: [-1], direction: -2 };
|
|
30
|
+
case Movements.DP:
|
|
31
|
+
return { axis: Axi.y, layers: [-1], direction: 1 };
|
|
32
|
+
case Movements.d:
|
|
33
|
+
return { axis: Axi.y, layers: [-1, 0], direction: -1 };
|
|
34
|
+
case Movements.d2:
|
|
35
|
+
return { axis: Axi.y, layers: [-1, 0], direction: -2 };
|
|
36
|
+
case Movements.dP:
|
|
37
|
+
return { axis: Axi.y, layers: [-1, 0], direction: 1 };
|
|
38
|
+
// Right
|
|
39
|
+
case Movements.R:
|
|
40
|
+
return { axis: Axi.x, layers: [1], direction: -1 };
|
|
41
|
+
case Movements.R2:
|
|
42
|
+
return { axis: Axi.x, layers: [1], direction: -2 };
|
|
43
|
+
case Movements.RP:
|
|
44
|
+
return { axis: Axi.x, layers: [1], direction: 1 };
|
|
45
|
+
case Movements.r:
|
|
46
|
+
return { axis: Axi.x, layers: [1, 0], direction: -1 };
|
|
47
|
+
case Movements.r2:
|
|
48
|
+
return { axis: Axi.x, layers: [1, 0], direction: -2 };
|
|
49
|
+
case Movements.rP:
|
|
50
|
+
return { axis: Axi.x, layers: [1, 0], direction: 1 };
|
|
51
|
+
// Left
|
|
52
|
+
case Movements.L:
|
|
53
|
+
return { axis: Axi.x, layers: [-1], direction: -1 };
|
|
54
|
+
case Movements.L2:
|
|
55
|
+
return { axis: Axi.x, layers: [-1], direction: -2 };
|
|
56
|
+
case Movements.LP:
|
|
57
|
+
return { axis: Axi.x, layers: [-1], direction: 1 };
|
|
58
|
+
case Movements.l:
|
|
59
|
+
return { axis: Axi.x, layers: [-1, 0], direction: -1 };
|
|
60
|
+
case Movements.l2:
|
|
61
|
+
return { axis: Axi.x, layers: [-1, 0], direction: -2 };
|
|
62
|
+
case Movements.lP:
|
|
63
|
+
return { axis: Axi.x, layers: [-1, 0], direction: 1 };
|
|
64
|
+
// Front
|
|
65
|
+
case Movements.F:
|
|
66
|
+
return { axis: Axi.z, layers: [1], direction: -1 };
|
|
67
|
+
case Movements.F2:
|
|
68
|
+
return { axis: Axi.z, layers: [1], direction: -2 };
|
|
69
|
+
case Movements.FP:
|
|
70
|
+
return { axis: Axi.z, layers: [1], direction: 1 };
|
|
71
|
+
case Movements.f:
|
|
72
|
+
return { axis: Axi.z, layers: [1, 0], direction: -1 };
|
|
73
|
+
case Movements.f2:
|
|
74
|
+
return { axis: Axi.z, layers: [1, 0], direction: -2 };
|
|
75
|
+
case Movements.fP:
|
|
76
|
+
return { axis: Axi.z, layers: [1, 0], direction: 1 };
|
|
77
|
+
// Back
|
|
78
|
+
case Movements.B:
|
|
79
|
+
return { axis: Axi.z, layers: [-1], direction: -1 };
|
|
80
|
+
case Movements.B2:
|
|
81
|
+
return { axis: Axi.z, layers: [-1], direction: -2 };
|
|
82
|
+
case Movements.BP:
|
|
83
|
+
return { axis: Axi.z, layers: [-1], direction: 1 };
|
|
84
|
+
case Movements.b:
|
|
85
|
+
return { axis: Axi.z, layers: [-1, 0], direction: -1 };
|
|
86
|
+
case Movements.b2:
|
|
87
|
+
return { axis: Axi.z, layers: [-1, 0], direction: -2 };
|
|
88
|
+
case Movements.bP:
|
|
89
|
+
return { axis: Axi.z, layers: [-1, 0], direction: 1 };
|
|
90
|
+
//Middle
|
|
91
|
+
case Movements.M:
|
|
92
|
+
return { axis: Axi.x, layers: [0], direction: -1 };
|
|
93
|
+
case Movements.M2:
|
|
94
|
+
return { axis: Axi.x, layers: [0], direction: -2 };
|
|
95
|
+
case Movements.MP:
|
|
96
|
+
return { axis: Axi.x, layers: [0], direction: 1 };
|
|
97
|
+
//Equator
|
|
98
|
+
case Movements.E:
|
|
99
|
+
return { axis: Axi.y, layers: [0], direction: -1 };
|
|
100
|
+
case Movements.E2:
|
|
101
|
+
return { axis: Axi.y, layers: [0], direction: -2 };
|
|
102
|
+
case Movements.EP:
|
|
103
|
+
return { axis: Axi.y, layers: [0], direction: 1 };
|
|
104
|
+
//Slice
|
|
105
|
+
case Movements.S:
|
|
106
|
+
return { axis: Axi.z, layers: [0], direction: -1 };
|
|
107
|
+
case Movements.S2:
|
|
108
|
+
return { axis: Axi.z, layers: [0], direction: -2 };
|
|
109
|
+
case Movements.SP:
|
|
110
|
+
return { axis: Axi.z, layers: [0], direction: 1 };
|
|
111
|
+
default:
|
|
112
|
+
throw Error(`Failed to get movement slice. invalid movement: ${movement}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @param {import("../core").Rotation} rotation
|
|
118
|
+
* @returns {Slice}
|
|
119
|
+
*/
|
|
120
|
+
export function GetRotationSlice(rotation) {
|
|
121
|
+
switch (rotation) {
|
|
122
|
+
case Rotations.x:
|
|
123
|
+
return { axis: Axi.x, layers: [], direction: -1 };
|
|
124
|
+
case Rotations.x2:
|
|
125
|
+
return { axis: Axi.x, layers: [], direction: -2 };
|
|
126
|
+
case Rotations.xP:
|
|
127
|
+
return { axis: Axi.x, layers: [], direction: 1 };
|
|
128
|
+
case Rotations.y:
|
|
129
|
+
return { axis: Axi.y, layers: [], direction: -1 };
|
|
130
|
+
case Rotations.y2:
|
|
131
|
+
return { axis: Axi.y, layers: [], direction: -2 };
|
|
132
|
+
case Rotations.yP:
|
|
133
|
+
return { axis: Axi.y, layers: [], direction: 1 };
|
|
134
|
+
case Rotations.z:
|
|
135
|
+
return { axis: Axi.z, layers: [], direction: -1 };
|
|
136
|
+
case Rotations.z2:
|
|
137
|
+
return { axis: Axi.z, layers: [], direction: -2 };
|
|
138
|
+
case Rotations.zP:
|
|
139
|
+
return { axis: Axi.z, layers: [], direction: 1 };
|
|
140
|
+
default:
|
|
141
|
+
throw Error(`Failed to get rotation slice. invalid rotation: ${rotation}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
package/src/debouncer.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* @param {number} delay
|
|
4
|
+
* @param {{ (entries: { contentRect: { width: number; height: number; }; }[]): void; apply?: any; }} f
|
|
5
|
+
*/
|
|
6
|
+
export function debounce(f, delay) {
|
|
7
|
+
let timer = 0;
|
|
8
|
+
/**
|
|
9
|
+
* @this {any}
|
|
10
|
+
* @param {any[]} args
|
|
11
|
+
*/
|
|
12
|
+
return function (...args) {
|
|
13
|
+
clearTimeout(timer);
|
|
14
|
+
timer = setTimeout(() => f.apply(this, args), delay);
|
|
15
|
+
};
|
|
16
|
+
}
|