@houstonp/rubiks-cube 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +304 -77
  2. package/package.json +18 -8
  3. package/src/{core.js → core/index.js} +72 -41
  4. package/src/rubiksCube/index.js +3 -0
  5. package/src/rubiksCube/rubiksCubeController.js +111 -0
  6. package/src/{three → rubiksCube3D}/centerPiece.js +37 -2
  7. package/src/{three → rubiksCube3D}/cornerPiece.js +56 -2
  8. package/src/rubiksCube3D/cubeConfig.js +87 -0
  9. package/src/rubiksCube3D/cubeSettings.js +30 -0
  10. package/src/{three → rubiksCube3D}/edgePiece.js +2 -1
  11. package/src/rubiksCube3D/index.js +3 -0
  12. package/src/rubiksCube3D/rubiksCube3D.js +383 -0
  13. package/src/{three → rubiksCube3D}/sticker.js +5 -4
  14. package/src/state/index.js +4 -0
  15. package/src/state/rubiksCubeState.js +471 -0
  16. package/src/state/slice.js +236 -0
  17. package/src/state/stickerState.js +185 -0
  18. package/src/{camera → webComponent}/cameraState.js +17 -25
  19. package/src/webComponent/constants.js +67 -0
  20. package/src/webComponent/index.js +7 -0
  21. package/src/webComponent/rubiksCubeElement.js +379 -0
  22. package/src/{settings.js → webComponent/settings.js} +36 -23
  23. package/tests/common.js +3 -20
  24. package/tests/core.test.js +56 -0
  25. package/tests/rubiksCube.solves.test.js +41 -0
  26. package/tests/rubiksCube3D.solves.test.js +185 -0
  27. package/tests/rubiksCubeState.solves.test.js +35 -0
  28. package/tests/testScrambles.js +194 -0
  29. package/types/{core.d.ts → core/index.d.ts} +45 -48
  30. package/types/rubiksCube/index.d.ts +3 -0
  31. package/types/rubiksCube/rubiksCubeController.d.ts +62 -0
  32. package/types/rubiksCube3D/centerPiece.d.ts +27 -0
  33. package/types/rubiksCube3D/cornerPiece.d.ts +38 -0
  34. package/types/rubiksCube3D/cubeConfig.d.ts +32 -0
  35. package/types/rubiksCube3D/cubeSettings.d.ts +33 -0
  36. package/types/{three → rubiksCube3D}/edgePiece.d.ts +5 -3
  37. package/types/rubiksCube3D/index.d.ts +3 -0
  38. package/types/rubiksCube3D/rubiksCube3D.d.ts +120 -0
  39. package/types/rubiksCube3D/sticker.d.ts +18 -0
  40. package/types/state/index.d.ts +5 -0
  41. package/types/state/rubiksCubeState.d.ts +108 -0
  42. package/types/state/slice.d.ts +46 -0
  43. package/types/state/stickerState.d.ts +34 -0
  44. package/types/webComponent/cameraState.d.ts +22 -0
  45. package/types/webComponent/constants.d.ts +57 -0
  46. package/types/webComponent/index.d.ts +6 -0
  47. package/types/webComponent/rubiksCubeElement.d.ts +89 -0
  48. package/types/{settings.d.ts → webComponent/settings.d.ts} +5 -8
  49. package/src/cube/animationSlice.js +0 -205
  50. package/src/cube/animationState.js +0 -96
  51. package/src/cube/cubeSettings.js +0 -19
  52. package/src/cube/cubeState.js +0 -337
  53. package/src/cube/stickerState.js +0 -188
  54. package/src/index.js +0 -621
  55. package/src/three/cube.js +0 -492
  56. package/tests/cube.five.test.js +0 -126
  57. package/tests/cube.four.test.js +0 -126
  58. package/tests/cube.seven.test.js +0 -126
  59. package/tests/cube.six.test.js +0 -126
  60. package/tests/cube.three.test.js +0 -151
  61. package/tests/cube.two.test.js +0 -125
  62. package/types/camera/cameraState.d.ts +0 -19
  63. package/types/cube/animationSlice.d.ts +0 -26
  64. package/types/cube/animationState.d.ts +0 -41
  65. package/types/cube/cubeSettings.d.ts +0 -17
  66. package/types/cube/cubeState.d.ts +0 -47
  67. package/types/cube/stickerState.d.ts +0 -21
  68. package/types/index.d.ts +0 -87
  69. package/types/three/centerPiece.d.ts +0 -15
  70. package/types/three/cornerPiece.d.ts +0 -24
  71. package/types/three/cube.d.ts +0 -130
  72. package/types/three/sticker.d.ts +0 -15
  73. /package/src/{debouncer.js → webComponent/debouncer.js} +0 -0
  74. /package/src/{globals.ts → webComponent/globals.ts} +0 -0
  75. /package/types/{debouncer.d.ts → webComponent/debouncer.d.ts} +0 -0
  76. /package/types/{globals.d.ts → webComponent/globals.d.ts} +0 -0
