@houstonp/rubiks-cube 2.0.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 +494 -63
- package/package.json +22 -12
- package/src/core/index.js +478 -0
- package/src/rubiksCube/index.js +3 -0
- package/src/rubiksCube/rubiksCubeController.js +111 -0
- package/src/rubiksCube3D/centerPiece.js +79 -0
- package/src/rubiksCube3D/cornerPiece.js +114 -0
- package/src/rubiksCube3D/cubeConfig.js +87 -0
- package/src/rubiksCube3D/cubeSettings.js +30 -0
- package/src/rubiksCube3D/edgePiece.js +51 -0
- package/src/rubiksCube3D/index.js +3 -0
- package/src/rubiksCube3D/rubiksCube3D.js +383 -0
- package/src/rubiksCube3D/sticker.js +38 -0
- 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/{cameraState.js → webComponent/cameraState.js} +17 -25
- package/src/webComponent/constants.js +67 -0
- package/src/{debouncer.js → webComponent/debouncer.js} +1 -1
- package/src/webComponent/index.js +7 -0
- package/src/webComponent/rubiksCubeElement.js +379 -0
- package/src/{settings.js → webComponent/settings.js} +47 -22
- package/tests/common.js +10 -0
- 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/setup.js +36 -0
- package/tests/testScrambles.js +194 -0
- package/types/core/index.d.ts +451 -0
- 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/rubiksCube3D/edgePiece.d.ts +18 -0
- 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} +9 -8
- package/src/core.js +0 -127
- package/src/cube/cube.js +0 -324
- package/src/cube/cubeRotation.js +0 -79
- package/src/cube/cubeSettings.js +0 -18
- package/src/cube/cubeState.js +0 -192
- package/src/cube/slice.js +0 -143
- package/src/index.js +0 -496
- package/src/schema.js +0 -22
- package/src/threejs/materials.js +0 -54
- package/src/threejs/pieces.js +0 -100
- package/src/threejs/stickers.js +0 -40
- package/types/cameraState.d.ts +0 -19
- package/types/core.d.ts +0 -125
- package/types/cube/cube.d.ts +0 -102
- package/types/cube/cubeRotation.d.ts +0 -33
- package/types/cube/cubeSettings.d.ts +0 -17
- package/types/cube/cubeState.d.ts +0 -16
- package/types/cube/slice.d.ts +0 -15
- package/types/index.d.ts +0 -65
- package/types/schema.d.ts +0 -11
- package/types/threejs/materials.d.ts +0 -21
- package/types/threejs/pieces.d.ts +0 -28
- package/types/threejs/stickers.d.ts +0 -6
- /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
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { Group, Mesh, MeshBasicMaterial, Object3D, Vector3 } from 'three';
|
|
3
|
+
import { CornerPiece } from './cornerPiece';
|
|
4
|
+
import RubiksCube3DSettings from './cubeSettings';
|
|
5
|
+
import { ColorToFace, FaceColors, getCubeConfig } from './cubeConfig';
|
|
6
|
+
import { EdgePiece } from './edgePiece';
|
|
7
|
+
import { CenterPiece } from './centerPiece';
|
|
8
|
+
import { defaultStickerState, getEmptyStickerState, getStickerFaceIndex } from '../state/stickerState';
|
|
9
|
+
import { CubeTypes, Faces } from '../core';
|
|
10
|
+
import { Axi } from '../state/slice';
|
|
11
|
+
import { RoundedBoxGeometry } from 'three/examples/jsm/Addons.js';
|
|
12
|
+
import { centers, corners, edges } from '../state/rubiksCubeState';
|
|
13
|
+
import { gsap } from 'gsap';
|
|
14
|
+
/** @import {RubiksCubeViewInterface as _RubiksCubeViewInterface} from '../rubiksCube/rubiksCubeController' */
|
|
15
|
+
/** @typedef {_RubiksCubeViewInterface} RubiksCubeViewInterface */
|
|
16
|
+
/** @import {CubeType} from '../core' */
|
|
17
|
+
/** @import {CubeConfig} from './cubeConfig' */
|
|
18
|
+
/** @import {StickerState} from '../state/stickerState' */
|
|
19
|
+
/** @import {Slice} from '../state/slice' */
|
|
20
|
+
|
|
21
|
+
const ERROR_MARGIN = 0.0001;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @implements {RubiksCubeViewInterface}
|
|
25
|
+
*/
|
|
26
|
+
export default class RubiksCube3D extends Object3D {
|
|
27
|
+
/**
|
|
28
|
+
* @public
|
|
29
|
+
* @param {RubiksCube3DSettings} cubeSettings
|
|
30
|
+
*/
|
|
31
|
+
constructor(cubeSettings) {
|
|
32
|
+
super();
|
|
33
|
+
/** @type {RubiksCube3DSettings} */
|
|
34
|
+
this._cubeSettings = cubeSettings ?? new RubiksCube3DSettings();
|
|
35
|
+
/** @type {number} */
|
|
36
|
+
this._pieceGap = this._cubeSettings.pieceGap;
|
|
37
|
+
/** @type {CubeType} */
|
|
38
|
+
this._cubeType = this._cubeSettings.cubeType;
|
|
39
|
+
/** @type {CubeConfig} */
|
|
40
|
+
this._cubeConfig = getCubeConfig(this._cubeType);
|
|
41
|
+
/** @type {Group} */
|
|
42
|
+
this._mainGroup = this.createCubeGroup();
|
|
43
|
+
/** @type {Group} */
|
|
44
|
+
this._animationGroup = new Group();
|
|
45
|
+
/** @type {GSAPAnimation | undefined} */
|
|
46
|
+
this._currentAnimation = undefined;
|
|
47
|
+
|
|
48
|
+
this.add(this._mainGroup, this._animationGroup);
|
|
49
|
+
this.setStickerState(defaultStickerState(this._cubeType));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Creates the main group containing all the pieces of the cube in their default position and rotation. Should only be called once during initialization.
|
|
54
|
+
* @private
|
|
55
|
+
**/
|
|
56
|
+
createCubeGroup() {
|
|
57
|
+
const cubeInfo = this._cubeConfig;
|
|
58
|
+
const pieceGap = this._pieceGap;
|
|
59
|
+
const outerLayerMultiplier = cubeInfo.outerLayerMultiplier;
|
|
60
|
+
const outerLayerOffset = (cubeInfo.pieceSize * (outerLayerMultiplier - 1)) / 2;
|
|
61
|
+
const group = new Group();
|
|
62
|
+
const core = new Mesh(
|
|
63
|
+
new RoundedBoxGeometry(2 * cubeInfo.coreSize, 2 * cubeInfo.coreSize, 2 * cubeInfo.coreSize, 3, 0.75),
|
|
64
|
+
new MeshBasicMaterial({ color: 'black' }),
|
|
65
|
+
);
|
|
66
|
+
group.add(core);
|
|
67
|
+
for (const piece of corners(this._cubeConfig.layers)) {
|
|
68
|
+
const corner = new CornerPiece();
|
|
69
|
+
corner.scale.set(cubeInfo.pieceSize * outerLayerMultiplier, cubeInfo.pieceSize * outerLayerMultiplier, cubeInfo.pieceSize * outerLayerMultiplier);
|
|
70
|
+
corner.position.set(
|
|
71
|
+
piece.position.x * (pieceGap + outerLayerOffset),
|
|
72
|
+
piece.position.y * (pieceGap + outerLayerOffset),
|
|
73
|
+
piece.position.z * (pieceGap + outerLayerOffset),
|
|
74
|
+
);
|
|
75
|
+
corner.rotation.set(piece.rotation.x, piece.rotation.y, piece.rotation.z);
|
|
76
|
+
corner.userData = {
|
|
77
|
+
position: Object.assign({}, piece.position),
|
|
78
|
+
rotation: Object.assign({}, piece.rotation),
|
|
79
|
+
};
|
|
80
|
+
group.add(corner);
|
|
81
|
+
}
|
|
82
|
+
for (const piece of edges(this._cubeConfig.layers)) {
|
|
83
|
+
const edge = new EdgePiece();
|
|
84
|
+
edge.scale.set(cubeInfo.pieceSize, cubeInfo.pieceSize * outerLayerMultiplier, cubeInfo.pieceSize * outerLayerMultiplier);
|
|
85
|
+
edge.position.set(
|
|
86
|
+
piece.position.x * (pieceGap + (Math.abs(piece.position.x) === 1 ? outerLayerOffset : 0)),
|
|
87
|
+
piece.position.y * (pieceGap + (Math.abs(piece.position.y) === 1 ? outerLayerOffset : 0)),
|
|
88
|
+
piece.position.z * (pieceGap + (Math.abs(piece.position.z) === 1 ? outerLayerOffset : 0)),
|
|
89
|
+
);
|
|
90
|
+
edge.rotation.set(piece.rotation.x, piece.rotation.y, piece.rotation.z);
|
|
91
|
+
edge.userData = {
|
|
92
|
+
position: Object.assign({}, piece.position),
|
|
93
|
+
rotation: Object.assign({}, piece.rotation),
|
|
94
|
+
};
|
|
95
|
+
group.add(edge);
|
|
96
|
+
}
|
|
97
|
+
for (const piece of centers(this._cubeConfig.layers)) {
|
|
98
|
+
const center = new CenterPiece();
|
|
99
|
+
center.scale.set(cubeInfo.pieceSize, cubeInfo.pieceSize, cubeInfo.pieceSize * outerLayerMultiplier);
|
|
100
|
+
center.position.set(
|
|
101
|
+
piece.position.x * (pieceGap + (Math.abs(piece.position.x) === 1 ? outerLayerOffset : 0)),
|
|
102
|
+
piece.position.y * (pieceGap + (Math.abs(piece.position.y) === 1 ? outerLayerOffset : 0)),
|
|
103
|
+
piece.position.z * (pieceGap + (Math.abs(piece.position.z) === 1 ? outerLayerOffset : 0)),
|
|
104
|
+
);
|
|
105
|
+
center.rotation.set(piece.rotation.x, piece.rotation.y, piece.rotation.z);
|
|
106
|
+
center.userData = {
|
|
107
|
+
position: Object.assign({}, piece.position),
|
|
108
|
+
rotation: Object.assign({}, piece.rotation),
|
|
109
|
+
};
|
|
110
|
+
group.add(center);
|
|
111
|
+
}
|
|
112
|
+
return group;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @param {Group} group
|
|
117
|
+
* @returns {(CornerPiece | EdgePiece | CenterPiece)[]}
|
|
118
|
+
*/
|
|
119
|
+
_getPieces(group = this._mainGroup) {
|
|
120
|
+
return group.children.filter((x) => x instanceof CornerPiece || x instanceof EdgePiece || x instanceof CenterPiece);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Returns the sticker state of the cube. Can only be called when an Animation is not in progress as not all pieces would be in the main group.
|
|
125
|
+
* @returns {StickerState}
|
|
126
|
+
*/
|
|
127
|
+
getStickerState() {
|
|
128
|
+
let state = getEmptyStickerState(this._cubeType);
|
|
129
|
+
this._getPieces().forEach((piece) => {
|
|
130
|
+
piece.stickers.forEach((sticker) => {
|
|
131
|
+
const color = ColorToFace(sticker.color);
|
|
132
|
+
const piecepos = new Vector3();
|
|
133
|
+
piecepos.copy(piece.userData.position);
|
|
134
|
+
const stickerpos = new Vector3();
|
|
135
|
+
sticker.getWorldPosition(stickerpos);
|
|
136
|
+
stickerpos.sub(piecepos);
|
|
137
|
+
stickerpos.normalize();
|
|
138
|
+
stickerpos.round();
|
|
139
|
+
const { face, i, j } = getStickerFaceIndex(stickerpos, piece.userData.position, this._cubeConfig.layers);
|
|
140
|
+
state[face][i][j] = color;
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
return state;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Sets the sticker state of the cube. Can only be called when an Animation is not in progress as not all pieces would be in the main group.
|
|
148
|
+
* @private
|
|
149
|
+
* @param {StickerState} stickerState
|
|
150
|
+
*/
|
|
151
|
+
setStickerState(stickerState) {
|
|
152
|
+
this._getPieces().forEach((piece) => {
|
|
153
|
+
piece.stickers.forEach((sticker) => {
|
|
154
|
+
const piecepos = new Vector3();
|
|
155
|
+
piecepos.copy(piece.userData.position);
|
|
156
|
+
const stickerpos = new Vector3();
|
|
157
|
+
sticker.getWorldPosition(stickerpos);
|
|
158
|
+
stickerpos.sub(piecepos);
|
|
159
|
+
stickerpos.normalize();
|
|
160
|
+
stickerpos.round();
|
|
161
|
+
const { face, i, j } = getStickerFaceIndex(stickerpos, piece.userData.position, this._cubeConfig.layers);
|
|
162
|
+
const stickerFaceValue = stickerState[face][i][j];
|
|
163
|
+
sticker.color = FaceColors[stickerFaceValue];
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
if (this._cubeSettings.logo) {
|
|
167
|
+
this.addLogo(this._cubeSettings.logo);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
*
|
|
172
|
+
* @param {string} logo
|
|
173
|
+
*/
|
|
174
|
+
addLogo(logo) {
|
|
175
|
+
this._mainGroup.children.filter((x) => x instanceof CornerPiece || x instanceof CenterPiece).forEach((x) => x.removeLogo());
|
|
176
|
+
|
|
177
|
+
if (this._cubeType === CubeTypes.Two) {
|
|
178
|
+
const corner = this._mainGroup.children
|
|
179
|
+
.filter((x) => x instanceof CornerPiece)
|
|
180
|
+
.filter((x) => x.frontSticker.color === FaceColors.U || x.topSticker.color === FaceColors.U || x.rightSticker.color === FaceColors.U)
|
|
181
|
+
.at(-1);
|
|
182
|
+
if (corner?.frontSticker.color === FaceColors.U) {
|
|
183
|
+
corner.addLogo(Faces.F, logo);
|
|
184
|
+
}
|
|
185
|
+
if (corner?.topSticker.color === FaceColors.U) {
|
|
186
|
+
corner.addLogo(Faces.U, logo);
|
|
187
|
+
}
|
|
188
|
+
if (corner?.rightSticker.color === FaceColors.U) {
|
|
189
|
+
corner.addLogo(Faces.R, logo);
|
|
190
|
+
}
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const center = this._mainGroup.children
|
|
194
|
+
.filter((x) => x instanceof CenterPiece)
|
|
195
|
+
.filter((x) => x.frontSticker.color === FaceColors.U)
|
|
196
|
+
.filter((x) => {
|
|
197
|
+
const layerCount = this._cubeConfig.layers.length;
|
|
198
|
+
const outerLayers = [this._cubeConfig.layers.at(0), this._cubeConfig.layers.at(layerCount - 1)];
|
|
199
|
+
let centerLayers = [];
|
|
200
|
+
if (layerCount % 2 === 1) {
|
|
201
|
+
centerLayers = [this._cubeConfig.layers.at(Math.floor(layerCount / 2))];
|
|
202
|
+
} else {
|
|
203
|
+
centerLayers = this._cubeConfig.layers.slice(layerCount / 2 - 1, layerCount / 2 + 1);
|
|
204
|
+
}
|
|
205
|
+
return [x.userData.position.x, x.userData.position.y, x.userData.position.z].every(
|
|
206
|
+
(layerValue) => centerLayers.includes(layerValue) || outerLayers.includes(layerValue),
|
|
207
|
+
);
|
|
208
|
+
})
|
|
209
|
+
.at(0);
|
|
210
|
+
center?.addLogo(logo);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Returns the pieces that should be rotated for a given slice. If the slice has no layers, all pieces will be returned. Should only be called before an Animation is started.
|
|
215
|
+
* @private
|
|
216
|
+
* @param {Slice} slice
|
|
217
|
+
* @returns {Object3D[]}
|
|
218
|
+
*/
|
|
219
|
+
getRotationLayer(slice) {
|
|
220
|
+
return this._getPieces().filter((piece) => {
|
|
221
|
+
switch (slice.axis) {
|
|
222
|
+
case Axi.x:
|
|
223
|
+
return slice.layerIds.map((id) => this._cubeConfig.layers[id]).some((layer) => Math.abs(layer - piece.userData.position.x) < ERROR_MARGIN);
|
|
224
|
+
case Axi.y:
|
|
225
|
+
return slice.layerIds.map((id) => this._cubeConfig.layers[id]).some((layer) => Math.abs(layer - piece.userData.position.y) < ERROR_MARGIN);
|
|
226
|
+
case Axi.z:
|
|
227
|
+
return slice.layerIds.map((id) => this._cubeConfig.layers[id]).some((layer) => Math.abs(layer - piece.userData.position.z) < ERROR_MARGIN);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Updates the gap of the pieces. To be used when the cube is not rotating
|
|
234
|
+
* @private
|
|
235
|
+
* @param {number} pieceGap
|
|
236
|
+
* @returns {void}
|
|
237
|
+
*/
|
|
238
|
+
updateGap(pieceGap) {
|
|
239
|
+
this._currentAnimation?.progress(1);
|
|
240
|
+
this._pieceGap = pieceGap;
|
|
241
|
+
const outerLayerMultiplier = this._cubeConfig.outerLayerMultiplier;
|
|
242
|
+
const outerLayerOffset = (this._cubeConfig.pieceSize * (outerLayerMultiplier - 1)) / 2;
|
|
243
|
+
this._getPieces().forEach((piece) => {
|
|
244
|
+
let xOuterLayer = Math.abs(Math.abs(piece.userData.position.x) - 1) < ERROR_MARGIN;
|
|
245
|
+
let yOuterLayer = Math.abs(Math.abs(piece.userData.position.y) - 1) < ERROR_MARGIN;
|
|
246
|
+
let zOuterLayer = Math.abs(Math.abs(piece.userData.position.z) - 1) < ERROR_MARGIN;
|
|
247
|
+
piece.position.set(
|
|
248
|
+
piece.userData.position.x * (pieceGap + (xOuterLayer ? outerLayerOffset : 0)),
|
|
249
|
+
piece.userData.position.y * (pieceGap + (yOuterLayer ? outerLayerOffset : 0)),
|
|
250
|
+
piece.userData.position.z * (pieceGap + (zOuterLayer ? outerLayerOffset : 0)),
|
|
251
|
+
);
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Adds pieces in the rotationGroup back into the main group.
|
|
257
|
+
* Updates the position and rotation of the pieces according to their world position and rotation, then resets the rotation of the rotation group.
|
|
258
|
+
* Should only be called when a rotation is in progress.
|
|
259
|
+
* @private
|
|
260
|
+
* @returns {void}
|
|
261
|
+
*/
|
|
262
|
+
clearAnimationGroup() {
|
|
263
|
+
const cubeInfo = this._cubeConfig;
|
|
264
|
+
const pieceGap = this._pieceGap;
|
|
265
|
+
const outerLayerMultiplier = cubeInfo.outerLayerMultiplier;
|
|
266
|
+
const outerLayerOffset = (cubeInfo.pieceSize * (outerLayerMultiplier - 1)) / 2;
|
|
267
|
+
const middleLayers = cubeInfo.layers.slice(1, -1);
|
|
268
|
+
this._getPieces(this._animationGroup).forEach((piece) => {
|
|
269
|
+
piece.getWorldPosition(piece.position);
|
|
270
|
+
piece.getWorldQuaternion(piece.quaternion);
|
|
271
|
+
if (middleLayers.some((layer) => Math.abs(layer - piece.position.x / pieceGap) < ERROR_MARGIN)) {
|
|
272
|
+
piece.userData.position.x = piece.position.x / pieceGap;
|
|
273
|
+
} else {
|
|
274
|
+
piece.userData.position.x = piece.position.x / (pieceGap + outerLayerOffset);
|
|
275
|
+
}
|
|
276
|
+
if (middleLayers.some((layer) => Math.abs(layer - piece.position.y / pieceGap) < ERROR_MARGIN)) {
|
|
277
|
+
piece.userData.position.y = piece.position.y / pieceGap;
|
|
278
|
+
} else {
|
|
279
|
+
piece.userData.position.y = piece.position.y / (pieceGap + outerLayerOffset);
|
|
280
|
+
}
|
|
281
|
+
if (middleLayers.some((layer) => Math.abs(layer - piece.position.z / pieceGap) < ERROR_MARGIN)) {
|
|
282
|
+
piece.userData.position.z = piece.position.z / pieceGap;
|
|
283
|
+
} else {
|
|
284
|
+
piece.userData.position.z = piece.position.z / (pieceGap + outerLayerOffset);
|
|
285
|
+
}
|
|
286
|
+
piece.userData.rotation.x = piece.rotation.x;
|
|
287
|
+
piece.userData.rotation.y = piece.rotation.y;
|
|
288
|
+
piece.userData.rotation.z = piece.rotation.z;
|
|
289
|
+
});
|
|
290
|
+
this._mainGroup.add(...this._animationGroup.children);
|
|
291
|
+
this._animationGroup.rotation.set(0, 0, 0);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* @private
|
|
296
|
+
* @param {Slice} slice
|
|
297
|
+
*/
|
|
298
|
+
fillAnimationGroup(slice) {
|
|
299
|
+
const pieces = this.getRotationLayer(slice);
|
|
300
|
+
this._animationGroup.add(...pieces);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* @public
|
|
305
|
+
* @param {number} animationSpeedMs
|
|
306
|
+
*/
|
|
307
|
+
setCurrentAnimationSpeed(animationSpeedMs) {
|
|
308
|
+
this._currentAnimation?.duration(animationSpeedMs / 1000);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* @public
|
|
313
|
+
*/
|
|
314
|
+
reset() {
|
|
315
|
+
this._currentAnimation?.progress(1);
|
|
316
|
+
this.setStickerState(defaultStickerState(this._cubeType));
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* @public
|
|
321
|
+
* @param {StickerState} stickerState
|
|
322
|
+
*/
|
|
323
|
+
setState(stickerState) {
|
|
324
|
+
this._currentAnimation?.progress(1);
|
|
325
|
+
this.setStickerState(stickerState);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* sets the state of the cube
|
|
330
|
+
* @public
|
|
331
|
+
* @param {CubeType} cubeType
|
|
332
|
+
* @returns {void}
|
|
333
|
+
*/
|
|
334
|
+
setType(cubeType) {
|
|
335
|
+
if (!Object.values(CubeTypes).includes(cubeType)) {
|
|
336
|
+
throw new Error(`Invalid cube type: ${cubeType}`);
|
|
337
|
+
}
|
|
338
|
+
this._currentAnimation?.progress(1);
|
|
339
|
+
this._cubeType = cubeType;
|
|
340
|
+
this._cubeConfig = getCubeConfig(cubeType);
|
|
341
|
+
this.remove(this._mainGroup);
|
|
342
|
+
this._mainGroup = this.createCubeGroup();
|
|
343
|
+
this.add(this._mainGroup);
|
|
344
|
+
this.setStickerState(defaultStickerState(cubeType));
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* @public
|
|
349
|
+
* @param {Slice} slice
|
|
350
|
+
* @param {{animationSpeedMs: number?, ease: gsap.EaseString | gsap.EaseFunction | undefined}} [options]
|
|
351
|
+
* @returns {Promise<void>}
|
|
352
|
+
*/
|
|
353
|
+
slice(slice, options) {
|
|
354
|
+
return new Promise((resolve, reject) => {
|
|
355
|
+
this._currentAnimation?.progress(1);
|
|
356
|
+
this.fillAnimationGroup(slice);
|
|
357
|
+
const target = { rotation: 0 };
|
|
358
|
+
const animationGroup = this._animationGroup;
|
|
359
|
+
let previousRotation = 0;
|
|
360
|
+
this._currentAnimation = gsap.to(target, {
|
|
361
|
+
rotation: (Math.abs(slice.direction) * Math.PI) / 2,
|
|
362
|
+
duration: (options?.animationSpeedMs ?? this._cubeSettings.animationSpeedMs) / 1000,
|
|
363
|
+
ease: options?.ease ?? this._cubeSettings.animationStyle ?? 'sine.out',
|
|
364
|
+
onComplete: () => {
|
|
365
|
+
this.clearAnimationGroup();
|
|
366
|
+
resolve();
|
|
367
|
+
},
|
|
368
|
+
onUpdate: () => {
|
|
369
|
+
const delta = target.rotation - (previousRotation || 0);
|
|
370
|
+
animationGroup.rotateOnWorldAxis(
|
|
371
|
+
new Vector3(
|
|
372
|
+
slice.axis === Axi.x ? slice.direction : 0,
|
|
373
|
+
slice.axis === Axi.y ? slice.direction : 0,
|
|
374
|
+
slice.axis === Axi.z ? slice.direction : 0,
|
|
375
|
+
).normalize(),
|
|
376
|
+
delta,
|
|
377
|
+
);
|
|
378
|
+
previousRotation = target.rotation;
|
|
379
|
+
},
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { Mesh, MeshStandardMaterial } from 'three';
|
|
3
|
+
/** @import {BufferGeometry, ColorRepresentation} from 'three' */
|
|
4
|
+
|
|
5
|
+
export class Sticker extends Mesh {
|
|
6
|
+
/**
|
|
7
|
+
* @param {BufferGeometry} geometry
|
|
8
|
+
*/
|
|
9
|
+
constructor(geometry) {
|
|
10
|
+
super(
|
|
11
|
+
geometry,
|
|
12
|
+
new MeshStandardMaterial({
|
|
13
|
+
color: 'white',
|
|
14
|
+
metalness: 0,
|
|
15
|
+
roughness: 0.4,
|
|
16
|
+
}),
|
|
17
|
+
);
|
|
18
|
+
/** @type {{ color: ColorRepresentation }} */
|
|
19
|
+
this.userData = { color: 'white' };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {ColorRepresentation} color
|
|
24
|
+
*/
|
|
25
|
+
set color(color) {
|
|
26
|
+
const material = /** @type {MeshStandardMaterial} */ (this.material);
|
|
27
|
+
material.color.set(color);
|
|
28
|
+
this.userData.color = color;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @returns {ColorRepresentation} color
|
|
33
|
+
*/
|
|
34
|
+
get color() {
|
|
35
|
+
const material = /** @type {MeshStandardMaterial} */ (this.material);
|
|
36
|
+
return this.userData.color;
|
|
37
|
+
}
|
|
38
|
+
}
|