@rednaxela101/cubing 0.63.9 → 0.63.10

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.
Files changed (28) hide show
  1. package/dist/bin/chunks/{chunk-F63RAIBR.js → chunk-3WUR6UXA.js} +2 -2
  2. package/dist/bin/order.js +1 -1
  3. package/dist/bin/puzzle-geometry-bin.js +1 -1
  4. package/dist/bin/scramble.js +1 -1
  5. package/dist/bin/svg.js +1 -1
  6. package/dist/lib/cubing/alg/index.d.ts +1 -1
  7. package/dist/lib/cubing/bluetooth/index.d.ts +2 -2
  8. package/dist/lib/cubing/chunks/{chunk-F765ZFNM.js → chunk-IOHGEWB2.js} +2 -2
  9. package/dist/lib/cubing/chunks/{twisty-dynamic-3d-MF76FSDA.js → twisty-dynamic-3d-3MGVD3TG.js} +634 -172
  10. package/dist/lib/cubing/chunks/twisty-dynamic-3d-3MGVD3TG.js.map +7 -0
  11. package/dist/lib/cubing/{index-ylnTp4i5.d.ts → index-0OM9yjf0.d.ts} +1 -1
  12. package/dist/lib/cubing/{index-FT5_Vsnm.d.ts → index-B_TS5TTk.d.ts} +13 -0
  13. package/dist/lib/cubing/kpuzzle/index.d.ts +1 -1
  14. package/dist/lib/cubing/notation/index.d.ts +1 -1
  15. package/dist/lib/cubing/{outside-D3tRtsxV.d.ts → outside-BUsE7O5Q.d.ts} +1 -1
  16. package/dist/lib/cubing/protocol/index.d.ts +1 -1
  17. package/dist/lib/cubing/puzzle-geometry/index.d.ts +1 -1
  18. package/dist/lib/cubing/puzzles/index.d.ts +1 -1
  19. package/dist/lib/cubing/scramble/index.d.ts +1 -1
  20. package/dist/lib/cubing/search/index.d.ts +1 -1
  21. package/dist/lib/cubing/stream/index.d.ts +1 -1
  22. package/dist/lib/cubing/twisty/index.d.ts +1 -1
  23. package/dist/lib/cubing/twisty/index.js +31 -2
  24. package/dist/lib/cubing/twisty/index.js.map +2 -2
  25. package/package.json +1 -1
  26. package/dist/lib/cubing/chunks/twisty-dynamic-3d-MF76FSDA.js.map +0 -7
  27. /package/dist/bin/chunks/{chunk-F63RAIBR.js.map → chunk-3WUR6UXA.js.map} +0 -0
  28. /package/dist/lib/cubing/chunks/{chunk-F765ZFNM.js.map → chunk-IOHGEWB2.js.map} +0 -0
@@ -3,7 +3,7 @@ import {
3
3
  bulk3DCode,
4
4
  haveStartedSharingRenderers,
5
5
  hintFaceletStyles
6
- } from "./chunk-F765ZFNM.js";
6
+ } from "./chunk-IOHGEWB2.js";
7
7
  import "./chunk-3ODTF4HE.js";