package/src/index.js DELETED
@@ -1,621 +0,0 @@
1
- // @ts-check
2
- /// <reference path="./globals.ts" preserve="true" />
3
- import { Scene, PerspectiveCamera, AmbientLight, DirectionalLight, Spherical, WebGLRenderer } from 'three';
4
- import { OrbitControls } from 'three/examples/jsm/Addons.js';
5
- import RubiksCube3D from './three/cube';
6
- import { debounce } from './debouncer';
7
- import { gsap } from 'gsap';
8
- import Settings from './settings';
9
- import CubeSettings from './cube/cubeSettings';
10
- import { CameraState } from './camera/cameraState';
11
- import { PeekTypes, Rotations } from './core';
12
-
13
- const maxAzimuthAngle = (5 * Math.PI) / 16;
14
- const polarAngleOffset = Math.PI / 2;
15
- const maxPolarAngle = (5 * Math.PI) / 16;
16
- const InternalEvents = Object.freeze({
17
- rotation: 'rotation',
18
- rotationComplete: 'rotationComplete',
19
- rotationFailed: 'rotationFailed',
20
- movement: 'movement',
21
- movementComplete: 'movementComplete',
22
- movementFailed: 'movementFailed',
23
- reset: 'reset',
24
- resetComplete: 'resetComplete',
25
- cameraRadiusChanged: 'cameraRadiusChanged',
26
- cameraSettingsChanged: 'cameraSettingsChanged',
27
- cameraFieldOfViewChanged: 'cameraFieldOfViewChanged',
28
- cameraPeek: 'cameraPeek',
29
- cameraPeekComplete: 'cameraPeekComplete',
30
- setState: 'setState',
31
- setStateComplete: 'setStateComplete',
32
- setStateFailed: 'setStateFailed',
33
- });
34
-
35
- /**
36
- * @typedef {typeof AttributeNames[keyof typeof AttributeNames]} AttributeName
37
- */
38
- export const AttributeNames = {
39
- /** @type {"cube-type"} */
40
- cubeType: 'cube-type',
41
- /** @type {"piece-gap"} */
42
- pieceGap: 'piece-gap',
43
- /** @type {"animation-speed-ms"} */
44
- animationSpeed: 'animation-speed-ms',
45
- /** @type {"animation-style"} */
46
- animationStyle: 'animation-style',
47
- /** @type {"camera-speed-ms"} */
48
- cameraSpeed: 'camera-speed-ms',
49
- /** @type {"camera-radius"} */
50
- cameraRadius: 'camera-radius',
51
- /** @type {"camera-field-of-view"} */
52
- cameraFieldOfView: 'camera-field-of-view',
53
- /** @type {"camera-peek-angle-horizontal"} */
54
- cameraPeekAngleHorizontal: 'camera-peek-angle-horizontal',
55
- /** @type {"camera-peek-angle-vertical"} */
56
- cameraPeekAngleVertical: 'camera-peek-angle-vertical',
57
- };
58
-
59
- export class RubiksCubeElement extends HTMLElement {
60
- constructor() {
61
- super();
62
- this.attachShadow({ mode: 'open' });
63
- const root = /** @type {ShadowRoot} */ (this.shadowRoot);
64
- root.innerHTML = `<canvas id="cube-canvas" style="display:block;"></canvas>`;
65
- /** @private @type {HTMLCanvasElement} */
66
- this.canvas = /** @type {HTMLCanvasElement} */ (root.getElementById('cube-canvas'));
67
- /** @private @type {Settings} */
68
- this.settings = new Settings();
69
- /** @private @type {CubeSettings} */
70
- this.cubeSettings = new CubeSettings(this.settings.pieceGap, this.settings.animationSpeedMs, this.settings.animationStyle, this.settings.cubeType);
71
- }
72
-
73
- /**
74
- * @param {string} tagName the name of the tag to register the web component under
75
- */
76
- static register(tagName = 'rubiks-cube') {
77
- customElements.define(tagName, this);
78
- }
79
-
80
- static get observedAttributes() {
81
- return [
82
- AttributeNames.cubeType,
83
- AttributeNames.pieceGap,
84
- AttributeNames.animationSpeed,
85
- AttributeNames.animationStyle,
86
- AttributeNames.cameraSpeed,
87
- AttributeNames.cameraRadius,
88
- AttributeNames.cameraFieldOfView,
89
- AttributeNames.cameraPeekAngleHorizontal,
90
- AttributeNames.cameraPeekAngleVertical,
91
- ];
92
- }
93
-
94
- /**
95
- * @param {string} name
96
- * @param {string} oldVal
97
- * @param {string} newVal
98
- * */
99
- attributeChangedCallback(name, oldVal, newVal) {
100
- switch (name) {
101
- case AttributeNames.cubeType:
102
- this.settings.setCubeType(newVal);
103
- this.cubeSettings.cubeType = this.settings.cubeType;
104
- break;
105
- case AttributeNames.pieceGap:
106
- this.settings.setPieceGap(newVal);
107
- this.cubeSettings.pieceGap = this.settings.pieceGap;
108
- break;
109
- case AttributeNames.animationSpeed:
110
- this.settings.setAnimationSpeed(newVal);
111
- this.cubeSettings.animationSpeedMs = this.settings.animationSpeedMs;
112
- break;
113
- case AttributeNames.animationStyle:
114
- this.settings.setAnimationStyle(newVal);
115
- this.cubeSettings.animationStyle = this.settings.animationStyle;
116
- break;
117
- case AttributeNames.cameraSpeed:
118
- this.settings.setCameraSpeed(newVal);
119
- break;
120
- case AttributeNames.cameraRadius:
121
- this.settings.setCameraRadius(newVal);
122
- if (oldVal !== newVal && oldVal !== null) {
123
- this.animateCameraRadius();
124
- }
125
- break;
126
- case AttributeNames.cameraFieldOfView:
127
- this.settings.setCameraFieldOfView(newVal);
128
- if (oldVal !== newVal && oldVal !== null) {
129
- this.updateCameraFOV();
130
- }
131
- break;
132
- case AttributeNames.cameraPeekAngleHorizontal:
133
- this.settings.setCameraPeekAngleHorizontal(newVal);
134
- if (oldVal !== newVal && oldVal !== null) {
135
- this.animateCameraSetting();
136
- }
137
- break;
138
- case AttributeNames.cameraPeekAngleVertical:
139
- this.settings.setCameraPeekAngleVertical(newVal);
140
- if (oldVal !== newVal && oldVal !== null) {
141
- this.animateCameraSetting();
142
- }
143
- break;
144
- }
145
- }
146
-
147
- connectedCallback() {
148
- if (this.hasAttribute(AttributeNames.cubeType)) {
149
- this.settings.setCubeType(this.getAttribute(AttributeNames.cubeType));
150
- this.cubeSettings.cubeType = this.settings.cubeType;
151
- }
152
- if (this.hasAttribute(AttributeNames.pieceGap)) {
153
- this.settings.setPieceGap(this.getAttribute(AttributeNames.pieceGap));
154
- this.cubeSettings.pieceGap = this.settings.pieceGap;
155
- }
156
- if (this.hasAttribute(AttributeNames.animationSpeed)) {
157
- this.settings.setAnimationSpeed(this.getAttribute(AttributeNames.animationSpeed));
158
- this.cubeSettings.animationSpeedMs = this.settings.animationSpeedMs;
159
- }
160
- if (this.hasAttribute(AttributeNames.animationStyle)) {
161
- this.settings.setAnimationStyle(this.getAttribute(AttributeNames.animationStyle));
162
- this.cubeSettings.animationStyle = this.settings.animationStyle;
163
- }
164
- if (this.hasAttribute(AttributeNames.cameraSpeed)) {
165
- this.settings.setCameraSpeed(this.getAttribute(AttributeNames.cameraSpeed));
166
- }
167
- if (this.hasAttribute(AttributeNames.cameraRadius)) {
168
- this.settings.setCameraRadius(this.getAttribute(AttributeNames.cameraRadius));
169
- }
170
- if (this.hasAttribute(AttributeNames.cameraFieldOfView)) {
171
- this.settings.setCameraFieldOfView(this.getAttribute(AttributeNames.cameraFieldOfView));
172
- }
173
- if (this.hasAttribute(AttributeNames.cameraPeekAngleHorizontal)) {
174
- this.settings.setCameraPeekAngleHorizontal(this.getAttribute(AttributeNames.cameraPeekAngleHorizontal));
175
- }
176
- if (this.hasAttribute(AttributeNames.cameraPeekAngleVertical)) {
177
- this.settings.setCameraPeekAngleVertical(this.getAttribute(AttributeNames.cameraPeekAngleVertical));
178
- }
179
- this.init();
180
- }
181
-
182
- /** @private */
183
- animateCameraSetting() {
184
- this.dispatchEvent(new CustomEvent(InternalEvents.cameraSettingsChanged));
185
- }
186
-
187
- /** @private */
188
- animateCameraRadius() {
189
- this.dispatchEvent(new CustomEvent(InternalEvents.cameraRadiusChanged));
190
- }
191
-
192
- /** @private */
193
- updateCameraFOV() {
194
- this.dispatchEvent(new CustomEvent(InternalEvents.cameraFieldOfViewChanged));
195
- }
196
-
197
- /** @import {Movement} from './core' */
198
- /** @internal @typedef {{eventId: string, move: Movement}} MovementEvent */
199
- /** @internal @typedef {{eventId: string, move: Movement, state: string}} MovementCompleteEventData */
200
- /** @internal @typedef {{eventId: string, move: Movement, reason: string}} MovementFailedEventData */
201
- /**
202
- * @param {Movement} move
203
- * @returns {Promise<string>}
204
- */
205
- move(move) {
206
- const regex = /^([23456])?([RLUDFB]w|[RLUDFBMES]|[rludfb])([123])?(\')?$/;
207
- if (!regex.test(move)) {
208
- return Promise.reject(`Invalid move - [${move}]. Valid move does not match pattern /^([23456])?([RLUDFB]w|[RLUDFBMES]|[rludfb])([123])?(\')?$/ `);
209
- }
210
- /** @type {MovementEvent} */
211
- const data = { eventId: crypto.randomUUID(), move };
212
- return new Promise((resolve, reject) => {
213
- /** @param {CustomEvent<MovementCompleteEventData> | Event} event */
214
- const completedHandler = (event) => {
215
- const customEvent = /** @type {CustomEvent<MovementCompleteEventData>} */ (event);
216
- if (customEvent.detail.eventId === data.eventId) {
217
- cleanup();
218
- resolve(customEvent.detail.state);
219
- }
220
- };
221
-
222
- /** @param {CustomEvent<MovementFailedEventData> | Event} event */
223
- const failedHandler = (event) => {
224
- const customEvent = /** @type {CustomEvent<MovementFailedEventData>} */ (event);
225
- if (customEvent.detail.eventId === data.eventId) {
226
- cleanup();
227
- reject(customEvent.detail.reason);
228
- }
229
- };
230
-
231
- const timeoutId = setTimeout(
232
- () => {
233
- cleanup();
234
- reject('movement timed out');
235
- },
236
- Math.max(this.settings.animationSpeedMs * 100, 100),
237
- );
238
-
239
- const cleanup = () => {
240
- this.removeEventListener(InternalEvents.movementComplete, completedHandler);
241
- this.removeEventListener(InternalEvents.movementFailed, failedHandler);
242
- clearTimeout(timeoutId);
243
- };
244
-
245
- this.addEventListener(InternalEvents.movementComplete, completedHandler);
246
- this.addEventListener(InternalEvents.movementFailed, failedHandler);
247
- this.dispatchEvent(new CustomEvent(InternalEvents.movement, { detail: data }));
248
- });
249
- }
250
-
251
- /** @import {Rotation} from './core' */
252
- /** @internal @typedef {{eventId: string, rotation: Rotation}} RotationEventData */
253
- /** @internal @typedef {{eventId: string, rotation: Rotation, state: string, }} RotationCompleteEventData*/
254
- /** @internal @typedef {{eventId: string, rotation: Rotation, reason: string, }} RotationFailedEventData*/
255
- /**
256
- * @param {Rotation} rotation
257
- * @returns {Promise<string>}
258
- */
259
- rotate(rotation) {
260
- if (!Object.values(Rotations).includes(rotation)) {
261
- return Promise.reject(`Invalid move - [${rotation}]. Valid moves are ${Object.values(Rotations).join(', ')}`);
262
- }
263
- /** @type {RotationEventData} */
264
- const data = { eventId: crypto.randomUUID(), rotation };
265
- return new Promise((resolve, reject) => {
266
- /** @param {CustomEvent<RotationCompleteEventData> | Event} event */
267
- const completeHanlder = (event) => {
268
- const customEvent = /** @type {CustomEvent<RotationCompleteEventData>} */ (event);
269
- if (customEvent.detail.eventId === data.eventId) {
270
- cleanup();
271
- resolve(customEvent.detail.state);
272
- }
273
- };
274
-
275
- /** @param {CustomEvent<RotationFailedEventData> | Event} event */
276
- const failedHandler = (event) => {
277
- const customEvent = /** @type {CustomEvent<RotationFailedEventData>} */ (event);
278
- if (customEvent.detail.eventId === data.eventId) {
279
- cleanup();
280
- reject(customEvent.detail.reason);
281
- }
282
- };
283
-
284
- const timeoutId = setTimeout(
285
- () => {
286
- cleanup();
287
- reject('rotation timed out');
288
- },
289
- Math.max(this.settings.animationSpeedMs * 100, 100),
290
- );
291
-
292
- const cleanup = () => {
293
- this.removeEventListener(InternalEvents.rotationComplete, completeHanlder);
294
- this.removeEventListener(InternalEvents.rotationFailed, failedHandler);
295
- clearTimeout(timeoutId);
296
- };
297
-
298
- this.addEventListener(InternalEvents.rotationComplete, completeHanlder);
299
- this.addEventListener(InternalEvents.rotationFailed, failedHandler);
300
- this.dispatchEvent(new CustomEvent(InternalEvents.rotation, { detail: data }));
301
- });
302
- }
303
-
304
- /** @internal @typedef {{state: string }} ResetCompleteEventData */
305
- /**
306
- * @returns {Promise<string>}
307
- */
308
- reset() {
309
- return new Promise((resolve, reject) => {
310
- /** @param {CustomEvent<ResetCompleteEventData> | Event} event */
311
- const handler = (event) => {
312
- const customEvent = /** @type {CustomEvent<ResetCompleteEventData>} */ (event);
313
- cleanup();
314
- resolve(customEvent.detail.state);
315
- };
316
-
317
- const timeoutId = setTimeout(() => {
318
- cleanup();
319
- reject('reset timed out');
320
- }, 1000);
321
-
322
- const cleanup = () => {
323
- this.removeEventListener(InternalEvents.resetComplete, handler);
324
- clearTimeout(timeoutId);
325
- };
326
-
327
- this.addEventListener(InternalEvents.resetComplete, handler);
328
- this.dispatchEvent(new CustomEvent(InternalEvents.reset));
329
- });
330
- }
331
-
332
- /** @import {PeekType} from './core' */
333
- /** @internal @typedef {{eventId: string, peekType: PeekType}} CameraPeekEventData */
334
- /** @import {PeekState} from './core' */
335
- /** @internal @typedef {{eventId: string, peekState: PeekState }} CameraPeekCompleteEventData */
336
- /**
337
- * This function changes the camera position to one of four states depending on the arguments passed.
338
- *
339
- * @param {PeekType} peekType
340
- * @returns {Promise<PeekState>}
341
- */
342
- peek(peekType) {
343
- if (!Object.values(PeekTypes).includes(peekType)) {
344
- return Promise.reject(`Invalid move - [${peekType}]. Valid moves are ${Object.values(PeekTypes).join(', ')}`);
345
- }
346
- /** @type {CameraPeekEventData} */
347
- const data = { eventId: crypto.randomUUID(), peekType };
348
- return new Promise((resolve, reject) => {
349
- /** @param {CustomEvent<CameraPeekCompleteEventData> | Event} event */ const handler = (event) => {
350
- const customEvent = /** @type {CustomEvent<CameraPeekCompleteEventData>} */ (event);
351
- if (customEvent.detail.eventId === data.eventId) {
352
- cleanup();
353
- resolve(customEvent.detail.peekState);
354
- }
355
- };
356
-
357
- const timeoutId = setTimeout(() => {
358
- cleanup();
359
- reject('peek timed out');
360
- }, 1000);
361
-
362
- const cleanup = () => {
363
- this.removeEventListener(InternalEvents.cameraPeekComplete, handler);
364
- clearTimeout(timeoutId);
365
- };
366
-
367
- this.addEventListener(InternalEvents.cameraPeekComplete, handler);
368
- this.dispatchEvent(new CustomEvent(InternalEvents.cameraPeek, { detail: data }));
369
- });
370
- }
371
-
372
- /** @internal @typedef {{state: string }} SetStateEventData */
373
- /** @internal @typedef {{state: string }} SetStateCompleteEventData */
374
- /** @internal @typedef {{reason: string }} SetStateFailedEventData */
375
- /**
376
- * @param {string} kociembaState
377
- * @returns {Promise<string>}
378
- */
379
- setState(kociembaState) {
380
- const data = /** @type {SetStateEventData} */ ({ state: kociembaState });
381
- return new Promise((resolve, reject) => {
382
- /** @param {CustomEvent<SetStateCompleteEventData> | Event} event */
383
- const handler = (event) => {
384
- const customEvent = /** @type {CustomEvent<SetStateCompleteEventData>} */ (event);
385
- cleanup();
386
- resolve(customEvent.detail.state);
387
- };
388
- /** @param {CustomEvent<SetStateFailedEventData> | Event} event */
389
- const failedHandler = (event) => {
390
- const customEvent = /** @type {CustomEvent<SetStateFailedEventData>} */ (event);
391
- cleanup();
392
- reject(customEvent.detail.reason);
393
- };
394
-
395
- const timeoutId = setTimeout(() => {
396
- cleanup();
397
- reject('SetState timed out');
398
- }, 1000);
399
-
400
- const cleanup = () => {
401
- this.removeEventListener(InternalEvents.setStateComplete, handler);
402
- this.removeEventListener(InternalEvents.setStateFailed, failedHandler);
403
- clearTimeout(timeoutId);
404
- };
405
-
406
- this.addEventListener(InternalEvents.setStateComplete, handler);
407
- this.addEventListener(InternalEvents.setStateFailed, failedHandler);
408
- this.dispatchEvent(new CustomEvent(InternalEvents.setState, { detail: data }));
409
- });
410
- }
411
-
412
- /** @private */
413
- init() {
414
- // defined core threejs objects
415
- const canvas = this.canvas;
416
- const scene = new Scene();
417
- const renderer = new WebGLRenderer({
418
- alpha: true,
419
- canvas,
420
- antialias: true,
421
- });
422
- renderer.setSize(this.clientWidth, this.clientHeight);
423
- renderer.setPixelRatio(window.devicePixelRatio);
424
-
425
- //update renderer and camera when container resizes. debouncing events to reduce frequency
426
- new ResizeObserver(
427
- debounce((/** @type {{ contentRect: { width: number; height: number; }; }[]} */ entries) => {
428
- const { width, height } = entries[0].contentRect;
429
- camera.aspect = width / height;
430
- camera.updateProjectionMatrix();
431
- renderer.setSize(width, height);
432
- }, 30),
433
- ).observe(this);
434
-
435
- // add camera
436
- const camera = new PerspectiveCamera(this.settings.cameraFieldOfView, this.clientWidth / this.clientHeight, 1, 2000);
437
- const cameraSpherical = new Spherical(50, (3 * Math.PI) / 8, -Math.PI / 4);
438
- camera.position.setFromSpherical(cameraSpherical);
439
- const cameraState = new CameraState();
440
-
441
- // add orbit controls for camera
442
- const controls = new OrbitControls(camera, renderer.domElement);
443
- controls.enableZoom = false;
444
- controls.enablePan = false;
445
- controls.enableDamping = true;
446
- // controls.maxAzimuthAngle = maxAzimuthAngle;
447
- // controls.minAzimuthAngle = -maxAzimuthAngle;
448
- // controls.maxPolarAngle = polarAngleOffset + maxPolarAngle;
449
- // controls.minPolarAngle = polarAngleOffset - maxPolarAngle;
450
-
451
- // add lighting to scene
452
- const ambientLight = new AmbientLight('white', 0.4);
453
- const spotLight1 = new DirectionalLight('white', 2);
454
- const spotLight2 = new DirectionalLight('white', 2);
455
- const spotLight3 = new DirectionalLight('white', 2);
456
- const spotLight4 = new DirectionalLight('white', 2);
457
- spotLight1.position.set(5, 5, 5);
458
- spotLight2.position.set(-5, 5, 5);
459
- spotLight3.position.set(5, -5, 0);
460
- spotLight4.position.set(-10, -5, -5);
461
- scene.add(ambientLight, spotLight1, spotLight2, spotLight3, spotLight4);
462
-
463
- // create cube and add to scene
464
- //const cube = new RubiksCube3D(this.cubeSettings);
465
- const cube = new RubiksCube3D(this.cubeSettings);
466
- scene.add(cube);
467
-
468
- // animation loop
469
- function animate() {
470
- controls.update();
471
- cube.update();
472
- renderer.render(scene, camera);
473
- }
474
- renderer.setAnimationLoop(animate);
475
-
476
- // Cube Events
477
- this.addEventListener(InternalEvents.rotation, (event) => {
478
- const customEvent = /** @type {CustomEvent<RotationEventData>} */ (event);
479
- const completedCallback = (/** @type {string} */ state) =>
480
- this.dispatchEvent(
481
- new CustomEvent(InternalEvents.rotationComplete, {
482
- detail: /** @type {RotationCompleteEventData} */ ({
483
- eventId: customEvent.detail.eventId,
484
- state: state,
485
- rotation: customEvent.detail.rotation,
486
- }),
487
- }),
488
- );
489
- const failedCallback = (/** @type {string} */ reason) =>
490
- this.dispatchEvent(
491
- new CustomEvent(InternalEvents.rotationFailed, {
492
- detail: /** @type {RotationFailedEventData} */ ({
493
- eventId: customEvent.detail.eventId,
494
- reason: reason,
495
- rotation: customEvent.detail.rotation,
496
- }),
497
- }),
498
- );
499
- cube.rotate(customEvent.detail.rotation, completedCallback, failedCallback);
500
- });
501
-
502
- this.addEventListener(InternalEvents.movement, (event) => {
503
- const customEvent = /** @type {CustomEvent<MovementEvent>} */ (event);
504
- const completedCallback = (/** @type {string} */ state) =>
505
- this.dispatchEvent(
506
- new CustomEvent(InternalEvents.movementComplete, {
507
- detail: /** @type {MovementCompleteEventData} */ ({
508
- eventId: customEvent.detail.eventId,
509
- state: state,
510
- move: customEvent.detail.move,
511
- }),
512
- }),
513
- );
514
- const failedCallback = (/** @type {string} */ reason) =>
515
- this.dispatchEvent(
516
- new CustomEvent(InternalEvents.movementFailed, {
517
- detail: /** @type {MovementFailedEventData} */ ({
518
- eventId: customEvent.detail.eventId,
519
- reason: reason,
520
- move: customEvent.detail.move,
521
- }),
522
- }),
523
- );
524
- cube.movement(customEvent.detail.move, completedCallback, failedCallback);
525
- });
526
-
527
- this.addEventListener(InternalEvents.reset, () => {
528
- const completedCallback = (/** @type {string} */ state) =>
529
- this.dispatchEvent(
530
- new CustomEvent(InternalEvents.resetComplete, {
531
- detail: /** @type {ResetCompleteEventData} */ ({
532
- state: state,
533
- }),
534
- }),
535
- );
536
- cube.reset(completedCallback);
537
- });
538
-
539
- this.addEventListener(InternalEvents.setState, (event) => {
540
- const customEvent = /** @type {CustomEvent<SetStateEventData>} */ (event);
541
- const completedCallback = (/** @type {string} */ state) =>
542
- this.dispatchEvent(
543
- new CustomEvent(InternalEvents.setStateComplete, {
544
- detail: /** @type {SetStateCompleteEventData} */ ({
545
- state: state,
546
- }),
547
- }),
548
- );
549
- const failedCallback = (/** @type {string} */ reason) =>
550
- this.dispatchEvent(
551
- new CustomEvent(InternalEvents.setStateFailed, {
552
- detail: /** @type {SetStateFailedEventData} */ ({
553
- reason: reason,
554
- }),
555
- }),
556
- );
557
- cube.setState(customEvent.detail.state, completedCallback, failedCallback);
558
- });
559
-
560
- // Camera Events
561
- /**
562
- * @returns {Spherical}
563
- */
564
- const getTargetCameraSpherical = () => {
565
- const phi = polarAngleOffset + (cameraState.Up ? -this.settings.cameraPeekAngleVertical : this.settings.cameraPeekAngleVertical) * maxPolarAngle;
566
- const theta = (cameraState.Right ? this.settings.cameraPeekAngleHorizontal : -this.settings.cameraPeekAngleHorizontal) * maxAzimuthAngle;
567
- return new Spherical(this.settings.cameraRadius, phi, theta);
568
- };
569
- /**
570
- * @param {Spherical} targetSpherical
571
- * @param {number} cameraSpeedMs
572
- * @param {gsap.EaseString | gsap.EaseFunction | undefined} ease
573
- * @param { undefined | (() => void) } completedCallback
574
- */
575
- const updateCameraPosition = (targetSpherical, cameraSpeedMs, ease, completedCallback = undefined) => {
576
- const startSpherical = new Spherical().setFromVector3(camera.position);
577
- gsap.to(startSpherical, {
578
- radius: targetSpherical.radius,
579
- theta: targetSpherical.theta,
580
- phi: targetSpherical.phi,
581
- duration: (cameraSpeedMs ? cameraSpeedMs : this.settings.cameraSpeedMs) / 1000,
582
- ease: ease,
583
- overwrite: false,
584
- onUpdate: () => {
585
- camera.position.setFromSpherical(startSpherical);
586
- camera.lookAt(cube.position);
587
- controls.update();
588
- },
589
- onComplete: completedCallback,
590
- });
591
- };
592
-
593
- this.addEventListener(InternalEvents.cameraPeek, (event) => {
594
- const customEvent = /** @type {CustomEvent<CameraPeekEventData>} */ (event);
595
- cameraState.peekCamera(customEvent.detail.peekType);
596
- /** @type {CameraPeekCompleteEventData} */
597
- const data = { eventId: customEvent.detail.eventId, peekState: cameraState.toPeekState() };
598
- const completedCallback = () => this.dispatchEvent(new CustomEvent(InternalEvents.cameraPeekComplete, { detail: data }));
599
- const targetSpherical = getTargetCameraSpherical();
600
- updateCameraPosition(targetSpherical, this.settings.cameraSpeedMs, 'none', completedCallback);
601
- });
602
-
603
- this.addEventListener(InternalEvents.cameraSettingsChanged, () => {
604
- const targetSpherical = getTargetCameraSpherical();
605
- updateCameraPosition(targetSpherical, this.settings.cameraSpeedMs, 'none');
606
- });
607
-
608
- this.addEventListener(InternalEvents.cameraRadiusChanged, () => {
609
- const targetSpherical = new Spherical().setFromVector3(camera.position);
610
- targetSpherical.radius = this.settings.cameraRadius;
611
- updateCameraPosition(targetSpherical, this.settings.cameraSpeedMs, 'none');
612
- });
613
-
614
- this.addEventListener(InternalEvents.cameraFieldOfViewChanged, () => {
615
- camera.fov = this.settings.cameraFieldOfView;
616
- camera.updateProjectionMatrix();
617
- });
618
-
619
- updateCameraPosition(getTargetCameraSpherical(), 1000, 'power4.inOut'); // initial animation
620
- }
621
- }