@houstonp/rubiks-cube 1.1.0 → 1.2.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/.prettierrc ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "printWidth": 160,
3
+ "tabWidth": 4,
4
+ "semi": true,
5
+ "singleQuote": true,
6
+ "trailingComma": "all"
7
+ }
package/README.md CHANGED
@@ -9,22 +9,30 @@ by adding the webcomponent tag.
9
9
 
10
10
  ```js
11
11
  //index.js
12
- import "@houstonp/rubiks-cube";
12
+ import '@houstonp/rubiks-cube';
13
13
  ```
14
14
 
15
15
  ```html
16
16
  <!DOCTYPE html>
17
17
  <html lang="en">
18
- <head>
19
- <meta charset="utf-8" />
20
- </head>
21
- <body>
22
- <rubiks-cube></rubiks-cube>
23
- <script type="module" src="index.js"></script>
24
- </body>
18
+ <head>
19
+ <meta charset="utf-8" />
20
+ </head>
21
+ <body>
22
+ <rubiks-cube animation-speed="1000" animation-style="exponential" gap="1.04"> </rubiks-cube>
23
+ <script type="module" src="index.js"></script>
24
+ </body>
25
25
  </html>
26
26
  ```
27
27
 
28
+ ## component attributes
29
+
30
+ | attribute | accepted values | Description |
31
+ | --------------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------- |
32
+ | animation-speed | integer greater than 0 | sets the speed of the animations in milliseconds |
33
+ | animation-style | "exponetial", "next", "fixed" | fixed: fixed animation lengths, next: skips to next animation, exponential: speeds up successive animations |
34
+ | gap | greater than 1 | sets the gap between rubiks cube pieces |
35
+
28
36
  ## state of the component
29
37
 
30
38
  A state event occurs when a movement animation is completed. The event details contains the current state of the cube. The state is an object containing the stickers of each face. A sticker is either "up", "down", "left", "right", "front" or "back".
@@ -32,9 +40,9 @@ A state event occurs when a movement animation is completed. The event details c
32
40
  To listen for the state event, add an event listener to the rubiks-cube element.
33
41
 
34
42
  ```js
35
- const cube = document.querySelector("rubiks-cube");
36
- cube.addEventListener("state", (e) => {
37
- console.log(e.detail.state);
43
+ const cube = document.querySelector('rubiks-cube');
44
+ cube.addEventListener('state', (e) => {
45
+ console.log(e.detail.state);
38
46
  });
39
47
  /*
40
48
  {
@@ -83,8 +91,8 @@ The rubiks-cube element listens for the `reset` custom event and resets the cube
83
91
  #### Example
84
92
 
85
93
  ```js
86
- const cube = document.querySelector("rubiks-cube");
87
- cube.dispatchEvent(new CustomEvent("reset"));
94
+ const cube = document.querySelector('rubiks-cube');
95
+ cube.dispatchEvent(new CustomEvent('reset'));
88
96
  ```
89
97
 
90
98
  ### Camera events
@@ -93,23 +101,23 @@ The rubiks-cube element listens for the `camera` custom event and moves the came
93
101
 
94
102
  The camera position specified in the event details must be one of the following:
95
103
 
96
- - `peek-right` - Camera is moved to the right of the cube so that the right face is visible
97
- - `peek-left` - Camera is moved to the left of the cube so that the left face is visible
98
- - `peek-top` - Camera is moved above the cube so that the top face is visible
99
- - `peek-bottom` - Camera is moved below the cube so that the bottom face is visible
100
- - `peek-toggle-horizontal` - Camera is moved to the opposite side of the cube in the horizontal plane
101
- - `peek-toggle-vertical` - Camera is moved to the opposite side of the cube in the vertical plane
104
+ - `peek-right` - Camera is moved to the right of the cube so that the right face is visible
105
+ - `peek-left` - Camera is moved to the left of the cube so that the left face is visible
106
+ - `peek-top` - Camera is moved above the cube so that the top face is visible
107
+ - `peek-bottom` - Camera is moved below the cube so that the bottom face is visible
108
+ - `peek-toggle-horizontal` - Camera is moved to the opposite side of the cube in the horizontal plane
109
+ - `peek-toggle-vertical` - Camera is moved to the opposite side of the cube in the vertical plane
102
110
 
103
111
  Note: The camera position cannot change to perform an equivalent cube rotation.
104
112
 
105
113
  #### Example
106
114
 
107
115
  ```js
