@rednaxela101/cubing 0.63.8 → 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 (29) hide show
  1. package/README.md +5 -5
  2. package/dist/bin/chunks/{chunk-EWR23OA4.js → chunk-3WUR6UXA.js} +2 -2
  3. package/dist/bin/order.js +1 -1
  4. package/dist/bin/puzzle-geometry-bin.js +1 -1
  5. package/dist/bin/scramble.js +1 -1
  6. package/dist/bin/svg.js +1 -1
  7. package/dist/lib/cubing/alg/index.d.ts +1 -1
  8. package/dist/lib/cubing/bluetooth/index.d.ts +2 -2
  9. package/dist/lib/cubing/chunks/{chunk-B2YKAP2I.js → chunk-IOHGEWB2.js} +2 -2
  10. package/dist/lib/cubing/chunks/{twisty-dynamic-3d-SLKSUJTE.js → twisty-dynamic-3d-3MGVD3TG.js} +928 -334
  11. package/dist/lib/cubing/chunks/twisty-dynamic-3d-3MGVD3TG.js.map +7 -0
  12. package/dist/lib/cubing/{index-ylnTp4i5.d.ts → index-0OM9yjf0.d.ts} +1 -1
  13. package/dist/lib/cubing/{index-FT5_Vsnm.d.ts → index-B_TS5TTk.d.ts} +13 -0
  14. package/dist/lib/cubing/kpuzzle/index.d.ts +1 -1
  15. package/dist/lib/cubing/notation/index.d.ts +1 -1
  16. package/dist/lib/cubing/{outside-D3tRtsxV.d.ts → outside-BUsE7O5Q.d.ts} +1 -1
  17. package/dist/lib/cubing/protocol/index.d.ts +1 -1
  18. package/dist/lib/cubing/puzzle-geometry/index.d.ts +1 -1
  19. package/dist/lib/cubing/puzzles/index.d.ts +1 -1
  20. package/dist/lib/cubing/scramble/index.d.ts +1 -1
  21. package/dist/lib/cubing/search/index.d.ts +1 -1
  22. package/dist/lib/cubing/stream/index.d.ts +1 -1
  23. package/dist/lib/cubing/twisty/index.d.ts +1 -1
  24. package/dist/lib/cubing/twisty/index.js +31 -2
  25. package/dist/lib/cubing/twisty/index.js.map +2 -2
  26. package/package.json +1 -1
  27. package/dist/lib/cubing/chunks/twisty-dynamic-3d-SLKSUJTE.js.map +0 -7
  28. /package/dist/bin/chunks/{chunk-EWR23OA4.js.map → chunk-3WUR6UXA.js.map} +0 -0
  29. /package/dist/lib/cubing/chunks/{chunk-B2YKAP2I.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-B2YKAP2I.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 } 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) {
@@ -87,7 +87,6 @@ function beveledCubieGeometry(faceNormals, outerFaceIndices, halfSize, outerAxis
87
87
  }
88
88
  const gridSize = samples.length;
89
89
  const positions = [];
90
- const normals = [];
91
90
  const indices = [];
92
91
  const geometry = new BufferGeometry();
93
92
  const point = new Vector3();
@@ -140,9 +139,6 @@ function beveledCubieGeometry(faceNormals, outerFaceIndices, halfSize, outerAxis
140
139
  core.y + offset.y * radii.y,
141
140
  core.z + offset.z * radii.z
142
141
  );
143
- normals.push(
144
- ...offset.set(offset.x / radii.x, offset.y / radii.y, offset.z / radii.z).normalize().toArray()
145
- );
146
142
  }
147
143
  }
148
144
  const indexStart = indices.length;
@@ -159,18 +155,12 @@ function beveledCubieGeometry(faceNormals, outerFaceIndices, halfSize, outerAxis
159
155
  "position",
160
156
  new BufferAttribute(new Float32Array(positions), 3)
161
157
  );
162
- geometry.setAttribute(
163
- "normal",
164
- new BufferAttribute(new Float32Array(normals), 3)
165
- );
166
158
  geometry.setIndex(indices);
167
159
  return geometry;
168
160
  }
169
161
 
170
162
  // src/cubing/twisty/views/3D/puzzles/CubieStyle.ts
171
- import { AmbientLight } from "three/src/lights/AmbientLight.js";
172
- import { DirectionalLight } from "three/src/lights/DirectionalLight.js";
173
- import { MeshPhongMaterial } from "three/src/materials/MeshPhongMaterial.js";
163
+ import { MeshBasicMaterial } from "three/src/materials/MeshBasicMaterial.js";
174
164
  import { Color } from "three/src/math/Color.js";
175
165
  import { Euler } from "three/src/math/Euler.js";
176
166
  import { Vector3 as Vector32 } from "three/src/math/Vector3.js";
@@ -268,18 +258,14 @@ var cubieBodyDimensions = {
268
258
  * a thin line of the puzzle's interior showing between them.
269
259
  */
270
260
  pieceScale: 0.95,
271
- roundingSegments: 7
261
+ roundingSegments: 3
272
262
  };
273
263
  function cubieBodyHalfExtent(layers) {
274
264
  return layers / 2 - 0.5 + cubieBodyDimensions.halfWidth * cubieBodyDimensions.pieceScale;
275
265
  }
