@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.
- package/README.md +100 -194
- package/package.json +34 -4
- package/src/cameraState.js +81 -0
- package/src/core.js +127 -0
- package/src/cube/cube.js +123 -75
- package/src/cube/cubeRotation.js +26 -10
- package/src/cube/cubeSettings.js +18 -0
- package/src/cube/cubeState.js +1 -0
- package/src/cube/slice.js +143 -0
- package/src/debouncer.js +16 -0
- package/src/globals.ts +9 -0
- package/src/index.js +496 -0
- package/src/schema.js +22 -0
- package/src/settings.js +126 -0
- package/src/threejs/materials.js +52 -40
- package/src/threejs/pieces.js +1 -4
- package/src/threejs/stickers.js +18 -26
- package/types/cameraState.d.ts +19 -0
- package/types/core.d.ts +125 -0
- package/types/cube/cube.d.ts +102 -0
- package/types/cube/cubeRotation.d.ts +33 -0
- package/types/cube/cubeSettings.d.ts +17 -0
- package/types/cube/cubeState.d.ts +16 -0
- package/types/cube/slice.d.ts +15 -0
- package/types/debouncer.d.ts +13 -0
- package/types/globals.d.ts +7 -0
- package/types/index.d.ts +65 -0
- package/types/schema.d.ts +11 -0
- package/types/settings.d.ts +34 -0
- package/types/threejs/materials.d.ts +21 -0
- package/types/threejs/pieces.d.ts +28 -0
- package/types/threejs/stickers.d.ts +6 -0
- package/.prettierrc +0 -7
- package/index.js +0 -274
- package/src/utils/debouncer.js +0 -7
- package/src/utils/rotation.js +0 -53
|
@@ -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';
|
package/.prettierrc
DELETED
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);
|
package/src/utils/debouncer.js
DELETED
package/src/utils/rotation.js
DELETED
|
@@ -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
|
-
}
|