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