8
8
  import {
9
9
  cube3x3x3,
@@ -15,20 +15,20 @@ import {
15
15
  } from "./chunk-XPDJUSZ7.js";
16
16
 
17
17
  // src/cubing/twisty/views/3D/puzzles/Cube3D.ts
18
- import { BackSide, DoubleSide, FrontSide } from "three/src/constants.js";
19
- import { BufferAttribute as BufferAttribute2 } from "three/src/core/BufferAttribute.js";
20
- import { BufferGeometry as BufferGeometry2 } from "three/src/core/BufferGeometry.js";
18
+ import { BackSide, DoubleSide, FrontSide as FrontSide2 } from "three/src/constants.js";
19
+ import { BufferAttribute as BufferAttribute3 } from "three/src/core/BufferAttribute.js";
20
+ import { BufferGeometry as BufferGeometry3 } from "three/src/core/BufferGeometry.js";
21
21
  import { Object3D } from "three/src/core/Object3D.js";
22
22
  import { BoxGeometry } from "three/src/geometries/BoxGeometry.js";
23
23
  import { TextureLoader } from "three/src/loaders/TextureLoader.js";
24
- import { MeshBasicMaterial as MeshBasicMaterial2 } from "three/src/materials/MeshBasicMaterial.js";
24
+ import { MeshBasicMaterial as MeshBasicMaterial3 } from "three/src/materials/MeshBasicMaterial.js";
25
25
  import { Color as Color2 } from "three/src/math/Color.js";
26
26
  import { Matrix4 } from "three/src/math/Matrix4.js";
27
27
  import { Quaternion } from "three/src/math/Quaternion.js";
28
28
  import { Vector2 } from "three/src/math/Vector2.js";
29
- import { Vector3 as Vector33 } from "three/src/math/Vector3.js";
29
+ import { Vector3 as Vector34 } from "three/src/math/Vector3.js";
30
30
  import { Group } from "three/src/objects/Group.js";
31
- import { Mesh } from "three/src/objects/Mesh.js";
31
+ import { Mesh as Mesh2 } from "three/src/objects/Mesh.js";
32
32
 
33
33
  // src/cubing/twisty/controllers/easing.ts
34
34
  function smootherStep(x) {
@@ -288,34 +288,240 @@ var hintMaskStyles = {
288
288
  mystery: { color: 15911883, opacity: 0.5 }
289
289
  };
290
290
 
291
+ // src/cubing/twisty/views/3D/puzzles/PuzzleLogo.ts
292
+ import { FrontSide } from "three/src/constants.js";
293
+ import { BufferAttribute as BufferAttribute2 } from "three/src/core/BufferAttribute.js";
294
+ import { BufferGeometry as BufferGeometry2 } from "three/src/core/BufferGeometry.js";
295
+ import { MeshBasicMaterial as MeshBasicMaterial2 } from "three/src/materials/MeshBasicMaterial.js";
296
+ import { Vector3 as Vector33 } from "three/src/math/Vector3.js";
297
+ import { Mesh } from "three/src/objects/Mesh.js";
298
+ var LOGO_FIT = 0.8;
299
+ var EPSILON = 1e-9;
300
+ function logoSurface(polygon, normal2, elevation) {
301
+ if (polygon.length < 3) {
302
+ return null;
303
+ }
304
+ const center = new Vector33();
305
+ for (const vertex of polygon) {
306
+ center.addScaledVector(vertex, 1 / polygon.length);
307
+ }
308
+ const u = new Vector33().subVectors(polygon[1], polygon[0]).addScaledVector(
309
+ normal2,
310
+ -new Vector33().subVectors(polygon[1], polygon[0]).dot(normal2)
311
+ );
312
+ if (u.lengthSq() < EPSILON) {
313
+ return null;
314
+ }
315
+ u.normalize();
316
+ const v = new Vector33().crossVectors(normal2, u).normalize();
317
+ let half = Number.POSITIVE_INFINITY;
318
+ for (let i = 0; i < polygon.length; i++) {
319
+ const edge = new Vector33().subVectors(
320
+ polygon[(i + 1) % polygon.length],
321
+ polygon[i]
322
+ );
323
+ const outward = new Vector33().crossVectors(edge, normal2);
324
+ if (outward.lengthSq() < EPSILON) {
325
+ continue;
326
+ }
327
+ outward.normalize();
328
+ const distance = Math.abs(
329
+ new Vector33().subVectors(polygon[i], center).dot(outward)
330
+ );
331
+ const reach = Math.abs(outward.dot(u)) + Math.abs(outward.dot(v));
332
+ if (reach > EPSILON) {
333
+ half = Math.min(half, distance / reach);
334
+ }
335
+ }
336
+ if (!Number.isFinite(half) || half <= 0) {
337
+ return null;
338
+ }
339
+ half *= LOGO_FIT;
340
+ return {
341
+ center: center.addScaledVector(normal2, elevation),
342
+ u: u.multiplyScalar(half),
343
+ v: v.multiplyScalar(half)
344
+ };
345
+ }
346
+ function rectangleLogoSurface(center, u, v) {
347
+ const half = LOGO_FIT * Math.min(u.length(), v.length());
348
+ return {
349
+ center,
350
+ u: u.clone().setLength(half),
351
+ v: v.clone().setLength(half)
352
+ };
353
+ }
354
+ function surfaceMatrix(surface, into) {
355
+ return into.makeBasis(
356
+ surface.u,
357
+ surface.v,
358
+ new Vector33().crossVectors(surface.u, surface.v).normalize()
359
+ ).setPosition(surface.center);
360
+ }
361
+ var cachedLogoGeometry;
362
+ function logoGeometry() {
363
+ if (cachedLogoGeometry) {
364
+ return cachedLogoGeometry;
365
+ }
366
+ const geometry = new BufferGeometry2();
367
+ geometry.setAttribute(
368
+ "position",
369
+ new BufferAttribute2(
370
+ // biome-ignore format: one vertex per line is less readable than one triangle per line.
371
+ new Float32Array([
372
+ -1,
373
+ -1,
374
+ 0,
375
+ 1,
376
+ -1,
377
+ 0,
378
+ 1,
379
+ 1,
380
+ 0,
381
+ -1,
382
+ -1,
383
+ 0,
384
+ 1,
385
+ 1,
386
+ 0,
387
+ -1,
388
+ 1,
389
+ 0
390
+ ]),
391
+ 3
392
+ )
393
+ );
394
+ geometry.setAttribute(
395
+ "uv",
396
+ // biome-ignore format: see above.
397
+ new BufferAttribute2(new Float32Array([
398
+ 0,
399
+ 0,
400
+ 1,
401
+ 0,
402
+ 1,
403
+ 1,
404
+ 0,
405
+ 0,
406
+ 1,
407
+ 1,
408
+ 0,
409
+ 1
410
+ ]), 2)
411
+ );
412
+ cachedLogoGeometry = geometry;
413
+ return geometry;
414
+ }
415
+ function newLogoMesh(texture) {
416
+ const mesh = new Mesh(
417
+ logoGeometry(),
418
+ new MeshBasicMaterial2({
419
+ map: texture,
420
+ side: FrontSide,
421
+ transparent: true,
422
+ // The logo floats just above the plastic, so it never has to compete with
423
+ // it for depth, and writing depth would let its transparent corners
424
+ // punch a hole in anything drawn after it.
425
+ depthWrite: false
426
+ })
427
+ );
428
+ mesh.matrixAutoUpdate = false;
429
+ mesh.renderOrder = 1;
430
+ return mesh;
431
+ }
432
+ var logoSpots = {
433
+ // Every piece of a 2×2×2 is a corner, so this is just "a piece of the white
434
+ // face".
435
+ "2x2x2": { face: "U", corner: true, toward: [1, 1, 1] },
436
+ // `Cube3D` draws the 3×3×3 by default and places its own logo; this is for
437
+ // `visualization="PG3D"`.
438
+ "3x3x3": { face: "U" },
439
+ "4x4x4": { face: "U", corner: true, toward: [1, 1, 1] },
440
+ "5x5x5": { face: "U", corner: true, toward: [1, 1, 1] },
441
+ "6x6x6": { face: "U", corner: true, toward: [1, 1, 1] },
442
+ "7x7x7": { face: "U", corner: true, toward: [1, 1, 1] },
443
+ megaminx: { face: "U" },
444
+ // A pyraminx has no center piece: the middle of a face is an edge's facelet.
445
+ pyraminx: { face: "D" },
446
+ skewb: { face: "U" }
447
+ // No logo on the FTO or the clock.
448
+ };
449
+ function centroid(coords) {
450
+ const center = new Vector33();
451
+ const vertices = coords.length / 3;
452
+ for (let i = 0; i < coords.length; i += 3) {
453
+ center.x += coords[i] / vertices;
454
+ center.y += coords[i + 1] / vertices;
455
+ center.z += coords[i + 2] / vertices;
456
+ }
457
+ return center;
458
+ }
459
+ function logoFaceletAddress(puzzleID, stickerDat) {
460
+ const spot = logoSpots[puzzleID];
461
+ if (!spot) {
462
+ return null;
463
+ }
464
+ const faceIdx = stickerDat.faces.findIndex((face2) => face2.name === spot.face);
465
+ if (faceIdx === -1) {
466
+ return null;
467
+ }
468
+ const faceCenter = centroid(stickerDat.faces[faceIdx].coords);
469
+ const faceletCounts = /* @__PURE__ */ new Map();
470
+ for (const sticker of stickerDat.stickers) {
471
+ if (sticker.isDup) {
472
+ continue;
473
+ }
474
+ const key = `${sticker.orbit}/${sticker.ord}`;
475
+ faceletCounts.set(key, (faceletCounts.get(key) ?? 0) + 1);
476
+ }
477
+ const toward = spot.toward ? new Vector33(...spot.toward) : null;
478
+ let best = null;
479
+ let bestScore = Number.NEGATIVE_INFINITY;
480
+ for (const sticker of stickerDat.stickers) {
481
+ if (sticker.isDup || sticker.face !== faceIdx) {
482
+ continue;
483
+ }
484
+ if (spot.corner && faceletCounts.get(`${sticker.orbit}/${sticker.ord}`) !== 3) {
485
+ continue;
486
+ }
487
+ const center = centroid(sticker.coords);
488
+ const score = toward ? center.dot(toward) : -center.distanceTo(faceCenter);
489
+ if (score > bestScore) {
490
+ bestScore = score;
491
+ best = { orbit: sticker.orbit, ord: sticker.ord, ori: sticker.ori };
492
+ }
493
+ }
494
+ return best;
495
+ }
496
+
291
497
  // src/cubing/twisty/views/3D/puzzles/Cube3D.ts
292
498
  var svgLoader = new TextureLoader();
293
499
  function newHintMaterial(style) {
294
- return new MeshBasicMaterial2({
500
+ return new MeshBasicMaterial3({
295
501
  color: new Color2(style.color).convertLinearToSRGB(),
296
502
  side: BackSide,
297
503
  transparent: true,
298
504
  opacity: style.opacity
299
505
  });
300
506
  }
301
- var ignoredMaterial = new MeshBasicMaterial2({
507
+ var ignoredMaterial = new MeshBasicMaterial3({
302
508
  color: new Color2(bodyMaskColors.ignored).convertLinearToSRGB()
303
509
  });
304
510
  var ignoredMaterialHint = newHintMaterial(hintMaskStyles.ignored);
305
- var invisibleMaterial = new MeshBasicMaterial2({
511
+ var invisibleMaterial = new MeshBasicMaterial3({
306
512
  visible: false
307
513
  });
308
- var orientedMaterial = new MeshBasicMaterial2({
514
+ var orientedMaterial = new MeshBasicMaterial3({
309
515
  color: bodyMaskColors.oriented
310
516
  });
311
517
  var orientedMaterialHint = newHintMaterial(hintMaskStyles.oriented);
312
- var experimentalOriented2Material = new MeshBasicMaterial2({
518
+ var experimentalOriented2Material = new MeshBasicMaterial3({
313
519
  color: bodyMaskColors.experimentalOriented2
314
520
  });
315
521
  var experimentalOriented2MaterialHint = newHintMaterial(
316
522
  hintMaskStyles.experimentalOriented2
317
523
  );
318
- var mysteryMaterial = new MeshBasicMaterial2({
524
+ var mysteryMaterial = new MeshBasicMaterial3({
319
525
  color: bodyMaskColors.mystery
320
526
  });
321
527
  var mysterMaterialHint = newHintMaterial(hintMaskStyles.mystery);
@@ -339,14 +545,14 @@ var AxisInfo = class {
339
545
  const colorLinearSRGB = new Color2(color).convertLinearToSRGB();
340
546
  const dimColorLinearSRGB = new Color2(dimColor).convertLinearToSRGB();
341
547
  this.stickerMaterial = {
342
- regular: new MeshBasicMaterial2({
548
+ regular: new MeshBasicMaterial3({
343
549
  color: colorLinearSRGB,
344
- side: FrontSide
550
+ side: FrontSide2
345
551
  // TODO: set to `DoubleSide` when hint facelets are disabled.
346
552
  }),
347
- dim: new MeshBasicMaterial2({
553
+ dim: new MeshBasicMaterial3({
348
554
  color: dimColorLinearSRGB,
349
- side: FrontSide
555
+ side: FrontSide2
350
556
  // TODO: set to `DoubleSide` when hint facelets are disabled.
351
557
  }),
352
558
  oriented: orientedMaterial,
@@ -437,6 +643,8 @@ var cubieDimensions = {
437
643
  */
438
644
  bodyVertexCut: 0.45
439
645
  };
646
+ var LOGO_PIECE = { orbit: "CENTERS", ord: 0, faceletIdx: 0 };
647
+ var LOGO_ELEVATION = 0.01;
440
648
  var EXPERIMENTAL_PICTURE_CUBE_HINT_ELEVATION = 2;
441
649
  var cube3DOptionsDefaults = {
442
650
  showMainStickers: true,
@@ -457,12 +665,12 @@ function getFaceletScale(options) {
457
665
  }
458
666
  return options.faceletScale;
459
667
  }
460
- var blackMesh = new MeshBasicMaterial2({
668
+ var blackMesh = new MeshBasicMaterial3({
461
669
  color: 0,
462
670
  opacity: 1,
463
671
  transparent: true
464
672
  });
465
- var blackTranslucentMesh = new MeshBasicMaterial2({
673
+ var blackTranslucentMesh = new MeshBasicMaterial3({
466
674
  color: 0,
467
675
  opacity: 0.3,
468
676
  transparent: true
@@ -485,18 +693,18 @@ function t(v, t4) {
485
693
  return new Quaternion().setFromAxisAngle(v, TAU * t4 / 4);
486
694
  }
487
695
  var r = {
488
- O: new Vector33(0, 0, 0),
489
- U: new Vector33(0, -1, 0),
490
- L: new Vector33(1, 0, 0),
491
- F: new Vector33(0, 0, -1),
492
- R: new Vector33(-1, 0, 0),
493
- B: new Vector33(0, 0, 1),
494
- D: new Vector33(0, 1, 0)
696
+ O: new Vector34(0, 0, 0),
697
+ U: new Vector34(0, -1, 0),
698
+ L: new Vector34(1, 0, 0),
699
+ F: new Vector34(0, 0, -1),
700
+ R: new Vector34(-1, 0, 0),
701
+ B: new Vector34(0, 0, 1),
702
+ D: new Vector34(0, 1, 0)
495
703
  };
496
704
  var firstPiecePosition = {
497
- EDGES: new Vector33(0, 1, 1),
498
- CORNERS: new Vector33(1, 1, 1),
499
- CENTERS: new Vector33(0, 1, 0)
705
+ EDGES: new Vector34(0, 1, 1),
706
+ CORNERS: new Vector34(1, 1, 1),
707
+ CENTERS: new Vector34(0, 1, 0)
500
708
  };
501
709
  var orientationRotation = {
502
710
  EDGES: [0, 1].map(
@@ -668,11 +876,11 @@ function sharedCubieFoundationGeometry() {
668
876
  ));
669
877
  }
670
878
  function newStickerGeometry() {
671
- const r2 = new BufferGeometry2();
879
+ const r2 = new BufferGeometry3();
672
880
  const half = 0.5;
673
881
  r2.setAttribute(
674
882
  "position",
675
- new BufferAttribute2(
883
+ new BufferAttribute3(
676
884
  new Float32Array([
677
885
  half,
678
886
  half,
@@ -698,7 +906,7 @@ function newStickerGeometry() {
698
906
  );
699
907
  r2.setAttribute(
700
908
  "uv",
701
- new BufferAttribute2(
909
+ new BufferAttribute3(
702
910
  new Float32Array([
703
911
  1,
704
912
  1,
@@ -977,7 +1185,9 @@ var Cube3D = class extends Object3D {
977
1185
  this.setHintFaceletsElevation(hintFaceletsElevation);
978
1186
  }
979
1187
  }
1188
+ #lastPosition = null;
980
1189
  onPositionChange(p) {
1190
+ this.#lastPosition = p;
981
1191
  const reid333 = p.pattern;
982
1192
  for (const orbit in pieceDefs) {
983
1193
  const pieces = pieceDefs[orbit];
@@ -1007,6 +1217,7 @@ var Cube3D = class extends Object3D {
1007
1217
  }
1008
1218
  }
1009
1219
  }
1220
+ this.#placeLogo(p);
1010
1221
  this.scheduleRenderCallback();
1011
1222
  }
1012
1223
  // TODO: Always create (but sometimes hide parts) so we can show them later,
@@ -1015,7 +1226,7 @@ var Cube3D = class extends Object3D {
1015
1226
  const cubieFaceletInfo = [];
1016
1227
  orbitFacelets.push(cubieFaceletInfo);
1017
1228
  const cubie = new Group();
1018
- const body = this.#stickerless() ? new Mesh(
1229
+ const body = this.#stickerless() ? new Mesh2(
1019
1230
  cubieBodyGeometry(
1020
1231
  orbit,
1021
1232
  cubieStickerOrder.slice(0, piece.stickerFaces.length)
@@ -1064,7 +1275,7 @@ var Cube3D = class extends Object3D {
1064
1275
  axesInfo[piece.stickerFaces[i]],
1065
1276
  hint
1066
1277
  );
1067
- mesh.material = new MeshBasicMaterial2({
1278
+ mesh.material = new MeshBasicMaterial3({
1068
1279
  map: texture,
1069
1280
  side: hint ? BackSide : DoubleSide,
1070
1281
  transparent: true
@@ -1093,7 +1304,7 @@ var Cube3D = class extends Object3D {
1093
1304
  }
1094
1305
  mesh.geometry.setAttribute(
1095
1306
  "uv",
1096
- new BufferAttribute2(
1307
+ new BufferAttribute3(
1097
1308
  new Float32Array([
1098
1309
  v3.x,
1099
1310
  v3.y,
@@ -1127,14 +1338,14 @@ var Cube3D = class extends Object3D {
1127
1338
  // TODO: Support creating only the outward-facing parts?
1128
1339
  createCubieFoundation() {
1129
1340
  const box = sharedCubieFoundationGeometry();
1130
- return new Mesh(
1341
+ return new Mesh2(
1131
1342
  box,
1132
1343
  this.options.experimentalStickeringMask?.specialBehaviour === "picture" ? blackMesh : blackTranslucentMesh
1133
1344
  );
1134
1345
  }
1135
1346
  createSticker(posAxisInfo, materialAxisInfo, isHint) {
1136
1347
  const geo = this.options.experimentalStickeringMask?.specialBehaviour === "picture" ? newStickerGeometry() : isHint ? this.#sharedHintStickerGeometry() : sharedStickerGeometry();
1137
- const stickerMesh = new Mesh(
1348
+ const stickerMesh = new Mesh2(
1138
1349
  geo,
1139
1350
  isHint ? materialAxisInfo.hintStickerMaterial.regular : materialAxisInfo.stickerMaterial.regular
1140
1351
  );
@@ -1161,6 +1372,82 @@ var Cube3D = class extends Object3D {
1161
1372
  stickerMesh.translateZ(elevation - 1);
1162
1373
  return stickerMesh;
1163
1374
  }
1375
+ #logoMesh = null;
1376
+ /** The logo's placement on its facelet, in the piece's own frame. */
1377
+ #logoSurface = null;
1378
+ #logoPieceMatrix = new Matrix4();
1379
+ /**
1380
+ * Prints a logo on the white center, or takes it off again when passed
1381
+ * `null`.
1382
+ *
1383
+ * The image is stretched onto a square sized to the piece, so its own
1384
+ * proportions don't matter. It rides the piece, so a rotation of the whole
1385
+ * cube carries it to whichever face the white center ends up on.
1386
+ */
1387
+ experimentalSetLogo(texture) {
1388
+ if (this.#logoMesh) {
1389
+ this.#logoMesh.removeFromParent();
1390
+ this.#logoMesh.material.dispose();
1391
+ this.#logoMesh = null;
1392
+ this.#logoSurface = null;
1393
+ }
1394
+ if (texture) {
1395
+ const normal2 = axesInfo[cubieStickerOrder[LOGO_PIECE.faceletIdx]].vector;
1396
+ const u = new Vector34(normal2.y, normal2.z, normal2.x);
1397
+ const v = new Vector34().crossVectors(normal2, u);
1398
+ const surface = rectangleLogoSurface(
1399
+ normal2.clone().multiplyScalar(this.#faceletDistance() + LOGO_ELEVATION),
1400
+ // Half-axes of the facelet, which is one cubie wide.
1401
+ u.multiplyScalar(0.5),
1402
+ v.multiplyScalar(0.5)
1403
+ );
1404
+ this.#logoMesh = newLogoMesh(texture);
1405
+ this.#logoSurface = surfaceMatrix(surface, new Matrix4());
1406
+ this.add(this.#logoMesh);
1407
+ if (this.#lastPosition) {
1408
+ this.#placeLogo(this.#lastPosition);
1409
+ }
1410
+ }
1411
+ this.scheduleRenderCallback?.();
1412
+ }
1413
+ /**
1414
+ * Puts the logo where its piece is.
1415
+ *
1416
+ * Almost all of this is the piece's own matrix, but not quite: the 3×3×3's
1417
+ * `KPuzzle` does not track how a center is turned (`orientationMod` is 1),
1418
+ * so a `U` ends in the state it started in as far as the white center is
1419
+ * concerned. Riding that animation would spin the logo through the turn and
1420
+ * snap it back at the end, so a move that leaves the piece in its own slot
1421
+ * leaves the logo alone. A move that carries the piece elsewhere — a cube
1422
+ * rotation — still takes the logo with it.
1423
+ */
1424
+ #placeLogo(p) {
1425
+ const mesh = this.#logoMesh;
1426
+ if (!mesh || !this.#logoSurface) {
1427
+ return;
1428
+ }
1429
+ const { orbit, ord } = LOGO_PIECE;
1430
+ const orbitPattern = p.pattern.patternData[orbit];
1431
+ const slot = orbitPattern.pieces.indexOf(ord);
1432
+ if (slot === -1) {
1433
+ mesh.visible = false;
1434
+ return;
1435
+ }
1436
+ mesh.visible = true;
1437
+ const untracked = orbitPattern.orientationMod?.[slot] === 1;
1438
+ const staysPut = untracked && p.movesInProgress.every(
1439
+ (moveProgress) => this.kpuzzle.moveToTransformation(
1440
+ moveProgress.move.modified({ amount: 1 })
1441
+ ).transformationData[orbit].permutation[slot] === slot
1442
+ );
1443
+ const pieceMatrix = staysPut ? this.#logoPieceMatrix.copy(pieceDefs[orbit][slot].matrix).multiply(orientationRotation[orbit][orbitPattern.orientation[slot]]) : this.pieces[orbit][ord].matrix;
1444
+ mesh.matrix.multiplyMatrices(pieceMatrix, this.#logoSurface);
1445
+ mesh.matrixWorldNeedsUpdate = true;
1446
+ }
1447
+ /** How far a facelet's surface sits from the center of its cubie. */
1448
+ #faceletDistance() {
1449
+ return this.#stickerless() ? cubieBodyDimensions.halfWidth * cubieBodyDimensions.pieceScale : cubieDimensions.stickerElevation;
1450
+ }
1164
1451
  /** @deprecated */
1165
1452
  experimentalSetFoundationOpacity(opacity) {
1166
1453
  if (this.experimentalFoundationMeshes.length === 0) {
@@ -1205,10 +1492,10 @@ var Cube3D = class extends Object3D {
1205
1492
  };
1206
1493
 
1207
1494
  // src/cubing/twisty/views/3D/puzzles/CubePieces.ts
1208
- import { BufferAttribute as BufferAttribute3 } from "three/src/core/BufferAttribute.js";
1209
- import { BufferGeometry as BufferGeometry3 } from "three/src/core/BufferGeometry.js";
1495
+ import { BufferAttribute as BufferAttribute4 } from "three/src/core/BufferAttribute.js";
1496
+ import { BufferGeometry as BufferGeometry4 } from "three/src/core/BufferGeometry.js";
1210
1497
  import { Matrix4 as Matrix42 } from "three/src/math/Matrix4.js";
1211
- import { Vector3 as Vector34 } from "three/src/math/Vector3.js";
1498
+ import { Vector3 as Vector35 } from "three/src/math/Vector3.js";
1212
1499
 
1213
1500
  // src/cubing/twisty/views/3D/puzzles/StickerlessPlan.ts
1214
1501
  var HINT_FACELET_ELEVATION = 0.5;
@@ -1308,6 +1595,7 @@ function paintInternal(colors, ranges) {
1308
1595
  // src/cubing/twisty/views/3D/puzzles/CubePieces.ts
1309
1596
  var PG_SCALE = 0.5;
1310
1597
  var FACE_COUNT = 6;
1598
+ var LOGO_ELEVATION2 = 0.01;
1311
1599
  function faceIdxForNormal(x, y, z) {
1312
1600
  for (let faceIdx = 0; faceIdx < FACE_COUNT; faceIdx++) {
1313
1601
  const { vector } = cubeFaceStyles[faceIdx];
@@ -1344,20 +1632,20 @@ function cubeLayout(stickerDat) {
1344
1632
  const faceColors = new Array(FACE_COUNT);
1345
1633
  const cubiesByPiece = /* @__PURE__ */ new Map();
1346
1634
  const cubies = [];
1347
- const centroid = [0, 0, 0];
1635
+ const centroid2 = [0, 0, 0];
1348
1636
  for (const sticker of stickers) {
1349
1637
  const { coords } = sticker;
1350
1638
  for (let axis = 0; axis < 3; axis++) {
1351
- centroid[axis] = (coords[axis] + coords[axis + 3] + coords[axis + 6] + coords[axis + 9]) / 4;
1639
+ centroid2[axis] = (coords[axis] + coords[axis + 3] + coords[axis + 6] + coords[axis + 9]) / 4;
1352
1640
  }
1353
1641
  let normalAxis = 0;
1354
1642
  for (let axis = 1; axis < 3; axis++) {
1355
- if (Math.abs(centroid[axis]) > Math.abs(centroid[normalAxis])) {
1643
+ if (Math.abs(centroid2[axis]) > Math.abs(centroid2[normalAxis])) {
1356
1644
  normalAxis = axis;
1357
1645
  }
1358
1646
  }
1359
- const outward = Math.sign(centroid[normalAxis]);
1360
- const center = new Vector34();
1647
+ const outward = Math.sign(centroid2[normalAxis]);
1648
+ const center = new Vector35();
1361
1649
  for (let axis = 0; axis < 3; axis++) {
1362
1650
  let expected;
1363
1651
  if (axis === normalAxis) {
@@ -1365,7 +1653,7 @@ function cubeLayout(stickerDat) {
1365
1653
  center.setComponent(axis, outward * (halfExtent - slotWidth / 2));
1366
1654
  } else {
1367
1655
  const slot = Math.round(
1368
- (centroid[axis] + halfExtent) / slotWidth - 0.5
1656
+ (centroid2[axis] + halfExtent) / slotWidth - 0.5
1369
1657
  );
1370
1658
  if (slot < 0 || slot >= layers) {
1371
1659
  return null;
@@ -1373,7 +1661,7 @@ function cubeLayout(stickerDat) {
1373
1661
  expected = -halfExtent + (slot + 0.5) * slotWidth;
1374
1662
  center.setComponent(axis, expected);
1375
1663
  }
1376
- if (Math.abs(centroid[axis] - expected) > epsilon) {
1664
+ if (Math.abs(centroid2[axis] - expected) > epsilon) {
1377
1665
  return null;
1378
1666
  }
1379
1667
  const halfSpan = axis === normalAxis ? 0 : slotWidth / 2;
@@ -1451,13 +1739,23 @@ function newCubieShape(outwardFaces, slotWidth) {
1451
1739
  const hintVertexRanges = /* @__PURE__ */ new Map();
1452
1740
  const hintHalfWidth = HINT_FACELET_SCALE * slotWidth / 2;
1453
1741
  const hintDistance = slotWidth / 2 + HINT_FACELET_ELEVATION;
1454
- const u = new Vector34();
1455
- const v = new Vector34();
1456
- const corner = new Vector34();
1742
+ const logoSurfaces = /* @__PURE__ */ new Map();
1743
+ const faceDistance = cubieBodyDimensions.halfWidth * cubieBodyDimensions.pieceScale * slotWidth;
1744
+ const u = new Vector35();
1745
+ const v = new Vector35();
1746
+ const corner = new Vector35();
1457
1747
  for (const faceIdx of outwardFaces) {
1458
1748
  const normal2 = cubeFaceStyles[faceIdx].vector;
1459
1749
  u.set(normal2.y, normal2.z, normal2.x);
1460
1750
  v.crossVectors(normal2, u);
1751
+ logoSurfaces.set(
1752
+ faceIdx,
1753
+ rectangleLogoSurface(
1754
+ normal2.clone().multiplyScalar(faceDistance + LOGO_ELEVATION2 * slotWidth),
1755
+ u.clone().multiplyScalar(slotWidth / 2),
1756
+ v.clone().multiplyScalar(slotWidth / 2)
1757
+ )
1758
+ );
1461
1759
  const start = hintPositions.length / 3;
1462
1760
  for (const [su, sv] of [
1463
1761
  [-1, -1],
@@ -1473,21 +1771,22 @@ function newCubieShape(outwardFaces, slotWidth) {
1473
1771
  }
1474
1772
  body.dispose();
1475
1773
  return {
1476
- bodyPosition: new BufferAttribute3(bodyPositions.slice(), 3),
1477
- bodyIndex: new BufferAttribute3(new Uint16Array(bodyIndices), 1),
1774
+ bodyPosition: new BufferAttribute4(bodyPositions.slice(), 3),
1775
+ bodyIndex: new BufferAttribute4(new Uint16Array(bodyIndices), 1),
1478
1776
  bodyVertexCount: bodyPositions.length / 3,
1479
- hintPosition: new BufferAttribute3(new Float32Array(hintPositions), 3),
1480
- hintIndex: new BufferAttribute3(new Uint16Array(hintIndices), 1),
1777
+ hintPosition: new BufferAttribute4(new Float32Array(hintPositions), 3),
1778
+ hintIndex: new BufferAttribute4(new Uint16Array(hintIndices), 1),
1481
1779
  hintVertexCount: hintPositions.length / 3,
1482
1780
  faceVertexRanges,
1483
- hintVertexRanges
1781
+ hintVertexRanges,
1782
+ logoSurfaces
1484
1783
  };
1485
1784
  }
1486
1785
  function newPieceMesh(position, index, vertexCount) {
1487
- const geometry = new BufferGeometry3();
1786
+ const geometry = new BufferGeometry4();
1488
1787
  geometry.setAttribute("position", position);
1489
1788
  geometry.setIndex(index);
1490
- const colors = new BufferAttribute3(new Uint8Array(4 * vertexCount), 4, true);
1789
+ const colors = new BufferAttribute4(new Uint8Array(4 * vertexCount), 4, true);
1491
1790
  geometry.setAttribute("color", colors);
1492
1791
  return { geometry, colors };
1493
1792
  }
@@ -1505,7 +1804,10 @@ function newCubiePiece(cubie, shape) {
1505
1804
  // A duplicate shares its square with the real facelet, which is the one
1506
1805
  // that gets painted.
1507
1806
  body: sticker.isDup ? [] : [shape.faceVertexRanges[sticker.faceIdx]],
1508
- hint: sticker.isDup ? [] : [shape.hintVertexRanges.get(sticker.faceIdx)].filter(Boolean)
1807
+ hint: sticker.isDup ? [] : [shape.hintVertexRanges.get(sticker.faceIdx)].filter(Boolean),
1808
+ // Set for a duplicate too: it is the same square, seen through another of
1809
+ // the piece's facelets.
1810
+ logo: shape.logoSurfaces.get(sticker.faceIdx) ?? null
1509
1811
  }));
1510
1812
  return {
1511
1813
  orbit: cubie.orbit,
@@ -1542,23 +1844,23 @@ function cubePuzzlePlan(stickerDat) {
1542
1844
  }
1543
1845
 
1544
1846
  // src/cubing/twisty/views/3D/puzzles/PG3D.ts
1545
- import { DoubleSide as DoubleSide2, FrontSide as FrontSide2 } from "three/src/constants.js";
1546
- import { BufferAttribute as BufferAttribute4 } from "three/src/core/BufferAttribute.js";
1547
- import { BufferGeometry as BufferGeometry4 } from "three/src/core/BufferGeometry.js";
1847
+ import { DoubleSide as DoubleSide2, FrontSide as FrontSide3 } from "three/src/constants.js";
1848
+ import { BufferAttribute as BufferAttribute5 } from "three/src/core/BufferAttribute.js";
1849
+ import { BufferGeometry as BufferGeometry5 } from "three/src/core/BufferGeometry.js";
1548
1850
  import { Object3D as Object3D2 } from "three/src/core/Object3D.js";
1549
- import { MeshBasicMaterial as MeshBasicMaterial3 } from "three/src/materials/MeshBasicMaterial.js";
1851
+ import { MeshBasicMaterial as MeshBasicMaterial4 } from "three/src/materials/MeshBasicMaterial.js";
1550
1852
  import { Color as Color3 } from "three/src/math/Color.js";
1551
- import { Vector3 as Vector35 } from "three/src/math/Vector3.js";
1853
+ import { Vector3 as Vector36 } from "three/src/math/Vector3.js";
1552
1854
  import { Group as Group2 } from "three/src/objects/Group.js";
1553
- import { Mesh as Mesh2 } from "three/src/objects/Mesh.js";
1554
- var foundationMaterial = new MeshBasicMaterial3({
1855
+ import { Mesh as Mesh3 } from "three/src/objects/Mesh.js";
1856
+ var foundationMaterial = new MeshBasicMaterial4({
1555
1857
  side: DoubleSide2,
1556
1858
  color: 0
1557
1859
  });
1558
- var invisMaterial = new MeshBasicMaterial3({
1860
+ var invisMaterial = new MeshBasicMaterial4({
1559
1861
  visible: false
1560
1862
  });
1561
- var basicStickerMaterial = new MeshBasicMaterial3({
1863
+ var basicStickerMaterial = new MeshBasicMaterial4({
1562
1864
  vertexColors: true
1563
1865
  });
1564
1866
  function dist(coords, a, b) {
@@ -1693,9 +1995,9 @@ var Filler = class {
1693
1995
  }
1694
1996
  }
1695
1997
  setAttributes(geo) {
1696
- geo.setAttribute("position", new BufferAttribute4(this.vertices, 3));
1998
+ geo.setAttribute("position", new BufferAttribute5(this.vertices, 3));
1697
1999
  const sa2 = this.colors.subarray(0, 9 * this.sz);
1698
- geo.setAttribute("color", new BufferAttribute4(sa2, 3, true));
2000
+ geo.setAttribute("color", new BufferAttribute5(sa2, 3, true));
1699
2001
  }
1700
2002
  makeGroups(geo) {
1701
2003
  geo.clearGroups();
@@ -1921,9 +2223,9 @@ var HitPlaneDef = class {
1921
2223
  filler.addUncolored(coords, g);
1922
2224
  filler.addUncolored(coords, g + 1);
1923
2225
  }
1924
- this.geo = new BufferGeometry4();
2226
+ this.geo = new BufferGeometry5();
1925
2227
  filler.setAttributes(this.geo);
1926
- const obj = new Mesh2(this.geo, invisMaterial);
2228
+ const obj = new Mesh3(this.geo, invisMaterial);
1927
2229
  obj.userData["quantumMove"] = stickerDat.notationMapper.notationToExternal(
1928
2230
  new Move(hitface.name)
1929
2231
  );
@@ -1936,7 +2238,7 @@ var AxisInfo2 = class {
1936
2238
  order;
1937
2239
  constructor(axisDat) {
1938
2240
  const vec = axisDat.coordinates;
1939
- this.axis = new Vector35(vec[0], vec[1], vec[2]);
2241
+ this.axis = new Vector36(vec[0], vec[1], vec[2]);
1940
2242
  this.order = axisDat.order;
1941
2243
  }
1942
2244
  };
@@ -1953,7 +2255,7 @@ var PG3D = class extends Object3D2 {
1953
2255
  if (stickerDat.stickers.length === 0) {
1954
2256
  throw Error("Reuse of stickerdat from pg; please don't do that.");
1955
2257
  }
1956
- this.hintMaterial = new MeshBasicMaterial3({
2258
+ this.hintMaterial = new MeshBasicMaterial4({
1957
2259
  vertexColors: true,
1958
2260
  transparent: true,
1959
2261
  opacity: 0.5
@@ -2041,13 +2343,13 @@ var PG3D = class extends Object3D2 {
2041
2343
  this.stickers[orbit][ori][ord].addFoundation(filler, sticker, black);
2042
2344
  }
2043
2345
  }
2044
- const fixedGeo = new BufferGeometry4();
2346
+ const fixedGeo = new BufferGeometry5();
2045
2347
  filler.setAttributes(fixedGeo);
2046
2348
  filler.makeGroups(fixedGeo);
2047
- const obj = new Mesh2(fixedGeo, this.materialArray1);
2349
+ const obj = new Mesh3(fixedGeo, this.materialArray1);
2048
2350
  obj.scale.set(PG_SCALE2, PG_SCALE2, PG_SCALE2);
2049
2351
  this.add(obj);
2050
- const obj2 = new Mesh2(fixedGeo, this.materialArray2);
2352
+ const obj2 = new Mesh3(fixedGeo, this.materialArray2);
2051
2353
  obj2.scale.set(PG_SCALE2, PG_SCALE2, PG_SCALE2);
2052
2354
  this.add(obj2);
2053
2355
  const hitfaces = this.stickerDat.faces;
@@ -2138,7 +2440,7 @@ var PG3D = class extends Object3D2 {
2138
2440
  }
2139
2441
  }
2140
2442
  for (const axis of this.stickerDat.axis) {
2141
- const product = point.dot(new Vector35(...axis.coordinates));
2443
+ const product = point.dot(new Vector36(...axis.coordinates));
2142
2444
  if (product > closestMoveDotProduct) {
2143
2445
  const modified = this.stickerDat.notationMapper.notationToExternal(
2144
2446
  modify(axis.quantumMove)
@@ -2372,7 +2674,7 @@ var PG3D = class extends Object3D2 {
2372
2674
  } else if (v === 1) {
2373
2675
  this.hintMaterial = this.stickerMaterial;
2374
2676
  } else {
2375
- this.hintMaterial = new MeshBasicMaterial3({
2677
+ this.hintMaterial = new MeshBasicMaterial4({
2376
2678
  vertexColors: true,
2377
2679
  transparent: true,
2378
2680
  opacity: v
@@ -2420,7 +2722,7 @@ var PG3D = class extends Object3D2 {
2420
2722
  }
2421
2723
  filler.uvs.copyWithin(6 * filler.sz, 0, 6 * filler.sz);
2422
2724
  const sa1 = filler.uvs.subarray(0, 6 * filler.sz);
2423
- this.fixedGeo.setAttribute("uv", new BufferAttribute4(sa1, 2, true));
2725
+ this.fixedGeo.setAttribute("uv", new BufferAttribute5(sa1, 2, true));
2424
2726
  }
2425
2727
  experimentalUpdateTexture(enabled, stickerTexture, hintTexture) {
2426
2728
  if (!stickerTexture) {
@@ -2435,9 +2737,9 @@ var PG3D = class extends Object3D2 {
2435
2737
  this.stickerMaterialDisposable = false;
2436
2738
  }
2437
2739
  if (enabled) {
2438
- this.stickerMaterial = new MeshBasicMaterial3({
2740
+ this.stickerMaterial = new MeshBasicMaterial4({
2439
2741
  map: stickerTexture,
2440
- side: FrontSide2,
2742
+ side: FrontSide3,
2441
2743
  transparent: false
2442
2744
  });
2443
2745
  this.stickerMaterialDisposable = true;
@@ -2449,9 +2751,9 @@ var PG3D = class extends Object3D2 {
2449
2751
  this.hintMaterialDisposable = false;
2450
2752
  }
2451
2753
  if (enabled) {
2452
- this.hintMaterial = new MeshBasicMaterial3({
2754
+ this.hintMaterial = new MeshBasicMaterial4({
2453
2755
  map: hintTexture,
2454
- side: FrontSide2,
2756
+ side: FrontSide3,
2455
2757
  transparent: true
2456
2758
  });
2457
2759
  this.hintMaterialDisposable = true;
@@ -2471,21 +2773,21 @@ var PG3D = class extends Object3D2 {
2471
2773
  };
2472
2774
 
2473
2775
  // src/cubing/twisty/views/3D/puzzles/SolidPieces.ts
2474
- import { BufferAttribute as BufferAttribute5 } from "three/src/core/BufferAttribute.js";
2475
- import { BufferGeometry as BufferGeometry5 } from "three/src/core/BufferGeometry.js";
2776
+ import { BufferAttribute as BufferAttribute6 } from "three/src/core/BufferAttribute.js";
2777
+ import { BufferGeometry as BufferGeometry6 } from "three/src/core/BufferGeometry.js";
2476
2778
  import { Color as Color4 } from "three/src/math/Color.js";
2477
2779
  import { Matrix4 as Matrix43 } from "three/src/math/Matrix4.js";
2478
- import { Vector3 as Vector37 } from "three/src/math/Vector3.js";
2780
+ import { Vector3 as Vector38 } from "three/src/math/Vector3.js";
2479
2781
 
2480
2782
  // src/cubing/twisty/views/3D/puzzles/SolidPieceGeometry.ts
2481
- import { Vector3 as Vector36 } from "three/src/math/Vector3.js";
2783
+ import { Vector3 as Vector37 } from "three/src/math/Vector3.js";
2482
2784
  var SOLID_PIECE_GROOVE = 0.018;
2483
2785
  var SOLID_PIECE_CHAMFER = 5e-3;
2484
2786
  var BOX_PLANE = -1;
2485
2787
  function boxFacet(planeIdx, normal2, size) {
2486
- const reference = Math.abs(normal2.y) > 0.5 ? new Vector36(0, 0, 1) : new Vector36(0, 1, 0);
2487
- const u = new Vector36().crossVectors(reference, normal2).normalize();
2488
- const v = new Vector36().crossVectors(normal2, u);
2788
+ const reference = Math.abs(normal2.y) > 0.5 ? new Vector37(0, 0, 1) : new Vector37(0, 1, 0);
2789
+ const u = new Vector37().crossVectors(reference, normal2).normalize();
2790
+ const v = new Vector37().crossVectors(normal2, u);
2489
2791
  const center = normal2.clone().multiplyScalar(size);
2490
2792
  return {
2491
2793
  plane: planeIdx,
@@ -2500,23 +2802,23 @@ function boxFacet(planeIdx, normal2, size) {
2500
2802
  };
2501
2803
  }
2502
2804
  var BOX_NORMALS = [
2503
- new Vector36(1, 0, 0),
2504
- new Vector36(-1, 0, 0),
2505
- new Vector36(0, 1, 0),
2506
- new Vector36(0, -1, 0),
2507
- new Vector36(0, 0, 1),
2508
- new Vector36(0, 0, -1)
2805
+ new Vector37(1, 0, 0),
2806
+ new Vector37(-1, 0, 0),
2807
+ new Vector37(0, 1, 0),
2808
+ new Vector37(0, -1, 0),
2809
+ new Vector37(0, 0, 1),
2810
+ new Vector37(0, 0, -1)
2509
2811
  ];
2510
2812
  function orderInPlane(points, normal2) {
2511
- const centroid = new Vector36();
2813
+ const centroid2 = new Vector37();
2512
2814
  for (const point of points) {
2513
- centroid.addScaledVector(point, 1 / points.length);
2815
+ centroid2.addScaledVector(point, 1 / points.length);
2514
2816
  }
2515
- const reference = new Vector36().subVectors(points[0], centroid).normalize();
2516
- const other = new Vector36().crossVectors(normal2, reference);
2517
- const offset = new Vector36();
2817
+ const reference = new Vector37().subVectors(points[0], centroid2).normalize();
2818
+ const other = new Vector37().crossVectors(normal2, reference);
2819
+ const offset = new Vector37();
2518
2820
  return points.map((point) => {
2519
- offset.subVectors(point, centroid);
2821
+ offset.subVectors(point, centroid2);
2520
2822
  return {
2521
2823
  point,
2522
2824
  angle: Math.atan2(offset.dot(other), offset.dot(reference))
@@ -2618,7 +2920,7 @@ function chamferPlanes(facets, planes, chamfer, epsilon) {
2618
2920
  continue;
2619
2921
  }
2620
2922
  seen.add(pairKey);
2621
- const sum = new Vector36().addVectors(
2923
+ const sum = new Vector37().addVectors(
2622
2924
  planes[colored].normal,
2623
2925
  planes[internal].normal
2624
2926
  );
@@ -2681,9 +2983,10 @@ function solidPieceGeometry(planes, options) {
2681
2983
 
2682
2984
  // src/cubing/twisty/views/3D/puzzles/SolidPieces.ts
2683
2985
  var PG_SCALE3 = 0.5;
2684
- var EPSILON = 1e-6;
2986
+ var EPSILON2 = 1e-6;
2987
+ var LOGO_ELEVATION3 = 0.01;
2685
2988
  function vertexAt(sticker, index) {
2686
- return new Vector37(
2989
+ return new Vector38(
2687
2990
  sticker.coords[3 * index],
2688
2991
  sticker.coords[3 * index + 1],
2689
2992
  sticker.coords[3 * index + 2]
@@ -2697,11 +3000,11 @@ function polygonOf(sticker) {
2697
3000
  return vertices;
2698
3001
  }
2699
3002
  function outwardNormal(polygon) {
2700
- const normal2 = new Vector37().crossVectors(
2701
- new Vector37().subVectors(polygon[1], polygon[0]),
2702
- new Vector37().subVectors(polygon[2], polygon[0])
3003
+ const normal2 = new Vector38().crossVectors(
3004
+ new Vector38().subVectors(polygon[1], polygon[0]),
3005
+ new Vector38().subVectors(polygon[2], polygon[0])
2703
3006
  );
2704
- if (normal2.length() < EPSILON) {
3007
+ if (normal2.length() < EPSILON2) {
2705
3008
  return null;
2706
3009
  }
2707
3010
  normal2.normalize();
@@ -2742,13 +3045,13 @@ function faceStyles(faceNormals, faceColors) {
2742
3045
  return styles;
2743
3046
  }
2744
3047
  function hintPolygon(polygon, normal2) {
2745
- const centroid = new Vector37();
3048
+ const centroid2 = new Vector38();
2746
3049
  for (const vertex of polygon) {
2747
- centroid.addScaledVector(vertex, 1 / polygon.length);
3050
+ centroid2.addScaledVector(vertex, 1 / polygon.length);
2748
3051
  }
2749
3052
  return polygon.map(
2750
- (vertex) => centroid.clone().addScaledVector(
2751
- new Vector37().subVectors(vertex, centroid),
3053
+ (vertex) => centroid2.clone().addScaledVector(
3054
+ new Vector38().subVectors(vertex, centroid2),
2752
3055
  HINT_FACELET_SCALE
2753
3056
  ).addScaledVector(normal2, HINT_FACELET_ELEVATION)
2754
3057
  );
@@ -2825,7 +3128,7 @@ function solidPuzzlePlan(stickerDat) {
2825
3128
  }
2826
3129
  faceletCount++;
2827
3130
  for (let i = 1; i < polygon.length - 1; i++) {
2828
- faceletArea += new Vector37().subVectors(polygon[i], polygon[0]).cross(new Vector37().subVectors(polygon[i + 1], polygon[0])).length() / 2;
3131
+ faceletArea += new Vector38().subVectors(polygon[i], polygon[0]).cross(new Vector38().subVectors(polygon[i + 1], polygon[0])).length() / 2;
2829
3132
  }
2830
3133
  piece.stickers.push({ sticker, polygon });
2831
3134
  for (let i = 0; i < polygon.length; i++) {
@@ -2852,7 +3155,7 @@ function solidPuzzlePlan(stickerDat) {
2852
3155
  const cuts = cutPlanes(
2853
3156
  order,
2854
3157
  edges,
2855
- stickerDat.axis.map((axis) => new Vector37(...axis.coordinates).normalize()),
3158
+ stickerDat.axis.map((axis) => new Vector38(...axis.coordinates).normalize()),
2856
3159
  quantum
2857
3160
  );
2858
3161
  if (!cuts) {
@@ -2946,7 +3249,7 @@ function newSolidPiece(piece, cuts, sizes) {
2946
3249
  }
2947
3250
  const planes = [];
2948
3251
  const planeOfSticker = /* @__PURE__ */ new Map();
2949
- const interior = new Vector37();
3252
+ const interior = new Vector38();
2950
3253
  for (const { polygon } of piece.stickers) {
2951
3254
  for (const vertex of polygon) {
2952
3255
  interior.addScaledVector(
@@ -3046,18 +3349,31 @@ function newSolidPiece(piece, cuts, sizes) {
3046
3349
  const body = newPieceMesh2(solid.positions);
3047
3350
  const hint = hintPositions.length > 0 ? newPieceMesh2(new Float32Array(hintPositions)) : null;
3048
3351
  paintInternal(body.colors, solid.internalRanges);
3352
+ const logoByFace = /* @__PURE__ */ new Map();
3353
+ for (const { sticker, polygon } of piece.stickers) {
3354
+ logoByFace.set(
3355
+ sticker.face,
3356
+ logoSurface(
3357
+ polygon,
3358
+ outwardNormal(polygon),
3359
+ LOGO_ELEVATION3 * sizes.featureScale
3360
+ )
3361
+ );
3362
+ }
3049
3363
  const facelets = piece.stickers.map(({ sticker }) => ({
3050
3364
  ori: sticker.ori,
3051
3365
  faceStyle: sticker.face,
3052
3366
  body: solid.ranges[planeOfSticker.get(sticker)],
3053
- hint: [hintRanges.get(sticker)]
3367
+ hint: [hintRanges.get(sticker)],
3368
+ logo: logoByFace.get(sticker.face) ?? null
3054
3369
  }));
3055
3370
  for (const sticker of piece.duplicates) {
3056
3371
  facelets.push({
3057
3372
  ori: sticker.ori,
3058
3373
  faceStyle: sticker.face,
3059
3374
  body: [],
3060
- hint: []
3375
+ hint: [],
3376
+ logo: logoByFace.get(sticker.face) ?? null
3061
3377
  });
3062
3378
  }
3063
3379
  return {
@@ -3071,9 +3387,9 @@ function newSolidPiece(piece, cuts, sizes) {
3071
3387
  };
3072
3388
  }
3073
3389
  function newPieceMesh2(positions) {
3074
- const geometry = new BufferGeometry5();
3075
- geometry.setAttribute("position", new BufferAttribute5(positions, 3));
3076
- const colors = new BufferAttribute5(
3390
+ const geometry = new BufferGeometry6();
3391
+ geometry.setAttribute("position", new BufferAttribute6(positions, 3));
3392
+ const colors = new BufferAttribute6(
3077
3393
  new Uint8Array(4 * positions.length / 3),
3078
3394
  4,
3079
3395
  true
@@ -3084,15 +3400,15 @@ function newPieceMesh2(positions) {
3084
3400
 
3085
3401
  // src/cubing/twisty/views/3D/puzzles/Square1_3D.ts
3086
3402
  import { DoubleSide as DoubleSide3 } from "three/src/constants.js";
3087
- import { BufferAttribute as BufferAttribute6 } from "three/src/core/BufferAttribute.js";
3088
- import { BufferGeometry as BufferGeometry6 } from "three/src/core/BufferGeometry.js";
3403
+ import { BufferAttribute as BufferAttribute7 } from "three/src/core/BufferAttribute.js";
3404
+ import { BufferGeometry as BufferGeometry7 } from "three/src/core/BufferGeometry.js";
3089
3405
  import { Object3D as Object3D3 } from "three/src/core/Object3D.js";
3090
- import { MeshBasicMaterial as MeshBasicMaterial4 } from "three/src/materials/MeshBasicMaterial.js";
3406
+ import { MeshBasicMaterial as MeshBasicMaterial5 } from "three/src/materials/MeshBasicMaterial.js";
3091
3407
  import { Color as Color5 } from "three/src/math/Color.js";
3092
3408
  import { Quaternion as Quaternion2 } from "three/src/math/Quaternion.js";
3093
- import { Vector3 as Vector38 } from "three/src/math/Vector3.js";
3409
+ import { Vector3 as Vector39 } from "three/src/math/Vector3.js";
3094
3410
  import { Group as Group3 } from "three/src/objects/Group.js";
3095
- import { Mesh as Mesh3 } from "three/src/objects/Mesh.js";
3411
+ import { Mesh as Mesh4 } from "three/src/objects/Mesh.js";
3096
3412
  var DEGREE = TAU / 360;
3097
3413
  var SLOT_ANGLE_DEGREES = 30;
3098
3414
  var WEDGES_PER_LAYER = 12;
@@ -3107,8 +3423,8 @@ var RING_COLORS = [2254591, 16750848, 65280, 16711680];
3107
3423
  var FRAME_OFFSET_DEGREES = 180;
3108
3424
  var BODY_COLOR = 1118481;
3109
3425
  var MIRROR = -1;
3110
- var Y_AXIS = new Vector38(0, 1, 0);
3111
- var SLICE_AXIS = new Vector38(
3426
+ var Y_AXIS = new Vector39(0, 1, 0);
3427
+ var SLICE_AXIS = new Vector39(
3112
3428
  MIRROR * Math.sin(15 * DEGREE),
3113
3429
  0,
3114
3430
  Math.cos(15 * DEGREE)
@@ -3125,7 +3441,7 @@ function slotQuaternion(slot) {
3125
3441
  );
3126
3442
  if (slot >= WEDGES_PER_LAYER) {
3127
3443
  quaternion.multiply(
3128
- new Quaternion2().setFromAxisAngle(new Vector38(0, 0, 1), TAU / 2)
3444
+ new Quaternion2().setFromAxisAngle(new Vector39(0, 0, 1), TAU / 2)
3129
3445
  );
3130
3446
  }
3131
3447
  return quaternion;
@@ -3214,15 +3530,15 @@ function prismStickerSpecs(spec) {
3214
3530
  const specs = [];
3215
3531
  if (topColor !== null) {
3216
3532
  specs.push({
3217
- corners: polygon.map(([x, z]) => new Vector38(x, yTop, z)),
3218
- normal: new Vector38(0, 1, 0),
3533
+ corners: polygon.map(([x, z]) => new Vector39(x, yTop, z)),
3534
+ normal: new Vector39(0, 1, 0),
3219
3535
  color: topColor
3220
3536
  });
3221
3537
  }
3222
3538
  if (bottomColor !== null) {
3223
3539
  specs.push({
3224
- corners: polygon.map(([x, z]) => new Vector38(x, yBottom, z)),
3225
- normal: new Vector38(0, -1, 0),
3540
+ corners: polygon.map(([x, z]) => new Vector39(x, yBottom, z)),
3541
+ normal: new Vector39(0, -1, 0),
3226
3542
  color: bottomColor
3227
3543
  });
3228
3544
  }
@@ -3239,17 +3555,17 @@ function prismStickerSpecs(spec) {
3239
3555
  }
3240
3556
  const [x0, z0] = polygon[i];
3241
3557
  const [x1, z1] = polygon[(i + 1) % polygon.length];
3242
- const normal2 = new Vector38(z1 - z0, 0, -(x1 - x0)).normalize();
3558
+ const normal2 = new Vector39(z1 - z0, 0, -(x1 - x0)).normalize();
3243
3559
  const outward = normal2.x * ((x0 + x1) / 2 - centroidX) + normal2.z * ((z0 + z1) / 2 - centroidZ);
3244
3560
  if (outward < 0) {
3245
3561
  normal2.negate();
3246
3562
  }
3247
3563
  specs.push({
3248
3564
  corners: [
3249
- new Vector38(x0, yBottom, z0),
3250
- new Vector38(x1, yBottom, z1),
3251
- new Vector38(x1, yTop, z1),
3252
- new Vector38(x0, yTop, z0)
3565
+ new Vector39(x0, yBottom, z0),
3566
+ new Vector39(x1, yBottom, z1),
3567
+ new Vector39(x1, yTop, z1),
3568
+ new Vector39(x0, yTop, z0)
3253
3569
  ],
3254
3570
  normal: normal2,
3255
3571
  color
@@ -3258,7 +3574,7 @@ function prismStickerSpecs(spec) {
3258
3574
  return specs;
3259
3575
  }
3260
3576
  function stickerPositions(spec, faceletScale) {
3261
- const center = new Vector38();
3577
+ const center = new Vector39();
3262
3578
  for (const corner of spec.corners) {
3263
3579
  center.addScaledVector(corner, 1 / spec.corners.length);
3264
3580
  }
@@ -3287,9 +3603,9 @@ var PUZZLE_RADIUS = CUBE_HALF_EDGE * Math.SQRT2;
3287
3603
  function prismPlanes(spec) {
3288
3604
  const { polygon, sideColors } = spec;
3289
3605
  const planes = [
3290
- { normal: new Vector38(0, 1, 0), offset: spec.yTop, color: spec.topColor },
3606
+ { normal: new Vector39(0, 1, 0), offset: spec.yTop, color: spec.topColor },
3291
3607
  {
3292
- normal: new Vector38(0, -1, 0),
3608
+ normal: new Vector39(0, -1, 0),
3293
3609
  offset: -spec.yBottom,
3294
3610
  color: spec.bottomColor
3295
3611
  }
@@ -3303,7 +3619,7 @@ function prismPlanes(spec) {
3303
3619
  for (let i = 0; i < polygon.length; i++) {
3304
3620
  const [x0, z0] = polygon[i];
3305
3621
  const [x1, z1] = polygon[(i + 1) % polygon.length];
3306
- const normal2 = new Vector38(z1 - z0, 0, -(x1 - x0)).normalize();
3622
+ const normal2 = new Vector39(z1 - z0, 0, -(x1 - x0)).normalize();
3307
3623
  if (normal2.x * (x0 - centroidX) + normal2.z * (z0 - centroidZ) < 0) {
3308
3624
  normal2.negate();
3309
3625
  }
@@ -3343,12 +3659,12 @@ function stickerlessPieceGeometry(spec) {
3343
3659
  paint(piece.ranges[i], color);
3344
3660
  }
3345
3661
  }
3346
- const geometry = new BufferGeometry6();
3347
- geometry.setAttribute("position", new BufferAttribute6(piece.positions, 3));
3348
- geometry.setAttribute("color", new BufferAttribute6(colors, 4, true));
3662
+ const geometry = new BufferGeometry7();
3663
+ geometry.setAttribute("position", new BufferAttribute7(piece.positions, 3));
3664
+ geometry.setAttribute("color", new BufferAttribute7(colors, 4, true));
3349
3665
  return geometry;
3350
3666
  }
3351
- var bodyMaterial = new MeshBasicMaterial4({
3667
+ var bodyMaterial = new MeshBasicMaterial5({
3352
3668
  color: new Color5(BODY_COLOR).convertLinearToSRGB(),
3353
3669
  side: DoubleSide3
3354
3670
  });
@@ -3360,7 +3676,7 @@ var stickerMaterialCache = /* @__PURE__ */ new Map();
3360
3676
  function stickerMaterial(color) {
3361
3677
  let material = stickerMaterialCache.get(color);
3362
3678
  if (!material) {
3363
- material = new MeshBasicMaterial4({
3679
+ material = new MeshBasicMaterial5({
3364
3680
  color: new Color5(color).convertLinearToSRGB(),
3365
3681
  side: DoubleSide3
3366
3682
  });
@@ -3375,28 +3691,28 @@ var Square1Piece = class extends Group3 {
3375
3691
  constructor(spec, faceletScale, stickerless) {
3376
3692
  super();
3377
3693
  if (stickerless) {
3378
- this.body = new Mesh3(
3694
+ this.body = new Mesh4(
3379
3695
  stickerlessPieceGeometry(spec),
3380
3696
  stickerlessMaterial()
3381
3697
  );
3382
3698
  this.add(this.body);
3383
3699
  return;
3384
3700
  }
3385
- const bodyGeometry = new BufferGeometry6();
3701
+ const bodyGeometry = new BufferGeometry7();
3386
3702
  bodyGeometry.setAttribute(
3387
3703
  "position",
3388
- new BufferAttribute6(prismBodyPositions(spec), 3)
3704
+ new BufferAttribute7(prismBodyPositions(spec), 3)
3389
3705
  );
3390
- this.body = new Mesh3(bodyGeometry, bodyMaterial);
3706
+ this.body = new Mesh4(bodyGeometry, bodyMaterial);
3391
3707
  this.add(this.body);
3392
3708
  this.#stickerSpecs = prismStickerSpecs(spec);
3393
3709
  for (const stickerSpec of this.#stickerSpecs) {
3394
- const geometry = new BufferGeometry6();
3710
+ const geometry = new BufferGeometry7();
3395
3711
  geometry.setAttribute(
3396
3712
  "position",
3397
- new BufferAttribute6(stickerPositions(stickerSpec, faceletScale), 3)
3713
+ new BufferAttribute7(stickerPositions(stickerSpec, faceletScale), 3)
3398
3714
  );
3399
- const mesh = new Mesh3(geometry, stickerMaterial(stickerSpec.color));
3715
+ const mesh = new Mesh4(geometry, stickerMaterial(stickerSpec.color));
3400
3716
  this.#stickerMeshes.push(mesh);
3401
3717
  this.add(mesh);
3402
3718
  }
@@ -3406,7 +3722,7 @@ var Square1Piece = class extends Group3 {
3406
3722
  const geometry = this.#stickerMeshes[i].geometry;
3407
3723
  geometry.setAttribute(
3408
3724
  "position",
3409
- new BufferAttribute6(
3725
+ new BufferAttribute7(
3410
3726
  stickerPositions(this.#stickerSpecs[i], faceletScale),
3411
3727
  3
3412
3728
  )
@@ -3461,6 +3777,38 @@ function equatorPrismSpec(homePiece) {
3461
3777
  ]
3462
3778
  };
3463
3779
  }
3780
+ var LOGO_EQUATOR_PIECE = 0;
3781
+ var LOGO_ELEVATION4 = 2 * STICKER_ELEVATION;
3782
+ function equatorLogoSurface(spec) {
3783
+ const { polygon, yBottom, yTop, sideColors } = spec;
3784
+ let best = null;
3785
+ let bestWidth = 0;
3786
+ for (let i = 0; i < polygon.length; i++) {
3787
+ if (sideColors[i] === null || sideColors[i] === void 0) {
3788
+ continue;
3789
+ }
3790
+ const [x0, z0] = polygon[i];
3791
+ const [x1, z1] = polygon[(i + 1) % polygon.length];
3792
+ const width = Math.hypot(x1 - x0, z1 - z0);
3793
+ if (width <= bestWidth) {
3794
+ continue;
3795
+ }
3796
+ const normal2 = new Vector39(z1 - z0, 0, -(x1 - x0)).normalize();
3797
+ const u = new Vector39(x1 - x0, 0, z1 - z0).multiplyScalar(0.5);
3798
+ const v = new Vector39(0, (yTop - yBottom) / 2, 0);
3799
+ if (new Vector39().crossVectors(u, v).dot(normal2) < 0) {
3800
+ u.negate();
3801
+ }
3802
+ const center = new Vector39(
3803
+ (x0 + x1) / 2,
3804
+ (yBottom + yTop) / 2,
3805
+ (z0 + z1) / 2
3806
+ ).addScaledVector(normal2, LOGO_ELEVATION4);
3807
+ bestWidth = width;
3808
+ best = rectangleLogoSurface(center, u, v);
3809
+ }
3810
+ return best;
3811
+ }
3464
3812
  function ringColorAt(phiDegrees) {
3465
3813
  const faceIndex = Math.round(phiDegrees / 90);
3466
3814
  return RING_COLORS[(faceIndex % 4 + 4) % 4];
@@ -3617,6 +3965,31 @@ var Square1_3D = class extends Object3D3 {
3617
3965
  }
3618
3966
  setStickeringMask(_stickeringMask) {
3619
3967
  }
3968
+ #logoMesh = null;
3969
+ /**
3970
+ * Prints a logo on the equator block, or takes it off again when passed
3971
+ * `null`.
3972
+ *
3973
+ * The image is stretched onto a square sized to the face, so its own
3974
+ * proportions don't matter. It is a child of the piece, so it turns and flips
3975
+ * with the equator.
3976
+ */
3977
+ experimentalSetLogo(texture) {
3978
+ if (this.#logoMesh) {
3979
+ this.#logoMesh.removeFromParent();
3980
+ this.#logoMesh.material.dispose();
3981
+ this.#logoMesh = null;
3982
+ }
3983
+ if (texture) {
3984
+ const surface = equatorLogoSurface(equatorPrismSpec(LOGO_EQUATOR_PIECE));
3985
+ if (surface) {
3986
+ this.#logoMesh = newLogoMesh(texture);
3987
+ surfaceMatrix(surface, this.#logoMesh.matrix);
3988
+ this.#equatorPieces[LOGO_EQUATOR_PIECE].add(this.#logoMesh);
3989
+ }
3990
+ }
3991
+ this.scheduleRenderCallback?.();
3992
+ }
3620
3993
  experimentalUpdateOptions(options) {
3621
3994
  let changed = false;
3622
3995
  if (typeof options.showFoundation === "boolean") {
@@ -3651,15 +4024,15 @@ var Square1_3D = class extends Object3D3 {
3651
4024
 
3652
4025
  // src/cubing/twisty/views/3D/puzzles/Stickerless3D.ts
3653
4026
  import { BackSide as BackSide2 } from "three/src/constants.js";
3654
- import { BufferAttribute as BufferAttribute7 } from "three/src/core/BufferAttribute.js";
3655
- import { BufferGeometry as BufferGeometry7 } from "three/src/core/BufferGeometry.js";
4027
+ import { BufferAttribute as BufferAttribute8 } from "three/src/core/BufferAttribute.js";
4028
+ import { BufferGeometry as BufferGeometry8 } from "three/src/core/BufferGeometry.js";
3656
4029
  import { Object3D as Object3D4 } from "three/src/core/Object3D.js";
3657
- import { MeshBasicMaterial as MeshBasicMaterial5 } from "three/src/materials/MeshBasicMaterial.js";
4030
+ import { MeshBasicMaterial as MeshBasicMaterial6 } from "three/src/materials/MeshBasicMaterial.js";
3658
4031
  import { Matrix4 as Matrix44 } from "three/src/math/Matrix4.js";
3659
- import { Vector3 as Vector39 } from "three/src/math/Vector3.js";
4032
+ import { Vector3 as Vector310 } from "three/src/math/Vector3.js";
3660
4033
  import { BatchedMesh } from "three/src/objects/BatchedMesh.js";
3661
- import { Mesh as Mesh4 } from "three/src/objects/Mesh.js";
3662
- var invisibleMaterial2 = new MeshBasicMaterial5({ visible: false });
4034
+ import { Mesh as Mesh5 } from "three/src/objects/Mesh.js";
4035
+ var invisibleMaterial2 = new MeshBasicMaterial6({ visible: false });
3663
4036
  function newBatch(meshes, material) {
3664
4037
  let vertices = 0;
3665
4038
  let indices = 0;
@@ -3684,7 +4057,8 @@ var Stickerless3D = class extends Object3D4 {
3684
4057
  this.kpuzzle = kpuzzle;
3685
4058
  this.stickerDat = stickerDat;
3686
4059
  this.plan = plan;
3687
- this.#hintMaterial = new MeshBasicMaterial5({
4060
+ this.#logoFacelet = options.logoFacelet ?? null;
4061
+ this.#hintMaterial = new MeshBasicMaterial6({
3688
4062
  vertexColors: true,
3689
4063
  transparent: true,
3690
4064
  // Hint facelets show the faces turned away from the camera, so they are
@@ -3694,7 +4068,7 @@ var Stickerless3D = class extends Object3D4 {
3694
4068
  this.#bodyMaterial = newVertexColorBodyMaterial();
3695
4069
  for (const axis of stickerDat.axis) {
3696
4070
  this.#axesInfo[axis.quantumMove.family] = {
3697
- axis: new Vector39(...axis.coordinates),
4071
+ axis: new Vector310(...axis.coordinates),
3698
4072
  order: axis.order
3699
4073
  };
3700
4074
  }
@@ -3758,6 +4132,11 @@ var Stickerless3D = class extends Object3D4 {
3758
4132
  #dirtyColors = /* @__PURE__ */ new Map();
3759
4133
  #lastPosition = null;
3760
4134
  #pendingStickeringUpdate = false;
4135
+ /** The facelet a logo is printed on, and where it currently shows. */
4136
+ #logoFacelet = null;
4137
+ #logoMesh = null;
4138
+ #logoSlot = null;
4139
+ #logoSurfaceMatrix = new Matrix44();
3761
4140
  #addPiece(plan) {
3762
4141
  const bodyInstance = addToBatch(this.#bodyBatch, plan.body, plan.home);
3763
4142
  const hintInstance = plan.hint && this.#hintBatch ? addToBatch(this.#hintBatch, plan.hint, plan.home) : -1;
@@ -3782,7 +4161,8 @@ var Stickerless3D = class extends Object3D4 {
3782
4161
  hint: hintInstance === -1 ? [] : shift(facelet.hint, hintInstance.vertexStart),
3783
4162
  appearanceKey: -1,
3784
4163
  mask: "regular",
3785
- hintMask: "regular"
4164
+ hintMask: "regular",
4165
+ logo: facelet.logo
3786
4166
  };
3787
4167
  }
3788
4168
  }
@@ -3807,15 +4187,94 @@ var Stickerless3D = class extends Object3D4 {
3807
4187
  }
3808
4188
  }
3809
4189
  }
3810
- const geometry = new BufferGeometry7();
3811
- geometry.setAttribute("position", new BufferAttribute7(positions, 3));
3812
- const mesh = new Mesh4(geometry, invisibleMaterial2);
4190
+ const geometry = new BufferGeometry8();
4191
+ geometry.setAttribute("position", new BufferAttribute8(positions, 3));
4192
+ const mesh = new Mesh5(geometry, invisibleMaterial2);
3813
4193
  mesh.userData["quantumMove"] = this.stickerDat.notationMapper.notationToExternal(new Move(face2.name));
3814
4194
  mesh.scale.setScalar(0.99);
3815
4195
  this.add(mesh);
3816
4196
  this.#controlTargets.push(mesh);
3817
4197
  }
4198
+ /**
4199
+ * Prints a logo on the piece this puzzle keeps one on, or takes it off again
4200
+ * when passed `null`.
4201
+ *
4202
+ * The image is stretched onto the square {@link PuzzlePlan} sized for that
4203
+ * facelet, so its own proportions don't matter. It follows the piece: the
4204
+ * logo is drawn wherever the piece currently is, including part-way through a
4205
+ * move. The one thing it does not follow is a piece turning in place — a
4206
+ * megaminx or skewb center whose facelets are all one square — which leaves
4207
+ * the logo upright rather than rotating it with the plastic.
4208
+ */
4209
+ experimentalSetLogo(texture) {
4210
+ if (!texture || !this.#logoFacelet) {
4211
+ this.#disposeLogo();
4212
+ this.scheduleRenderCallback();
4213
+ return;
4214
+ }
4215
+ if (this.#logoMesh) {
4216
+ const material = this.#logoMesh.material;
4217
+ material.map = texture;
4218
+ material.needsUpdate = true;
4219
+ } else {
4220
+ this.#logoMesh = newLogoMesh(texture);
4221
+ this.add(this.#logoMesh);
4222
+ }
4223
+ this.#updateLogoSlot();
4224
+ this.#placeLogo();
4225
+ this.scheduleRenderCallback();
4226
+ }
4227
+ #disposeLogo() {
4228
+ if (!this.#logoMesh) {
4229
+ return;
4230
+ }
4231
+ this.remove(this.#logoMesh);
4232
+ this.#logoMesh.material.dispose();
4233
+ this.#logoMesh = null;
4234
+ this.#logoSlot = null;
4235
+ }
4236
+ /** Which slot is showing the logo's facelet, as of the last position. */
4237
+ #updateLogoSlot() {
4238
+ this.#logoSlot = null;
4239
+ const address = this.#logoFacelet;
4240
+ const pattern = this.#lastPosition?.pattern;
4241
+ if (!address || !pattern) {
4242
+ return;
4243
+ }
4244
+ const orbitFacelets = this.#facelets[address.orbit];
4245
+ const orbitPattern = pattern.patternData[address.orbit];
4246
+ if (!orbitFacelets || !orbitPattern) {
4247
+ return;
4248
+ }
4249
+ const numOrientations = orbitFacelets.length;
4250
+ for (let ord = 0; ord < orbitPattern.pieces.length; ord++) {
4251
+ if (orbitPattern.pieces[ord] !== address.ord) {
4252
+ continue;
4253
+ }
4254
+ const ori = numOrientations === 1 ? 0 : (address.ori + orbitPattern.orientation[ord]) % numOrientations;
4255
+ this.#logoSlot = orbitFacelets[ori]?.[ord] ?? null;
4256
+ return;
4257
+ }
4258
+ }
4259
+ #placeLogo() {
4260
+ const mesh = this.#logoMesh;
4261
+ if (!mesh) {
4262
+ return;
4263
+ }
4264
+ const slot = this.#logoSlot;
4265
+ if (!slot?.logo) {
4266
+ mesh.visible = false;
4267
+ return;
4268
+ }
4269
+ mesh.visible = true;
4270
+ mesh.matrix.multiplyMatrices(
4271
+ slot.piece.matrix,
4272
+ surfaceMatrix(slot.logo, this.#logoSurfaceMatrix)
4273
+ );
4274
+ mesh.matrixWorldNeedsUpdate = true;
4275
+ }
3818
4276
  dispose() {
4277
+ this.#disposeLogo();
3819
4278
  this.#bodyBatch.dispose();
3820
4279
  this.#bodyBatch.geometry.dispose();
3821
4280
  this.#hintBatch?.dispose();
@@ -3852,7 +4311,7 @@ var Stickerless3D = class extends Object3D4 {
3852
4311
  }
3853
4312
  }
3854
4313
  for (const axis of this.stickerDat.axis) {
3855
- const product = point.dot(new Vector39(...axis.coordinates));
4314
+ const product = point.dot(new Vector310(...axis.coordinates));
3856
4315
  if (product > closestMoveDotProduct) {
3857
4316
  const modified = this.stickerDat.notationMapper.notationToExternal(
3858
4317
  modify(axis.quantumMove)
@@ -3929,6 +4388,7 @@ var Stickerless3D = class extends Object3D4 {
3929
4388
  this.#updateColors(position);
3930
4389
  this.#lastPosition = position;
3931
4390
  this.#pendingStickeringUpdate = false;
4391
+ this.#updateLogoSlot();
3932
4392
  }
3933
4393
  const wereTurning = this.#turningPieces;
3934
4394
  for (const piece of wereTurning) {
@@ -3968,6 +4428,7 @@ var Stickerless3D = class extends Object3D4 {
3968
4428
  for (const piece of this.#turningPieces) {
3969
4429
  this.#setMatrix(piece);
3970
4430
  }
4431
+ this.#placeLogo();
3971
4432
  this.scheduleRenderCallback();
3972
4433
  }
3973
4434
  /**
@@ -4086,7 +4547,7 @@ import { Raycaster } from "three/src/core/Raycaster.js";
4086
4547
  import { TextureLoader as TextureLoader2 } from "three/src/loaders/TextureLoader.js";
4087
4548
  import { Spherical } from "three/src/math/Spherical.js";
4088
4549
  import { Vector2 as Vector22 } from "three/src/math/Vector2.js";
4089
- import { Vector3 as Vector310 } from "three/src/math/Vector3.js";
4550
+ import { Vector3 as Vector311 } from "three/src/math/Vector3.js";
4090
4551
  import { WebGLRenderer } from "three/src/renderers/WebGLRenderer.js";
4091
4552
  import { Scene } from "three/src/scenes/Scene.js";
4092
4553
 
@@ -4134,7 +4595,8 @@ async function pg3dShim(renderCallback, puzzleLoader, hintFacelets, faceletScale
4134
4595
  const plan = darkIgnoredOrbits || pictureCube ? null : cubePuzzlePlan(stickerDat) ?? solidPuzzlePlan(stickerDat);
4135
4596
  if (plan) {
4136
4597
  return new Stickerless3D(renderCallback, kpuzzle, stickerDat, plan, {
4137
- hintFacelets
4598
+ hintFacelets,
4599
+ logoFacelet: logoFaceletAddress(puzzleLoader.id, stickerDat)
4138
4600
  });
4139
4601
  }
4140
4602
  return new PG3D(
@@ -4158,11 +4620,11 @@ export {
4158
4620
  Spherical as ThreeSpherical,
4159
4621
  TextureLoader2 as ThreeTextureLoader,
4160
4622
  Vector22 as ThreeVector2,
4161
- Vector310 as ThreeVector3,
4623
+ Vector311 as ThreeVector3,
4162
4624
  WebGLRenderer as ThreeWebGLRenderer,
4163
4625
  Twisty3DScene,
4164
4626
  cube3DShim,
4165
4627
  pg3dShim,
4166
4628
  square1_3DShim
4167
4629
  };
4168
- //# sourceMappingURL=twisty-dynamic-3d-MF76FSDA.js.map
4630
+ //# sourceMappingURL=twisty-dynamic-3d-3MGVD3TG.js.map