@houstonp/rubiks-cube 1.5.2 → 2.1.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.
Files changed (51) hide show
  1. package/README.md +280 -170
  2. package/package.json +33 -3
  3. package/src/camera/cameraState.js +81 -0
  4. package/src/core.js +447 -0
  5. package/src/cube/animationSlice.js +205 -0
  6. package/src/cube/animationState.js +96 -0
  7. package/src/cube/cubeSettings.js +19 -0
  8. package/src/cube/cubeState.js +285 -139
  9. package/src/cube/stickerState.js +188 -0
  10. package/src/debouncer.js +16 -0
  11. package/src/globals.ts +9 -0
  12. package/src/index.js +621 -0
  13. package/src/settings.js +138 -0
  14. package/src/three/centerPiece.js +44 -0
  15. package/src/three/cornerPiece.js +60 -0
  16. package/src/three/cube.js +492 -0
  17. package/src/three/edgePiece.js +50 -0
  18. package/src/three/sticker.js +37 -0
  19. package/tests/common.js +27 -0
  20. package/tests/cube.five.test.js +126 -0
  21. package/tests/cube.four.test.js +126 -0
  22. package/tests/cube.seven.test.js +126 -0
  23. package/tests/cube.six.test.js +126 -0
  24. package/tests/cube.three.test.js +151 -0
  25. package/tests/cube.two.test.js +125 -0
  26. package/tests/setup.js +36 -0
  27. package/types/camera/cameraState.d.ts +19 -0
  28. package/types/core.d.ts +454 -0
  29. package/types/cube/animationSlice.d.ts +26 -0
  30. package/types/cube/animationState.d.ts +41 -0
  31. package/types/cube/cubeSettings.d.ts +17 -0
  32. package/types/cube/cubeState.d.ts +47 -0
  33. package/types/cube/stickerState.d.ts +21 -0
  34. package/types/debouncer.d.ts +13 -0
  35. package/types/globals.d.ts +7 -0
  36. package/types/index.d.ts +87 -0
  37. package/types/settings.d.ts +38 -0
  38. package/types/three/centerPiece.d.ts +15 -0
  39. package/types/three/cornerPiece.d.ts +24 -0
  40. package/types/three/cube.d.ts +130 -0
  41. package/types/three/edgePiece.d.ts +16 -0
  42. package/types/three/sticker.d.ts +15 -0
  43. package/.prettierrc +0 -7
  44. package/index.js +0 -274
  45. package/src/cube/cube.js +0 -276
  46. package/src/cube/cubeRotation.js +0 -63
  47. package/src/threejs/materials.js +0 -42
  48. package/src/threejs/pieces.js +0 -103
  49. package/src/threejs/stickers.js +0 -48
  50. package/src/utils/debouncer.js +0 -7
  51. package/src/utils/rotation.js +0 -53