276
- var BODY_SHININESS = 30;
277
- var BODY_SPECULAR = 657930;
278
266
  function newBodyMaterial(color) {
279
- return new MeshPhongMaterial({
280
- color: typeof color === "number" ? new Color(color).convertLinearToSRGB() : color,
281
- shininess: BODY_SHININESS,
282
- specular: new Color(BODY_SPECULAR).convertLinearToSRGB()
267
+ return new MeshBasicMaterial({
268
+ color: typeof color === "number" ? new Color(color).convertLinearToSRGB() : color
283
269
  });
284
270
  }
285
271
  function newVertexColorBodyMaterial() {
@@ -301,51 +287,241 @@ var hintMaskStyles = {
301
287
  experimentalOriented2: { color: 16775545, opacity: 0.5 },
302
288
  mystery: { color: 15911883, opacity: 0.5 }
303
289
  };
304
- var AMBIENT_LIGHT_INTENSITY = 2.04;
305
- var KEY_LIGHT_INTENSITY = 1.55;
306
- var FILL_LIGHT_INTENSITY = 0.3;
307
- var RIM_LIGHT_INTENSITY = 0.3;
308
- function addCubieBodyLighting(target) {
309
- target.add(new AmbientLight(16777215, AMBIENT_LIGHT_INTENSITY));
310
- const keyLight = new DirectionalLight(16777215, KEY_LIGHT_INTENSITY);
311
- keyLight.position.set(3, 5, 4);
312
- target.add(keyLight);
313
- const fillLight = new DirectionalLight(16777215, FILL_LIGHT_INTENSITY);
314
- fillLight.position.set(-5, 2, 3);
315
- target.add(fillLight);
316
- const rimLight = new DirectionalLight(16777215, RIM_LIGHT_INTENSITY);
317
- rimLight.position.set(-3, -2, -4);
318
- target.add(rimLight);
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;
319
495
  }
320
496
 
321
497
  // src/cubing/twisty/views/3D/puzzles/Cube3D.ts
322
498
  var svgLoader = new TextureLoader();
323
499
  function newHintMaterial(style) {
324
- return new MeshBasicMaterial({
500
+ return new MeshBasicMaterial3({
325
501
  color: new Color2(style.color).convertLinearToSRGB(),
326
502
  side: BackSide,
327
503
  transparent: true,
328
504
  opacity: style.opacity
329
505
  });
330
506
  }
331
- var ignoredMaterial = new MeshBasicMaterial({
507
+ var ignoredMaterial = new MeshBasicMaterial3({
332
508
  color: new Color2(bodyMaskColors.ignored).convertLinearToSRGB()
333
509
  });
334
510
  var ignoredMaterialHint = newHintMaterial(hintMaskStyles.ignored);
335
- var invisibleMaterial = new MeshBasicMaterial({
511
+ var invisibleMaterial = new MeshBasicMaterial3({
336
512
  visible: false
337
513
  });
338
- var orientedMaterial = new MeshBasicMaterial({
514
+ var orientedMaterial = new MeshBasicMaterial3({
339
515
  color: bodyMaskColors.oriented
340
516
  });
341
517
  var orientedMaterialHint = newHintMaterial(hintMaskStyles.oriented);
342
- var experimentalOriented2Material = new MeshBasicMaterial({
518
+ var experimentalOriented2Material = new MeshBasicMaterial3({
343
519
  color: bodyMaskColors.experimentalOriented2
344
520
  });
345
521
  var experimentalOriented2MaterialHint = newHintMaterial(
346
522
  hintMaskStyles.experimentalOriented2
347
523
  );
348
- var mysteryMaterial = new MeshBasicMaterial({
524
+ var mysteryMaterial = new MeshBasicMaterial3({
349
525
  color: bodyMaskColors.mystery
350
526
  });
351
527
  var mysterMaterialHint = newHintMaterial(hintMaskStyles.mystery);
@@ -369,14 +545,14 @@ var AxisInfo = class {
369
545
  const colorLinearSRGB = new Color2(color).convertLinearToSRGB();
370
546
  const dimColorLinearSRGB = new Color2(dimColor).convertLinearToSRGB();
371
547
  this.stickerMaterial = {
372
- regular: new MeshBasicMaterial({
548
+ regular: new MeshBasicMaterial3({
373
549
  color: colorLinearSRGB,
374
- side: FrontSide
550
+ side: FrontSide2
375
551
  // TODO: set to `DoubleSide` when hint facelets are disabled.
376
552
  }),
377
- dim: new MeshBasicMaterial({
553
+ dim: new MeshBasicMaterial3({
378
554
  color: dimColorLinearSRGB,
379
- side: FrontSide
555
+ side: FrontSide2
380
556
  // TODO: set to `DoubleSide` when hint facelets are disabled.
381
557
  }),
382
558
  oriented: orientedMaterial,
@@ -467,6 +643,8 @@ var cubieDimensions = {
467
643
  */
468
644
  bodyVertexCut: 0.45
469
645
  };
646
+ var LOGO_PIECE = { orbit: "CENTERS", ord: 0, faceletIdx: 0 };
647
+ var LOGO_ELEVATION = 0.01;
470
648
  var EXPERIMENTAL_PICTURE_CUBE_HINT_ELEVATION = 2;
471
649
  var cube3DOptionsDefaults = {
472
650
  showMainStickers: true,
@@ -487,12 +665,12 @@ function getFaceletScale(options) {
487
665
  }
488
666
  return options.faceletScale;
489
667
  }
490
- var blackMesh = new MeshBasicMaterial({
668
+ var blackMesh = new MeshBasicMaterial3({
491
669
  color: 0,
492
670
  opacity: 1,
493
671
  transparent: true
494
672
  });
495
- var blackTranslucentMesh = new MeshBasicMaterial({
673
+ var blackTranslucentMesh = new MeshBasicMaterial3({
496
674
  color: 0,
497
675
  opacity: 0.3,
498
676
  transparent: true
@@ -515,18 +693,18 @@ function t(v, t4) {
515
693
  return new Quaternion().setFromAxisAngle(v, TAU * t4 / 4);
516
694
  }
517
695
  var r = {
518
- O: new Vector33(0, 0, 0),
519
- U: new Vector33(0, -1, 0),
520
- L: new Vector33(1, 0, 0),
521
- F: new Vector33(0, 0, -1),
522
- R: new Vector33(-1, 0, 0),
523
- B: new Vector33(0, 0, 1),
524
- 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)
525
703
  };
526
704
  var firstPiecePosition = {
527
- EDGES: new Vector33(0, 1, 1),
528
- CORNERS: new Vector33(1, 1, 1),
529
- 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)
530
708
  };
531
709
  var orientationRotation = {
532
710
  EDGES: [0, 1].map(
@@ -698,11 +876,11 @@ function sharedCubieFoundationGeometry() {
698
876
  ));
699
877
  }
700
878
  function newStickerGeometry() {
701
- const r2 = new BufferGeometry2();
879
+ const r2 = new BufferGeometry3();
702
880
  const half = 0.5;
703
881
  r2.setAttribute(
704
882
  "position",
705
- new BufferAttribute2(
883
+ new BufferAttribute3(
706
884
  new Float32Array([
707
885
  half,
708
886
  half,
@@ -728,7 +906,7 @@ function newStickerGeometry() {
728
906
  );
729
907
  r2.setAttribute(
730
908
  "uv",
731
- new BufferAttribute2(
909
+ new BufferAttribute3(
732
910
  new Float32Array([
733
911
  1,
734
912
  1,
@@ -810,9 +988,6 @@ var Cube3D = class extends Object3D {
810
988
  this.createCubie.bind(this, orbit, orbitFaceletInfo)
811
989
  );
812
990
  }
813
- if (this.#stickerless()) {
814
- addCubieBodyLighting(this);
815
- }
816
991
  const scale = cubeScale(this.#stickerless());
817
992
  this.scale.set(scale, scale, scale);
818
993
  if (this.options.experimentalStickeringMask) {
@@ -1010,7 +1185,9 @@ var Cube3D = class extends Object3D {
1010
1185
  this.setHintFaceletsElevation(hintFaceletsElevation);
1011
1186
  }
1012
1187
  }
1188
+ #lastPosition = null;
1013
1189
  onPositionChange(p) {
1190
+ this.#lastPosition = p;
1014
1191
  const reid333 = p.pattern;
1015
1192
  for (const orbit in pieceDefs) {
1016
1193
  const pieces = pieceDefs[orbit];
@@ -1040,6 +1217,7 @@ var Cube3D = class extends Object3D {
1040
1217
  }
1041
1218
  }
1042
1219
  }
1220
+ this.#placeLogo(p);
1043
1221
  this.scheduleRenderCallback();
1044
1222
  }
1045
1223
  // TODO: Always create (but sometimes hide parts) so we can show them later,
@@ -1048,7 +1226,7 @@ var Cube3D = class extends Object3D {
1048
1226
  const cubieFaceletInfo = [];
1049
1227
  orbitFacelets.push(cubieFaceletInfo);
1050
1228
  const cubie = new Group();
1051
- const body = this.#stickerless() ? new Mesh(
1229
+ const body = this.#stickerless() ? new Mesh2(
1052
1230
  cubieBodyGeometry(
1053
1231
  orbit,
1054
1232
  cubieStickerOrder.slice(0, piece.stickerFaces.length)
@@ -1097,7 +1275,7 @@ var Cube3D = class extends Object3D {
1097
1275
  axesInfo[piece.stickerFaces[i]],
1098
1276
  hint
1099
1277
  );
1100
- mesh.material = new MeshBasicMaterial({
1278
+ mesh.material = new MeshBasicMaterial3({
1101
1279
  map: texture,
1102
1280
  side: hint ? BackSide : DoubleSide,
1103
1281
  transparent: true
@@ -1126,7 +1304,7 @@ var Cube3D = class extends Object3D {
1126
1304
  }
1127
1305
  mesh.geometry.setAttribute(
1128
1306
  "uv",
1129
- new BufferAttribute2(
1307
+ new BufferAttribute3(
1130
1308
  new Float32Array([
1131
1309
  v3.x,
1132
1310
  v3.y,
@@ -1160,14 +1338,14 @@ var Cube3D = class extends Object3D {
1160
1338
  // TODO: Support creating only the outward-facing parts?
1161
1339
  createCubieFoundation() {
1162
1340
  const box = sharedCubieFoundationGeometry();
1163
- return new Mesh(
1341
+ return new Mesh2(
1164
1342
  box,
1165
1343
  this.options.experimentalStickeringMask?.specialBehaviour === "picture" ? blackMesh : blackTranslucentMesh
1166
1344
  );
1167
1345
  }
1168
1346
  createSticker(posAxisInfo, materialAxisInfo, isHint) {
1169
1347
  const geo = this.options.experimentalStickeringMask?.specialBehaviour === "picture" ? newStickerGeometry() : isHint ? this.#sharedHintStickerGeometry() : sharedStickerGeometry();
1170
- const stickerMesh = new Mesh(
1348
+ const stickerMesh = new Mesh2(
1171
1349
  geo,
1172
1350
  isHint ? materialAxisInfo.hintStickerMaterial.regular : materialAxisInfo.stickerMaterial.regular
1173
1351
  );
@@ -1194,6 +1372,82 @@ var Cube3D = class extends Object3D {
1194
1372
  stickerMesh.translateZ(elevation - 1);
1195
1373
  return stickerMesh;
1196
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
+ }
1197
1451
  /** @deprecated */
1198
1452
  experimentalSetFoundationOpacity(opacity) {
1199
1453
  if (this.experimentalFoundationMeshes.length === 0) {
@@ -1238,16 +1492,14 @@ var Cube3D = class extends Object3D {
1238
1492
  };
1239
1493
 
1240
1494
  // src/cubing/twisty/views/3D/puzzles/CubePieces.ts
1241
- import { BufferAttribute as BufferAttribute3 } from "three/src/core/BufferAttribute.js";
1242
- 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";
1243
1497
  import { Matrix4 as Matrix42 } from "three/src/math/Matrix4.js";
1244
- import { Vector3 as Vector34 } from "three/src/math/Vector3.js";
1498
+ import { Vector3 as Vector35 } from "three/src/math/Vector3.js";
1245
1499
 
1246
1500
  // src/cubing/twisty/views/3D/puzzles/StickerlessPlan.ts
1247
1501
  var HINT_FACELET_ELEVATION = 0.5;
1248
1502
  var HINT_FACELET_SCALE = 0.85;
1249
- var BODY_MATERIAL_INDEX = 0;
1250
- var HINT_MATERIAL_INDEX = 1;
1251
1503
  var faceletMeshStickeringMasks = [
1252
1504
  "regular",
1253
1505
  "dim",
@@ -1343,6 +1595,7 @@ function paintInternal(colors, ranges) {
1343
1595
  // src/cubing/twisty/views/3D/puzzles/CubePieces.ts
1344
1596
  var PG_SCALE = 0.5;
1345
1597
  var FACE_COUNT = 6;
1598
+ var LOGO_ELEVATION2 = 0.01;
1346
1599
  function faceIdxForNormal(x, y, z) {
1347
1600
  for (let faceIdx = 0; faceIdx < FACE_COUNT; faceIdx++) {
1348
1601
  const { vector } = cubeFaceStyles[faceIdx];
@@ -1379,20 +1632,20 @@ function cubeLayout(stickerDat) {
1379
1632
  const faceColors = new Array(FACE_COUNT);
1380
1633
  const cubiesByPiece = /* @__PURE__ */ new Map();
1381
1634
  const cubies = [];
1382
- const centroid = [0, 0, 0];
1635
+ const centroid2 = [0, 0, 0];
1383
1636
  for (const sticker of stickers) {
1384
1637
  const { coords } = sticker;
1385
1638
  for (let axis = 0; axis < 3; axis++) {
1386
- 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;
1387
1640
  }
1388
1641
  let normalAxis = 0;
1389
1642
  for (let axis = 1; axis < 3; axis++) {
1390
- if (Math.abs(centroid[axis]) > Math.abs(centroid[normalAxis])) {
1643
+ if (Math.abs(centroid2[axis]) > Math.abs(centroid2[normalAxis])) {
1391
1644
  normalAxis = axis;
1392
1645
  }
1393
1646
  }
1394
- const outward = Math.sign(centroid[normalAxis]);
1395
- const center = new Vector34();
1647
+ const outward = Math.sign(centroid2[normalAxis]);
1648
+ const center = new Vector35();
1396
1649
  for (let axis = 0; axis < 3; axis++) {
1397
1650
  let expected;
1398
1651
  if (axis === normalAxis) {
@@ -1400,7 +1653,7 @@ function cubeLayout(stickerDat) {
1400
1653
  center.setComponent(axis, outward * (halfExtent - slotWidth / 2));
1401
1654
  } else {
1402
1655
  const slot = Math.round(
1403
- (centroid[axis] + halfExtent) / slotWidth - 0.5
1656
+ (centroid2[axis] + halfExtent) / slotWidth - 0.5
1404
1657
  );
1405
1658
  if (slot < 0 || slot >= layers) {
1406
1659
  return null;
@@ -1408,7 +1661,7 @@ function cubeLayout(stickerDat) {
1408
1661
  expected = -halfExtent + (slot + 0.5) * slotWidth;
1409
1662
  center.setComponent(axis, expected);
1410
1663
  }
1411
- if (Math.abs(centroid[axis] - expected) > epsilon) {
1664
+ if (Math.abs(centroid2[axis] - expected) > epsilon) {
1412
1665
  return null;
1413
1666
  }
1414
1667
  const halfSpan = axis === normalAxis ? 0 : slotWidth / 2;
@@ -1470,7 +1723,6 @@ function newCubieShape(outwardFaces, slotWidth) {
1470
1723
  cubieBodyDimensions.roundingSegments
1471
1724
  );
1472
1725
  const bodyPositions = body.getAttribute("position").array;
1473
- const bodyNormals = body.getAttribute("normal").array;
1474
1726
  const bodyIndices = Array.from(body.getIndex().array);
1475
1727
  const faceVertexRanges = [];
1476
1728
  for (const group of body.groups) {
@@ -1482,20 +1734,29 @@ function newCubieShape(outwardFaces, slotWidth) {
1482
1734
  }
1483
1735
  faceVertexRanges[group.materialIndex] = { start, count: end - start };
1484
1736
  }
1485
- const positions = Array.from(bodyPositions);
1486
- const normals = Array.from(bodyNormals);
1487
- const indices = bodyIndices.slice();
1737
+ const hintPositions = [];
1738
+ const hintIndices = [];
1488
1739
  const hintVertexRanges = /* @__PURE__ */ new Map();
1489
1740
  const hintHalfWidth = HINT_FACELET_SCALE * slotWidth / 2;
1490
1741
  const hintDistance = slotWidth / 2 + HINT_FACELET_ELEVATION;
1491
- const u = new Vector34();
1492
- const v = new Vector34();
1493
- 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();
1494
1747
  for (const faceIdx of outwardFaces) {
1495
1748
  const normal2 = cubeFaceStyles[faceIdx].vector;
1496
1749
  u.set(normal2.y, normal2.z, normal2.x);
1497
1750
  v.crossVectors(normal2, u);
1498
- const start = positions.length / 3;
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
+ );
1759
+ const start = hintPositions.length / 3;
1499
1760
  for (const [su, sv] of [
1500
1761
  [-1, -1],
1501
1762
  [1, -1],
@@ -1503,58 +1764,57 @@ function newCubieShape(outwardFaces, slotWidth) {
1503
1764
  [-1, 1]
1504
1765
  ]) {
1505
1766
  corner.copy(normal2).multiplyScalar(hintDistance).addScaledVector(u, su * hintHalfWidth).addScaledVector(v, sv * hintHalfWidth);
1506
- positions.push(corner.x, corner.y, corner.z);
1507
- normals.push(normal2.x, normal2.y, normal2.z);
1767
+ hintPositions.push(corner.x, corner.y, corner.z);
1508
1768
  }
1509
- indices.push(start, start + 1, start + 2, start, start + 2, start + 3);
1769
+ hintIndices.push(start, start + 1, start + 2, start, start + 2, start + 3);
1510
1770
  hintVertexRanges.set(faceIdx, { start, count: 4 });
1511
1771
  }
1512
1772
  body.dispose();
1513
1773
  return {
1514
- position: new BufferAttribute3(new Float32Array(positions), 3),
1515
- normal: new BufferAttribute3(new Float32Array(normals), 3),
1516
- index: new BufferAttribute3(new Uint16Array(indices), 1),
1517
- vertexCount: positions.length / 3,
1518
- bodyIndexCount: bodyIndices.length,
1519
- hintIndexCount: indices.length - bodyIndices.length,
1774
+ bodyPosition: new BufferAttribute4(bodyPositions.slice(), 3),
1775
+ bodyIndex: new BufferAttribute4(new Uint16Array(bodyIndices), 1),
1776
+ bodyVertexCount: bodyPositions.length / 3,
1777
+ hintPosition: new BufferAttribute4(new Float32Array(hintPositions), 3),
1778
+ hintIndex: new BufferAttribute4(new Uint16Array(hintIndices), 1),
1779
+ hintVertexCount: hintPositions.length / 3,
1520
1780
  faceVertexRanges,
1521
- hintVertexRanges
1781
+ hintVertexRanges,
1782
+ logoSurfaces
1522
1783
  };
1523
1784
  }
1785
+ function newPieceMesh(position, index, vertexCount) {
1786
+ const geometry = new BufferGeometry4();
1787
+ geometry.setAttribute("position", position);
1788
+ geometry.setIndex(index);
1789
+ const colors = new BufferAttribute4(new Uint8Array(4 * vertexCount), 4, true);
1790
+ geometry.setAttribute("color", colors);
1791
+ return { geometry, colors };
1792
+ }
1524
1793
  function newCubiePiece(cubie, shape) {
1525
- const geometry = new BufferGeometry3();
1526
- geometry.setAttribute("position", shape.position);
1527
- geometry.setAttribute("normal", shape.normal);
1528
- geometry.setIndex(shape.index);
1529
- const colors = new BufferAttribute3(
1530
- new Uint8Array(4 * shape.vertexCount),
1531
- 4,
1532
- true
1794
+ const body = newPieceMesh(
1795
+ shape.bodyPosition,
1796
+ shape.bodyIndex,
1797
+ shape.bodyVertexCount
1533
1798
  );
1534
- geometry.setAttribute("color", colors);
1535
- geometry.addGroup(0, shape.bodyIndexCount, BODY_MATERIAL_INDEX);
1536
- if (shape.hintIndexCount > 0) {
1537
- geometry.addGroup(
1538
- shape.bodyIndexCount,
1539
- shape.hintIndexCount,
1540
- HINT_MATERIAL_INDEX
1541
- );
1542
- }
1543
- paintInternal(colors, shape.faceVertexRanges);
1799
+ const hint = shape.hintVertexCount > 0 ? newPieceMesh(shape.hintPosition, shape.hintIndex, shape.hintVertexCount) : null;
1800
+ paintInternal(body.colors, shape.faceVertexRanges);
1544
1801
  const facelets = cubie.stickers.map((sticker) => ({
1545
1802
  ori: sticker.ori,
1546
1803
  faceStyle: sticker.faceIdx,
1547
1804
  // A duplicate shares its square with the real facelet, which is the one
1548
1805
  // that gets painted.
1549
1806
  body: sticker.isDup ? [] : [shape.faceVertexRanges[sticker.faceIdx]],
1550
- 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
1551
1811
  }));
1552
1812
  return {
1553
1813
  orbit: cubie.orbit,
1554
1814
  ord: cubie.ord,
1555
1815
  home: new Matrix42().setPosition(cubie.center),
1556
- geometry,
1557
- colors,
1816
+ body,
1817
+ hint,
1558
1818
  facelets
1559
1819
  };
1560
1820
  }
@@ -1584,23 +1844,23 @@ function cubePuzzlePlan(stickerDat) {
1584
1844
  }
1585
1845
 
1586
1846
  // src/cubing/twisty/views/3D/puzzles/PG3D.ts
1587
- import { DoubleSide as DoubleSide2, FrontSide as FrontSide2 } from "three/src/constants.js";
1588
- import { BufferAttribute as BufferAttribute4 } from "three/src/core/BufferAttribute.js";
1589
- 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";
1590
1850
  import { Object3D as Object3D2 } from "three/src/core/Object3D.js";
1591
- import { MeshBasicMaterial as MeshBasicMaterial2 } from "three/src/materials/MeshBasicMaterial.js";
1851
+ import { MeshBasicMaterial as MeshBasicMaterial4 } from "three/src/materials/MeshBasicMaterial.js";
1592
1852
  import { Color as Color3 } from "three/src/math/Color.js";
1593
- import { Vector3 as Vector35 } from "three/src/math/Vector3.js";
1853
+ import { Vector3 as Vector36 } from "three/src/math/Vector3.js";
1594
1854
  import { Group as Group2 } from "three/src/objects/Group.js";
1595
- import { Mesh as Mesh2 } from "three/src/objects/Mesh.js";
1596
- var foundationMaterial = new MeshBasicMaterial2({
1855
+ import { Mesh as Mesh3 } from "three/src/objects/Mesh.js";
1856
+ var foundationMaterial = new MeshBasicMaterial4({
1597
1857
  side: DoubleSide2,
1598
1858
  color: 0
1599
1859
  });
1600
- var invisMaterial = new MeshBasicMaterial2({
1860
+ var invisMaterial = new MeshBasicMaterial4({
1601
1861
  visible: false
1602
1862
  });
1603
- var basicStickerMaterial = new MeshBasicMaterial2({
1863
+ var basicStickerMaterial = new MeshBasicMaterial4({
1604
1864
  vertexColors: true
1605
1865
  });
1606
1866
  function dist(coords, a, b) {
@@ -1735,9 +1995,9 @@ var Filler = class {
1735
1995
  }
1736
1996
  }
1737
1997
  setAttributes(geo) {
1738
- geo.setAttribute("position", new BufferAttribute4(this.vertices, 3));
1998
+ geo.setAttribute("position", new BufferAttribute5(this.vertices, 3));
1739
1999
  const sa2 = this.colors.subarray(0, 9 * this.sz);
1740
- geo.setAttribute("color", new BufferAttribute4(sa2, 3, true));
2000
+ geo.setAttribute("color", new BufferAttribute5(sa2, 3, true));
1741
2001
  }
1742
2002
  makeGroups(geo) {
1743
2003
  geo.clearGroups();
@@ -1963,9 +2223,9 @@ var HitPlaneDef = class {
1963
2223
  filler.addUncolored(coords, g);
1964
2224
  filler.addUncolored(coords, g + 1);
1965
2225
  }
1966
- this.geo = new BufferGeometry4();
2226
+ this.geo = new BufferGeometry5();
1967
2227
  filler.setAttributes(this.geo);
1968
- const obj = new Mesh2(this.geo, invisMaterial);
2228
+ const obj = new Mesh3(this.geo, invisMaterial);
1969
2229
  obj.userData["quantumMove"] = stickerDat.notationMapper.notationToExternal(
1970
2230
  new Move(hitface.name)
1971
2231
  );
@@ -1978,7 +2238,7 @@ var AxisInfo2 = class {
1978
2238
  order;
1979
2239
  constructor(axisDat) {
1980
2240
  const vec = axisDat.coordinates;
1981
- this.axis = new Vector35(vec[0], vec[1], vec[2]);
2241
+ this.axis = new Vector36(vec[0], vec[1], vec[2]);
1982
2242
  this.order = axisDat.order;
1983
2243
  }
1984
2244
  };
@@ -1995,7 +2255,7 @@ var PG3D = class extends Object3D2 {
1995
2255
  if (stickerDat.stickers.length === 0) {
1996
2256
  throw Error("Reuse of stickerdat from pg; please don't do that.");
1997
2257
  }
1998
- this.hintMaterial = new MeshBasicMaterial2({
2258
+ this.hintMaterial = new MeshBasicMaterial4({
1999
2259
  vertexColors: true,
2000
2260
  transparent: true,
2001
2261
  opacity: 0.5
@@ -2083,13 +2343,13 @@ var PG3D = class extends Object3D2 {
2083
2343
  this.stickers[orbit][ori][ord].addFoundation(filler, sticker, black);
2084
2344
  }
2085
2345
  }
2086
- const fixedGeo = new BufferGeometry4();
2346
+ const fixedGeo = new BufferGeometry5();
2087
2347
  filler.setAttributes(fixedGeo);
2088
2348
  filler.makeGroups(fixedGeo);
2089
- const obj = new Mesh2(fixedGeo, this.materialArray1);
2349
+ const obj = new Mesh3(fixedGeo, this.materialArray1);
2090
2350
  obj.scale.set(PG_SCALE2, PG_SCALE2, PG_SCALE2);
2091
2351
  this.add(obj);
2092
- const obj2 = new Mesh2(fixedGeo, this.materialArray2);
2352
+ const obj2 = new Mesh3(fixedGeo, this.materialArray2);
2093
2353
  obj2.scale.set(PG_SCALE2, PG_SCALE2, PG_SCALE2);
2094
2354
  this.add(obj2);
2095
2355
  const hitfaces = this.stickerDat.faces;
@@ -2180,7 +2440,7 @@ var PG3D = class extends Object3D2 {
2180
2440
  }
2181
2441
  }
2182
2442
  for (const axis of this.stickerDat.axis) {
2183
- const product = point.dot(new Vector35(...axis.coordinates));
2443
+ const product = point.dot(new Vector36(...axis.coordinates));
2184
2444
  if (product > closestMoveDotProduct) {
2185
2445
  const modified = this.stickerDat.notationMapper.notationToExternal(
2186
2446
  modify(axis.quantumMove)
@@ -2414,7 +2674,7 @@ var PG3D = class extends Object3D2 {
2414
2674
  } else if (v === 1) {
2415
2675
  this.hintMaterial = this.stickerMaterial;
2416
2676
  } else {
2417
- this.hintMaterial = new MeshBasicMaterial2({
2677
+ this.hintMaterial = new MeshBasicMaterial4({
2418
2678
  vertexColors: true,
2419
2679
  transparent: true,
2420
2680
  opacity: v
@@ -2462,7 +2722,7 @@ var PG3D = class extends Object3D2 {
2462
2722
  }
2463
2723
  filler.uvs.copyWithin(6 * filler.sz, 0, 6 * filler.sz);
2464
2724
  const sa1 = filler.uvs.subarray(0, 6 * filler.sz);
2465
- this.fixedGeo.setAttribute("uv", new BufferAttribute4(sa1, 2, true));
2725
+ this.fixedGeo.setAttribute("uv", new BufferAttribute5(sa1, 2, true));
2466
2726
  }
2467
2727
  experimentalUpdateTexture(enabled, stickerTexture, hintTexture) {
2468
2728
  if (!stickerTexture) {
@@ -2477,9 +2737,9 @@ var PG3D = class extends Object3D2 {
2477
2737
  this.stickerMaterialDisposable = false;
2478
2738
  }
2479
2739
  if (enabled) {
2480
- this.stickerMaterial = new MeshBasicMaterial2({
2740
+ this.stickerMaterial = new MeshBasicMaterial4({
2481
2741
  map: stickerTexture,
2482
- side: FrontSide2,
2742
+ side: FrontSide3,
2483
2743
  transparent: false
2484
2744
  });
2485
2745
  this.stickerMaterialDisposable = true;
@@ -2491,9 +2751,9 @@ var PG3D = class extends Object3D2 {
2491
2751
  this.hintMaterialDisposable = false;
2492
2752
  }
2493
2753
  if (enabled) {
2494
- this.hintMaterial = new MeshBasicMaterial2({
2754
+ this.hintMaterial = new MeshBasicMaterial4({
2495
2755
  map: hintTexture,
2496
- side: FrontSide2,
2756
+ side: FrontSide3,
2497
2757
  transparent: true
2498
2758
  });
2499
2759
  this.hintMaterialDisposable = true;
@@ -2513,21 +2773,21 @@ var PG3D = class extends Object3D2 {
2513
2773
  };
2514
2774
 
2515
2775
  // src/cubing/twisty/views/3D/puzzles/SolidPieces.ts
2516
- import { BufferAttribute as BufferAttribute5 } from "three/src/core/BufferAttribute.js";
2517
- 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";
2518
2778
  import { Color as Color4 } from "three/src/math/Color.js";
2519
2779
  import { Matrix4 as Matrix43 } from "three/src/math/Matrix4.js";
2520
- import { Vector3 as Vector37 } from "three/src/math/Vector3.js";
2780
+ import { Vector3 as Vector38 } from "three/src/math/Vector3.js";
2521
2781
 
2522
2782
  // src/cubing/twisty/views/3D/puzzles/SolidPieceGeometry.ts
2523
- import { Vector3 as Vector36 } from "three/src/math/Vector3.js";
2783
+ import { Vector3 as Vector37 } from "three/src/math/Vector3.js";
2524
2784
  var SOLID_PIECE_GROOVE = 0.018;
2525
2785
  var SOLID_PIECE_CHAMFER = 5e-3;
2526
2786
  var BOX_PLANE = -1;
2527
2787
  function boxFacet(planeIdx, normal2, size) {
2528
- const reference = Math.abs(normal2.y) > 0.5 ? new Vector36(0, 0, 1) : new Vector36(0, 1, 0);
2529
- const u = new Vector36().crossVectors(reference, normal2).normalize();
2530
- 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);
2531
2791
  const center = normal2.clone().multiplyScalar(size);
2532
2792
  return {
2533
2793
  plane: planeIdx,
@@ -2542,23 +2802,23 @@ function boxFacet(planeIdx, normal2, size) {
2542
2802
  };
2543
2803
  }
2544
2804
  var BOX_NORMALS = [
2545
- new Vector36(1, 0, 0),
2546
- new Vector36(-1, 0, 0),
2547
- new Vector36(0, 1, 0),
2548
- new Vector36(0, -1, 0),
2549
- new Vector36(0, 0, 1),
2550
- 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)
2551
2811
  ];
2552
2812
  function orderInPlane(points, normal2) {
2553
- const centroid = new Vector36();
2813
+ const centroid2 = new Vector37();
2554
2814
  for (const point of points) {
2555
- centroid.addScaledVector(point, 1 / points.length);
2815
+ centroid2.addScaledVector(point, 1 / points.length);
2556
2816
  }
2557
- const reference = new Vector36().subVectors(points[0], centroid).normalize();
2558
- const other = new Vector36().crossVectors(normal2, reference);
2559
- 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();
2560
2820
  return points.map((point) => {
2561
- offset.subVectors(point, centroid);
2821
+ offset.subVectors(point, centroid2);
2562
2822
  return {
2563
2823
  point,
2564
2824
  angle: Math.atan2(offset.dot(other), offset.dot(reference))
@@ -2660,7 +2920,7 @@ function chamferPlanes(facets, planes, chamfer, epsilon) {
2660
2920
  continue;
2661
2921
  }
2662
2922
  seen.add(pairKey);
2663
- const sum = new Vector36().addVectors(
2923
+ const sum = new Vector37().addVectors(
2664
2924
  planes[colored].normal,
2665
2925
  planes[internal].normal
2666
2926
  );
@@ -2690,11 +2950,9 @@ function solidPieceGeometry(planes, options) {
2690
2950
  const withChamfers = options.chamfer > 0 ? working.concat(chamferPlanes(plain, working, options.chamfer, epsilon)) : working;
2691
2951
  const facets = (options.chamfer > 0 ? buildFacets(withChamfers, options.radius, epsilon) : plain) ?? plain;
2692
2952
  const positions = [];
2693
- const normals = [];
2694
2953
  const ranges = planes.map(() => []);
2695
2954
  const internalRanges = [];
2696
2955
  for (const facet of facets) {
2697
- const plane = withChamfers[facet.plane];
2698
2956
  const start = positions.length / 3;
2699
2957
  for (let i = 1; i < facet.vertices.length - 1; i++) {
2700
2958
  for (const vertex of [
@@ -2703,22 +2961,20 @@ function solidPieceGeometry(planes, options) {
2703
2961
  facet.vertices[i + 1]
2704
2962
  ]) {
2705
2963
  positions.push(vertex.x, vertex.y, vertex.z);
2706
- normals.push(plane.normal.x, plane.normal.y, plane.normal.z);
2707
2964
  }
2708
2965
  }
2709
2966
  const range = { start, count: positions.length / 3 - start };
2710
2967
  if (range.count === 0) {
2711
2968
  continue;
2712
2969
  }
2713
- if (plane.source === -1) {
2970
+ if (withChamfers[facet.plane].source === -1) {
2714
2971
  internalRanges.push(range);
2715
2972
  } else {
2716
- ranges[plane.source].push(range);
2973
+ ranges[withChamfers[facet.plane].source].push(range);
2717
2974
  }
2718
2975
  }
2719
2976
  return {
2720
2977
  positions: new Float32Array(positions),
2721
- normals: new Float32Array(normals),
2722
2978
  vertexCount: positions.length / 3,
2723
2979
  ranges,
2724
2980
  internalRanges
@@ -2727,9 +2983,10 @@ function solidPieceGeometry(planes, options) {
2727
2983
 
2728
2984
  // src/cubing/twisty/views/3D/puzzles/SolidPieces.ts
2729
2985
  var PG_SCALE3 = 0.5;
2730
- var EPSILON = 1e-6;
2986
+ var EPSILON2 = 1e-6;
2987
+ var LOGO_ELEVATION3 = 0.01;
2731
2988
  function vertexAt(sticker, index) {
2732
- return new Vector37(
2989
+ return new Vector38(
2733
2990
  sticker.coords[3 * index],
2734
2991
  sticker.coords[3 * index + 1],
2735
2992
  sticker.coords[3 * index + 2]
@@ -2743,11 +3000,11 @@ function polygonOf(sticker) {
2743
3000
  return vertices;
2744
3001
  }
2745
3002
  function outwardNormal(polygon) {
2746
- const normal2 = new Vector37().crossVectors(
2747
- new Vector37().subVectors(polygon[1], polygon[0]),
2748
- 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])
2749
3006
  );
2750
- if (normal2.length() < EPSILON) {
3007
+ if (normal2.length() < EPSILON2) {
2751
3008
  return null;
2752
3009
  }
2753
3010
  normal2.normalize();
@@ -2788,13 +3045,13 @@ function faceStyles(faceNormals, faceColors) {
2788
3045
  return styles;
2789
3046
  }
2790
3047
  function hintPolygon(polygon, normal2) {
2791
- const centroid = new Vector37();
3048
+ const centroid2 = new Vector38();
2792
3049
  for (const vertex of polygon) {
2793
- centroid.addScaledVector(vertex, 1 / polygon.length);
3050
+ centroid2.addScaledVector(vertex, 1 / polygon.length);
2794
3051
  }
2795
3052
  return polygon.map(
2796
- (vertex) => centroid.clone().addScaledVector(
2797
- new Vector37().subVectors(vertex, centroid),
3053
+ (vertex) => centroid2.clone().addScaledVector(
3054
+ new Vector38().subVectors(vertex, centroid2),
2798
3055
  HINT_FACELET_SCALE
2799
3056
  ).addScaledVector(normal2, HINT_FACELET_ELEVATION)
2800
3057
  );
@@ -2805,6 +3062,8 @@ function solidPuzzlePlan(stickerDat) {
2805
3062
  return null;
2806
3063
  }
2807
3064
  let radius = 0;
3065
+ let faceletArea = 0;
3066
+ let faceletCount = 0;
2808
3067
  for (const sticker of stickers) {
2809
3068
  if (sticker.coords.length < 9 || sticker.coords.length % 3 !== 0) {
2810
3069
  return null;
@@ -2867,6 +3126,10 @@ function solidPuzzlePlan(stickerDat) {
2867
3126
  if (faceColors[sticker.face] !== sticker.color) {
2868
3127
  return null;
2869
3128
  }
3129
+ faceletCount++;
3130
+ for (let i = 1; i < polygon.length - 1; i++) {
3131
+ faceletArea += new Vector38().subVectors(polygon[i], polygon[0]).cross(new Vector38().subVectors(polygon[i + 1], polygon[0])).length() / 2;
3132
+ }
2870
3133
  piece.stickers.push({ sticker, polygon });
2871
3134
  for (let i = 0; i < polygon.length; i++) {
2872
3135
  const edgeKey = [
@@ -2885,16 +3148,30 @@ function solidPuzzlePlan(stickerDat) {
2885
3148
  if (!styles) {
2886
3149
  return null;
2887
3150
  }
2888
- const axes = stickerDat.axis.map(
2889
- (axis) => new Vector37(...axis.coordinates).normalize()
3151
+ const featureScale = Math.min(
3152
+ faceDistance,
3153
+ 1.5 * Math.sqrt(faceletArea / faceletCount)
3154
+ );
3155
+ const cuts = cutPlanes(
3156
+ order,
3157
+ edges,
3158
+ stickerDat.axis.map((axis) => new Vector38(...axis.coordinates).normalize()),
3159
+ quantum
2890
3160
  );
3161
+ if (!cuts) {
3162
+ return null;
3163
+ }
2891
3164
  const plans = [];
2892
3165
  for (const piece of order) {
2893
- const plan = newSolidPiece(piece, edges, axes, {
2894
- quantum,
2895
- radius,
2896
- faceDistance
2897
- });
3166
+ const plan = newSolidPiece(
3167
+ piece,
3168
+ { planes: cuts, edges },
3169
+ {
3170
+ quantum,
3171
+ radius,
3172
+ featureScale
3173
+ }
3174
+ );
2898
3175
  if (!plan) {
2899
3176
  return null;
2900
3177
  }
@@ -2902,19 +3179,82 @@ function solidPuzzlePlan(stickerDat) {
2902
3179
  }
2903
3180
  return { faceStyles: styles, pieces: plans, scale: PG_SCALE3 };
2904
3181
  }
2905
- function newSolidPiece(piece, edges, axes, sizes) {
3182
+ function cutPlanes(pieces, edges, axes, quantum) {
3183
+ const candidates = /* @__PURE__ */ new Map();
3184
+ const boundaryEdges = [];
3185
+ for (const piece of pieces) {
3186
+ const pieceKey = `${piece.orbit}/${piece.ord}`;
3187
+ for (const { polygon } of piece.stickers) {
3188
+ for (let i = 0; i < polygon.length; i++) {
3189
+ const a = polygon[i];
3190
+ const b = polygon[(i + 1) % polygon.length];
3191
+ const sharing = edges.get(
3192
+ [keyOf(a, quantum), keyOf(b, quantum)].sort().join("|")
3193
+ );
3194
+ if (!sharing || ![...sharing].some((key) => key !== pieceKey)) {
3195
+ continue;
3196
+ }
3197
+ boundaryEdges.push([a, b]);
3198
+ for (const axis of axes) {
3199
+ if (Math.abs(a.dot(axis) - b.dot(axis)) > quantum) {
3200
+ continue;
3201
+ }
3202
+ const offset = a.dot(axis);
3203
+ const key = `${keyOf(axis, 1e-4)}|${Math.round(offset / quantum)}`;
3204
+ const candidate = candidates.get(key);
3205
+ if (candidate) {
3206
+ candidate.uses++;
3207
+ } else {
3208
+ candidates.set(key, { normal: axis, offset, uses: 1 });
3209
+ }
3210
+ }
3211
+ }
3212
+ }
3213
+ }
3214
+ const cuts = [...candidates.values()].sort((a, b) => b.uses - a.uses);
3215
+ const kept = cuts.filter(({ normal: normal2, offset }) => {
3216
+ for (const piece of pieces) {
3217
+ let above = false;
3218
+ let below = false;
3219
+ for (const { polygon } of piece.stickers) {
3220
+ let onIt = true;
3221
+ for (const vertex of polygon) {
3222
+ above ||= vertex.dot(normal2) > offset + quantum;
3223
+ below ||= vertex.dot(normal2) < offset - quantum;
3224
+ onIt &&= Math.abs(vertex.dot(normal2) - offset) <= quantum;
3225
+ }
3226
+ if (onIt) {
3227
+ return false;
3228
+ }
3229
+ }
3230
+ if (above && below) {
3231
+ return false;
3232
+ }
3233
+ }
3234
+ return true;
3235
+ });
3236
+ for (const [a, b] of boundaryEdges) {
3237
+ const covered = kept.some(
3238
+ ({ normal: normal2, offset }) => Math.abs(a.dot(normal2) - offset) < quantum && Math.abs(b.dot(normal2) - offset) < quantum
3239
+ );
3240
+ if (!covered) {
3241
+ return null;
3242
+ }
3243
+ }
3244
+ return kept;
3245
+ }
3246
+ function newSolidPiece(piece, cuts, sizes) {
2906
3247
  if (piece.stickers.length === 0) {
2907
3248
  return null;
2908
3249
  }
2909
- const pieceKey = `${piece.orbit}/${piece.ord}`;
2910
3250
  const planes = [];
2911
3251
  const planeOfSticker = /* @__PURE__ */ new Map();
2912
- const interior = new Vector37();
3252
+ const interior = new Vector38();
2913
3253
  for (const { polygon } of piece.stickers) {
2914
3254
  for (const vertex of polygon) {
2915
3255
  interior.addScaledVector(
2916
3256
  vertex,
2917
- 0.5 / polygon.length / piece.stickers.length
3257
+ 1 / polygon.length / piece.stickers.length
2918
3258
  );
2919
3259
  }
2920
3260
  }
@@ -2927,100 +3267,113 @@ function newSolidPiece(piece, edges, axes, sizes) {
2927
3267
  color: sticker.face
2928
3268
  });
2929
3269
  }
3270
+ const pieceKey = `${piece.orbit}/${piece.ord}`;
2930
3271
  for (const { polygon } of piece.stickers) {
2931
3272
  for (let i = 0; i < polygon.length; i++) {
2932
3273
  const a = polygon[i];
2933
3274
  const b = polygon[(i + 1) % polygon.length];
2934
- const edgeKey = [keyOf(a, sizes.quantum), keyOf(b, sizes.quantum)].sort().join("|");
2935
- const sharing = edges.get(edgeKey);
2936
- if (sharing && sharing.size === 1 && sharing.has(pieceKey)) {
3275
+ const sharing = cuts.edges.get(
3276
+ [keyOf(a, sizes.quantum), keyOf(b, sizes.quantum)].sort().join("|")
3277
+ );
3278
+ if (!sharing || ![...sharing].some((key) => key !== pieceKey)) {
2937
3279
  continue;
2938
3280
  }
2939
- const normal2 = new Vector37().crossVectors(a, b);
2940
- if (normal2.length() < sizes.quantum) {
2941
- return null;
2942
- }
2943
- normal2.normalize();
2944
- if (normal2.dot(interior) > 0) {
2945
- normal2.negate();
2946
- }
2947
- if (!axes.some((axis) => Math.abs(axis.dot(normal2)) > 0.9999)) {
3281
+ const cut = cuts.planes.find(
3282
+ ({ normal: normal3, offset: offset2 }) => Math.abs(a.dot(normal3) - offset2) < sizes.quantum && Math.abs(b.dot(normal3) - offset2) < sizes.quantum
3283
+ );
3284
+ if (!cut) {
2948
3285
  return null;
2949
3286
  }
2950
- for (const other of piece.stickers) {
2951
- for (const vertex of other.polygon) {
2952
- if (vertex.dot(normal2) > sizes.quantum) {
2953
- return null;
2954
- }
2955
- }
2956
- }
3287
+ const outward = interior.dot(cut.normal) < cut.offset;
3288
+ const normal2 = outward ? cut.normal.clone() : cut.normal.clone().negate();
3289
+ const offset = outward ? cut.offset : -cut.offset;
2957
3290
  if (planes.some(
2958
- (plane) => plane.color === null && plane.normal.dot(normal2) > 0.9999 && Math.abs(plane.offset) < sizes.quantum
3291
+ (plane) => plane.color === null && plane.normal.dot(normal2) > 0.9999 && Math.abs(plane.offset - offset) < sizes.quantum
2959
3292
  )) {
2960
3293
  continue;
2961
3294
  }
2962
- planes.push({ normal: normal2, offset: 0, color: null });
3295
+ planes.push({ normal: normal2, offset, color: null });
3296
+ }
3297
+ }
3298
+ for (const cut of cuts.planes) {
3299
+ let touches = false;
3300
+ let above = false;
3301
+ let below = false;
3302
+ for (const { polygon } of piece.stickers) {
3303
+ for (const vertex of polygon) {
3304
+ const side = vertex.dot(cut.normal) - cut.offset;
3305
+ if (Math.abs(side) <= sizes.quantum) {
3306
+ touches = true;
3307
+ } else if (side > 0) {
3308
+ above = true;
3309
+ } else {
3310
+ below = true;
3311
+ }
3312
+ }
3313
+ }
3314
+ if (touches || above === below) {
3315
+ continue;
2963
3316
  }
3317
+ planes.push(
3318
+ above ? {
3319
+ normal: cut.normal.clone().negate(),
3320
+ offset: -cut.offset,
3321
+ color: null
3322
+ } : { normal: cut.normal.clone(), offset: cut.offset, color: null }
3323
+ );
2964
3324
  }
2965
3325
  const solid = solidPieceGeometry(planes, {
2966
- groove: SOLID_PIECE_GROOVE * sizes.faceDistance,
2967
- chamfer: SOLID_PIECE_CHAMFER * sizes.faceDistance,
3326
+ groove: SOLID_PIECE_GROOVE * sizes.featureScale,
3327
+ chamfer: SOLID_PIECE_CHAMFER * sizes.featureScale,
2968
3328
  radius: sizes.radius
2969
3329
  });
2970
3330
  if (!solid) {
2971
3331
  return null;
2972
3332
  }
2973
- const positions = Array.from(solid.positions);
2974
- const normals = Array.from(solid.normals);
3333
+ const hintPositions = [];
2975
3334
  const hintRanges = /* @__PURE__ */ new Map();
2976
3335
  for (const { sticker, polygon } of piece.stickers) {
2977
3336
  const normal2 = outwardNormal(polygon);
2978
- const hint = hintPolygon(polygon, normal2);
2979
- const start = positions.length / 3;
2980
- for (let i = 1; i < hint.length - 1; i++) {
2981
- for (const vertex of [hint[0], hint[i], hint[i + 1]]) {
2982
- positions.push(vertex.x, vertex.y, vertex.z);
2983
- normals.push(normal2.x, normal2.y, normal2.z);
3337
+ const hint2 = hintPolygon(polygon, normal2);
3338
+ const start = hintPositions.length / 3;
3339
+ for (let i = 1; i < hint2.length - 1; i++) {
3340
+ for (const vertex of [hint2[0], hint2[i], hint2[i + 1]]) {
3341
+ hintPositions.push(vertex.x, vertex.y, vertex.z);
2984
3342
  }
2985
3343
  }
2986
3344
  hintRanges.set(sticker, {
2987
3345
  start,
2988
- count: positions.length / 3 - start
3346
+ count: hintPositions.length / 3 - start
2989
3347
  });
2990
3348
  }
2991
- const vertexCount = positions.length / 3;
2992
- const geometry = new BufferGeometry5();
2993
- geometry.setAttribute(
2994
- "position",
2995
- new BufferAttribute5(new Float32Array(positions), 3)
2996
- );
2997
- geometry.setAttribute(
2998
- "normal",
2999
- new BufferAttribute5(new Float32Array(normals), 3)
3000
- );
3001
- const colors = new BufferAttribute5(new Uint8Array(4 * vertexCount), 4, true);
3002
- geometry.setAttribute("color", colors);
3003
- geometry.addGroup(0, solid.vertexCount, BODY_MATERIAL_INDEX);
3004
- if (vertexCount > solid.vertexCount) {
3005
- geometry.addGroup(
3006
- solid.vertexCount,
3007
- vertexCount - solid.vertexCount,
3008
- HINT_MATERIAL_INDEX
3349
+ const body = newPieceMesh2(solid.positions);
3350
+ const hint = hintPositions.length > 0 ? newPieceMesh2(new Float32Array(hintPositions)) : null;
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
+ )
3009
3361
  );
3010
3362
  }
3011
- paintInternal(colors, solid.internalRanges);
3012
3363
  const facelets = piece.stickers.map(({ sticker }) => ({
3013
3364
  ori: sticker.ori,
3014
3365
  faceStyle: sticker.face,
3015
3366
  body: solid.ranges[planeOfSticker.get(sticker)],
3016
- hint: [hintRanges.get(sticker)]
3367
+ hint: [hintRanges.get(sticker)],
3368
+ logo: logoByFace.get(sticker.face) ?? null
3017
3369
  }));
3018
3370
  for (const sticker of piece.duplicates) {
3019
3371
  facelets.push({
3020
3372
  ori: sticker.ori,
3021
3373
  faceStyle: sticker.face,
3022
3374
  body: [],
3023
- hint: []
3375
+ hint: [],
3376
+ logo: logoByFace.get(sticker.face) ?? null
3024
3377
  });
3025
3378
  }
3026
3379
  return {
@@ -3028,23 +3381,34 @@ function newSolidPiece(piece, edges, axes, sizes) {
3028
3381
  ord: piece.ord,
3029
3382
  // The geometry is already where the piece belongs.
3030
3383
  home: new Matrix43(),
3031
- geometry,
3032
- colors,
3384
+ body,
3385
+ hint,
3033
3386
  facelets
3034
3387
  };
3035
3388
  }
3389
+ function newPieceMesh2(positions) {
3390
+ const geometry = new BufferGeometry6();
3391
+ geometry.setAttribute("position", new BufferAttribute6(positions, 3));
3392
+ const colors = new BufferAttribute6(
3393
+ new Uint8Array(4 * positions.length / 3),
3394
+ 4,
3395
+ true
3396
+ );
3397
+ geometry.setAttribute("color", colors);
3398
+ return { geometry, colors };
3399
+ }
3036
3400
 
3037
3401
  // src/cubing/twisty/views/3D/puzzles/Square1_3D.ts
3038
3402
  import { DoubleSide as DoubleSide3 } from "three/src/constants.js";
3039
- import { BufferAttribute as BufferAttribute6 } from "three/src/core/BufferAttribute.js";
3040
- 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";
3041
3405
  import { Object3D as Object3D3 } from "three/src/core/Object3D.js";
3042
- import { MeshBasicMaterial as MeshBasicMaterial3 } from "three/src/materials/MeshBasicMaterial.js";
3406
+ import { MeshBasicMaterial as MeshBasicMaterial5 } from "three/src/materials/MeshBasicMaterial.js";
3043
3407
  import { Color as Color5 } from "three/src/math/Color.js";
3044
3408
  import { Quaternion as Quaternion2 } from "three/src/math/Quaternion.js";
3045
- import { Vector3 as Vector38 } from "three/src/math/Vector3.js";
3409
+ import { Vector3 as Vector39 } from "three/src/math/Vector3.js";
3046
3410
  import { Group as Group3 } from "three/src/objects/Group.js";
3047
- import { Mesh as Mesh3 } from "three/src/objects/Mesh.js";
3411
+ import { Mesh as Mesh4 } from "three/src/objects/Mesh.js";
3048
3412
  var DEGREE = TAU / 360;
3049
3413
  var SLOT_ANGLE_DEGREES = 30;
3050
3414
  var WEDGES_PER_LAYER = 12;
@@ -3059,8 +3423,8 @@ var RING_COLORS = [2254591, 16750848, 65280, 16711680];
3059
3423
  var FRAME_OFFSET_DEGREES = 180;
3060
3424
  var BODY_COLOR = 1118481;
3061
3425
  var MIRROR = -1;
3062
- var Y_AXIS = new Vector38(0, 1, 0);
3063
- var SLICE_AXIS = new Vector38(
3426
+ var Y_AXIS = new Vector39(0, 1, 0);
3427
+ var SLICE_AXIS = new Vector39(
3064
3428
  MIRROR * Math.sin(15 * DEGREE),
3065
3429
  0,
3066
3430
  Math.cos(15 * DEGREE)
@@ -3077,7 +3441,7 @@ function slotQuaternion(slot) {
3077
3441
  );
3078
3442
  if (slot >= WEDGES_PER_LAYER) {
3079
3443
  quaternion.multiply(
3080
- new Quaternion2().setFromAxisAngle(new Vector38(0, 0, 1), TAU / 2)
3444
+ new Quaternion2().setFromAxisAngle(new Vector39(0, 0, 1), TAU / 2)
3081
3445
  );
3082
3446
  }
3083
3447
  return quaternion;
@@ -3166,15 +3530,15 @@ function prismStickerSpecs(spec) {
3166
3530
  const specs = [];
3167
3531
  if (topColor !== null) {
3168
3532
  specs.push({
3169
- corners: polygon.map(([x, z]) => new Vector38(x, yTop, z)),
3170
- normal: new Vector38(0, 1, 0),
3533
+ corners: polygon.map(([x, z]) => new Vector39(x, yTop, z)),
3534
+ normal: new Vector39(0, 1, 0),
3171
3535
  color: topColor
3172
3536
  });
3173
3537
  }
3174
3538
  if (bottomColor !== null) {
3175
3539
  specs.push({
3176
- corners: polygon.map(([x, z]) => new Vector38(x, yBottom, z)),
3177
- normal: new Vector38(0, -1, 0),
3540
+ corners: polygon.map(([x, z]) => new Vector39(x, yBottom, z)),
3541
+ normal: new Vector39(0, -1, 0),
3178
3542
  color: bottomColor
3179
3543
  });
3180
3544
  }
@@ -3191,17 +3555,17 @@ function prismStickerSpecs(spec) {
3191
3555
  }
3192
3556
  const [x0, z0] = polygon[i];
3193
3557
  const [x1, z1] = polygon[(i + 1) % polygon.length];
3194
- const normal2 = new Vector38(z1 - z0, 0, -(x1 - x0)).normalize();
3558
+ const normal2 = new Vector39(z1 - z0, 0, -(x1 - x0)).normalize();
3195
3559
  const outward = normal2.x * ((x0 + x1) / 2 - centroidX) + normal2.z * ((z0 + z1) / 2 - centroidZ);
3196
3560
  if (outward < 0) {
3197
3561
  normal2.negate();
3198
3562
  }
3199
3563
  specs.push({
3200
3564
  corners: [
3201
- new Vector38(x0, yBottom, z0),
3202
- new Vector38(x1, yBottom, z1),
3203
- new Vector38(x1, yTop, z1),
3204
- 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)
3205
3569
  ],
3206
3570
  normal: normal2,
3207
3571
  color
@@ -3210,7 +3574,7 @@ function prismStickerSpecs(spec) {
3210
3574
  return specs;
3211
3575
  }
3212
3576
  function stickerPositions(spec, faceletScale) {
3213
- const center = new Vector38();
3577
+ const center = new Vector39();
3214
3578
  for (const corner of spec.corners) {
3215
3579
  center.addScaledVector(corner, 1 / spec.corners.length);
3216
3580
  }
@@ -3239,9 +3603,9 @@ var PUZZLE_RADIUS = CUBE_HALF_EDGE * Math.SQRT2;
3239
3603
  function prismPlanes(spec) {
3240
3604
  const { polygon, sideColors } = spec;
3241
3605
  const planes = [
3242
- { 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 },
3243
3607
  {
3244
- normal: new Vector38(0, -1, 0),
3608
+ normal: new Vector39(0, -1, 0),
3245
3609
  offset: -spec.yBottom,
3246
3610
  color: spec.bottomColor
3247
3611
  }
@@ -3255,7 +3619,7 @@ function prismPlanes(spec) {
3255
3619
  for (let i = 0; i < polygon.length; i++) {
3256
3620
  const [x0, z0] = polygon[i];
3257
3621
  const [x1, z1] = polygon[(i + 1) % polygon.length];
3258
- const normal2 = new Vector38(z1 - z0, 0, -(x1 - x0)).normalize();
3622
+ const normal2 = new Vector39(z1 - z0, 0, -(x1 - x0)).normalize();
3259
3623
  if (normal2.x * (x0 - centroidX) + normal2.z * (z0 - centroidZ) < 0) {
3260
3624
  normal2.negate();
3261
3625
  }
@@ -3295,13 +3659,12 @@ function stickerlessPieceGeometry(spec) {
3295
3659
  paint(piece.ranges[i], color);
3296
3660
  }
3297
3661
  }
3298
- const geometry = new BufferGeometry6();
3299
- geometry.setAttribute("position", new BufferAttribute6(piece.positions, 3));
3300
- geometry.setAttribute("normal", new BufferAttribute6(piece.normals, 3));
3301
- 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));
3302
3665
  return geometry;
3303
3666
  }
3304
- var bodyMaterial = new MeshBasicMaterial3({
3667
+ var bodyMaterial = new MeshBasicMaterial5({
3305
3668
  color: new Color5(BODY_COLOR).convertLinearToSRGB(),
3306
3669
  side: DoubleSide3
3307
3670
  });
@@ -3313,7 +3676,7 @@ var stickerMaterialCache = /* @__PURE__ */ new Map();
3313
3676
  function stickerMaterial(color) {
3314
3677
  let material = stickerMaterialCache.get(color);
3315
3678
  if (!material) {
3316
- material = new MeshBasicMaterial3({
3679
+ material = new MeshBasicMaterial5({
3317
3680
  color: new Color5(color).convertLinearToSRGB(),
3318
3681
  side: DoubleSide3
3319
3682
  });
@@ -3328,28 +3691,28 @@ var Square1Piece = class extends Group3 {
3328
3691
  constructor(spec, faceletScale, stickerless) {
3329
3692
  super();
3330
3693
  if (stickerless) {
3331
- this.body = new Mesh3(
3694
+ this.body = new Mesh4(
3332
3695
  stickerlessPieceGeometry(spec),
3333
3696
  stickerlessMaterial()
3334
3697
  );
3335
3698
  this.add(this.body);
3336
3699
  return;
3337
3700
  }
3338
- const bodyGeometry = new BufferGeometry6();
3701
+ const bodyGeometry = new BufferGeometry7();
3339
3702
  bodyGeometry.setAttribute(
3340
3703
  "position",
3341
- new BufferAttribute6(prismBodyPositions(spec), 3)
3704
+ new BufferAttribute7(prismBodyPositions(spec), 3)
3342
3705
  );
3343
- this.body = new Mesh3(bodyGeometry, bodyMaterial);
3706
+ this.body = new Mesh4(bodyGeometry, bodyMaterial);
3344
3707
  this.add(this.body);
3345
3708
  this.#stickerSpecs = prismStickerSpecs(spec);
3346
3709
  for (const stickerSpec of this.#stickerSpecs) {
3347
- const geometry = new BufferGeometry6();
3710
+ const geometry = new BufferGeometry7();
3348
3711
  geometry.setAttribute(
3349
3712
  "position",
3350
- new BufferAttribute6(stickerPositions(stickerSpec, faceletScale), 3)
3713
+ new BufferAttribute7(stickerPositions(stickerSpec, faceletScale), 3)
3351
3714
  );
3352
- const mesh = new Mesh3(geometry, stickerMaterial(stickerSpec.color));
3715
+ const mesh = new Mesh4(geometry, stickerMaterial(stickerSpec.color));
3353
3716
  this.#stickerMeshes.push(mesh);
3354
3717
  this.add(mesh);
3355
3718
  }
@@ -3359,7 +3722,7 @@ var Square1Piece = class extends Group3 {
3359
3722
  const geometry = this.#stickerMeshes[i].geometry;
3360
3723
  geometry.setAttribute(
3361
3724
  "position",
3362
- new BufferAttribute6(
3725
+ new BufferAttribute7(
3363
3726
  stickerPositions(this.#stickerSpecs[i], faceletScale),
3364
3727
  3
3365
3728
  )
@@ -3414,6 +3777,38 @@ function equatorPrismSpec(homePiece) {
3414
3777
  ]
3415
3778
  };
3416
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
+ }
3417
3812
  function ringColorAt(phiDegrees) {
3418
3813
  const faceIndex = Math.round(phiDegrees / 90);
3419
3814
  return RING_COLORS[(faceIndex % 4 + 4) % 4];
@@ -3468,9 +3863,6 @@ var Square1_3D = class extends Object3D3 {
3468
3863
  this.#equatorPieces.push(equator);
3469
3864
  this.add(equator);
3470
3865
  }
3471
- if (this.#stickerless) {
3472
- addCubieBodyLighting(this);
3473
- }
3474
3866
  this.scale.set(PUZZLE_SCALE, PUZZLE_SCALE, PUZZLE_SCALE);
3475
3867
  }
3476
3868
  scheduleRenderCallback;
@@ -3573,6 +3965,31 @@ var Square1_3D = class extends Object3D3 {
3573
3965
  }
3574
3966
  setStickeringMask(_stickeringMask) {
3575
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
+ }
3576
3993
  experimentalUpdateOptions(options) {
3577
3994
  let changed = false;
3578
3995
  if (typeof options.showFoundation === "boolean") {
@@ -3607,14 +4024,32 @@ var Square1_3D = class extends Object3D3 {
3607
4024
 
3608
4025
  // src/cubing/twisty/views/3D/puzzles/Stickerless3D.ts
3609
4026
  import { BackSide as BackSide2 } from "three/src/constants.js";
3610
- import { BufferAttribute as BufferAttribute7 } from "three/src/core/BufferAttribute.js";
3611
- 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";
3612
4029
  import { Object3D as Object3D4 } from "three/src/core/Object3D.js";
3613
- import { MeshBasicMaterial as MeshBasicMaterial4 } from "three/src/materials/MeshBasicMaterial.js";
4030
+ import { MeshBasicMaterial as MeshBasicMaterial6 } from "three/src/materials/MeshBasicMaterial.js";
3614
4031
  import { Matrix4 as Matrix44 } from "three/src/math/Matrix4.js";
3615
- import { Vector3 as Vector39 } from "three/src/math/Vector3.js";
3616
- import { Mesh as Mesh4 } from "three/src/objects/Mesh.js";
3617
- var invisibleMaterial2 = new MeshBasicMaterial4({ visible: false });
4032
+ import { Vector3 as Vector310 } from "three/src/math/Vector3.js";
4033
+ import { BatchedMesh } from "three/src/objects/BatchedMesh.js";
4034
+ import { Mesh as Mesh5 } from "three/src/objects/Mesh.js";
4035
+ var invisibleMaterial2 = new MeshBasicMaterial6({ visible: false });
4036
+ function newBatch(meshes, material) {
4037
+ let vertices = 0;
4038
+ let indices = 0;
4039
+ for (const { geometry } of meshes) {
4040
+ vertices += geometry.getAttribute("position").count;
4041
+ indices += geometry.getIndex()?.count ?? 0;
4042
+ }
4043
+ return new BatchedMesh(meshes.length, vertices, indices, material);
4044
+ }
4045
+ function addToBatch(batch, mesh, home) {
4046
+ const geometryId = batch.addGeometry(mesh.geometry);
4047
+ const instance = batch.addInstance(geometryId);
4048
+ batch.setMatrixAt(instance, home);
4049
+ mesh.geometry.dispose();
4050
+ const range = batch.getGeometryRangeAt(geometryId);
4051
+ return { instance, vertexStart: range.vertexStart };
4052
+ }
3618
4053
  var Stickerless3D = class extends Object3D4 {
3619
4054
  constructor(scheduleRenderCallback, kpuzzle, stickerDat, plan, options = {}) {
3620
4055
  super();
@@ -3622,7 +4057,8 @@ var Stickerless3D = class extends Object3D4 {
3622
4057
  this.kpuzzle = kpuzzle;
3623
4058
  this.stickerDat = stickerDat;
3624
4059
  this.plan = plan;
3625
- this.#hintMaterial = new MeshBasicMaterial4({
4060
+ this.#logoFacelet = options.logoFacelet ?? null;
4061
+ this.#hintMaterial = new MeshBasicMaterial6({
3626
4062
  vertexColors: true,
3627
4063
  transparent: true,
3628
4064
  // Hint facelets show the faces turned away from the camera, so they are
@@ -3630,20 +4066,35 @@ var Stickerless3D = class extends Object3D4 {
3630
4066
  side: BackSide2
3631
4067
  });
3632
4068
  this.#bodyMaterial = newVertexColorBodyMaterial();
3633
- this.#materials = [this.#bodyMaterial, this.#hintMaterial];
3634
- this.experimentalUpdateOptions({
3635
- hintFacelets: options.hintFacelets ?? "floating"
3636
- });
3637
4069
  for (const axis of stickerDat.axis) {
3638
4070
  this.#axesInfo[axis.quantumMove.family] = {
3639
- axis: new Vector39(...axis.coordinates),
4071
+ axis: new Vector310(...axis.coordinates),
3640
4072
  order: axis.order
3641
4073
  };
3642
4074
  }
4075
+ this.#bodyBatch = newBatch(
4076
+ plan.pieces.map((piecePlan) => piecePlan.body),
4077
+ this.#bodyMaterial
4078
+ );
4079
+ this.#bodyBatch.sortObjects = false;
4080
+ this.#bodyBatch.perObjectFrustumCulled = false;
4081
+ this.add(this.#bodyBatch);
4082
+ const hints = plan.pieces.map((piecePlan) => piecePlan.hint).filter((hint) => hint !== null);
4083
+ if (hints.length > 0) {
4084
+ this.#hintBatch = newBatch(hints, this.#hintMaterial);
4085
+ this.#hintBatch.perObjectFrustumCulled = false;
4086
+ this.add(this.#hintBatch);
4087
+ }
3643
4088
  for (const piecePlan of plan.pieces) {
3644
4089
  this.#addPiece(piecePlan);
3645
4090
  }
3646
- addCubieBodyLighting(this);
4091
+ this.#bodyColors = this.#bodyBatch.geometry.getAttribute(
4092
+ "color"
4093
+ );
4094
+ this.#hintColors = this.#hintBatch?.geometry.getAttribute("color") ?? null;
4095
+ this.experimentalUpdateOptions({
4096
+ hintFacelets: options.hintFacelets ?? "floating"
4097
+ });
3647
4098
  for (const face2 of stickerDat.faces) {
3648
4099
  this.#addControlTarget(face2);
3649
4100
  }
@@ -3657,11 +4108,19 @@ var Stickerless3D = class extends Object3D4 {
3657
4108
  stickerDat;
3658
4109
  plan;
3659
4110
  #pieces = [];
4111
+ /**
4112
+ * One draw call for every piece of the puzzle, and one for every hint
4113
+ * facelet. A batch keeps a matrix per instance, so a move still moves the
4114
+ * pieces it sweeps and nothing else.
4115
+ */
4116
+ #bodyBatch;
4117
+ #hintBatch = null;
4118
+ #bodyColors;
4119
+ #hintColors = null;
3660
4120
  /** Indexed `[orbit][ori][ord]`, like the orbits of the `KPuzzle`. */
3661
4121
  #facelets = {};
3662
4122
  #axesInfo = {};
3663
4123
  #controlTargets = [];
3664
- #materials;
3665
4124
  #bodyMaterial;
3666
4125
  #hintMaterial;
3667
4126
  #appearances = /* @__PURE__ */ new Map();
@@ -3669,29 +4128,50 @@ var Stickerless3D = class extends Object3D4 {
3669
4128
  #turningPieces = [];
3670
4129
  /** Which pieces each quantum move sweeps, by the move's notation. */
3671
4130
  #turnedPieces = /* @__PURE__ */ new Map();
3672
- #dirtyColors = /* @__PURE__ */ new Set();
4131
+ /** The span of each color attribute that changed, so only it is uploaded. */
4132
+ #dirtyColors = /* @__PURE__ */ new Map();
3673
4133
  #lastPosition = null;
3674
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();
3675
4140
  #addPiece(plan) {
3676
- const mesh = new Mesh4(plan.geometry, this.#materials);
3677
- mesh.matrixAutoUpdate = false;
3678
- mesh.matrix.copy(plan.home);
3679
- this.add(mesh);
3680
- const piece = { mesh, colors: plan.colors, home: plan.home };
4141
+ const bodyInstance = addToBatch(this.#bodyBatch, plan.body, plan.home);
4142
+ const hintInstance = plan.hint && this.#hintBatch ? addToBatch(this.#hintBatch, plan.hint, plan.home) : -1;
4143
+ const piece = {
4144
+ bodyInstance: bodyInstance.instance,
4145
+ hintInstance: hintInstance === -1 ? -1 : hintInstance.instance,
4146
+ home: plan.home,
4147
+ matrix: plan.home.clone(),
4148
+ turning: false
4149
+ };
3681
4150
  this.#pieces.push(piece);
4151
+ const shift = (ranges, offset) => ranges.map((range) => ({
4152
+ start: range.start + offset,
4153
+ count: range.count
4154
+ }));
3682
4155
  const orbitFacelets = this.#facelets[plan.orbit] ??= [];
3683
4156
  for (const facelet of plan.facelets) {
3684
4157
  (orbitFacelets[facelet.ori] ??= [])[plan.ord] = {
3685
4158
  piece,
3686
4159
  faceStyle: facelet.faceStyle,
3687
- body: facelet.body,
3688
- hint: facelet.hint,
4160
+ body: shift(facelet.body, bodyInstance.vertexStart),
4161
+ hint: hintInstance === -1 ? [] : shift(facelet.hint, hintInstance.vertexStart),
3689
4162
  appearanceKey: -1,
3690
4163
  mask: "regular",
3691
- hintMask: "regular"
4164
+ hintMask: "regular",
4165
+ logo: facelet.logo
3692
4166
  };
3693
4167
  }
3694
4168
  }
4169
+ #setMatrix(piece) {
4170
+ this.#bodyBatch.setMatrixAt(piece.bodyInstance, piece.matrix);
4171
+ if (piece.hintInstance !== -1) {
4172
+ this.#hintBatch.setMatrixAt(piece.hintInstance, piece.matrix);
4173
+ }
4174
+ }
3695
4175
  #addControlTarget(face2) {
3696
4176
  const { coords } = face2;
3697
4177
  const triangles = coords.length / 3 - 2;
@@ -3707,18 +4187,98 @@ var Stickerless3D = class extends Object3D4 {
3707
4187
  }
3708
4188
  }
3709
4189
  }
3710
- const geometry = new BufferGeometry7();
3711
- geometry.setAttribute("position", new BufferAttribute7(positions, 3));
3712
- 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);
3713
4193
  mesh.userData["quantumMove"] = this.stickerDat.notationMapper.notationToExternal(new Move(face2.name));
3714
4194
  mesh.scale.setScalar(0.99);
3715
4195
  this.add(mesh);
3716
4196
  this.#controlTargets.push(mesh);
3717
4197
  }
3718
- dispose() {
3719
- for (const piece of this.#pieces) {
3720
- piece.mesh.geometry.dispose();
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;
3721
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
+ }
4276
+ dispose() {
4277
+ this.#disposeLogo();
4278
+ this.#bodyBatch.dispose();
4279
+ this.#bodyBatch.geometry.dispose();
4280
+ this.#hintBatch?.dispose();
4281
+ this.#hintBatch?.geometry.dispose();
3722
4282
  this.#bodyMaterial.dispose();
3723
4283
  this.#hintMaterial.dispose();
3724
4284
  }
@@ -3751,7 +4311,7 @@ var Stickerless3D = class extends Object3D4 {
3751
4311
  }
3752
4312
  }
3753
4313
  for (const axis of this.stickerDat.axis) {
3754
- const product = point.dot(new Vector39(...axis.coordinates));
4314
+ const product = point.dot(new Vector310(...axis.coordinates));
3755
4315
  if (product > closestMoveDotProduct) {
3756
4316
  const modified = this.stickerDat.notationMapper.notationToExternal(
3757
4317
  modify(axis.quantumMove)
@@ -3809,8 +4369,8 @@ var Stickerless3D = class extends Object3D4 {
3809
4369
  }
3810
4370
  /** @deprecated */
3811
4371
  experimentalUpdateOptions(options) {
3812
- if (options.hintFacelets !== void 0) {
3813
- this.#materials[HINT_MATERIAL_INDEX] = options.hintFacelets === "none" ? invisibleMaterial2 : this.#hintMaterial;
4372
+ if (options.hintFacelets !== void 0 && this.#hintBatch) {
4373
+ this.#hintBatch.visible = options.hintFacelets !== "none";
3814
4374
  this.scheduleRenderCallback();
3815
4375
  }
3816
4376
  }
@@ -3828,11 +4388,13 @@ var Stickerless3D = class extends Object3D4 {
3828
4388
  this.#updateColors(position);
3829
4389
  this.#lastPosition = position;
3830
4390
  this.#pendingStickeringUpdate = false;
4391
+ this.#updateLogoSlot();
3831
4392
  }
3832
- for (const piece of this.#turningPieces) {
3833
- piece.mesh.matrix.copy(piece.home);
4393
+ const wereTurning = this.#turningPieces;
4394
+ for (const piece of wereTurning) {
4395
+ piece.turning = false;
3834
4396
  }
3835
- this.#turningPieces.length = 0;
4397
+ this.#turningPieces = [];
3836
4398
  const turnMatrix = new Matrix44();
3837
4399
  for (const moveProgress of position.movesInProgress) {
3838
4400
  const move = moveProgress.move;
@@ -3849,10 +4411,24 @@ var Stickerless3D = class extends Object3D4 {
3849
4411
  -smootherStep(moveProgress.fraction) * moveProgress.direction * unswizzled.amount * TAU / axisInfo.order
3850
4412
  );
3851
4413
  for (const piece of this.#piecesTurnedBy(move)) {
3852
- piece.mesh.matrix.premultiply(turnMatrix);
3853
- this.#turningPieces.push(piece);
4414
+ if (!piece.turning) {
4415
+ piece.turning = true;
4416
+ piece.matrix.copy(piece.home);
4417
+ this.#turningPieces.push(piece);
4418
+ }
4419
+ piece.matrix.premultiply(turnMatrix);
3854
4420
  }
3855
4421
  }
4422
+ for (const piece of wereTurning) {
4423
+ if (!piece.turning) {
4424
+ piece.matrix.copy(piece.home);
4425
+ this.#setMatrix(piece);
4426
+ }
4427
+ }
4428
+ for (const piece of this.#turningPieces) {
4429
+ this.#setMatrix(piece);
4430
+ }
4431
+ this.#placeLogo();
3856
4432
  this.scheduleRenderCallback();
3857
4433
  }
3858
4434
  /**
@@ -3935,17 +4511,34 @@ var Stickerless3D = class extends Object3D4 {
3935
4511
  source.mask,
3936
4512
  source.hintMask
3937
4513
  );
3938
- writeColor(slot.piece.colors, slot.body, appearance);
3939
- writeColor(slot.piece.colors, slot.hint, appearance, 4);
3940
- this.#dirtyColors.add(slot.piece.colors);
4514
+ this.#paint(this.#bodyColors, slot.body, appearance, 0);
4515
+ if (this.#hintColors) {
4516
+ this.#paint(this.#hintColors, slot.hint, appearance, 4);
4517
+ }
3941
4518
  }
3942
4519
  }
3943
4520
  }
3944
- for (const colors of this.#dirtyColors) {
4521
+ for (const [colors, span] of this.#dirtyColors) {
4522
+ colors.addUpdateRange(4 * span.first, 4 * (span.last - span.first + 1));
3945
4523
  colors.needsUpdate = true;
3946
4524
  }
3947
4525
  this.#dirtyColors.clear();
3948
4526
  }
4527
+ #paint(colors, ranges, appearance, offset) {
4528
+ if (ranges.length === 0) {
4529
+ return;
4530
+ }
4531
+ writeColor(colors, ranges, appearance, offset);
4532
+ let span = this.#dirtyColors.get(colors);
4533
+ if (!span) {
4534
+ span = { first: Number.POSITIVE_INFINITY, last: 0 };
4535
+ this.#dirtyColors.set(colors, span);
4536
+ }
4537
+ for (const range of ranges) {
4538
+ span.first = Math.min(span.first, range.start);
4539
+ span.last = Math.max(span.last, range.start + range.count - 1);
4540
+ }
4541
+ }
3949
4542
  };
3950
4543
 
3951
4544
  // src/cubing/twisty/heavy-code-imports/dynamic-entries/twisty-dynamic-3d.ts
@@ -3954,7 +4547,7 @@ import { Raycaster } from "three/src/core/Raycaster.js";
3954
4547
  import { TextureLoader as TextureLoader2 } from "three/src/loaders/TextureLoader.js";
3955
4548
  import { Spherical } from "three/src/math/Spherical.js";
3956
4549
  import { Vector2 as Vector22 } from "three/src/math/Vector2.js";
3957
- import { Vector3 as Vector310 } from "three/src/math/Vector3.js";
4550
+ import { Vector3 as Vector311 } from "three/src/math/Vector3.js";
3958
4551
  import { WebGLRenderer } from "three/src/renderers/WebGLRenderer.js";
3959
4552
  import { Scene } from "three/src/scenes/Scene.js";
3960
4553
 
@@ -4002,7 +4595,8 @@ async function pg3dShim(renderCallback, puzzleLoader, hintFacelets, faceletScale
4002
4595
  const plan = darkIgnoredOrbits || pictureCube ? null : cubePuzzlePlan(stickerDat) ?? solidPuzzlePlan(stickerDat);
4003
4596
  if (plan) {
4004
4597
  return new Stickerless3D(renderCallback, kpuzzle, stickerDat, plan, {
4005
- hintFacelets
4598
+ hintFacelets,
4599
+ logoFacelet: logoFaceletAddress(puzzleLoader.id, stickerDat)
4006
4600
  });
4007
4601
  }
4008
4602
  return new PG3D(
@@ -4026,11 +4620,11 @@ export {
4026
4620
  Spherical as ThreeSpherical,
4027
4621
  TextureLoader2 as ThreeTextureLoader,
4028
4622
  Vector22 as ThreeVector2,
4029
- Vector310 as ThreeVector3,
4623
+ Vector311 as ThreeVector3,
4030
4624
  WebGLRenderer as ThreeWebGLRenderer,
4031
4625
  Twisty3DScene,
4032
4626
  cube3DShim,
4033
4627
  pg3dShim,
4034
4628
  square1_3DShim
4035
4629
  };
4036
- //# sourceMappingURL=twisty-dynamic-3d-SLKSUJTE.js.map
4630
+ //# sourceMappingURL=twisty-dynamic-3d-3MGVD3TG.js.map