rubiks-cube-3d 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.
@@ -0,0 +1,932 @@
1
+ import * as e from "three";
2
+ import { RoomEnvironment as t } from "three/examples/jsm/environments/RoomEnvironment.js";
3
+ import n from "matter-js";
4
+ import { RoundedBoxGeometry as r } from "three/examples/jsm/geometries/RoundedBoxGeometry.js";
5
+ //#region src/core/physics.ts
6
+ var i = 1e3 / 60, a = 6, o = 6.5, s = class {
7
+ constructor(e) {
8
+ this.accumulator = 0, this.elapsed = 0, this.spinningUp = !1, this.bobbing = !1, this.snapping = !1, this.opts = e, this.engine = n.Engine.create({ gravity: {
9
+ x: 0,
10
+ y: 0,
11
+ scale: 0
12
+ } }), this.flywheel = n.Bodies.circle(0, 0, 1, {
13
+ frictionAir: e.scrambleFriction,
14
+ collisionFilter: {
15
+ group: -1,
16
+ mask: 0
17
+ }
18
+ }), n.Body.setMass(this.flywheel, 1), this.floater = n.Bodies.circle(0, 0, 1, {
19
+ frictionAir: .012,
20
+ collisionFilter: {
21
+ group: -1,
22
+ mask: 0
23
+ }
24
+ }), n.Body.setMass(this.floater, 1), this.floatSpring = n.Constraint.create({
25
+ pointA: {
26
+ x: 0,
27
+ y: 0
28
+ },
29
+ bodyB: this.floater,
30
+ length: 0,
31
+ stiffness: e.floatStiffness,
32
+ damping: e.floatDamping
33
+ }), this.snapper = n.Bodies.circle(0, 0, 1, {
34
+ frictionAir: 0,
35
+ collisionFilter: {
36
+ group: -1,
37
+ mask: 0
38
+ }
39
+ }), n.Body.setMass(this.snapper, 1), this.snapSpring = n.Constraint.create({
40
+ pointA: {
41
+ x: 0,
42
+ y: 0
43
+ },
44
+ bodyB: this.snapper,
45
+ length: 0,
46
+ stiffness: e.snapStiffness,
47
+ damping: e.snapDamping
48
+ }), n.Composite.add(this.engine.world, [
49
+ this.flywheel,
50
+ this.floater,
51
+ this.floatSpring,
52
+ this.snapper,
53
+ this.snapSpring
54
+ ]);
55
+ }
56
+ setOptions(e) {
57
+ this.opts = e, this.flywheel.frictionAir = e.scrambleFriction, this.floatSpring.stiffness = e.floatStiffness, this.floatSpring.damping = e.floatDamping, this.snapSpring.stiffness = e.snapStiffness, this.snapSpring.damping = e.snapDamping;
58
+ }
59
+ resetFlywheel() {
60
+ n.Body.setPosition(this.flywheel, {
61
+ x: 0,
62
+ y: 0
63
+ }), n.Body.setVelocity(this.flywheel, {
64
+ x: 0,
65
+ y: 0
66
+ }), this.spinningUp = !1;
67
+ }
68
+ setSpinningUp(e) {
69
+ this.spinningUp = e;
70
+ }
71
+ get scrambleSpeed() {
72
+ let e = Math.abs(this.flywheel.velocity.x);
73
+ return Math.min(this.opts.maxSpeed, Math.max(this.opts.minSpeed, e));
74
+ }
75
+ get flywheelVelocity() {
76
+ return this.flywheel.velocity.x;
77
+ }
78
+ get quarterTurnsToCoast() {
79
+ let e = Math.abs(this.flywheel.velocity.x), t = Math.max(1e-4, this.opts.scrambleFriction);
80
+ return Math.max(0, e - this.opts.minSpeed) / (60 * t) / (Math.PI / 2);
81
+ }
82
+ setFloatTarget(e) {
83
+ this.floatSpring.pointA.y = e;
84
+ }
85
+ setBobbing(e) {
86
+ this.bobbing = e;
87
+ }
88
+ get floatY() {
89
+ return this.floater.position.y;
90
+ }
91
+ beginSnap(e, t, r) {
92
+ n.Body.setPosition(this.snapper, {
93
+ x: e,
94
+ y: 0
95
+ }), n.Body.setVelocity(this.snapper, {
96
+ x: t / 60,
97
+ y: 0
98
+ }), this.snapSpring.pointA.x = r, this.snapping = !0;
99
+ }
100
+ endSnap() {
101
+ this.snapping = !1;
102
+ }
103
+ get snapAngle() {
104
+ return this.snapper.position.x;
105
+ }
106
+ get snapTarget() {
107
+ return this.snapSpring.pointA.x;
108
+ }
109
+ get snapSettled() {
110
+ return this.snapping && Math.abs(this.snapper.position.x - this.snapSpring.pointA.x) < .004 && Math.abs(this.snapper.velocity.x) < .004;
111
+ }
112
+ step(e) {
113
+ this.accumulator += Math.min(e, .1) * 1e3;
114
+ let t = 0;
115
+ for (; this.accumulator >= i && t < a;) this.substep(), this.accumulator -= i, t++;
116
+ t === a && (this.accumulator = 0);
117
+ }
118
+ substep() {
119
+ this.elapsed += i / 1e3;
120
+ let { opts: e } = this;
121
+ if (this.spinningUp && n.Body.applyForce(this.flywheel, this.flywheel.position, {
122
+ x: e.scrambleForce,
123
+ y: 0
124
+ }), n.Body.setPosition(this.flywheel, {
125
+ x: 0,
126
+ y: 0
127
+ }), this.bobbing) {
128
+ let t = 2 * Math.PI * e.floatFrequency, r = Math.sin(this.elapsed * t) * e.floatAmplitude * e.floatStiffness * o;
129
+ n.Body.applyForce(this.floater, this.floater.position, {
130
+ x: 0,
131
+ y: r / 277.8
132
+ });
133
+ }
134
+ n.Body.setPosition(this.floater, {
135
+ x: 0,
136
+ y: this.floater.position.y
137
+ }), n.Engine.update(this.engine, i);
138
+ }
139
+ dispose() {
140
+ n.World.clear(this.engine.world, !1), n.Engine.clear(this.engine);
141
+ }
142
+ }, c = {
143
+ R: new e.Vector3(1, 0, 0),
144
+ L: new e.Vector3(-1, 0, 0),
145
+ U: new e.Vector3(0, 1, 0),
146
+ D: new e.Vector3(0, -1, 0),
147
+ F: new e.Vector3(0, 0, 1),
148
+ B: new e.Vector3(0, 0, -1)
149
+ };
150
+ function l(t, n) {
151
+ let r = t / 2, i = new e.Shape();
152
+ return i.moveTo(-r + n, -r), i.lineTo(r - n, -r), i.quadraticCurveTo(r, -r, r, -r + n), i.lineTo(r, r - n), i.quadraticCurveTo(r, r, r - n, r), i.lineTo(-r + n, r), i.quadraticCurveTo(-r, r, -r, r - n), i.lineTo(-r, -r + n), i.quadraticCurveTo(-r, -r, -r + n, -r), i;
153
+ }
154
+ function u(t) {
155
+ return new e.MeshPhysicalMaterial({
156
+ color: new e.Color(t.body),
157
+ roughness: .42,
158
+ metalness: 0,
159
+ clearcoat: .55,
160
+ clearcoatRoughness: .3,
161
+ envMapIntensity: .9
162
+ });
163
+ }
164
+ function d(t, n) {
165
+ return new e.MeshPhysicalMaterial({
166
+ color: new e.Color(t),
167
+ roughness: n,
168
+ metalness: 0,
169
+ clearcoat: .9,
170
+ clearcoatRoughness: .18,
171
+ envMapIntensity: .9
172
+ });
173
+ }
174
+ function f(t) {
175
+ let { size: n, colors: i, sticker: a } = t, o = 1 - t.cubieGap, s = (n - 1) / 2, f = [], p = [], m = new r(o, o, o, 5, t.cubieRounding * o);
176
+ f.push(m);
177
+ let h = u(i);
178
+ p.push(h);
179
+ let g = .006, _ = a.inset * o - 2 * g, v = new e.ExtrudeGeometry(l(_, a.cornerRadius * o), {
180
+ depth: Math.max(.002, a.height - g),
181
+ bevelEnabled: !0,
182
+ bevelThickness: g,
183
+ bevelSize: g,
184
+ bevelSegments: 2,
185
+ curveSegments: 6
186
+ });
187
+ v.computeVertexNormals(), f.push(v);
188
+ let y = {
189
+ U: d(i.U, a.roughness),
190
+ D: d(i.D, a.roughness),
191
+ F: d(i.F, a.roughness),
192
+ B: d(i.B, a.roughness),
193
+ L: d(i.L, a.roughness),
194
+ R: d(i.R, a.roughness)
195
+ };
196
+ p.push(...Object.values(y));
197
+ let b = [], x = new e.Vector3(0, 0, 1);
198
+ for (let t = 0; t < n; t++) for (let r = 0; r < n; r++) for (let i = 0; i < n; i++) {
199
+ let n = t - s, a = r - s, l = i - s;
200
+ if (Math.abs(n) !== s && Math.abs(a) !== s && Math.abs(l) !== s) continue;
201
+ let u = new e.Group();
202
+ u.position.set(n, a, l);
203
+ let d = new e.Mesh(m, h);
204
+ d.castShadow = !0, d.receiveShadow = !0, u.add(d);
205
+ let f = [], p = [];
206
+ n === s && p.push("R"), n === -s && p.push("L"), a === s && p.push("U"), a === -s && p.push("D"), l === s && p.push("F"), l === -s && p.push("B");
207
+ for (let t of p) {
208
+ let n = c[t], r = new e.Mesh(v, y[t]);
209
+ r.quaternion.setFromUnitVectors(x, n), r.position.copy(n).multiplyScalar(o / 2 - .002), r.castShadow = !1, r.receiveShadow = !0, r.userData.face = t, u.add(r), f.push({
210
+ face: t,
211
+ localNormal: n.clone(),
212
+ mesh: r
213
+ });
214
+ }
215
+ let g = {
216
+ object: u,
217
+ body: d,
218
+ stickers: f,
219
+ grid: new e.Vector3(n, a, l),
220
+ home: new e.Vector3(n, a, l)
221
+ };
222
+ u.userData.cubie = g, d.userData.cubie = g, b.push(g);
223
+ }
224
+ return {
225
+ cubies: b,
226
+ resources: {
227
+ geometries: f,
228
+ materials: p
229
+ }
230
+ };
231
+ }
232
+ function p(t) {
233
+ let n = new e.Matrix4().makeRotationFromQuaternion(t), r = n.elements;
234
+ for (let e = 0; e < 16; e++) r[e] = Math.round(r[e]);
235
+ t.setFromRotationMatrix(n);
236
+ }
237
+ function m(e) {
238
+ e.x = Math.round(e.x * 2) / 2, e.y = Math.round(e.y * 2) / 2, e.z = Math.round(e.z * 2) / 2, Object.is(e.x, -0) && (e.x = 0), Object.is(e.y, -0) && (e.y = 0), Object.is(e.z, -0) && (e.z = 0);
239
+ }
240
+ //#endregion
241
+ //#region src/core/notation.ts
242
+ var h = {
243
+ R: {
244
+ axis: "x",
245
+ sign: 1,
246
+ cw: -1
247
+ },
248
+ L: {
249
+ axis: "x",
250
+ sign: -1,
251
+ cw: 1
252
+ },
253
+ U: {
254
+ axis: "y",
255
+ sign: 1,
256
+ cw: -1
257
+ },
258
+ D: {
259
+ axis: "y",
260
+ sign: -1,
261
+ cw: 1
262
+ },
263
+ F: {
264
+ axis: "z",
265
+ sign: 1,
266
+ cw: -1
267
+ },
268
+ B: {
269
+ axis: "z",
270
+ sign: -1,
271
+ cw: 1
272
+ }
273
+ }, g = {
274
+ M: {
275
+ axis: "x",
276
+ cw: 1
277
+ },
278
+ E: {
279
+ axis: "y",
280
+ cw: 1
281
+ },
282
+ S: {
283
+ axis: "z",
284
+ cw: -1
285
+ }
286
+ }, _ = {
287
+ x: {
288
+ axis: "x",
289
+ cw: -1
290
+ },
291
+ y: {
292
+ axis: "y",
293
+ cw: -1
294
+ },
295
+ z: {
296
+ axis: "z",
297
+ cw: -1
298
+ }
299
+ }, v = /^(\d*)([UDFBLRudfblrMESxyz])(w?)(['’2]*)$/;
300
+ function y(e) {
301
+ let t = (e - 1) / 2, n = [];
302
+ for (let r = 0; r < e; r++) n.push(r - t);
303
+ return n;
304
+ }
305
+ function b(e) {
306
+ let t = 1;
307
+ for (let n of e) t *= n === "2" ? 2 : -1;
308
+ return t;
309
+ }
310
+ function x(e, t) {
311
+ let n = (t - 1) / 2, r = [];
312
+ for (let i of e.trim().split(/\s+/)) {
313
+ if (!i) continue;
314
+ let e = v.exec(i);
315
+ if (!e) throw Error(`rubiks-cube-3d: cannot parse move "${i}"`);
316
+ let [, a, o, s, c] = e, l = b(c), u = a ? parseInt(a, 10) : 0;
317
+ if (o === "x" || o === "y" || o === "z") {
318
+ let e = _[o];
319
+ r.push({
320
+ axis: e.axis,
321
+ layers: y(t),
322
+ quarterTurns: e.cw * l
323
+ });
324
+ continue;
325
+ }
326
+ if (o === "M" || o === "E" || o === "S") {
327
+ let e = g[o], n = t % 2 == 1 ? [0] : [-.5, .5];
328
+ r.push({
329
+ axis: e.axis,
330
+ layers: n,
331
+ quarterTurns: e.cw * l
332
+ });
333
+ continue;
334
+ }
335
+ let d = o === o.toLowerCase(), f = h[o.toUpperCase()], p = d || s === "w", m = [];
336
+ if (p) {
337
+ let e = Math.min(t - 1, Math.max(2, u || 2));
338
+ for (let t = 0; t < e; t++) m.push(f.sign * (n - t));
339
+ } else u > 1 ? m.push(f.sign * (n - Math.min(u, t) + 1)) : m.push(f.sign * n);
340
+ r.push({
341
+ axis: f.axis,
342
+ layers: m,
343
+ quarterTurns: f.cw * l
344
+ });
345
+ }
346
+ return r;
347
+ }
348
+ function S(e, t) {
349
+ let n = (t - 1) / 2, r = (e.quarterTurns % 4 + 4) % 4;
350
+ if (r === 0) return "";
351
+ let i = (e) => {
352
+ let t = e * (r === 3 ? -1 : r);
353
+ return t === 2 || t === -2 ? "2" : t > 0 ? "" : "'";
354
+ }, a = [...e.layers].sort((e, t) => e - t);
355
+ if (a.length === t) {
356
+ let t = _[e.axis];
357
+ return `${e.axis}${i(t.cw)}`;
358
+ }
359
+ let o = a.find((e) => Math.abs(e) === n), s = o === void 0 ? a[a.length - 1] > 0 : o > 0, c = Object.keys(h).find((t) => h[t].axis === e.axis && h[t].sign > 0 === s), l = h[c];
360
+ if (a.length === 1) {
361
+ let r = a[0];
362
+ if (Math.abs(r) === n) return `${c}${i(l.cw)}`;
363
+ if (t % 2 == 1 && r === 0) {
364
+ let t = e.axis === "x" ? "M" : e.axis === "y" ? "E" : "S";
365
+ return `${t}${i(g[t].cw)}`;
366
+ }
367
+ return `${Math.round(n - Math.abs(r)) + 1}${c}${i(l.cw)}`;
368
+ }
369
+ if (t % 2 == 0 && a.length === 2 && a[0] === -.5 && a[1] === .5) {
370
+ let t = e.axis === "x" ? "M" : e.axis === "y" ? "E" : "S";
371
+ return `${t}${i(g[t].cw)}`;
372
+ }
373
+ let u = a.length;
374
+ return `${u > 2 ? u : ""}${c}w${i(l.cw)}`;
375
+ }
376
+ function C(e, t, n = Math.random) {
377
+ let r = [
378
+ "x",
379
+ "y",
380
+ "z"
381
+ ], i = y(e), a = (e - 1) / 2, o = [], s = null, c = 0;
382
+ for (let l = 0; l < t; l++) {
383
+ let t;
384
+ do
385
+ t = r[Math.floor(n() * 3)];
386
+ while (t === s && c >= 1 && n() < .85);
387
+ c = t === s ? c + 1 : 0, s = t;
388
+ let l;
389
+ l = e === 3 || n() < .7 ? n() < .5 ? a : -a : i[Math.floor(n() * i.length)];
390
+ let u = n() < .15 ? 2 : n() < .5 ? 1 : -1;
391
+ o.push({
392
+ axis: t,
393
+ layers: [l],
394
+ quarterTurns: u
395
+ });
396
+ }
397
+ return o;
398
+ }
399
+ //#endregion
400
+ //#region src/core/types.ts
401
+ var w = {
402
+ U: "#ffd60a",
403
+ D: "#f7f7f7",
404
+ F: "#e5352b",
405
+ B: "#ff7f11",
406
+ L: "#3a7bd5",
407
+ R: "#3cb44b",
408
+ body: "#0b0b0d"
409
+ }, T = {
410
+ scrambleForce: 66e-5,
411
+ scrambleFriction: .013,
412
+ maxSpeed: 11,
413
+ minSpeed: 1.6,
414
+ floatLift: .22,
415
+ floatAmplitude: .09,
416
+ floatFrequency: .45,
417
+ floatStiffness: .004,
418
+ floatDamping: .03,
419
+ idleSpin: .25,
420
+ idleTumble: .06,
421
+ snapStiffness: .03,
422
+ snapDamping: .07
423
+ }, E = {
424
+ enabled: !0,
425
+ dragPixelsPerQuarterTurn: 110,
426
+ orbit: !0,
427
+ orbitSensitivity: .008,
428
+ interruptScramble: !1
429
+ }, D = {
430
+ fov: 32,
431
+ pitch: 31,
432
+ yaw: 38
433
+ }, O = {
434
+ inset: .84,
435
+ cornerRadius: .09,
436
+ height: .018,
437
+ roughness: .28
438
+ }, k = {
439
+ size: 3,
440
+ background: "transparent",
441
+ colors: {},
442
+ scrambleOnMount: !0,
443
+ scrambleMoves: 25,
444
+ scrambleDelay: 350,
445
+ moveDuration: 260,
446
+ physics: {},
447
+ interaction: {},
448
+ camera: {},
449
+ sticker: {},
450
+ shadow: !0,
451
+ shadowOpacity: .28,
452
+ shadowMapSize: 2048,
453
+ cubieRounding: .1,
454
+ cubieGap: .035,
455
+ maxPixelRatio: 2,
456
+ antialias: !0,
457
+ exposure: 1,
458
+ idleOnMount: !0
459
+ };
460
+ function A(e) {
461
+ let t = {};
462
+ if (!e) return t;
463
+ for (let n of Object.keys(e)) e[n] !== void 0 && (t[n] = e[n]);
464
+ return t;
465
+ }
466
+ function j(e = {}) {
467
+ let t = A(e), n = Math.max(2, Math.min(7, Math.round(t.size ?? k.size)));
468
+ return {
469
+ ...k,
470
+ ...t,
471
+ size: n,
472
+ colors: {
473
+ ...w,
474
+ ...A(t.colors)
475
+ },
476
+ physics: {
477
+ ...T,
478
+ ...A(t.physics)
479
+ },
480
+ interaction: {
481
+ ...E,
482
+ ...A(t.interaction)
483
+ },
484
+ camera: {
485
+ ...D,
486
+ ...A(t.camera)
487
+ },
488
+ sticker: {
489
+ ...O,
490
+ ...A(t.sticker)
491
+ }
492
+ };
493
+ }
494
+ //#endregion
495
+ //#region src/core/RubiksCubeEngine.ts
496
+ var M = Math.PI / 2, N = {
497
+ x: new e.Vector3(1, 0, 0),
498
+ y: new e.Vector3(0, 1, 0),
499
+ z: new e.Vector3(0, 0, 1)
500
+ }, P = class {
501
+ constructor(n, r = {}) {
502
+ this.cubeGroup = new e.Group(), this.cubies = [], this.raycaster = new e.Raycaster(), this.queue = [], this.active = null, this.drag = null, this.scrambleRemaining = 0, this.scrambleResolve = null, this.hovering = !1, this.solved = !0, this.timer = new e.Timer(), this.elapsed = 0, this.rafId = 0, this.disposed = !1, this.touchLeaveTimer = 0, this.listeners = /* @__PURE__ */ new Map(), this.mountTimer = 0, this.squareMode = !1, this.resize = () => {
503
+ let e = this.container.clientWidth || 300;
504
+ this.container.clientHeight || (this.squareMode = !0);
505
+ let t = this.squareMode ? e : this.container.clientHeight;
506
+ this.canvas.style.height = this.squareMode ? `${e}px` : "100%", this.renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, this.options.maxPixelRatio)), this.renderer.setSize(e, t, !1), this.camera.aspect = e / t, this.camera.updateProjectionMatrix();
507
+ }, this.tick = () => {
508
+ if (this.disposed) return;
509
+ this.rafId = requestAnimationFrame(this.tick), this.timer.update();
510
+ let e = Math.min(this.timer.getDelta(), .1);
511
+ this.elapsed += e, this.physics.step(e), this.updateTurn(e), this.updateIdle(e), this.cubeGroup.position.y = this.physics.floatY, this.renderer.render(this.scene, this.camera);
512
+ }, this.preventDefault = (e) => e.preventDefault(), this.onPointerEnter = (e) => {
513
+ e.pointerType !== "touch" && (window.clearTimeout(this.touchLeaveTimer), this.setHover(!0));
514
+ }, this.onPointerLeave = (e) => {
515
+ e.pointerType !== "touch" && (this.drag || this.setHover(!1));
516
+ }, this.onPointerDown = (e) => {
517
+ let t = this.options.interaction;
518
+ if (!t.enabled || e.pointerType === "mouse" && e.button !== 0 || (e.pointerType === "touch" && (window.clearTimeout(this.touchLeaveTimer), this.setHover(!0)), this.drag)) return;
519
+ let n = this.state === "scrambling";
520
+ if (n && !t.interruptScramble) return;
521
+ let r = this.pickCubie(e);
522
+ if (r) {
523
+ if (this.active) {
524
+ n && this.cancelQueue();
525
+ return;
526
+ }
527
+ this.drag = {
528
+ pointerId: e.pointerId,
529
+ startX: e.clientX,
530
+ startY: e.clientY,
531
+ lastX: e.clientX,
532
+ lastY: e.clientY,
533
+ kind: "undecided",
534
+ cubie: r.cubie,
535
+ normal: r.normal,
536
+ hit: r.point,
537
+ samples: []
538
+ };
539
+ } else if (t.orbit) this.drag = {
540
+ pointerId: e.pointerId,
541
+ startX: e.clientX,
542
+ startY: e.clientY,
543
+ lastX: e.clientX,
544
+ lastY: e.clientY,
545
+ kind: "orbit",
546
+ samples: []
547
+ };
548
+ else return;
549
+ this.canvas.setPointerCapture(e.pointerId), this.canvas.style.cursor = "grabbing", e.preventDefault();
550
+ }, this.onPointerMove = (t) => {
551
+ let n = this.drag;
552
+ if (!n || n.pointerId !== t.pointerId) {
553
+ !n && t.pointerType === "mouse" && (this.canvas.style.cursor = this.pickCubie(t) ? "grab" : "default");
554
+ return;
555
+ }
556
+ let r = t.clientX - n.lastX, i = t.clientY - n.lastY;
557
+ if (n.lastX = t.clientX, n.lastY = t.clientY, n.kind === "orbit") {
558
+ let t = this.options.interaction.orbitSensitivity, n = new e.Quaternion().setFromAxisAngle(N.y, r * t), a = new e.Quaternion().setFromAxisAngle(N.x, i * t);
559
+ this.cubeGroup.quaternion.premultiply(n).premultiply(a);
560
+ return;
561
+ }
562
+ let a = t.clientX - n.startX, o = t.clientY - n.startY;
563
+ if (n.kind === "undecided" && (Math.hypot(a, o) < 6 || (this.decideLayerDrag(n, new e.Vector2(a, o)), n.kind !== "layer"))) return;
564
+ let s = new e.Vector2(a, o).dot(n.screenDir) / this.options.interaction.dragPixelsPerQuarterTurn * M * n.sign;
565
+ this.setTurnAngle(s), n.samples.push({
566
+ t: performance.now(),
567
+ angle: s
568
+ }), n.samples.length > 6 && n.samples.shift();
569
+ }, this.onPointerUp = (e) => {
570
+ let t = this.drag;
571
+ if (e.pointerType === "touch" && (window.clearTimeout(this.touchLeaveTimer), this.touchLeaveTimer = window.setTimeout(() => this.setHover(!1), 1500)), t && t.pointerId === e.pointerId) {
572
+ if (this.drag = null, this.canvas.hasPointerCapture(e.pointerId) && this.canvas.releasePointerCapture(e.pointerId), this.canvas.style.cursor = "grab", t.kind === "layer" && this.active?.mode === "drag") {
573
+ let e = this.active, n = 0;
574
+ if (t.samples.length >= 2) {
575
+ let e = t.samples[0], r = t.samples[t.samples.length - 1], i = r.t - e.t;
576
+ i > 0 && (n = (r.angle - e.angle) / i * 1e3);
577
+ }
578
+ let r = e.angle + n * .12, i = Math.floor(e.angle / M) * M, a = Math.ceil(e.angle / M) * M, o = Math.max(i, Math.min(a, Math.round(r / M) * M));
579
+ e.mode = "snap", e.target = o, this.physics.beginSnap(e.angle, n, o);
580
+ }
581
+ if (e.pointerType === "mouse") {
582
+ let t = this.canvas.getBoundingClientRect();
583
+ e.clientX >= t.left && e.clientX <= t.right && e.clientY >= t.top && e.clientY <= t.bottom || this.setHover(!1);
584
+ }
585
+ }
586
+ }, this.container = n, this.options = j(r), this.idleEnabled = this.options.idleOnMount, this.renderer = new e.WebGLRenderer({
587
+ alpha: !0,
588
+ antialias: this.options.antialias,
589
+ powerPreference: "high-performance",
590
+ premultipliedAlpha: !0
591
+ }), this.renderer.shadowMap.enabled = !0, this.renderer.shadowMap.type = e.PCFShadowMap, this.renderer.toneMapping = e.NeutralToneMapping, this.renderer.toneMappingExposure = this.options.exposure, this.renderer.outputColorSpace = e.SRGBColorSpace, this.canvas = this.renderer.domElement, Object.assign(this.canvas.style, {
592
+ display: "block",
593
+ width: "100%",
594
+ height: "100%",
595
+ touchAction: "none",
596
+ userSelect: "none",
597
+ cursor: "grab",
598
+ outline: "none"
599
+ }), this.canvas.setAttribute("aria-label", "Interactive Rubik's cube"), this.canvas.setAttribute("role", "img"), n.appendChild(this.canvas), this.applyBackground(), this.scene = new e.Scene(), this.pmrem = new e.PMREMGenerator(this.renderer), this.envTexture = this.pmrem.fromScene(new t(), .04).texture, this.scene.environment = this.envTexture, this.scene.environmentIntensity = .7, this.keyLight = new e.DirectionalLight(16777215, 2.4), this.keyLight.position.set(4, 9, 6), this.keyLight.castShadow = !0, this.keyLight.shadow.mapSize.set(this.options.shadowMapSize, this.options.shadowMapSize), this.keyLight.shadow.bias = -4e-4, this.keyLight.shadow.normalBias = .02, this.keyLight.shadow.radius = 4, this.scene.add(this.keyLight), this.scene.add(this.keyLight.target);
600
+ let i = new e.DirectionalLight(14674175, .55);
601
+ i.position.set(-6, 2, -4), this.scene.add(i);
602
+ let a = new e.DirectionalLight(16777215, .35);
603
+ a.position.set(0, -3, -6), this.scene.add(a), this.scene.add(this.cubeGroup), this.camera = new e.PerspectiveCamera(this.options.camera.fov, 1, .1, 100), this.placeCamera(), this.physics = new s(this.options.physics), this.buildCube(), this.buildShadow(), this.resize(), typeof ResizeObserver < "u" ? (this.resizeObserver = new ResizeObserver(() => this.resize()), this.resizeObserver.observe(n)) : window.addEventListener("resize", this.resize), this.bindPointerEvents();
604
+ for (let e of [
605
+ "onMove",
606
+ "onScrambleStart",
607
+ "onScrambleEnd",
608
+ "onSolved",
609
+ "onStateChange",
610
+ "onHoverChange"
611
+ ]) {
612
+ let t = this.options[e];
613
+ t && this.on(e.slice(2).replace(/^[A-Z]/, (e) => e.toLowerCase()), t);
614
+ }
615
+ this.applyIdleState(), this.rafId = requestAnimationFrame(this.tick), this.options.scrambleOnMount && (this.mountTimer = window.setTimeout(() => {
616
+ this.mountTimer = 0, this.scramble();
617
+ }, this.options.scrambleDelay));
618
+ }
619
+ get state() {
620
+ return this.scrambleRemaining > 0 || this.active?.mode === "flywheel" ? "scrambling" : this.drag?.kind === "layer" ? "dragging" : this.active?.mode === "snap" ? "snapping" : this.active || this.queue.length ? "animating" : "idle";
621
+ }
622
+ get isHovering() {
623
+ return this.hovering;
624
+ }
625
+ isSolved() {
626
+ return this.solved;
627
+ }
628
+ on(e, t) {
629
+ return this.listeners.has(e) || this.listeners.set(e, /* @__PURE__ */ new Set()), this.listeners.get(e).add(t), () => this.listeners.get(e)?.delete(t);
630
+ }
631
+ off(e, t) {
632
+ this.listeners.get(e)?.delete(t);
633
+ }
634
+ emit(e, ...t) {
635
+ this.listeners.get(e)?.forEach((e) => {
636
+ try {
637
+ e(...t);
638
+ } catch (e) {
639
+ console.error(e);
640
+ }
641
+ });
642
+ }
643
+ scramble(e = this.options.scrambleMoves) {
644
+ this.cancelQueue(), this.scrambleRemaining > 0 && this.finishScramble();
645
+ let t = C(this.options.size, e);
646
+ return this.physics.resetFlywheel(), this.physics.setSpinningUp(!0), this.scrambleRemaining = t.length, this.emit("scrambleStart"), new Promise((e) => {
647
+ this.scrambleResolve = e;
648
+ for (let e of t) this.queue.push({
649
+ turn: e,
650
+ mode: "flywheel",
651
+ duration: 0,
652
+ resolve: () => {}
653
+ });
654
+ });
655
+ }
656
+ move(e, t = this.options.moveDuration) {
657
+ let n = x(e, this.options.size);
658
+ return Promise.all(n.map((e) => this.turn(e, t))).then(() => void 0);
659
+ }
660
+ turn(e, t = this.options.moveDuration) {
661
+ return t <= 0 && !this.active ? (this.applyInstant(e), Promise.resolve()) : new Promise((n) => {
662
+ this.queue.push({
663
+ turn: e,
664
+ mode: "timed",
665
+ duration: t,
666
+ resolve: n
667
+ });
668
+ });
669
+ }
670
+ reset() {
671
+ this.cancelQueue(), this.abortActive();
672
+ for (let e of this.cubies) e.grid.copy(e.home), e.object.position.copy(e.home), e.object.quaternion.identity();
673
+ this.updateSolved(!0);
674
+ }
675
+ setHover(e) {
676
+ this.hovering !== e && (this.hovering = e, this.applyIdleState(), this.emit("hoverChange", e));
677
+ }
678
+ setIdle(e) {
679
+ this.idleEnabled = e, this.applyIdleState();
680
+ }
681
+ resetOrientation() {
682
+ this.cubeGroup.quaternion.identity();
683
+ }
684
+ setOptions(e) {
685
+ let t = this.options, n = A(e);
686
+ this.options = j({
687
+ ...t,
688
+ ...n,
689
+ colors: {
690
+ ...t.colors,
691
+ ...A(n.colors)
692
+ },
693
+ physics: {
694
+ ...t.physics,
695
+ ...A(n.physics)
696
+ },
697
+ interaction: {
698
+ ...t.interaction,
699
+ ...A(n.interaction)
700
+ },
701
+ camera: {
702
+ ...t.camera,
703
+ ...A(n.camera)
704
+ },
705
+ sticker: {
706
+ ...t.sticker,
707
+ ...A(n.sticker)
708
+ }
709
+ });
710
+ let r = this.options;
711
+ this.physics.setOptions(r.physics), this.renderer.toneMappingExposure = r.exposure, this.applyBackground(), this.camera.fov = r.camera.fov, this.camera.updateProjectionMatrix(), this.placeCamera(), this.shadowPlane && (this.shadowPlane.material.opacity = r.shadowOpacity), r.shadow !== t.shadow && this.buildShadow(), (r.size !== t.size || r.cubieGap !== t.cubieGap || r.cubieRounding !== t.cubieRounding || JSON.stringify(r.colors) !== JSON.stringify(t.colors) || JSON.stringify(r.sticker) !== JSON.stringify(t.sticker)) && (this.buildCube(), this.buildShadow()), this.applyIdleState();
712
+ }
713
+ dispose() {
714
+ this.disposed || (this.disposed = !0, cancelAnimationFrame(this.rafId), window.clearTimeout(this.mountTimer), window.clearTimeout(this.touchLeaveTimer), this.cancelQueue(), this.resizeObserver?.disconnect(), window.removeEventListener("resize", this.resize), this.unbindPointerEvents(), this.disposeCubies(), this.shadowPlane?.geometry.dispose(), this.shadowPlane?.material.dispose(), this.envTexture.dispose(), this.pmrem.dispose(), this.physics.dispose(), this.renderer.dispose(), this.canvas.remove(), this.listeners.clear());
715
+ }
716
+ applyBackground() {
717
+ let t = (this.options.background ?? "").trim();
718
+ t && t !== "transparent" && t !== "none" && (typeof CSS > "u" || typeof CSS.supports != "function" || CSS.supports("color", t)) ? (this.renderer.setClearColor(new e.Color(t), 1), this.canvas.style.background = "") : (this.renderer.setClearColor(0, 0), this.canvas.style.background = t && t !== "transparent" && t !== "none" ? t : "");
719
+ }
720
+ placeCamera() {
721
+ let { size: t, camera: n } = this.options, r = n.distance ?? 3.5 * t, i = e.MathUtils.degToRad(n.pitch), a = e.MathUtils.degToRad(n.yaw);
722
+ this.camera.position.set(r * Math.cos(i) * Math.sin(a), r * Math.sin(i), r * Math.cos(i) * Math.cos(a)), this.camera.lookAt(0, 0, 0);
723
+ }
724
+ buildCube() {
725
+ this.abortActive(), this.disposeCubies();
726
+ let { cubies: e, resources: t } = f(this.options);
727
+ this.cubies = e, this.resources = t;
728
+ for (let t of e) this.cubeGroup.add(t.object);
729
+ let n = this.options.size, r = this.keyLight.shadow.camera;
730
+ r.left = r.bottom = -n * 1.4, r.right = r.top = n * 1.4, r.near = 1, r.far = 40, r.updateProjectionMatrix(), this.updateSolved(!0);
731
+ }
732
+ disposeCubies() {
733
+ for (let e of this.cubies) this.cubeGroup.remove(e.object);
734
+ this.cubies = [], this.resources &&= (this.resources.geometries.forEach((e) => e.dispose()), this.resources.materials.forEach((e) => e.dispose()), void 0);
735
+ }
736
+ buildShadow() {
737
+ if (this.shadowPlane &&= (this.scene.remove(this.shadowPlane), this.shadowPlane.geometry.dispose(), this.shadowPlane.material.dispose(), void 0), !this.options.shadow) return;
738
+ let t = this.options.size, n = new e.Mesh(new e.PlaneGeometry(t * 6, t * 6), new e.ShadowMaterial({
739
+ opacity: this.options.shadowOpacity,
740
+ transparent: !0
741
+ }));
742
+ n.rotation.x = -Math.PI / 2, n.position.y = -(t / 2) * 1.05 - .35, n.receiveShadow = !0, this.scene.add(n), this.shadowPlane = n;
743
+ }
744
+ updateIdle(t) {
745
+ if (this.hovering || !this.idleEnabled) return;
746
+ let n = this.options.physics, r = new e.Quaternion().setFromAxisAngle(N.y, n.idleSpin * t), i = new e.Quaternion().setFromAxisAngle(N.x, Math.sin(this.elapsed * .7) * n.idleTumble * t);
747
+ this.cubeGroup.quaternion.premultiply(i).premultiply(r);
748
+ }
749
+ applyIdleState() {
750
+ let e = !this.hovering && this.idleEnabled;
751
+ this.physics.setBobbing(e), this.physics.setFloatTarget(e ? this.options.physics.floatLift : 0);
752
+ }
753
+ cubiesInLayers(e, t) {
754
+ return this.cubies.filter((n) => t.some((t) => Math.abs(n.grid[e] - t) < .001));
755
+ }
756
+ beginTurn(t, n, r, i, a, o) {
757
+ let s = new e.Group();
758
+ this.cubeGroup.add(s);
759
+ let c = this.cubiesInLayers(t, n);
760
+ for (let e of c) s.attach(e.object);
761
+ let l = {
762
+ axis: t,
763
+ layers: n,
764
+ mode: r,
765
+ pivot: s,
766
+ cubies: c,
767
+ angle: 0,
768
+ target: i,
769
+ startedAt: this.elapsed,
770
+ duration: a,
771
+ resolve: o
772
+ };
773
+ return this.active = l, l;
774
+ }
775
+ setTurnAngle(e) {
776
+ this.active && (this.active.angle = e, this.active.pivot.rotation.set(0, 0, 0), this.active.pivot.rotation[this.active.axis] = e);
777
+ }
778
+ completeTurn() {
779
+ let e = this.active;
780
+ if (!e) return;
781
+ let t = Math.round(e.angle / M);
782
+ this.setTurnAngle(t * M), e.pivot.updateMatrixWorld(!0);
783
+ for (let t of e.cubies) this.cubeGroup.attach(t.object), m(t.object.position), p(t.object.quaternion), t.grid.copy(t.object.position);
784
+ if (this.cubeGroup.remove(e.pivot), this.active = null, e.mode === "snap" && this.physics.endSnap(), e.resolve?.(), t !== 0) {
785
+ let n = {
786
+ axis: e.axis,
787
+ layers: e.layers,
788
+ quarterTurns: t
789
+ };
790
+ this.emit("move", n, S(n, this.options.size)), this.updateSolved();
791
+ }
792
+ e.mode === "flywheel" && (--this.scrambleRemaining, this.scrambleRemaining <= 0 && this.finishScramble());
793
+ }
794
+ abortActive() {
795
+ let e = this.active;
796
+ if (e) {
797
+ this.setTurnAngle(0);
798
+ for (let t of e.cubies) this.cubeGroup.attach(t.object), t.object.position.copy(t.grid), p(t.object.quaternion);
799
+ this.cubeGroup.remove(e.pivot), this.active = null, this.physics.endSnap(), e.resolve?.(), e.mode === "flywheel" && this.scrambleRemaining > 0 && this.finishScramble(), this.drag?.kind === "layer" && (this.drag = null);
800
+ }
801
+ }
802
+ applyInstant(e) {
803
+ this.beginTurn(e.axis, e.layers, "timed", e.quarterTurns * M, 0), this.setTurnAngle(e.quarterTurns * M), this.completeTurn();
804
+ }
805
+ cancelQueue() {
806
+ let e = this.queue;
807
+ this.queue = [], e.forEach((e) => e.resolve()), this.scrambleRemaining > 0 && (this.active?.mode === "flywheel" ? this.scrambleRemaining = 1 : this.finishScramble());
808
+ }
809
+ finishScramble() {
810
+ this.scrambleRemaining = 0, this.physics.setSpinningUp(!1);
811
+ let e = this.scrambleResolve;
812
+ this.scrambleResolve = null, this.emit("scrambleEnd"), e?.();
813
+ }
814
+ updateTurn(e) {
815
+ if (!this.active && this.queue.length && this.drag?.kind !== "layer") {
816
+ let e = this.queue.shift(), t = e.turn.quarterTurns * M;
817
+ this.beginTurn(e.turn.axis, e.turn.layers, e.mode, t, e.duration / 1e3, e.resolve);
818
+ }
819
+ let t = this.active;
820
+ if (t) switch (t.mode) {
821
+ case "timed": {
822
+ let e = t.duration > 0 ? Math.min(1, (this.elapsed - t.startedAt) / t.duration) : 1, n = e < .5 ? 4 * e * e * e : 1 - (-2 * e + 2) ** 3 / 2;
823
+ this.setTurnAngle(t.target * n), e >= 1 && this.completeTurn();
824
+ break;
825
+ }
826
+ case "flywheel": {
827
+ let n = this.scrambleRemaining - Math.abs(t.angle) / M;
828
+ this.physics.setSpinningUp(n > this.physics.quarterTurnsToCoast + .6);
829
+ let r = Math.sign(t.target), i = t.angle + r * this.physics.scrambleSpeed * e;
830
+ Math.abs(i) >= Math.abs(t.target) ? (this.setTurnAngle(t.target), this.completeTurn()) : this.setTurnAngle(i);
831
+ break;
832
+ }
833
+ case "drag": break;
834
+ case "snap": this.setTurnAngle(this.physics.snapAngle), this.physics.snapSettled && (this.setTurnAngle(this.physics.snapTarget), this.completeTurn());
835
+ }
836
+ }
837
+ updateSolved(e) {
838
+ let t = e ?? this.computeSolved(), n = t !== this.solved;
839
+ this.solved = t, (n || e !== void 0) && this.emit("stateChange", t), n && t && this.emit("solved");
840
+ }
841
+ computeSolved() {
842
+ let t = /* @__PURE__ */ new Map(), n = new e.Vector3();
843
+ for (let e of this.cubies) for (let r of e.stickers) {
844
+ n.copy(r.localNormal).applyQuaternion(e.object.quaternion);
845
+ let i = `${Math.round(n.x)},${Math.round(n.y)},${Math.round(n.z)}`, a = t.get(i);
846
+ if (a === void 0) t.set(i, r.face);
847
+ else if (a !== r.face) return !1;
848
+ }
849
+ return !0;
850
+ }
851
+ bindPointerEvents() {
852
+ let e = this.canvas;
853
+ e.addEventListener("pointerdown", this.onPointerDown), e.addEventListener("pointermove", this.onPointerMove), e.addEventListener("pointerup", this.onPointerUp), e.addEventListener("pointercancel", this.onPointerUp), e.addEventListener("pointerenter", this.onPointerEnter), e.addEventListener("pointerleave", this.onPointerLeave), e.addEventListener("contextmenu", this.preventDefault);
854
+ }
855
+ unbindPointerEvents() {
856
+ let e = this.canvas;
857
+ e.removeEventListener("pointerdown", this.onPointerDown), e.removeEventListener("pointermove", this.onPointerMove), e.removeEventListener("pointerup", this.onPointerUp), e.removeEventListener("pointercancel", this.onPointerUp), e.removeEventListener("pointerenter", this.onPointerEnter), e.removeEventListener("pointerleave", this.onPointerLeave), e.removeEventListener("contextmenu", this.preventDefault);
858
+ }
859
+ pointerNdc(t) {
860
+ let n = this.canvas.getBoundingClientRect();
861
+ return new e.Vector2((t.clientX - n.left) / n.width * 2 - 1, -((t.clientY - n.top) / n.height) * 2 + 1);
862
+ }
863
+ pickCubie(e) {
864
+ this.raycaster.setFromCamera(this.pointerNdc(e), this.camera);
865
+ let t = this.raycaster.intersectObjects(this.cubies.map((e) => e.body), !1)[0];
866
+ if (!t || !t.face) return null;
867
+ let n = t.object.userData.cubie, r = t.face.normal.clone().applyQuaternion(n.object.quaternion), i = Math.abs(r.x), a = Math.abs(r.y), o = Math.abs(r.z);
868
+ return i >= a && i >= o ? r.set(Math.sign(r.x), 0, 0) : a >= i && a >= o ? r.set(0, Math.sign(r.y), 0) : r.set(0, 0, Math.sign(r.z)), {
869
+ cubie: n,
870
+ normal: r,
871
+ point: this.cubeGroup.worldToLocal(t.point.clone())
872
+ };
873
+ }
874
+ toScreen(t) {
875
+ let n = this.canvas.getBoundingClientRect(), r = this.cubeGroup.localToWorld(t.clone()).project(this.camera);
876
+ return new e.Vector2((r.x + 1) / 2 * n.width, (1 - r.y) / 2 * n.height);
877
+ }
878
+ decideLayerDrag(t, n) {
879
+ let r = t.normal, i = t.hit, a = t.cubie;
880
+ if (this.active) return;
881
+ let o = [
882
+ "x",
883
+ "y",
884
+ "z"
885
+ ].filter((e) => Math.abs(r[e]) < .5).map((e) => N[e].clone()), s = this.toScreen(i), c = null;
886
+ for (let e of o) {
887
+ let t = this.toScreen(i.clone().addScaledVector(e, .5)).sub(s);
888
+ if (t.lengthSq() < 1e-6) continue;
889
+ t.normalize();
890
+ let r = n.clone().normalize().dot(t);
891
+ (!c || Math.abs(r) > Math.abs(c.dot)) && (c = {
892
+ t: e,
893
+ dir: t,
894
+ dot: r
895
+ });
896
+ }
897
+ if (!c) {
898
+ this.drag = null;
899
+ return;
900
+ }
901
+ let l = new e.Vector3().crossVectors(r, c.t), u = Math.abs(l.x) > .5 ? "x" : Math.abs(l.y) > .5 ? "y" : "z", d = l[u] > 0 ? 1 : -1, f = a.grid[u];
902
+ t.kind = "layer", t.screenDir = c.dir, t.sign = d, this.beginTurn(u, [f], "drag", 0, 0);
903
+ }
904
+ };
905
+ //#endregion
906
+ //#region src/core/mount.ts
907
+ function F(e, t = {}) {
908
+ let n = typeof e == "string" ? document.querySelector(e) : e;
909
+ if (!n) throw Error(`rubiks-cube-3d: container not found (${String(e)})`);
910
+ let r = new P(n, t);
911
+ return {
912
+ engine: r,
913
+ scramble: (e) => r.scramble(e),
914
+ move: (e, t) => r.move(e, t),
915
+ turn: (e, t) => r.turn(e, t),
916
+ reset: () => r.reset(),
917
+ resetOrientation: () => r.resetOrientation(),
918
+ isSolved: () => r.isSolved(),
919
+ get state() {
920
+ return r.state;
921
+ },
922
+ setHover: (e) => r.setHover(e),
923
+ setIdle: (e) => r.setIdle(e),
924
+ setOptions: (e) => r.setOptions(e),
925
+ on: (e, t) => r.on(e, t),
926
+ destroy: () => r.dispose()
927
+ };
928
+ }
929
+ //#endregion
930
+ export { E as a, O as c, x as d, C as f, w as i, j as l, s as m, P as n, k as o, S as p, D as r, T as s, F as t, y as u };
931
+
932
+ //# sourceMappingURL=core-L1AjC-fV.js.map