@houstonp/rubiks-cube 1.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 +95 -0
- package/index.js +194 -0
- package/package.json +21 -0
- package/src/animation.js +158 -0
- package/src/center.js +23 -0
- package/src/corner.js +45 -0
- package/src/cube.js +318 -0
- package/src/cubeState.js +128 -0
- package/src/edge.js +36 -0
- package/src/materials.js +42 -0
- package/src/rotation.js +52 -0
- package/src/stickers.js +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Rubiks Cube Web Component
|
|
2
|
+
|
|
3
|
+
This package is a rubiks cube web component built with threejs. Camera animation smoothing is done with the tweenjs package.
|
|
4
|
+
|
|
5
|
+
## adding the component
|
|
6
|
+
|
|
7
|
+
You can dd the component to a webpage by adding a module script tag with the index.js file. And then
|
|
8
|
+
by adding the webcomponent tag.
|
|
9
|
+
|
|
10
|
+
```html
|
|
11
|
+
<!DOCTYPE html>
|
|
12
|
+
<html lang="en">
|
|
13
|
+
<head>
|
|
14
|
+
<meta charset="utf-8" />
|
|
15
|
+
</head>
|
|
16
|
+
<body>
|
|
17
|
+
<rubiks-cube></rubiks-cube>
|
|
18
|
+
<script type="module" src="index.js"></script>
|
|
19
|
+
</body>
|
|
20
|
+
</html>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## updating the component
|
|
24
|
+
|
|
25
|
+
The Rubiks cube web component listens for custom events to perform twists, rotations and camera changes. As per convention, the starting rotation has green facing forward, white facing up and red facing to the right.
|
|
26
|
+
|
|
27
|
+
### Camera events
|
|
28
|
+
|
|
29
|
+
The rubiks-cube element listens for the `camera` custom event and moves the camera to the specified position.
|
|
30
|
+
|
|
31
|
+
The camera position specified in the event details must be one of the following:
|
|
32
|
+
|
|
33
|
+
- `peek-right` - Camera is moved to the right of the cube so that the right face is visible
|
|
34
|
+
- `peek-left` - Camera is moved to the left of the cube so that the left face is visible
|
|
35
|
+
- `peek-top` - Camera is moved above the cube so that the top face is visible
|
|
36
|
+
- `peek-bottom` - Camera is moved below the cube so that the bottom face is visible
|
|
37
|
+
- `peek-toggle-horizontal` - Camera is moved to the opposite side of the cube in the horizontal plane
|
|
38
|
+
- `peek-toggle-vertical` - Camera is moved to the opposite side of the cube in the vertical plane
|
|
39
|
+
|
|
40
|
+
#### Example
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
const cube = document.querySelector("rubiks-cube");
|
|
44
|
+
cube.dispatchEvent(
|
|
45
|
+
new CustomEvent("camera", {
|
|
46
|
+
detail: { action: "peek-right" },
|
|
47
|
+
})
|
|
48
|
+
);
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Rotation event
|
|
52
|
+
|
|
53
|
+
The rubiks-cube element listens for the `rotate` custom event and rotates a face or entire cube in the direction specified by the event details.
|
|
54
|
+
|
|
55
|
+
The rotation type specified in the event details must follow standard rubiks cube notation.
|
|
56
|
+
|
|
57
|
+
#### Rubiks Cube Notation
|
|
58
|
+
|
|
59
|
+
Notations can include the number of roations of a face. For example, `U2` means rotate the upper face 180 degrees.
|
|
60
|
+
|
|
61
|
+
Noations can also include a prime symbol `'` to indicate a counter clockwise rotation. For example, `U'` means rotate the upper face counter clockwise. The direction is always determined relative to the face being moved.
|
|
62
|
+
|
|
63
|
+
When both a number and a prime symbol are included the number is stated before the prime symbol. For example, `U2'` means rotate the upper face 180 degrees counter clockwise. and `U'2` is invalid.
|
|
64
|
+
|
|
65
|
+
| Notation | Movement |
|
|
66
|
+
| -------- | ------------------------------------------------ |
|
|
67
|
+
| U | Top face clockwise |
|
|
68
|
+
| u | Top two layers clockwise |
|
|
69
|
+
| D | Bottom face clockwise |
|
|
70
|
+
| d | Bottom two layers clockwise |
|
|
71
|
+
| L | Left face clockwise |
|
|
72
|
+
| l | Left two layers clockwise |
|
|
73
|
+
| R | Right face clockwise |
|
|
74
|
+
| r | Right two layers clockwise |
|
|
75
|
+
| F | Front face clockwise |
|
|
76
|
+
| f | Front two layers clockwise |
|
|
77
|
+
| B | Back face clockwise |
|
|
78
|
+
| b | Back two layers clockwise |
|
|
79
|
+
| M | Middle layer clockwise (relative to L) |
|
|
80
|
+
| E | Equatorial layer clockwise (relative to D) |
|
|
81
|
+
| S | Standing layer clockwise (relative to F) |
|
|
82
|
+
| x | Rotate cube on x axis clockwise (direction of R) |
|
|
83
|
+
| y | Rotate cube on y axis clockwise (direction of U) |
|
|
84
|
+
| z | Rotate cube on z axis clockwise (direction of F) |
|
|
85
|
+
|
|
86
|
+
#### Example
|
|
87
|
+
|
|
88
|
+
```js
|
|
89
|
+
const cube = document.querySelector("rubiks-cube");
|
|
90
|
+
cube.dispatchEvent(
|
|
91
|
+
new CustomEvent("rotate", {
|
|
92
|
+
detail: { action: "u2'" },
|
|
93
|
+
})
|
|
94
|
+
);
|
|
95
|
+
```
|
package/index.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
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";
|
|
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
|
+
}
|
|
15
|
+
|
|
16
|
+
connectedCallback() {
|
|
17
|
+
this.init();
|
|
18
|
+
}
|
|
19
|
+
|
|
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);
|
|
32
|
+
|
|
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
|
+
};
|
|
40
|
+
}
|
|
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
|
+
|
|
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;
|
|
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;
|
|
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);
|
|
83
|
+
|
|
84
|
+
// create cube and add to scene
|
|
85
|
+
const cube = new Cube();
|
|
86
|
+
scene.add(cube.group);
|
|
87
|
+
|
|
88
|
+
// animation queue
|
|
89
|
+
const animationQueue = new AnimationQueue();
|
|
90
|
+
|
|
91
|
+
// initial camera animation
|
|
92
|
+
new TWEEN.Tween(camera.position)
|
|
93
|
+
.to({ x: 3, y: 3, z: 4 }, 1000)
|
|
94
|
+
.easing(TWEEN.Easing.Cubic.InOut)
|
|
95
|
+
.start();
|
|
96
|
+
|
|
97
|
+
// animation loop
|
|
98
|
+
function animate() {
|
|
99
|
+
TWEEN.update();
|
|
100
|
+
controls.update();
|
|
101
|
+
animationQueue.update();
|
|
102
|
+
const animationGroup = animationQueue.getAnimationGroup();
|
|
103
|
+
if (animationGroup !== undefined) scene.add(animationGroup);
|
|
104
|
+
renderer.render(scene, camera);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// add event listeners for rotation and camera controls
|
|
108
|
+
this.addEventListener("rotate", (e) => {
|
|
109
|
+
const action = getRotationDetails(e.detail.action);
|
|
110
|
+
if (action !== undefined) {
|
|
111
|
+
const animation = new Animation(
|
|
112
|
+
cube,
|
|
113
|
+
action.axis,
|
|
114
|
+
action.layers,
|
|
115
|
+
action.direction,
|
|
116
|
+
200
|
|
117
|
+
);
|
|
118
|
+
animationQueue.add(animation);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
this.addEventListener("camera", (e) => {
|
|
122
|
+
console.log(cube.getStickerState());
|
|
123
|
+
new TWEEN.Tween(camera.position);
|
|
124
|
+
if (e.detail.action === "peek-toggle-horizontal") {
|
|
125
|
+
new TWEEN.Tween(camera.position)
|
|
126
|
+
.to(
|
|
127
|
+
{
|
|
128
|
+
x: camera.position.x > 0 ? -2.5 : 2.5,
|
|
129
|
+
y: camera.position.y > 0 ? 2.5 : -2.5,
|
|
130
|
+
z: 4,
|
|
131
|
+
},
|
|
132
|
+
200
|
|
133
|
+
)
|
|
134
|
+
.start();
|
|
135
|
+
} else if (e.detail.action === "peek-toggle-vertical") {
|
|
136
|
+
new TWEEN.Tween(camera.position)
|
|
137
|
+
.to(
|
|
138
|
+
{
|
|
139
|
+
x: camera.position.x > 0 ? 2.5 : -2.5,
|
|
140
|
+
y: camera.position.y > 0 ? -2.5 : 2.5,
|
|
141
|
+
z: 4,
|
|
142
|
+
},
|
|
143
|
+
200
|
|
144
|
+
)
|
|
145
|
+
.start();
|
|
146
|
+
} else if (e.detail.action === "peek-right") {
|
|
147
|
+
new TWEEN.Tween(camera.position)
|
|
148
|
+
.to(
|
|
149
|
+
{
|
|
150
|
+
x: 2.5,
|
|
151
|
+
y: camera.position.y > 0 ? 2.5 : -2.5,
|
|
152
|
+
z: 4,
|
|
153
|
+
},
|
|
154
|
+
200
|
|
155
|
+
)
|
|
156
|
+
.start();
|
|
157
|
+
} else if (e.detail.action === "peek-left") {
|
|
158
|
+
new TWEEN.Tween(camera.position)
|
|
159
|
+
.to(
|
|
160
|
+
{
|
|
161
|
+
x: -2.5,
|
|
162
|
+
y: camera.position.y > 0 ? 2.5 : -2.5,
|
|
163
|
+
z: 4,
|
|
164
|
+
},
|
|
165
|
+
200
|
|
166
|
+
)
|
|
167
|
+
.start();
|
|
168
|
+
} else if (e.detail.action === "peek-up") {
|
|
169
|
+
new TWEEN.Tween(camera.position)
|
|
170
|
+
.to(
|
|
171
|
+
{
|
|
172
|
+
x: camera.position.x > 0 ? 2.5 : -2.5,
|
|
173
|
+
y: 2.5,
|
|
174
|
+
z: 4,
|
|
175
|
+
},
|
|
176
|
+
200
|
|
177
|
+
)
|
|
178
|
+
.start();
|
|
179
|
+
} else if (e.detail.action === "peek-down") {
|
|
180
|
+
new TWEEN.Tween(camera.position)
|
|
181
|
+
.to(
|
|
182
|
+
{
|
|
183
|
+
x: camera.position.x > 0 ? 2.5 : -2.5,
|
|
184
|
+
y: -2.5,
|
|
185
|
+
z: 4,
|
|
186
|
+
},
|
|
187
|
+
200
|
|
188
|
+
)
|
|
189
|
+
.start();
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
customElements.define("rubiks-cube", RubiksCube);
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@houstonp/rubiks-cube",
|
|
3
|
+
"version": "1.0.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
|
+
}
|
package/src/animation.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
import Cube from "./cube";
|
|
3
|
+
|
|
4
|
+
export class AnimationQueue {
|
|
5
|
+
constructor(type = "exponential", factor = 1.3) {
|
|
6
|
+
/** @type {Animation[]} */
|
|
7
|
+
this.queue = [];
|
|
8
|
+
/** @type {Animation | undefined} */
|
|
9
|
+
this.currentAnimation = undefined;
|
|
10
|
+
/** @type {{type: "fast-forward" | "exponential", factor: number}} */
|
|
11
|
+
this.type = type;
|
|
12
|
+
this.factor = factor;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {Animation} animation
|
|
17
|
+
*/
|
|
18
|
+
add(animation) {
|
|
19
|
+
if (this.type === "fast-forward") {
|
|
20
|
+
this.fastForward();
|
|
21
|
+
} else if (this.type === "exponential") {
|
|
22
|
+
this.exponential();
|
|
23
|
+
}
|
|
24
|
+
this.queue.push(animation);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* exponentially increases the animation speed with the depth of the queue */
|
|
28
|
+
exponential() {
|
|
29
|
+
let animations = [];
|
|
30
|
+
if (this.currentAnimation) animations.push(this.currentAnimation);
|
|
31
|
+
animations = animations.concat(this.queue);
|
|
32
|
+
for (let i = 0; i < animations.length; i++) {
|
|
33
|
+
animations[i].setSpeed(this.factor ** (animations.length - i - 1));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* instantly completes any queued animations */
|
|
38
|
+
fastForward() {
|
|
39
|
+
if (this.currentAnimation) {
|
|
40
|
+
this.currentAnimation.setFastForward();
|
|
41
|
+
}
|
|
42
|
+
if (this.queue.length) {
|
|
43
|
+
for (const a of this.queue) {
|
|
44
|
+
a.setFastForward();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
update() {
|
|
50
|
+
if (
|
|
51
|
+
this.currentAnimation === undefined ||
|
|
52
|
+
this.currentAnimation.finished()
|
|
53
|
+
) {
|
|
54
|
+
this.currentAnimation = this.queue.shift();
|
|
55
|
+
}
|
|
56
|
+
if (this.currentAnimation === undefined) return;
|
|
57
|
+
this.currentAnimation.update();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
*
|
|
62
|
+
* @returns {THREE.Group | undefined}
|
|
63
|
+
*/
|
|
64
|
+
getAnimationGroup() {
|
|
65
|
+
if (this.currentAnimation === undefined) return undefined;
|
|
66
|
+
return this.currentAnimation.getGroup();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export class Animation {
|
|
71
|
+
/**
|
|
72
|
+
*
|
|
73
|
+
* @param {Cube} cube
|
|
74
|
+
* @param {"x"|"y"|"z"} axis
|
|
75
|
+
* @param {(-1|0|1)[]} layers
|
|
76
|
+
* @param {1|-1|2|-2} direction
|
|
77
|
+
* @param {number} duration milliseconds
|
|
78
|
+
*/
|
|
79
|
+
constructor(cube, axis, layers, direction, duration) {
|
|
80
|
+
this._cube = cube;
|
|
81
|
+
this._axis = axis;
|
|
82
|
+
this._layers = layers;
|
|
83
|
+
this._direction = direction;
|
|
84
|
+
this._duration = duration;
|
|
85
|
+
this._layerGroup = new THREE.Group();
|
|
86
|
+
this._finished = false;
|
|
87
|
+
this._lastUpdate = undefined;
|
|
88
|
+
this._totalRotation = 0;
|
|
89
|
+
this.fastforward = false;
|
|
90
|
+
this.speed = 1;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
setFastForward(value = true) {
|
|
94
|
+
this.fastforward = value;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
setSpeed(value = 1) {
|
|
98
|
+
this.speed = value;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
init() {
|
|
102
|
+
this._lastUpdate = Date.now();
|
|
103
|
+
const layerObjects = this._cube.getRotationLayer(this._axis, this._layers);
|
|
104
|
+
this._layerGroup.add(...layerObjects);
|
|
105
|
+
this._cube.group.remove(...layerObjects);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
teardown() {
|
|
109
|
+
this._finished = true;
|
|
110
|
+
this._layerGroup.children.forEach((piece) => {
|
|
111
|
+
piece.getWorldPosition(piece.position);
|
|
112
|
+
piece.getWorldQuaternion(piece.quaternion);
|
|
113
|
+
piece.userData.position.x = Math.round(piece.position.x);
|
|
114
|
+
piece.userData.position.y = Math.round(piece.position.y);
|
|
115
|
+
piece.userData.position.z = Math.round(piece.position.z);
|
|
116
|
+
piece.userData.rotation.x = piece.rotation.x;
|
|
117
|
+
piece.userData.rotation.y = piece.rotation.y;
|
|
118
|
+
piece.userData.rotation.z = piece.rotation.z;
|
|
119
|
+
});
|
|
120
|
+
this._cube.group.add(...this._layerGroup.children);
|
|
121
|
+
this._layerGroup.clear();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
update() {
|
|
125
|
+
if (this._lastUpdate === undefined) {
|
|
126
|
+
this.init();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
var interval = (Date.now() - this._lastUpdate) * this.speed;
|
|
130
|
+
this._lastUpdate = Date.now();
|
|
131
|
+
if (this.fastforward || interval + this._totalRotation > this._duration) {
|
|
132
|
+
interval = this._duration - this._totalRotation;
|
|
133
|
+
}
|
|
134
|
+
const rotationIncrement =
|
|
135
|
+
(Math.abs(this._direction) * ((interval / this._duration) * Math.PI)) / 2;
|
|
136
|
+
this._totalRotation += interval;
|
|
137
|
+
this._layerGroup.rotateOnWorldAxis(
|
|
138
|
+
new THREE.Vector3(
|
|
139
|
+
this._axis === "x" ? this._direction : 0,
|
|
140
|
+
this._axis === "y" ? this._direction : 0,
|
|
141
|
+
this._axis === "z" ? this._direction : 0
|
|
142
|
+
).normalize(),
|
|
143
|
+
rotationIncrement
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
if (this._totalRotation >= this._duration) {
|
|
147
|
+
this.teardown();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
getGroup() {
|
|
152
|
+
return this._layerGroup;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
finished() {
|
|
156
|
+
return this._finished;
|
|
157
|
+
}
|
|
158
|
+
}
|
package/src/center.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
/**
|
|
3
|
+
* @param {THREE.Geometry} sticker
|
|
4
|
+
* @param {THREE.Material} frontMaterial
|
|
5
|
+
* @param {THREE.Material} topMaterial
|
|
6
|
+
* @param {THREE.Material} coreMaterial
|
|
7
|
+
* @returns {{group: THREE.Group, frontSticker: THREE.Mesh, topSticker:THREE.Mesh}}
|
|
8
|
+
*/
|
|
9
|
+
export default function newCenter(sticker, frontMaterial, coreMaterial) {
|
|
10
|
+
const group = new THREE.Group();
|
|
11
|
+
const boxGeom = new THREE.BoxGeometry(1, 1, 1);
|
|
12
|
+
const boxMesh = new THREE.Mesh(boxGeom, coreMaterial);
|
|
13
|
+
boxMesh.userData = { type: "piece" };
|
|
14
|
+
group.add(boxMesh);
|
|
15
|
+
|
|
16
|
+
const frontSticker = new THREE.Mesh(sticker, frontMaterial);
|
|
17
|
+
frontSticker.userData = { type: "sticker" };
|
|
18
|
+
frontSticker.position.set(0, 0, 0.5);
|
|
19
|
+
frontSticker.rotation.set(0, 0, 0);
|
|
20
|
+
group.add(frontSticker);
|
|
21
|
+
|
|
22
|
+
return { group, frontSticker };
|
|
23
|
+
}
|
package/src/corner.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
/**
|
|
3
|
+
* @param {THREE.Geometry} sticker
|
|
4
|
+
* @param {THREE.Material} frontMaterial
|
|
5
|
+
* @param {THREE.Material} rightMaterial
|
|
6
|
+
* @param {THREE.Material} topMaterial
|
|
7
|
+
* @param {THREE.Material} coreMaterial
|
|
8
|
+
* @returns {{group: THREE.Group, frontSticker: THREE.Mesh, rightSticker:THREE.Mesh, topSticker:THREE.Mesh}}
|
|
9
|
+
*/
|
|
10
|
+
export default function newCorner(
|
|
11
|
+
sticker,
|
|
12
|
+
frontMaterial,
|
|
13
|
+
rightMaterial,
|
|
14
|
+
topMaterial,
|
|
15
|
+
coreMaterial
|
|
16
|
+
) {
|
|
17
|
+
const group = new THREE.Group();
|
|
18
|
+
const boxGeom = new THREE.BoxGeometry(1, 1, 1);
|
|
19
|
+
const boxMesh = new THREE.Mesh(boxGeom, coreMaterial);
|
|
20
|
+
boxMesh.userData = { type: "piece" };
|
|
21
|
+
group.add(boxMesh);
|
|
22
|
+
|
|
23
|
+
// front
|
|
24
|
+
const frontSticker = new THREE.Mesh(sticker, frontMaterial);
|
|
25
|
+
frontSticker.userData = { type: "sticker" };
|
|
26
|
+
frontSticker.position.set(0, 0, 0.5);
|
|
27
|
+
frontSticker.rotation.set(0, 0, 0);
|
|
28
|
+
group.add(frontSticker);
|
|
29
|
+
|
|
30
|
+
//right
|
|
31
|
+
const rightSticker = new THREE.Mesh(sticker, rightMaterial);
|
|
32
|
+
rightSticker.userData = { type: "sticker" };
|
|
33
|
+
rightSticker.position.set(0.5, 0, 0);
|
|
34
|
+
rightSticker.rotation.set(Math.PI / 2, Math.PI / 2, 0);
|
|
35
|
+
group.add(rightSticker);
|
|
36
|
+
|
|
37
|
+
//white/yellow
|
|
38
|
+
const topSticker = new THREE.Mesh(sticker, topMaterial);
|
|
39
|
+
topSticker.userData = { type: "sticker" };
|
|
40
|
+
topSticker.position.set(0, 0.5, 0);
|
|
41
|
+
topSticker.rotation.set(-Math.PI / 2, 0, -Math.PI / 2);
|
|
42
|
+
group.add(topSticker);
|
|
43
|
+
|
|
44
|
+
return { group, frontSticker, rightSticker, topSticker };
|
|
45
|
+
}
|
package/src/cube.js
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
import Materials from "./materials";
|
|
3
|
+
import Stickers from "./stickers";
|
|
4
|
+
import newCorner from "./corner";
|
|
5
|
+
import newEdge from "./edge";
|
|
6
|
+
import newCenter from "./center";
|
|
7
|
+
import { newCubeState } from "./cubeState";
|
|
8
|
+
|
|
9
|
+
export default class Cube {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.group = new THREE.Group();
|
|
12
|
+
const core = this.createCore();
|
|
13
|
+
core.userData = {
|
|
14
|
+
position: { x: 0, y: 0, z: 0 },
|
|
15
|
+
rotation: { x: 0, y: 0, z: 0 },
|
|
16
|
+
initialPosition: { x: 0, y: 0, z: 0 },
|
|
17
|
+
initialRotation: { x: 0, y: 0, z: 0 },
|
|
18
|
+
type: "core",
|
|
19
|
+
};
|
|
20
|
+
this.group.add(core);
|
|
21
|
+
|
|
22
|
+
for (const state of newCubeState()) {
|
|
23
|
+
const piece = this.createPiece(state.position, state.type);
|
|
24
|
+
piece.position.set(
|
|
25
|
+
state.position.x * 1.04,
|
|
26
|
+
state.position.y * 1.04,
|
|
27
|
+
state.position.z * 1.04
|
|
28
|
+
);
|
|
29
|
+
piece.rotation.set(state.rotation.x, state.rotation.y, state.rotation.z);
|
|
30
|
+
piece.userData = {
|
|
31
|
+
position: state.position,
|
|
32
|
+
rotation: state.rotation,
|
|
33
|
+
initialPosition: state.position,
|
|
34
|
+
initialRotation: state.rotation,
|
|
35
|
+
type: state.type,
|
|
36
|
+
};
|
|
37
|
+
this.group.add(piece);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* @param {"x"|"y"|"z"} axis
|
|
42
|
+
* @param {{-1|0|1}[]} layers
|
|
43
|
+
* @returns {THREE.Object3D[]}
|
|
44
|
+
*/
|
|
45
|
+
getRotationLayer(axis, layers) {
|
|
46
|
+
if (layers.length === 0) {
|
|
47
|
+
return [...this.group.children];
|
|
48
|
+
}
|
|
49
|
+
return this.group.children.filter((piece) => {
|
|
50
|
+
if (axis === "x") {
|
|
51
|
+
return layers.includes(Math.round(piece.userData.position.x));
|
|
52
|
+
} else if (axis === "y") {
|
|
53
|
+
return layers.includes(Math.round(piece.userData.position.y));
|
|
54
|
+
} else if (axis === "z") {
|
|
55
|
+
return layers.includes(Math.round(piece.userData.position.z));
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
getStickerState() {
|
|
62
|
+
const state = {
|
|
63
|
+
up: [[], [], []],
|
|
64
|
+
down: [[], [], []],
|
|
65
|
+
front: [[], [], []],
|
|
66
|
+
back: [[], [], []],
|
|
67
|
+
left: [[], [], []],
|
|
68
|
+
right: [[], [], []],
|
|
69
|
+
};
|
|
70
|
+
this.group.children.forEach((piece) => {
|
|
71
|
+
if (piece.userData.type === "core") {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
piece.children.forEach((mesh) => {
|
|
75
|
+
if (mesh.userData.type === "sticker") {
|
|
76
|
+
const piecepos = new THREE.Vector3();
|
|
77
|
+
piecepos.copy(piece.position);
|
|
78
|
+
piecepos.round();
|
|
79
|
+
const stickerpos = new THREE.Vector3();
|
|
80
|
+
mesh.getWorldPosition(stickerpos);
|
|
81
|
+
stickerpos.sub(piecepos);
|
|
82
|
+
stickerpos.multiplyScalar(2);
|
|
83
|
+
stickerpos.round();
|
|
84
|
+
if (stickerpos.x === 1) {
|
|
85
|
+
state.right[1 - Math.round(piece.position.y)][
|
|
86
|
+
1 - Math.round(piece.position.z)
|
|
87
|
+
] = mesh.material.userData.face;
|
|
88
|
+
} else if (stickerpos.x === -1) {
|
|
89
|
+
state.left[1 - Math.round(piece.position.y)][
|
|
90
|
+
1 + Math.round(piece.position.z)
|
|
91
|
+
] = mesh.material.userData.face;
|
|
92
|
+
} else if (stickerpos.y === 1) {
|
|
93
|
+
state.up[1 + Math.round(piece.position.z)][
|
|
94
|
+
1 + Math.round(piece.position.x)
|
|
95
|
+
] = mesh.material.userData.face;
|
|
96
|
+
} else if (stickerpos.y === -1) {
|
|
97
|
+
state.down[1 - Math.round(piece.position.z)][
|
|
98
|
+
1 + Math.round(piece.position.x)
|
|
99
|
+
] = mesh.material.userData.face;
|
|
100
|
+
} else if (stickerpos.z === 1) {
|
|
101
|
+
state.front[1 - Math.round(piece.position.y)][
|
|
102
|
+
1 + Math.round(piece.position.x)
|
|
103
|
+
] = mesh.material.userData.face;
|
|
104
|
+
} else if (stickerpos.z === -1) {
|
|
105
|
+
state.back[1 - Math.round(piece.position.y)][
|
|
106
|
+
1 - Math.round(piece.position.x)
|
|
107
|
+
] = mesh.material.userData.face;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
return state;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @param {{x:number,y:number,z:number}} position
|
|
117
|
+
* @param {"corner | edge | center"} type
|
|
118
|
+
* @returns {THREE.Group}
|
|
119
|
+
*/
|
|
120
|
+
createPiece(position, type) {
|
|
121
|
+
if (type === "corner") {
|
|
122
|
+
return this.createCorner(position).group;
|
|
123
|
+
} else if (type === "edge") {
|
|
124
|
+
return this.createEdge(position).group;
|
|
125
|
+
} else if (type === "center") {
|
|
126
|
+
return this.createCenter(position).group;
|
|
127
|
+
} else {
|
|
128
|
+
throw new Error("Invalid type: " + type);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* @param {{x:number,y:number,z:number}} position
|
|
133
|
+
* @returns {THREE.Group}
|
|
134
|
+
*/
|
|
135
|
+
createCorner(position) {
|
|
136
|
+
if (position.x == 1 && position.y == 1 && position.z == 1) {
|
|
137
|
+
return newCorner(
|
|
138
|
+
Stickers.corner,
|
|
139
|
+
Materials.front,
|
|
140
|
+
Materials.right,
|
|
141
|
+
Materials.up,
|
|
142
|
+
Materials.core
|
|
143
|
+
);
|
|
144
|
+
} else if (position.x == 1 && position.y == 1 && position.z == -1) {
|
|
145
|
+
return newCorner(
|
|
146
|
+
Stickers.corner,
|
|
147
|
+
Materials.right,
|
|
148
|
+
Materials.back,
|
|
149
|
+
Materials.up,
|
|
150
|
+
Materials.core
|
|
151
|
+
);
|
|
152
|
+
} else if (position.x == 1 && position.y == -1 && position.z == 1) {
|
|
153
|
+
return newCorner(
|
|
154
|
+
Stickers.corner,
|
|
155
|
+
Materials.right,
|
|
156
|
+
Materials.front,
|
|
157
|
+
Materials.down,
|
|
158
|
+
Materials.core
|
|
159
|
+
);
|
|
160
|
+
} else if (position.x == 1 && position.y == -1 && position.z == -1) {
|
|
161
|
+
return newCorner(
|
|
162
|
+
Stickers.corner,
|
|
163
|
+
Materials.back,
|
|
164
|
+
Materials.right,
|
|
165
|
+
Materials.down,
|
|
166
|
+
Materials.core
|
|
167
|
+
);
|
|
168
|
+
} else if (position.x == -1 && position.y == 1 && position.z == 1) {
|
|
169
|
+
return newCorner(
|
|
170
|
+
Stickers.corner,
|
|
171
|
+
Materials.left,
|
|
172
|
+
Materials.front,
|
|
173
|
+
Materials.up,
|
|
174
|
+
Materials.core
|
|
175
|
+
);
|
|
176
|
+
} else if (position.x == -1 && position.y == 1 && position.z == -1) {
|
|
177
|
+
return newCorner(
|
|
178
|
+
Stickers.corner,
|
|
179
|
+
Materials.back,
|
|
180
|
+
Materials.left,
|
|
181
|
+
Materials.up,
|
|
182
|
+
Materials.core
|
|
183
|
+
);
|
|
184
|
+
} else if (position.x == -1 && position.y == -1 && position.z == 1) {
|
|
185
|
+
return newCorner(
|
|
186
|
+
Stickers.corner,
|
|
187
|
+
Materials.front,
|
|
188
|
+
Materials.left,
|
|
189
|
+
Materials.down,
|
|
190
|
+
Materials.core
|
|
191
|
+
);
|
|
192
|
+
} else if (position.x == -1 && position.y == -1 && position.z == -1) {
|
|
193
|
+
return newCorner(
|
|
194
|
+
Stickers.corner,
|
|
195
|
+
Materials.left,
|
|
196
|
+
Materials.back,
|
|
197
|
+
Materials.down,
|
|
198
|
+
Materials.core
|
|
199
|
+
);
|
|
200
|
+
} else {
|
|
201
|
+
throw new Error("Invalid corner position: " + position);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* @param {{x:number,y:number,z:number}} position
|
|
206
|
+
* @returns {THREE.Group}
|
|
207
|
+
*/
|
|
208
|
+
createEdge(position) {
|
|
209
|
+
if (position.x == 1 && position.y == 1 && position.z == 0) {
|
|
210
|
+
return newEdge(
|
|
211
|
+
Stickers.edge,
|
|
212
|
+
Materials.right,
|
|
213
|
+
Materials.up,
|
|
214
|
+
Materials.core
|
|
215
|
+
);
|
|
216
|
+
} else if (position.x == 1 && position.y == -1 && position.z == 0) {
|
|
217
|
+
return newEdge(
|
|
218
|
+
Stickers.edge,
|
|
219
|
+
Materials.right,
|
|
220
|
+
Materials.down,
|
|
221
|
+
Materials.core
|
|
222
|
+
);
|
|
223
|
+
} else if (position.x == 1 && position.y == 0 && position.z == 1) {
|
|
224
|
+
return newEdge(
|
|
225
|
+
Stickers.edge,
|
|
226
|
+
Materials.front,
|
|
227
|
+
Materials.right,
|
|
228
|
+
Materials.core
|
|
229
|
+
);
|
|
230
|
+
} else if (position.x == 1 && position.y == 0 && position.z == -1) {
|
|
231
|
+
return newEdge(
|
|
232
|
+
Stickers.edge,
|
|
233
|
+
Materials.right,
|
|
234
|
+
Materials.back,
|
|
235
|
+
Materials.core
|
|
236
|
+
);
|
|
237
|
+
} else if (position.x == -1 && position.y == 1 && position.z == 0) {
|
|
238
|
+
return newEdge(
|
|
239
|
+
Stickers.edge,
|
|
240
|
+
Materials.left,
|
|
241
|
+
Materials.up,
|
|
242
|
+
Materials.core
|
|
243
|
+
);
|
|
244
|
+
} else if (position.x == -1 && position.y == -1 && position.z == 0) {
|
|
245
|
+
return newEdge(
|
|
246
|
+
Stickers.edge,
|
|
247
|
+
Materials.left,
|
|
248
|
+
Materials.down,
|
|
249
|
+
Materials.core
|
|
250
|
+
);
|
|
251
|
+
} else if (position.x == -1 && position.y == 0 && position.z == 1) {
|
|
252
|
+
return newEdge(
|
|
253
|
+
Stickers.edge,
|
|
254
|
+
Materials.front,
|
|
255
|
+
Materials.left,
|
|
256
|
+
Materials.core
|
|
257
|
+
);
|
|
258
|
+
} else if (position.x == -1 && position.y == 0 && position.z == -1) {
|
|
259
|
+
return newEdge(
|
|
260
|
+
Stickers.edge,
|
|
261
|
+
Materials.left,
|
|
262
|
+
Materials.back,
|
|
263
|
+
Materials.core
|
|
264
|
+
);
|
|
265
|
+
} else if (position.x == 0 && position.y == 1 && position.z == 1) {
|
|
266
|
+
return newEdge(
|
|
267
|
+
Stickers.edge,
|
|
268
|
+
Materials.front,
|
|
269
|
+
Materials.up,
|
|
270
|
+
Materials.core
|
|
271
|
+
);
|
|
272
|
+
} else if (position.x == 0 && position.y == 1 && position.z == -1) {
|
|
273
|
+
return newEdge(
|
|
274
|
+
Stickers.edge,
|
|
275
|
+
Materials.up,
|
|
276
|
+
Materials.back,
|
|
277
|
+
Materials.core
|
|
278
|
+
);
|
|
279
|
+
} else if (position.x == 0 && position.y == -1 && position.z == 1) {
|
|
280
|
+
return newEdge(
|
|
281
|
+
Stickers.edge,
|
|
282
|
+
Materials.down,
|
|
283
|
+
Materials.front,
|
|
284
|
+
Materials.core
|
|
285
|
+
);
|
|
286
|
+
} else if (position.x == 0 && position.y == -1 && position.z == -1) {
|
|
287
|
+
return newEdge(
|
|
288
|
+
Stickers.edge,
|
|
289
|
+
Materials.back,
|
|
290
|
+
Materials.down,
|
|
291
|
+
Materials.core
|
|
292
|
+
);
|
|
293
|
+
} else {
|
|
294
|
+
throw new Error("Invalid edge position: " + position);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* @param {{x:number,y:number,z:number}} position
|
|
299
|
+
* @returns {THREE.Group}
|
|
300
|
+
*/
|
|
301
|
+
createCenter(position) {
|
|
302
|
+
var centerColor = Materials.up;
|
|
303
|
+
if (position.x !== 0) {
|
|
304
|
+
centerColor = position.x > 0 ? Materials.right : Materials.left;
|
|
305
|
+
} else if (position.y !== 0) {
|
|
306
|
+
centerColor = position.y > 0 ? Materials.up : Materials.down;
|
|
307
|
+
} else if (position.z !== 0) {
|
|
308
|
+
centerColor = position.z > 0 ? Materials.front : Materials.back;
|
|
309
|
+
}
|
|
310
|
+
return newCenter(Stickers.center, centerColor, Materials.core);
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* @returns {THREE.Group}
|
|
314
|
+
*/
|
|
315
|
+
createCore() {
|
|
316
|
+
return new THREE.Mesh(new THREE.SphereGeometry(1.55), Materials.core);
|
|
317
|
+
}
|
|
318
|
+
}
|
package/src/cubeState.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {{x: number,y: number,z: number}} vector
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @type {vector[]}
|
|
7
|
+
*/
|
|
8
|
+
const corners = [
|
|
9
|
+
{ position: { x: 1, y: 1, z: 1 }, rotation: { x: 0, y: 0, z: 0 } },
|
|
10
|
+
{ position: { x: 1, y: 1, z: -1 }, rotation: { x: 0, y: Math.PI / 2, z: 0 } },
|
|
11
|
+
{
|
|
12
|
+
position: { x: 1, y: -1, z: 1 },
|
|
13
|
+
rotation: { x: 0, y: Math.PI / 2, z: Math.PI },
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
position: { x: 1, y: -1, z: -1 },
|
|
17
|
+
rotation: { x: 0, y: Math.PI, z: Math.PI },
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
{
|
|
21
|
+
position: { x: -1, y: 1, z: 1 },
|
|
22
|
+
rotation: { x: 0, y: -Math.PI / 2, z: 0 },
|
|
23
|
+
},
|
|
24
|
+
{ position: { x: -1, y: 1, z: -1 }, rotation: { x: 0, y: Math.PI, z: 0 } },
|
|
25
|
+
{ position: { x: -1, y: -1, z: 1 }, rotation: { x: 0, y: 0, z: Math.PI } },
|
|
26
|
+
{
|
|
27
|
+
position: { x: -1, y: -1, z: -1 },
|
|
28
|
+
rotation: { x: 0, y: -Math.PI / 2, z: Math.PI },
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @type {vector[]}
|
|
34
|
+
*/
|
|
35
|
+
const edges = [
|
|
36
|
+
{ position: { x: 1, y: 1, z: 0 }, rotation: { x: 0, y: Math.PI / 2, z: 0 } },
|
|
37
|
+
{
|
|
38
|
+
position: { x: 1, y: 0, z: 1 },
|
|
39
|
+
rotation: { x: 0, y: 0, z: -Math.PI / 2 },
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
position: { x: 1, y: 0, z: -1 },
|
|
43
|
+
rotation: { x: 0, y: Math.PI / 2, z: -Math.PI / 2 },
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
position: { x: 1, y: -1, z: 0 },
|
|
47
|
+
rotation: { x: Math.PI, y: Math.PI / 2, z: 0 },
|
|
48
|
+
},
|
|
49
|
+
{ position: { x: 0, y: 1, z: 1 }, rotation: { x: 0, y: 0, z: 0 } },
|
|
50
|
+
{
|
|
51
|
+
position: { x: 0, y: 1, z: -1 },
|
|
52
|
+
rotation: { x: -Math.PI / 2, y: 0, z: 0 },
|
|
53
|
+
},
|
|
54
|
+
{ position: { x: 0, y: -1, z: 1 }, rotation: { x: Math.PI / 2, y: 0, z: 0 } },
|
|
55
|
+
{ position: { x: 0, y: -1, z: -1 }, rotation: { x: Math.PI, y: 0, z: 0 } },
|
|
56
|
+
{
|
|
57
|
+
position: { x: -1, y: 1, z: 0 },
|
|
58
|
+
rotation: { x: 0, y: -Math.PI / 2, z: 0 },
|
|
59
|
+
},
|
|
60
|
+
{ position: { x: -1, y: 0, z: 1 }, rotation: { x: 0, y: 0, z: Math.PI / 2 } },
|
|
61
|
+
{
|
|
62
|
+
position: { x: -1, y: 0, z: -1 },
|
|
63
|
+
rotation: { x: 0, y: -Math.PI / 2, z: Math.PI / 2 },
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
position: { x: -1, y: -1, z: 0 },
|
|
67
|
+
rotation: { x: 0, y: -Math.PI / 2, z: Math.PI },
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @type {vector[]}
|
|
73
|
+
*/
|
|
74
|
+
const centers = [
|
|
75
|
+
{ position: { x: 1, y: 0, z: 0 }, rotation: { x: 0, y: Math.PI / 2, z: 0 } },
|
|
76
|
+
{ position: { x: 0, y: 1, z: 0 }, rotation: { x: -Math.PI / 2, y: 0, z: 0 } },
|
|
77
|
+
{ position: { x: 0, y: 0, z: 1 }, rotation: { x: 0, y: 0, z: 0 } },
|
|
78
|
+
{ position: { x: 0, y: 0, z: -1 }, rotation: { x: 0, y: Math.PI, z: 0 } },
|
|
79
|
+
{ position: { x: 0, y: -1, z: 0 }, rotation: { x: Math.PI / 2, y: 0, z: 0 } },
|
|
80
|
+
{
|
|
81
|
+
position: { x: -1, y: 0, z: 0 },
|
|
82
|
+
rotation: { x: 0, y: -Math.PI / 2, z: 0 },
|
|
83
|
+
},
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @typedef {Object} state
|
|
88
|
+
* @property {string} type
|
|
89
|
+
* @property {vector} initialPosition
|
|
90
|
+
* @property {vector} initialRotation
|
|
91
|
+
* @property {vector} position
|
|
92
|
+
* @property {vector} rotation
|
|
93
|
+
*/
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @returns {state[]}
|
|
97
|
+
*/
|
|
98
|
+
export function newCubeState() {
|
|
99
|
+
let cubeState = [];
|
|
100
|
+
corners.forEach(({ position, rotation }) => {
|
|
101
|
+
cubeState.push({
|
|
102
|
+
type: "corner",
|
|
103
|
+
initialPosition: position,
|
|
104
|
+
initialRotation: rotation,
|
|
105
|
+
position: position,
|
|
106
|
+
rotation: rotation,
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
edges.forEach(({ position, rotation }) => {
|
|
110
|
+
cubeState.push({
|
|
111
|
+
type: "edge",
|
|
112
|
+
initialPosition: position,
|
|
113
|
+
initialRotation: rotation,
|
|
114
|
+
position: position,
|
|
115
|
+
rotation: rotation,
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
centers.forEach(({ position, rotation }) => {
|
|
119
|
+
cubeState.push({
|
|
120
|
+
type: "center",
|
|
121
|
+
initialPosition: position,
|
|
122
|
+
initialRotation: rotation,
|
|
123
|
+
position: position,
|
|
124
|
+
rotation: rotation,
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
return cubeState;
|
|
128
|
+
}
|
package/src/edge.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
/**
|
|
3
|
+
* @param {THREE.Geometry} sticker
|
|
4
|
+
* @param {THREE.Material} frontMaterial
|
|
5
|
+
* @param {THREE.Material} topMaterial
|
|
6
|
+
* @param {THREE.Material} coreMaterial
|
|
7
|
+
* @returns {{group: THREE.Group, frontSticker: THREE.Mesh, topSticker:THREE.Mesh}}
|
|
8
|
+
*/
|
|
9
|
+
export default function newEdge(
|
|
10
|
+
sticker,
|
|
11
|
+
frontMaterial,
|
|
12
|
+
topMaterial,
|
|
13
|
+
coreMaterial
|
|
14
|
+
) {
|
|
15
|
+
const group = new THREE.Group();
|
|
16
|
+
const boxGeom = new THREE.BoxGeometry(1, 1, 1);
|
|
17
|
+
const boxMesh = new THREE.Mesh(boxGeom, coreMaterial);
|
|
18
|
+
boxMesh.userData = { type: "piece" };
|
|
19
|
+
group.add(boxMesh);
|
|
20
|
+
|
|
21
|
+
// front
|
|
22
|
+
const frontSticker = new THREE.Mesh(sticker, frontMaterial);
|
|
23
|
+
frontSticker.userData = { type: "sticker" };
|
|
24
|
+
frontSticker.position.set(0, 0, 0.5);
|
|
25
|
+
frontSticker.rotation.set(0, 0, 0);
|
|
26
|
+
group.add(frontSticker);
|
|
27
|
+
|
|
28
|
+
// top
|
|
29
|
+
const topSticker = new THREE.Mesh(sticker, topMaterial);
|
|
30
|
+
topSticker.userData = { type: "sticker" };
|
|
31
|
+
topSticker.position.set(0, 0.5, 0);
|
|
32
|
+
topSticker.rotation.set(-Math.PI / 2, 0, Math.PI);
|
|
33
|
+
group.add(topSticker);
|
|
34
|
+
|
|
35
|
+
return { group, frontSticker, topSticker };
|
|
36
|
+
}
|
package/src/materials.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
export default class Materials {
|
|
3
|
+
static front = new THREE.MeshStandardMaterial({
|
|
4
|
+
color: "#2cbf13",
|
|
5
|
+
metalness: 0,
|
|
6
|
+
roughness: 0.4,
|
|
7
|
+
userData: { face: "front", color: "green" },
|
|
8
|
+
});
|
|
9
|
+
static back = new THREE.MeshStandardMaterial({
|
|
10
|
+
color: "blue",
|
|
11
|
+
metalness: 0,
|
|
12
|
+
roughness: 0.4,
|
|
13
|
+
userData: { face: "back", color: "blue" },
|
|
14
|
+
});
|
|
15
|
+
static up = new THREE.MeshStandardMaterial({
|
|
16
|
+
color: "white",
|
|
17
|
+
metalness: 0,
|
|
18
|
+
roughness: 0.4,
|
|
19
|
+
userData: { face: "up", color: "white" },
|
|
20
|
+
});
|
|
21
|
+
static down = new THREE.MeshStandardMaterial({
|
|
22
|
+
color: "yellow",
|
|
23
|
+
metalness: 0,
|
|
24
|
+
roughness: 0.4,
|
|
25
|
+
userData: { face: "down", color: "yellow" },
|
|
26
|
+
});
|
|
27
|
+
static left = new THREE.MeshStandardMaterial({
|
|
28
|
+
color: "#fc9a05",
|
|
29
|
+
metalness: 0,
|
|
30
|
+
roughness: 0.4,
|
|
31
|
+
userData: { face: "left", color: "orange" },
|
|
32
|
+
});
|
|
33
|
+
static right = new THREE.MeshStandardMaterial({
|
|
34
|
+
color: "red",
|
|
35
|
+
metalness: 0,
|
|
36
|
+
roughness: 0.4,
|
|
37
|
+
userData: { face: "right", color: "red" },
|
|
38
|
+
});
|
|
39
|
+
static core = new THREE.MeshBasicMaterial({
|
|
40
|
+
color: "black",
|
|
41
|
+
});
|
|
42
|
+
}
|
package/src/rotation.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
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 getRotationDetails(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
|
+
return undefined;
|
|
52
|
+
}
|
package/src/stickers.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
import { SVGLoader } from "three/examples/jsm/Addons.js";
|
|
3
|
+
|
|
4
|
+
const loader = new SVGLoader();
|
|
5
|
+
const cornerSVG = loader.parse(`
|
|
6
|
+
<svg viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg" xmlns:bx="https://boxy-svg.com">
|
|
7
|
+
<path d="M 25 0 H 500 V 500 H 0 V 25 A 25 25 0 0 1 25 0 Z" bx:shape="rect 0 0 500 500 25 0 0 0 1@a864c1ee"/>
|
|
8
|
+
</svg>
|
|
9
|
+
`);
|
|
10
|
+
const edgeSVG = loader.parse(`
|
|
11
|
+
<svg viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg">
|
|
12
|
+
<path d="M 150 0 L 350 0 C 450 0 500 50 500 120 L 500 500 L 0 500 L 0 120 C 0 50 50 0 150 0 Z"></path>
|
|
13
|
+
</svg>
|
|
14
|
+
`);
|
|
15
|
+
const centerSVG = loader.parse(`
|
|
16
|
+
<svg viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg">
|
|
17
|
+
<path d="M 120 0 L 380 0 C 450 0 500 50 500 120 L 500 380 C 500 450 450 500 380 500 L 120 500 C 50 500 0 450 0 380 L 0 120 C 0 50 50 0 120 0 Z"></path>
|
|
18
|
+
</svg>
|
|
19
|
+
`);
|
|
20
|
+
|
|
21
|
+
export default class Stickers {
|
|
22
|
+
static center = new THREE.ExtrudeGeometry(
|
|
23
|
+
SVGLoader.createShapes(centerSVG.paths[0])[0],
|
|
24
|
+
{
|
|
25
|
+
depth: 15,
|
|
26
|
+
}
|
|
27
|
+
)
|
|
28
|
+
.scale(0.002, 0.002, 0.002)
|
|
29
|
+
.translate(-0.5, -0.5, 0);
|
|
30
|
+
|
|
31
|
+
static edge = new THREE.ExtrudeGeometry(
|
|
32
|
+
SVGLoader.createShapes(edgeSVG.paths[0])[0],
|
|
33
|
+
{
|
|
34
|
+
depth: 15,
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
.scale(0.002, 0.002, 0.002)
|
|
38
|
+
.translate(-0.5, -0.5, 0);
|
|
39
|
+
|
|
40
|
+
static corner = new THREE.ExtrudeGeometry(
|
|
41
|
+
SVGLoader.createShapes(cornerSVG.paths[0])[0],
|
|
42
|
+
{
|
|
43
|
+
depth: 15,
|
|
44
|
+
}
|
|
45
|
+
)
|
|
46
|
+
.scale(0.002, 0.002, 0.002)
|
|
47
|
+
.translate(-0.5, -0.5, 0);
|
|
48
|
+
}
|