108
- const cube = document.querySelector("rubiks-cube");
116
+ const cube = document.querySelector('rubiks-cube');
109
117
  cube.dispatchEvent(
110
- new CustomEvent("camera", {
111
- detail: { action: "peek-right" },
112
- })
118
+ new CustomEvent('camera', {
119
+ detail: { action: 'peek-right' },
120
+ }),
113
121
  );
114
122
  ```
115
123
 
@@ -151,10 +159,10 @@ When both a number and a prime symbol are included the number is stated before t
151
159
  #### Example
152
160
 
153
161
  ```js
154
- const cube = document.querySelector("rubiks-cube");
162
+ const cube = document.querySelector('rubiks-cube');
155
163
  cube.dispatchEvent(
156
- new CustomEvent("rotate", {
157
- detail: { action: "u2'" },
158
- })
164
+ new CustomEvent('rotate', {
165
+ detail: { action: "u2'" },
166
+ }),
159
167
  );
160
168
  ```
package/index.js CHANGED
@@ -1,223 +1,217 @@
1
- import * as THREE from "three";
2
- import * as TWEEN from "@tweenjs/tween.js";
3
- import { OrbitControls } from "three/examples/jsm/Addons.js";
4
- import Cube from "./src/cube";
5
- import getRotationDetails from "./src/rotation";
6
- import { AnimationQueue, Animation } from "./src/animation";
1
+ import { Scene, WebGLRenderer, PerspectiveCamera, AmbientLight, DirectionalLight } from 'three';
2
+ import { Tween, Group, Easing } from '@tweenjs/tween.js';
3
+ import { OrbitControls } from 'three/examples/jsm/Addons.js';
4
+ import Cube from './src/cube/cube';
5
+ import getRotationDetailsFromNotation from './src/utils/rotation';
6
+ import { debounce } from './src/utils/debouncer';
7
7
 
8
- class RubiksCube extends HTMLElement {
9
- constructor() {
10
- super();
11
- this.attachShadow({ mode: "open" });
12
- this.shadowRoot.innerHTML = `<canvas id="cube-canvas" style="display:block;"></canvas>`;
13
- this.canvas = this.shadowRoot.getElementById("cube-canvas");
14
- }
8
+ const defaultAnimationSpeed = 100;
9
+ const defaultAnimationStyle = 'exponential';
10
+ const defaultGap = 1.04;
15
11
 
16
- connectedCallback() {
17
- this.init();
18
- }
12
+ class RubiksCube extends HTMLElement {
13
+ constructor() {
14
+ super();
15
+ /** @type {number} */
16
+ this.animationSpeed = defaultAnimationSpeed;
17
+ /** @type {"exponential" | "instant"} */
18
+ this.animationStyle = this.getAttribute('animation-style') || defaultAnimationStyle;
19
+ this.attachShadow({ mode: 'open' });
20
+ this.shadowRoot.innerHTML = `<canvas id="cube-canvas" style="display:block;"></canvas>`;
21
+ this.canvas = this.shadowRoot.getElementById('cube-canvas');
22
+ /** @type {{style: "exponential" | "next" | "fixed", speed: number, gap: number}} */
23
+ this.settings = {
24
+ speed: this.getAttribute('animation-speed') || defaultAnimationSpeed,
25
+ style: this.getAttribute('animation-style') || defaultAnimationStyle,
26
+ gap: this.getAttribute('gap') || defaultGap,
27
+ };
28
+ }
19
29
 
20
- init() {
21
- // defined core threejs objects
22
- const canvas = this.canvas;
23
- const scene = new THREE.Scene();
24
- const renderer = new THREE.WebGLRenderer({
25
- alpha: true,
26
- canvas,
27
- antialias: true,
28
- });
29
- renderer.setSize(this.clientWidth, this.clientHeight);
30
- renderer.setAnimationLoop(animate);
31
- renderer.setPixelRatio(2);
30
+ static get observedAttributes() {
31
+ return ['animation-style', 'animation-speed'];
32
+ }
32
33
 
33
- //update renderer and camera when container resizes. debouncing events to reduce frequency
34
- function debounce(f, delay) {
35
- let timer = 0;
36
- return function (...args) {
37
- clearTimeout(timer);
38
- timer = setTimeout(() => f.apply(this, args), delay);
39
- };
34
+ attributeChangedCallback(name, oldVal, newVal) {
35
+ if (name === 'animation-style') {
36
+ this.settings.style = newVal;
37
+ }
38
+ if (name === 'animation-speed') {
39
+ this.settings.speed = Number(newVal);
40
+ }
41
+ if (name === 'gap') {
42
+ this.settings.gap = Number(newVal);
43
+ }
44
+ }
45
+ connectedCallback() {
46
+ this.init();
40
47
  }
41
- const resizeObserver = new ResizeObserver(
42
- debounce((entries) => {
43
- const { width, height } = entries[0].contentRect;
44
- camera.aspect = width / height;
45
- camera.updateProjectionMatrix();
46
- renderer.setSize(width, height);
47
- }, 30)
48
- );
49
- resizeObserver.observe(this);
50
48
 
51
- // add camera
52
- const camera = new THREE.PerspectiveCamera(
53
- 75,
54
- this.clientWidth / this.clientHeight,
55
- 0.1,
56
- 1000
57
- );
58
- camera.position.z = 4;
59
- camera.position.y = 3;
60
- camera.position.x = 0;
49
+ init() {
50
+ // defined core threejs objects
51
+ const canvas = this.canvas;
52
+ const scene = new Scene();
53
+ const renderer = new WebGLRenderer({
54
+ alpha: true,
55
+ canvas,
56
+ antialias: true,
57
+ });
58
+ renderer.setSize(this.clientWidth, this.clientHeight);
59
+ renderer.setAnimationLoop(animate);
60
+ renderer.setPixelRatio(2);
61
61
 
62
- // add orbit controls for camera
63
- const controls = new OrbitControls(camera, renderer.domElement);
64
- controls.enableZoom = false;
65
- controls.enablePan = false;
66
- controls.enableDamping = true;
67
- controls.maxAzimuthAngle = Math.PI / 4;
68
- controls.minAzimuthAngle = -Math.PI / 4;
69
- controls.maxPolarAngle = (3 * Math.PI) / 4;
70
- controls.minPolarAngle = Math.PI / 4;
62
+ //update renderer and camera when container resizes. debouncing events to reduce frequency
63
+ new ResizeObserver(
64
+ debounce((entries) => {
65
+ const { width, height } = entries[0].contentRect;
66
+ camera.aspect = width / height;
67
+ camera.updateProjectionMatrix();
68
+ renderer.setSize(width, height);
69
+ }, 30),
70
+ ).observe(this);
71
71
 
72
- // add lighting to scene
73
- const ambientLight = new THREE.AmbientLight("white", 0.5);
74
- const spotLight1 = new THREE.DirectionalLight("white", 2);
75
- const spotLight2 = new THREE.DirectionalLight("white", 2);
76
- const spotLight3 = new THREE.DirectionalLight("white", 2);
77
- const spotLight4 = new THREE.DirectionalLight("white", 2);
78
- spotLight1.position.set(5, 5, 5);
79
- spotLight2.position.set(-5, 5, 5);
80
- spotLight3.position.set(5, -5, 0);
81
- spotLight4.position.set(-10, -5, -5);
82
- scene.add(ambientLight, spotLight1, spotLight2, spotLight3, spotLight4);
72
+ // add camera
73
+ const camera = new PerspectiveCamera(75, this.clientWidth / this.clientHeight, 0.1, 1000);
74
+ camera.position.z = 4;
75
+ camera.position.y = 3;
76
+ camera.position.x = 0;
83
77
 
84
- // create cube and add to scene
85
- const cube = new Cube();
86
- scene.add(cube.group);
78
+ // add orbit controls for camera
79
+ const controls = new OrbitControls(camera, renderer.domElement);
80
+ controls.enableZoom = false;
81
+ controls.enablePan = false;
82
+ controls.enableDamping = true;
83
+ controls.maxAzimuthAngle = Math.PI / 4;
84
+ controls.minAzimuthAngle = -Math.PI / 4;
85
+ controls.maxPolarAngle = (3 * Math.PI) / 4;
86
+ controls.minPolarAngle = Math.PI / 4;
87
87
 
88
- // animation queue
89
- const animationQueue = new AnimationQueue();
88
+ // add lighting to scene
89
+ const ambientLight = new AmbientLight('white', 0.5);
90
+ const spotLight1 = new DirectionalLight('white', 2);
91
+ const spotLight2 = new DirectionalLight('white', 2);
92
+ const spotLight3 = new DirectionalLight('white', 2);
93
+ const spotLight4 = new DirectionalLight('white', 2);
94
+ spotLight1.position.set(5, 5, 5);
95
+ spotLight2.position.set(-5, 5, 5);
96
+ spotLight3.position.set(5, -5, 0);
97
+ spotLight4.position.set(-10, -5, -5);
98
+ scene.add(ambientLight, spotLight1, spotLight2, spotLight3, spotLight4);
90
99
 
91
- // initial camera animation
92
- const cameraAnimationGroup = new TWEEN.Group();
93
- cameraAnimationGroup.add(
94
- new TWEEN.Tween(camera.position)
95
- .to({ x: 3, y: 3, z: 4 }, 1000)
96
- .easing(TWEEN.Easing.Cubic.InOut)
97
- .start()
98
- );
100
+ // create cube and add to scene
101
+ const cube = new Cube(this.settings);
102
+ scene.add(cube.group, cube.rotationGroup);
99
103
 
100
- const sendState = () => {
101
- const state = cube.getStickerState();
102
- const event = new CustomEvent("state", { detail: { state } });
103
- this.dispatchEvent(event);
104
- };
104
+ // initial camera animation
105
+ const cameraAnimationGroup = new Group();
106
+ cameraAnimationGroup.add(new Tween(camera.position).to({ x: 2.5, y: 2.5, z: 4 }, 1000).easing(Easing.Cubic.InOut).start());
105
107
 
106
- // animation loop
107
- function animate() {
108
- cameraAnimationGroup.update();
109
- controls.update();
110
- animationQueue.update();
111
- if (animationQueue.currentAnimation) {
112
- scene.add(animationQueue.getAnimationGroup());
113
- }
114
- if (animationQueue.finished()) {
115
- sendState();
116
- scene.remove(animationQueue.getAnimationGroup());
117
- }
118
- renderer.render(scene, camera);
119
- }
108
+ const sendState = () => {
109
+ const state = cube.getStickerState();
110
+ const event = new CustomEvent('state', { detail: { state } });
111
+ this.dispatchEvent(event);
112
+ };
120
113
 
121
- this.addEventListener("reset", () => {
122
- animationQueue.clear();
123
- cube.reset();
124
- });
114
+ // animation loop
115
+ function animate() {
116
+ cameraAnimationGroup.update();
117
+ controls.update();
118
+ cube.update();
119
+ renderer.render(scene, camera);
120
+ }
125
121
 
126
- // add event listeners for rotation and camera controls
127
- this.addEventListener("rotate", (e) => {
128
- const action = getRotationDetails(e.detail.action);
129
- if (action !== undefined) {
130
- const animation = new Animation(
131
- cube,
132
- action.axis,
133
- action.layers,
134
- action.direction,
135
- 200
136
- );
137
- animationQueue.add(animation);
138
- }
139
- });
140
- this.addEventListener("camera", (e) => {
141
- if (e.detail.action === "peek-toggle-horizontal") {
142
- cameraAnimationGroup.add(
143
- new TWEEN.Tween(camera.position)
144
- .to(
145
- {
146
- x: camera.position.x > 0 ? -2.5 : 2.5,
147
- y: camera.position.y > 0 ? 2.5 : -2.5,
148
- z: 4,
149
- },
150
- 200
151
- )
152
- .start()
153
- );
154
- } else if (e.detail.action === "peek-toggle-vertical") {
155
- cameraAnimationGroup.add(
156
- new TWEEN.Tween(camera.position)
157
- .to(
158
- {
159
- x: camera.position.x > 0 ? 2.5 : -2.5,
160
- y: camera.position.y > 0 ? -2.5 : 2.5,
161
- z: 4,
162
- },
163
- 200
164
- )
165
- .start()
166
- );
167
- } else if (e.detail.action === "peek-right") {
168
- cameraAnimationGroup.add(
169
- new TWEEN.Tween(camera.position)
170
- .to(
171
- {
172
- x: 2.5,
173
- y: camera.position.y > 0 ? 2.5 : -2.5,
174
- z: 4,
175
- },
176
- 200
177
- )
178
- .start()
179
- );
180
- } else if (e.detail.action === "peek-left") {
181
- cameraAnimationGroup.add(
182
- new TWEEN.Tween(camera.position)
183
- .to(
184
- {
185
- x: -2.5,
186
- y: camera.position.y > 0 ? 2.5 : -2.5,
187
- z: 4,
188
- },
189
- 200
190
- )
191
- .start()
192
- );
193
- } else if (e.detail.action === "peek-up") {
194
- cameraAnimationGroup.add(
195
- new TWEEN.Tween(camera.position)
196
- .to(
197
- {
198
- x: camera.position.x > 0 ? 2.5 : -2.5,
199
- y: 2.5,
200
- z: 4,
201
- },
202
- 200
203
- )
204
- .start()
205
- );
206
- } else if (e.detail.action === "peek-down") {
207
- cameraAnimationGroup.add(
208
- new TWEEN.Tween(camera.position)
209
- .to(
210
- {
211
- x: camera.position.x > 0 ? 2.5 : -2.5,
212
- y: -2.5,
213
- z: 4,
214
- },
215
- 200
216
- )
217
- .start()
218
- );
219
- }
220
- });
221
- }
122
+ this.addEventListener('reset', () => {
123
+ cube.reset();
124
+ sendState();
125
+ });
126
+
127
+ // add event listeners for rotation and camera controls
128
+ this.addEventListener('rotate', (e) => {
129
+ const action = getRotationDetailsFromNotation(e.detail.action);
130
+ if (action !== undefined) {
131
+ cube.rotate(action);
132
+ }
133
+ });
134
+ this.addEventListener('camera', (e) => {
135
+ if (e.detail.action === 'peek-toggle-horizontal') {
136
+ cameraAnimationGroup.add(
137
+ new Tween(camera.position)
138
+ .to(
139
+ {
140
+ x: camera.position.x > 0 ? -2.5 : 2.5,
141
+ y: camera.position.y > 0 ? 2.5 : -2.5,
142
+ z: 4,
143
+ },
144
+ 200,
145
+ )
146
+ .start(),
147
+ );
148
+ } else if (e.detail.action === 'peek-toggle-vertical') {
149
+ cameraAnimationGroup.add(
150
+ new Tween(camera.position)
151
+ .to(
152
+ {
153
+ x: camera.position.x > 0 ? 2.5 : -2.5,
154
+ y: camera.position.y > 0 ? -2.5 : 2.5,
155
+ z: 4,
156
+ },
157
+ 200,
158
+ )
159
+ .start(),
160
+ );
161
+ } else if (e.detail.action === 'peek-right') {
162
+ cameraAnimationGroup.add(
163
+ new Tween(camera.position)
164
+ .to(
165
+ {
166
+ x: 2.5,
167
+ y: camera.position.y > 0 ? 2.5 : -2.5,
168
+ z: 4,
169
+ },
170
+ 200,
171
+ )
172
+ .start(),
173
+ );
174
+ } else if (e.detail.action === 'peek-left') {
175
+ cameraAnimationGroup.add(
176
+ new Tween(camera.position)
177
+ .to(
178
+ {
179
+ x: -2.5,
180
+ y: camera.position.y > 0 ? 2.5 : -2.5,
181
+ z: 4,
182
+ },
183
+ 200,
184
+ )
185
+ .start(),
186
+ );
187
+ } else if (e.detail.action === 'peek-up') {
188
+ cameraAnimationGroup.add(
189
+ new Tween(camera.position)
190
+ .to(
191
+ {
192
+ x: camera.position.x > 0 ? 2.5 : -2.5,
193
+ y: 2.5,
194
+ z: 4,
195
+ },
196
+ 200,
197
+ )
198
+ .start(),
199
+ );
200
+ } else if (e.detail.action === 'peek-down') {
201
+ cameraAnimationGroup.add(
202
+ new Tween(camera.position)
203
+ .to(
204
+ {
205
+ x: camera.position.x > 0 ? 2.5 : -2.5,
206
+ y: -2.5,
207
+ z: 4,
208
+ },
209
+ 200,
210
+ )
211
+ .start(),
212
+ );
213
+ }
214
+ });
215
+ }
222
216
  }
223
- customElements.define("rubiks-cube", RubiksCube);
217
+ customElements.define('rubiks-cube', RubiksCube);
package/package.json CHANGED
@@ -1,21 +1,21 @@
1
1
  {
2
- "name": "@houstonp/rubiks-cube",
3
- "version": "1.1.0",
4
- "description": "Rubiks Cube Web Component built with threejs",
5
- "main": "index.js",
6
- "author": "Houston Pearse",
7
- "license": "MIT",
8
- "dependencies": {
9
- "@tweenjs/tween.js": "^25.0.0",
10
- "three": "^0.167.1"
11
- },
12
- "devDependencies": {},
13
- "repository": {
14
- "type": "git",
15
- "url": "git+https://github.com/houstonpearse/rubiks-cube.git"
16
- },
17
- "bugs": {
18
- "url": "https://github.com/houstonpearse/rubiks-cube/issues"
19
- },
20
- "homepage": "https://github.com/houstonpearse/rubiks-cube#readme"
21
- }
2
+ "name": "@houstonp/rubiks-cube",
3
+ "version": "1.2.0",
4
+ "description": "Rubiks Cube Web Component built with threejs",
5
+ "main": "index.js",
6
+ "author": "Houston Pearse",
7
+ "license": "MIT",
8
+ "dependencies": {
9
+ "@tweenjs/tween.js": "^25.0.0",
10
+ "three": "^0.167.1"
11
+ },
12
+ "devDependencies": {},
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/houstonpearse/rubiks-cube.git"
16
+ },
17
+ "bugs": {
18
+ "url": "https://github.com/houstonpearse/rubiks-cube/issues"
19
+ },
20
+ "homepage": "https://github.com/houstonpearse/rubiks-cube#readme"
21
+ }