@houstonp/rubiks-cube 1.5.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @param {Material} frontMaterial
3
+ * @param {Material} rightMaterial
4
+ * @param {Material} topMaterial
5
+ * @param {Material} coreMaterial
6
+ * @returns {Group}
7
+ */
8
+ export function createCornerGroup(frontMaterial: Material, rightMaterial: Material, topMaterial: Material, coreMaterial: Material): Group;
9
+ /**
10
+ * @param {Material} frontMaterial
11
+ * @param {Material} topMaterial
12
+ * @param {Material} coreMaterial
13
+ * @returns {Group}
14
+ */
15
+ export function createEdgeGroup(frontMaterial: Material, topMaterial: Material, coreMaterial: Material): Group;
16
+ /**
17
+ * @param {Material} frontMaterial
18
+ * @param {Material} coreMaterial
19
+ * @returns {Group}
20
+ */
21
+ export function createCenterGroup(frontMaterial: Material, coreMaterial: Material): Group;
22
+ /**
23
+ * @returns {Mesh}
24
+ */
25
+ export function createCoreMesh(): Mesh;
26
+ import { Material } from 'three';
27
+ import { Group } from 'three';
28
+ import { Mesh } from 'three';
@@ -0,0 +1,6 @@
1
+ export default class Stickers {
2
+ static center: ExtrudeGeometry;
3
+ static edge: ExtrudeGeometry;
4
+ static corner: ExtrudeGeometry;
5
+ }
6
+ import { ExtrudeGeometry } from 'three';
package/.prettierrc DELETED
@@ -1,7 +0,0 @@
1
- {
2
- "printWidth": 160,
3
- "tabWidth": 4,
4
- "semi": true,
5
- "singleQuote": true,
6
- "trailingComma": "all"
7
- }
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, 'none'); // initial animation
272
- }
273
- }
274
- customElements.define('rubiks-cube', RubiksCube);
@@ -1,7 +0,0 @@
1
- export function debounce(f, delay) {
2
- let timer = 0;
3
- return function (...args) {
4
- clearTimeout(timer);
5
- timer = setTimeout(() => f.apply(this, args), delay);
6
- };
7
- }
@@ -1,53 +0,0 @@
1
- /**
2
- * @param {string} action
3
- * @returns {{axis: "x"|"y"|"z", layers: (0|1|-1)[], direction: (1|-1|2|-2)}}
4
- */
5
- export default function getRotationDetailsFromNotation(action) {
6
- if (!action) return;
7
- const reverse = action.includes("'") ? -1 : 1;
8
- action = action.replace("'", '');
9
- const multiplier = action.includes('2') ? 2 : 1;
10
- action = action.replace('2', '');
11
- if (!action) return;
12
- const move = action[0];
13
-
14
- if (move === 'x') {
15
- return { axis: 'x', layers: [], direction: -reverse * multiplier };
16
- } else if (move === 'y') {
17
- return { axis: 'y', layers: [], direction: -reverse * multiplier };
18
- } else if (move === 'z') {
19
- return { axis: 'z', layers: [], direction: -reverse * multiplier };
20
- } else if (move === 'U') {
21
- return { axis: 'y', layers: [1], direction: -reverse * multiplier };
22
- } else if (move === 'u') {
23
- return { axis: 'y', layers: [1, 0], direction: -reverse * multiplier };
24
- } else if (move === 'R') {
25
- return { axis: 'x', layers: [1], direction: -reverse * multiplier };
26
- } else if (move === 'r') {
27
- return { axis: 'x', layers: [1, 0], direction: -reverse * multiplier };
28
- } else if (move === 'L') {
29
- return { axis: 'x', layers: [-1], direction: -reverse * multiplier };
30
- } else if (move == 'l') {
31
- return { axis: 'x', layers: [-1, 0], direction: -reverse * multiplier };
32
- } else if (move === 'D') {
33
- return { axis: 'y', layers: [-1], direction: -reverse * multiplier };
34
- } else if (move === 'd') {
35
- return { axis: 'y', layers: [-1, 0], direction: -reverse * multiplier };
36
- } else if (move === 'F') {
37
- return { axis: 'z', layers: [1], direction: -reverse * multiplier };
38
- } else if (move === 'f') {
39
- return { axis: 'z', layers: [1, 0], direction: -reverse * multiplier };
40
- } else if (move === 'B') {
41
- return { axis: 'z', layers: [-1], direction: -reverse * multiplier };
42
- } else if (move === 'b') {
43
- return { axis: 'z', layers: [-1, 0], direction: -reverse * multiplier };
44
- } else if (move === 'M') {
45
- return { axis: 'x', layers: [0], direction: -reverse * multiplier };
46
- } else if (move === 'E') {
47
- return { axis: 'y', layers: [0], direction: -reverse * multiplier };
48
- } else if (move === 'S') {
49
- return { axis: 'z', layers: [0], direction: -reverse * multiplier };
50
- }
51
- console.log(`rubiks-cube invalid Action: ${action}`);
52
- return undefined;
53
- }