package/index.js DELETED
@@ -1,274 +0,0 @@
1
- import { Scene, WebGLRenderer, PerspectiveCamera, AmbientLight, DirectionalLight, Spherical } from 'three';
2
- import { OrbitControls } from 'three/examples/jsm/Addons.js';
3
- import Cube from './src/cube/cube';
4
- import getRotationDetailsFromNotation from './src/utils/rotation';
5
- import { debounce } from './src/utils/debouncer';
6
- import gsap from 'gsap';
7
-
8
- /** @typedef {{ animationStyle: "exponential" | "next" | "fixed" | "match", animationSpeedMs: number, pieceGap: number, cameraSpeedMs: number, cameraRadius: number, cameraPeekAngleHorizontal: number, cameraPeekAngleVertical: number, cameraFieldOfView: number }} Settings */
9
- /** @type {Settings} */
10
- const defaultSettings = {
11
- animationSpeedMs: 100,
12
- animationStyle: 'fixed',
13
- pieceGap: 1.04,
14
- cameraSpeedMs: 100,
15
- cameraRadius: 5,
16
- cameraPeekAngleHorizontal: 0.6,
17
- cameraPeekAngleVertical: 0.6,
18
- cameraFieldOfView: 75,
19
- };
20
- const minGap = 1;
21
- const minRadius = 4;
22
- const minFieldOfView = 30;
23
- const maxFieldOfView = 100;
24
- const maxAzimuthAngle = (5 * Math.PI) / 16;
25
- const polarAngleOffset = Math.PI / 2;
26
- const maxPolarAngle = (5 * Math.PI) / 16;
27
-
28
- class RubiksCube extends HTMLElement {
29
- constructor() {
30
- super();
31
- this.attachShadow({ mode: 'open' });
32
- this.shadowRoot.innerHTML = `<canvas id="cube-canvas" style="display:block;"></canvas>`;
33
- this.canvas = this.shadowRoot.getElementById('cube-canvas');
34
- /** @type {Settings} */
35
- this.settings = {
36
- animationSpeedMs: this.getAttribute('animation-speed-ms') || defaultSettings.animationSpeedMs,
37
- animationStyle: this.getAttribute('animation-style') || defaultSettings.animationStyle,
38
- pieceGap: this.getAttribute('piece-gap') || defaultSettings.pieceGap,
39
- cameraSpeedMs: this.getAttribute('camera-speed-ms') || defaultSettings.cameraSpeedMs,
40
- cameraRadius: this.getAttribute('camera-radius') || defaultSettings.cameraRadius,
41
- cameraPeekAngleHorizontal: this.getAttribute('camera-peek-angle-horizontal') || defaultSettings.cameraPeekAngleHorizontal,
42
- cameraPeekAngleVertical: this.getAttribute('camera-peek-angle-vertical') || defaultSettings.cameraPeekAngleVertical,
43
- cameraFieldOfView: this.getAttribute('camera-field-of-view') || defaultSettings.cameraFieldOfView,
44
- };
45
- }
46
-
47
- static get observedAttributes() {
48
- return [
49
- 'animation-style',
50
- 'animation-speed-ms',
51
- 'piece-gap',
52
- 'camera-speed-ms',
53
- 'camera-radius',
54
- 'camera-peek-angle-horizontal',
55
- 'camera-peek-angle-vertical',
56
- 'camera-field-of-view',
57
- ];
58
- }
59
-
60
- attributeChangedCallback(name, oldVal, newVal) {
61
- if (name === 'animation-style') {
62
- this.settings.animationStyle = newVal;
63
- }
64
- if (name === 'animation-speed-ms') {
65
- var speed = Number(newVal);
66
- this.settings.animationSpeedMs = speed > 0 ? speed : 0;
67
- }
68
- if (name === 'piece-gap') {
69
- var gap = Number(newVal);
70
- this.settings.pieceGap = gap < minGap ? minGap : gap;
71
- }
72
- if (name === 'camera-speed-ms') {
73
- var speed = Number(newVal);
74
- this.settings.cameraSpeedMs = speed > 0 ? speed : 0;
75
- }
76
- if (name === 'camera-radius') {
77
- var radius = Number(newVal);
78
- this.settings.cameraRadius = radius < minRadius ? minRadius : radius;
79
- if (oldVal !== newVal && oldVal !== null) {
80
- this.dispatchEvent(new CustomEvent('cameraSettingsChanged'));
81
- }
82
- }
83
- if (name === 'camera-peek-angle-horizontal') {
84
- var angle = Number(newVal);
85
- angle = angle > 0 ? angle : 0;
86
- angle = angle < 1 ? angle : 1;
87
- this.settings.cameraPeekAngleHorizontal = angle > 0 ? angle : 0;
88
- if (oldVal !== newVal && oldVal !== null) {
89
- this.dispatchEvent(new CustomEvent('cameraSettingsChanged'));
90
- }
91
- }
92
- if (name === 'camera-peek-angle-vertical') {
93
- var angle = Number(newVal);
94
- angle = angle > 0 ? angle : 0;
95
- angle = angle < 1 ? angle : 1;
96
- this.settings.cameraPeekAngleVertical = angle;
97
- if (oldVal !== newVal && oldVal !== null) {
98
- this.dispatchEvent(new CustomEvent('cameraSettingsChanged'));
99
- }
100
- }
101
- if (name == 'camera-field-of-view') {
102
- var fov = Number(newVal);
103
- fov = fov > minFieldOfView ? fov : minFieldOfView;
104
- fov = fov < maxFieldOfView ? fov : maxFieldOfView;
105
- this.settings.cameraFieldOfView = fov;
106
- if (oldVal !== newVal && oldVal !== null) {
107
- this.dispatchEvent(new CustomEvent('cameraFieldOfViewChanged'));
108
- }
109
- }
110
- }
111
-
112
- connectedCallback() {
113
- this.init();
114
- }
115
-
116
- init() {
117
- // defined core threejs objects
118
- const canvas = this.canvas;
119
- const scene = new Scene();
120
- const renderer = new WebGLRenderer({
121
- alpha: true,
122
- canvas,
123
- antialias: true,
124
- });
125
- renderer.setSize(this.clientWidth, this.clientHeight);
126
- renderer.setAnimationLoop(animate);
127
- renderer.setPixelRatio(2);
128
-
129
- //update renderer and camera when container resizes. debouncing events to reduce frequency
130
- new ResizeObserver(
131
- debounce((entries) => {
132
- const { width, height } = entries[0].contentRect;
133
- camera.aspect = width / height;
134
- camera.updateProjectionMatrix();
135
- renderer.setSize(width, height);
136
- }, 30),
137
- ).observe(this);
138
-
139
- // add camera
140
- const camera = new PerspectiveCamera(this.settings.cameraFieldOfView, this.clientWidth / this.clientHeight, 1, 2000);
141
- const cameraSpherical = new Spherical(50, (3 * Math.PI) / 8, -Math.PI / 4);
142
- camera.position.setFromSpherical(cameraSpherical);
143
- /** @type {{ Up: boolean, Right: boolean }} */
144
- const cameraState = { Up: true, Right: true };
145
-
146
- // add orbit controls for camera
147
- const controls = new OrbitControls(camera, renderer.domElement);
148
- controls.enableZoom = false;
149
- controls.enablePan = false;
150
- controls.enableDamping = true;
151
- controls.maxAzimuthAngle = maxAzimuthAngle;
152
- controls.minAzimuthAngle = -maxAzimuthAngle;
153
- controls.maxPolarAngle = polarAngleOffset + maxPolarAngle;
154
- controls.minPolarAngle = polarAngleOffset - maxPolarAngle;
155
-
156
- // add lighting to scene
157
- const ambientLight = new AmbientLight('white', 0.5);
158
- const spotLight1 = new DirectionalLight('white', 2);
159
- const spotLight2 = new DirectionalLight('white', 2);
160
- const spotLight3 = new DirectionalLight('white', 2);
161
- const spotLight4 = new DirectionalLight('white', 2);
162
- spotLight1.position.set(5, 5, 5);
163
- spotLight2.position.set(-5, 5, 5);
164
- spotLight3.position.set(5, -5, 0);
165
- spotLight4.position.set(-10, -5, -5);
166
- scene.add(ambientLight, spotLight1, spotLight2, spotLight3, spotLight4);
167
-
168
- // create cube and add to scene
169
- const cube = new Cube(this.settings);
170
- scene.add(cube.group, cube.rotationGroup);
171
-
172
- const sendState = (eventId) => {
173
- const event = new CustomEvent('state', { detail: { eventId, state: cube.currentState } });
174
- this.dispatchEvent(event);
175
- };
176
-
177
- // animation loop
178
- function animate() {
179
- controls.update();
180
-
181
- var eventId = cube.update();
182
- if (eventId) {
183
- sendState(eventId);
184
- }
185
- renderer.render(scene, camera);
186
- }
187
-
188
- // add event listeners for rotation and camera controls
189
- this.addEventListener('reset', () => {
190
- cube.reset();
191
- sendState('reset');
192
- });
193
-
194
- this.addEventListener('action', (e) => {
195
- /** @type {{eventId: string, action: {type: "movement" | "camera" | "rotation", actionId: string }}} move */
196
- var move = e.detail.move;
197
- if (move.action.type === 'camera') {
198
- updateCameraState(move.action.actionId);
199
- updateCameraPosition();
200
- return;
201
- }
202
- if (move.action.type === 'movement' || move.action.type === 'rotation') {
203
- handleRotationAction(move.eventId, move.action.actionId);
204
- return;
205
- }
206
- });
207
-
208
- /**
209
- * @param {string} eventId
210
- * @param {string} actionId
211
- */
212
- const handleRotationAction = (eventId, actionId) => {
213
- const rotationDetails = getRotationDetailsFromNotation(actionId);
214
- if (rotationDetails !== undefined) {
215
- cube.rotate(eventId, rotationDetails);
216
- }
217
- };
218
-
219
- /**
220
- * @param {'peek-toggle-horizontal' | 'peek-toggle-vertical' | 'peek-right' | 'peek-left' | 'peek-up' | 'peek-down' } actionId
221
- */
222
- const updateCameraState = (actionId) => {
223
- if (actionId === 'peek-toggle-horizontal') {
224
- cameraState.Right = !cameraState.Right;
225
- } else if (actionId === 'peek-toggle-vertical') {
226
- cameraState.Up = !cameraState.Up;
227
- } else if (actionId === 'peek-right') {
228
- cameraState.Right = true;
229
- } else if (actionId === 'peek-left') {
230
- cameraState.Right = false;
231
- } else if (actionId === 'peek-up') {
232
- cameraState.Up = true;
233
- } else if (actionId === 'peek-down') {
234
- cameraState.Up = false;
235
- }
236
- };
237
-
238
- /**
239
- * @param {number | null} cameraSpeedMs
240
- * @param {gsap.EaseString | gsap.EaseFunction | undefined} ease
241
- */
242
- const updateCameraPosition = (cameraSpeedMs = this.settings.cameraSpeedMs, ease = 'none') => {
243
- cameraSpeedMs = cameraSpeedMs ? cameraSpeedMs : this.settings.cameraSpeedMs;
244
- var phi = polarAngleOffset + (cameraState.Up ? -this.settings.cameraPeekAngleVertical : this.settings.cameraPeekAngleVertical) * maxPolarAngle;
245
- var theta = (cameraState.Right ? this.settings.cameraPeekAngleHorizontal : -this.settings.cameraPeekAngleHorizontal) * maxAzimuthAngle;
246
- const startSpherical = new Spherical().setFromVector3(camera.position);
247
- const targetSpherical = new Spherical(this.settings.cameraRadius, phi, theta);
248
- gsap.to(startSpherical, {
249
- radius: targetSpherical.radius,
250
- theta: targetSpherical.theta,
251
- phi: targetSpherical.phi,
252
- duration: cameraSpeedMs / 1000,
253
- ease: ease,
254
- onUpdate: function () {
255
- camera.position.setFromSpherical(startSpherical);
256
- camera.lookAt(cube.group.position);
257
- controls.update();
258
- },
259
- });
260
- };
261
-
262
- this.addEventListener('cameraSettingsChanged', () => {
263
- updateCameraPosition(); // animate settings changes
264
- });
265
-
266
- this.addEventListener('cameraFieldOfViewChanged', () => {
267
- camera.fov = this.settings.cameraFieldOfView;
268
- camera.updateProjectionMatrix();
269
- });
270
-
271
- updateCameraPosition(1000, 'power4.inOut'); // initial animation
272
- }
273
- }
274
- customElements.define('rubiks-cube', RubiksCube);
package/src/cube/cube.js DELETED
@@ -1,276 +0,0 @@
1
- import { Group, Vector3 } from 'three';
2
- import { createCoreMesh } from '../threejs/pieces';
3
- import { createCubeState } from './cubeState';
4
- import { CubeRotation } from './cubeRotation';
5
-
6
- export default class Cube {
7
- /**
8
- * @param {import('../..').Settings} settings
9
- */
10
- constructor(settings) {
11
- /** @type {import('../..').Settings} */
12
- this.settings = settings;
13
- /** @type {Group} */
14
- this.group = this.createCubeGroup();
15
- /** @type {Group} */
16
- this.rotationGroup = new Group();
17
- /** @type {CubeRotation[]} */
18
- this.rotationQueue = [];
19
- /** @type {CubeRotation | undefined} */
20
- this.currentRotation = undefined;
21
- /** @type {{ up: string[][], down: string[][], front: string[][], back: string[][], left: string[][], right: string[][] }} */
22
- this.currentState = this.getStickerState();
23
- /** @type {number | undefined} */
24
- this._matchSpeed = undefined;
25
- /** @type {number} */
26
- this._lastGap = settings.gap;
27
- }
28
-
29
- /**
30
- * creates a ThreeJS group with all the required pieces for a cube
31
- * @param {Group} group
32
- * @returns {Group}
33
- */
34
- createCubeGroup(group) {
35
- var group = new Group();
36
- const core = createCoreMesh();
37
- core.userData = {
38
- position: { x: 0, y: 0, z: 0 },
39
- rotation: { x: 0, y: 0, z: 0 },
40
- initialPosition: { x: 0, y: 0, z: 0 },
41
- initialRotation: { x: 0, y: 0, z: 0 },
42
- type: 'core',
43
- };
44
- group.add(core);
45
-
46
- for (const piece of createCubeState()) {
47
- var pieceGroup = piece.group;
48
- pieceGroup.position.set(
49
- piece.position.x * this.settings.pieceGap,
50
- piece.position.y * this.settings.pieceGap,
51
- piece.position.z * this.settings.pieceGap,
52
- );
53
- pieceGroup.rotation.set(piece.rotation.x, piece.rotation.y, piece.rotation.z);
54
- pieceGroup.userData = {
55
- position: Object.assign({}, piece.position),
56
- rotation: Object.assign({}, piece.rotation),
57
- initialPosition: Object.assign({}, piece.position),
58
- initialRotation: Object.assign({}, piece.rotation),
59
- type: piece.type,
60
- };
61
- group.add(pieceGroup);
62
- }
63
- return group;
64
- }
65
-
66
- /**
67
- * update the cube and continue any rotations
68
- * @returns {string}
69
- */
70
- update() {
71
- if (this.currentRotation === undefined) {
72
- if (this._lastGap !== this.settings.pieceGap) {
73
- this.updateGap();
74
- }
75
- this.currentRotation = this.rotationQueue.shift();
76
- if (this.currentRotation === undefined) {
77
- this._matchSpeed = undefined; // reset speed for the match animation options
78
- return undefined;
79
- }
80
- }
81
- if (this.currentRotation.status === 'pending') {
82
- this.rotationGroup.add(...this.getRotationLayer(this.currentRotation.rotation));
83
- this.currentRotation.initialise();
84
- }
85
- if (this.currentRotation.status === 'initialised') {
86
- var speed = this.getRotationSpeed();
87
- this.currentRotation.update(this.rotationGroup, speed);
88
- }
89
- if (this.currentRotation.status === 'complete') {
90
- this.clearRotationGroup();
91
- var eventId = this.currentRotation.eventId;
92
- this.currentRotation = undefined;
93
- this.currentState = this.getStickerState();
94
- return eventId;
95
- }
96
- return undefined;
97
- }
98
-
99
- /**
100
- * Updates the gap of the pieces. To be used when the cube is not rotating
101
- * @returns {void}
102
- */
103
- updateGap() {
104
- if (this.currentRotation === undefined) {
105
- this.group.children.forEach((piece) => {
106
- var { x, y, z } = piece.userData.position;
107
- piece.position.set(x * this.settings.pieceGap, y * this.settings.pieceGap, z * this.settings.pieceGap);
108
- });
109
- this._lastGap = this.settings.pieceGap;
110
- }
111
- }
112
-
113
- /**
114
- *
115
- * calculates the current speed of the current rotation in ms.
116
- * calculation is dependent on animation style and animation speed settings
117
- * - exponential: speeds up rotations depending on the queue length
118
- * - next: an animation speed of 0 when there is another animation in the queue
119
- * - match: will match the speed of rotations to the frequency of key presses.
120
- * - fixed: will return a constant value
121
- * @returns {number}
122
- */
123
- getRotationSpeed() {
124
- if (this.settings.animationStyle === 'exponential') {
125
- return this.settings.animationSpeedMs / 2 ** this.rotationQueue.length;
126
- }
127
- if (this.settings.animationStyle === 'next') {
128
- return this.rotationQueue.length > 0 ? 0 : this.settings.animationSpeedMs;
129
- }
130
- if (this.settings.animationStyle === 'match') {
131
- if (this.rotationQueue.length > 0) {
132
- var lastTimeStamp = this.currentRotation.timestampMs;
133
- var minGap = this._matchSpeed ?? this.settings.animationSpeedMs;
134
- for (var i = 0; i < this.rotationQueue.length; i++) {
135
- var gap = this.rotationQueue[i].timestampMs - lastTimeStamp;
136
- if (gap < minGap) {
137
- minGap = gap;
138
- }
139
- }
140
- this._matchSpeed = minGap;
141
- }
142
- if (this._matchSpeed !== undefined) {
143
- return this._matchSpeed;
144
- }
145
- return this.settings.animationSpeedMs;
146
- }
147
- if (this.settings.animationStyle === 'fixed') {
148
- return this.settings.animationSpeedMs;
149
- }
150
- return this.settings.animationSpeedMs;
151
- }
152
-
153
- /**
154
- * Complete the current rotation and reset the cube
155
- * @returns {void}
156
- */
157
- reset() {
158
- this.rotationQueue = [];
159
- if (this.currentRotation) {
160
- this.currentRotation.update(this.rotationGroup, 0);
161
- this.clearRotationGroup();
162
- this.currentRotation = undefined;
163
- }
164
- this.group.children.forEach((piece) => {
165
- const { x, y, z } = piece.userData.initialPosition;
166
- const { x: u, y: v, z: w } = piece.userData.initialRotation;
167
- piece.position.set(x * this.settings.pieceGap, y * this.settings.pieceGap, z * this.settings.pieceGap);
168
- piece.rotation.set(u, v, w);
169
- piece.userData.position.x = x;
170
- piece.userData.position.y = y;
171
- piece.userData.position.z = z;
172
- piece.userData.rotation.x = u;
173
- piece.userData.rotation.y = v;
174
- piece.userData.rotation.z = w;
175
- });
176
- }
177
-
178
- /**
179
- * Adds pieces in the rotationGroup back into the main group.
180
- * @returns {void}
181
- */
182
- clearRotationGroup() {
183
- if (this.currentRotation.status != 'complete') {
184
- throw Error('cannot clear rotation group while rotating');
185
- }
186
- this.rotationGroup.children.forEach((piece) => {
187
- piece.getWorldPosition(piece.position);
188
- piece.getWorldQuaternion(piece.quaternion);
189
- var x = Math.round(piece.position.x);
190
- var y = Math.round(piece.position.y);
191
- var z = Math.round(piece.position.z);
192
- piece.userData.position.x = Math.abs(x) > 1 ? Math.sign(x) : x;
193
- piece.userData.position.y = Math.abs(y) > 1 ? Math.sign(y) : y;
194
- piece.userData.position.z = Math.abs(z) > 1 ? Math.sign(z) : z;
195
- piece.userData.rotation.x = piece.rotation.x;
196
- piece.userData.rotation.y = piece.rotation.y;
197
- piece.userData.rotation.z = piece.rotation.z;
198
- });
199
- this.group.add(...this.rotationGroup.children);
200
- this.rotationGroup.rotation.set(0, 0, 0);
201
- this.currentRotation.status = 'disposed';
202
- }
203
-
204
- /**
205
- * @param {string} eventId
206
- * @param {{axis: "x"|"y"|"z", layers: (-1|0|1)[], direction: 1|-1|2|-2}} input
207
- */
208
- rotate(eventId, input) {
209
- this.rotationQueue.push(new CubeRotation(eventId, input));
210
- }
211
-
212
- /**
213
- * @param {{axis: "x"|"y"|"z", layers: (-1|0|1)[], direction: 1|-1|2|-2}}
214
- * @returns {Object3D[]}
215
- */
216
- getRotationLayer({ axis, layers, direction }) {
217
- if (layers.length === 0) {
218
- return [...this.group.children];
219
- }
220
- return this.group.children.filter((piece) => {
221
- if (axis === 'x') {
222
- return layers.includes(Math.round(piece.userData.position.x));
223
- } else if (axis === 'y') {
224
- return layers.includes(Math.round(piece.userData.position.y));
225
- } else if (axis === 'z') {
226
- return layers.includes(Math.round(piece.userData.position.z));
227
- }
228
- return false;
229
- });
230
- }
231
-
232
- /**
233
- * @returns {{ up: string[][], down: string[][], front: string[][], back: string[][], left: string[][], right: string[][] }}
234
- */
235
- getStickerState() {
236
- const state = {
237
- up: [[], [], []],
238
- down: [[], [], []],
239
- front: [[], [], []],
240
- back: [[], [], []],
241
- left: [[], [], []],
242
- right: [[], [], []],
243
- };
244
- this.group.children.forEach((piece) => {
245
- if (piece.userData.type === 'core') {
246
- return;
247
- }
248
- piece.children.forEach((mesh) => {
249
- if (mesh.userData.type === 'sticker') {
250
- const piecepos = new Vector3();
251
- piecepos.copy(piece.userData.position);
252
- piecepos.round();
253
- const stickerpos = new Vector3();
254
- mesh.getWorldPosition(stickerpos);
255
- stickerpos.sub(piecepos);
256
- stickerpos.multiplyScalar(2);
257
- stickerpos.round();
258
- if (stickerpos.x === 1) {
259
- state.right[1 - Math.round(piecepos.y)][1 - Math.round(piecepos.z)] = mesh.material.userData.face;
260
- } else if (stickerpos.x === -1) {
261
- state.left[1 - Math.round(piecepos.y)][1 + Math.round(piecepos.z)] = mesh.material.userData.face;
262
- } else if (stickerpos.y === 1) {
263
- state.up[1 + Math.round(piecepos.z)][1 + Math.round(piecepos.x)] = mesh.material.userData.face;
264
- } else if (stickerpos.y === -1) {
265
- state.down[1 - Math.round(piecepos.z)][1 + Math.round(piecepos.x)] = mesh.material.userData.face;
266
- } else if (stickerpos.z === 1) {
267
- state.front[1 - Math.round(piecepos.y)][1 + Math.round(piecepos.x)] = mesh.material.userData.face;
268
- } else if (stickerpos.z === -1) {
269
- state.back[1 - Math.round(piecepos.y)][1 - Math.round(piecepos.x)] = mesh.material.userData.face;
270
- }
271
- }
272
- });
273
- });
274
- return state;
275
- }
276
- }
@@ -1,63 +0,0 @@
1
- import { Vector3, Group } from 'three';
2
-
3
- export class CubeRotation {
4
- /**
5
- * @param {string} eventId
6
- * @param {{axis: "x"|"y"|"z", layers: (-1|0|1)[], direction: 1|-1|2|-2}} rotationDetails
7
- */
8
- constructor(eventId, rotationDetails) {
9
- /** @type {string} */
10
- this.eventId = eventId;
11
- /** @type {{axis: "x"|"y"|"z", layers: (-1|0|1)[], direction: 1|-1|2|-2}} */
12
- this.rotation = rotationDetails;
13
- /** @type {"pending" | "initialised" | "complete" | "disposed"} */
14
- this.status = 'pending';
15
- /** @type {number} */
16
- this.timestampMs = performance.now();
17
- /** @type {number} */
18
- this._lastUpdatedTimeMs = undefined;
19
- /** @type {number} */
20
- this._rotationPercentage = 0;
21
- }
22
-
23
- initialise() {
24
- this._lastUpdatedTimeMs = performance.now();
25
- this.status = 'initialised';
26
- }
27
-
28
- /**
29
- *
30
- * @param {Group} rotationGroup
31
- * @param {number} speedMs
32
- */
33
- update(rotationGroup, speedMs) {
34
- var intervalMs = performance.now() - this._lastUpdatedTimeMs;
35
- this._lastUpdatedTimeMs = performance.now();
36
-
37
- var increment = 100 - this._rotationPercentage;
38
- if (speedMs != 0) {
39
- var potentialIncrement = (intervalMs / speedMs) * 100;
40
- if (potentialIncrement + this._rotationPercentage < 100) {
41
- increment = potentialIncrement;
42
- }
43
- }
44
- const rotationIncrement = (Math.abs(this.rotation.direction) * ((increment / 100) * Math.PI)) / 2;
45
- this._rotationPercentage += increment;
46
- rotationGroup.rotateOnWorldAxis(
47
- new Vector3(
48
- this.rotation.axis === 'x' ? this.rotation.direction : 0,
49
- this.rotation.axis === 'y' ? this.rotation.direction : 0,
50
- this.rotation.axis === 'z' ? this.rotation.direction : 0,
51
- ).normalize(),
52
- rotationIncrement,
53
- );
54
-
55
- if (this._rotationPercentage === 100) {
56
- this.status = 'complete';
57
- }
58
-
59
- if (this._rotationPercentage > 100) {
60
- throw new Error('rotation percentage > 100');
61
- }
62
- }
63
- }
@@ -1,42 +0,0 @@
1
- import { MeshStandardMaterial, MeshBasicMaterial } from "three";
2
- export default class Materials {
3
- static front = new MeshStandardMaterial({
4
- color: "#2cbf13",
5
- metalness: 0,
6
- roughness: 0.4,
7
- userData: { face: "front", color: "green" },
8
- });
9
- static back = new MeshStandardMaterial({
10
- color: "blue",
11
- metalness: 0,
12
- roughness: 0.4,
13
- userData: { face: "back", color: "blue" },
14
- });
15
- static up = new MeshStandardMaterial({
16
- color: "white",
17
- metalness: 0,
18
- roughness: 0.4,
19
- userData: { face: "up", color: "white" },
20
- });
21
- static down = new MeshStandardMaterial({
22
- color: "yellow",
23
- metalness: 0,
24
- roughness: 0.4,
25
- userData: { face: "down", color: "yellow" },
26
- });
27
- static left = new MeshStandardMaterial({
28
- color: "#fc9a05",
29
- metalness: 0,
30
- roughness: 0.4,
31
- userData: { face: "left", color: "orange" },
32
- });
33
- static right = new MeshStandardMaterial({
34
- color: "red",
35
- metalness: 0,
36
- roughness: 0.4,
37
- userData: { face: "right", color: "red" },
38
- });
39
- static core = new MeshBasicMaterial({
40
- color: "black",
41
- });
42
- }