@rednaxela101/cubing 0.63.8 → 0.63.9

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.
@@ -3,7 +3,7 @@ import {
3
3
  bulk3DCode,
4
4
  haveStartedSharingRenderers,
5
5
  hintFaceletStyles
6
- } from "./chunk-B2YKAP2I.js";
6
+ } from "./chunk-F765ZFNM.js";
7
7
  import "./chunk-3ODTF4HE.js";
8
8
  import {
9
9
  cube3x3x3,
@@ -21,7 +21,7 @@ import { BufferGeometry as BufferGeometry2 } from "three/src/core/BufferGeometry
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 MeshBasicMaterial2 } 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";
@@ -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,35 @@ 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);
319
- }
320
290
 
321
291
  // src/cubing/twisty/views/3D/puzzles/Cube3D.ts
322
292
  var svgLoader = new TextureLoader();
323
293
  function newHintMaterial(style) {
324
- return new MeshBasicMaterial({
294
+ return new MeshBasicMaterial2({
325
295
  color: new Color2(style.color).convertLinearToSRGB(),
326
296
  side: BackSide,
327
297
  transparent: true,
328
298
  opacity: style.opacity
329
299
  });
330
300
  }
331
- var ignoredMaterial = new MeshBasicMaterial({
301
+ var ignoredMaterial = new MeshBasicMaterial2({
332
302
  color: new Color2(bodyMaskColors.ignored).convertLinearToSRGB()
333
303
  });
334
304
  var ignoredMaterialHint = newHintMaterial(hintMaskStyles.ignored);
335
- var invisibleMaterial = new MeshBasicMaterial({
305
+ var invisibleMaterial = new MeshBasicMaterial2({
336
306
  visible: false
337
307
  });
338
- var orientedMaterial = new MeshBasicMaterial({
308
+ var orientedMaterial = new MeshBasicMaterial2({
339
309
  color: bodyMaskColors.oriented
340
310
  });
341
311
  var orientedMaterialHint = newHintMaterial(hintMaskStyles.oriented);
342
- var experimentalOriented2Material = new MeshBasicMaterial({
312
+ var experimentalOriented2Material = new MeshBasicMaterial2({
343
313
  color: bodyMaskColors.experimentalOriented2
344
314
  });
345
315
  var experimentalOriented2MaterialHint = newHintMaterial(
346
316
  hintMaskStyles.experimentalOriented2
347
317
  );
348
- var mysteryMaterial = new MeshBasicMaterial({
318
+ var mysteryMaterial = new MeshBasicMaterial2({
349
319
  color: bodyMaskColors.mystery
350
320
  });
351
321
  var mysterMaterialHint = newHintMaterial(hintMaskStyles.mystery);
@@ -369,12 +339,12 @@ var AxisInfo = class {
369
339
  const colorLinearSRGB = new Color2(color).convertLinearToSRGB();
370
340
  const dimColorLinearSRGB = new Color2(dimColor).convertLinearToSRGB();
371
341
  this.stickerMaterial = {
372
- regular: new MeshBasicMaterial({
342
+ regular: new MeshBasicMaterial2({
373
343
  color: colorLinearSRGB,
374
344
  side: FrontSide
375
345
  // TODO: set to `DoubleSide` when hint facelets are disabled.
376
346
  }),
377
- dim: new MeshBasicMaterial({
347
+ dim: new MeshBasicMaterial2({
378
348
  color: dimColorLinearSRGB,
379
349
  side: FrontSide
380
350
  // TODO: set to `DoubleSide` when hint facelets are disabled.
@@ -487,12 +457,12 @@ function getFaceletScale(options) {
487
457
  }
488
458
  return options.faceletScale;
489
459
  }
490
- var blackMesh = new MeshBasicMaterial({
460
+ var blackMesh = new MeshBasicMaterial2({
491
461
  color: 0,
492
462
  opacity: 1,
493
463
  transparent: true
494
464
  });
495
- var blackTranslucentMesh = new MeshBasicMaterial({
465
+ var blackTranslucentMesh = new MeshBasicMaterial2({
496
466
  color: 0,
497
467
  opacity: 0.3,
498
468
  transparent: true
@@ -810,9 +780,6 @@ var Cube3D = class extends Object3D {
810
780
  this.createCubie.bind(this, orbit, orbitFaceletInfo)
811
781
  );
812
782
  }
813
- if (this.#stickerless()) {
814
- addCubieBodyLighting(this);
815
- }
816
783
  const scale = cubeScale(this.#stickerless());
817
784
  this.scale.set(scale, scale, scale);
818
785
  if (this.options.experimentalStickeringMask) {
@@ -1097,7 +1064,7 @@ var Cube3D = class extends Object3D {
1097
1064
  axesInfo[piece.stickerFaces[i]],
1098
1065
  hint
1099
1066
  );
1100
- mesh.material = new MeshBasicMaterial({
1067
+ mesh.material = new MeshBasicMaterial2({
1101
1068
  map: texture,
1102
1069
  side: hint ? BackSide : DoubleSide,
1103
1070
  transparent: true
@@ -1246,8 +1213,6 @@ import { Vector3 as Vector34 } from "three/src/math/Vector3.js";
1246
1213
  // src/cubing/twisty/views/3D/puzzles/StickerlessPlan.ts
1247
1214
  var HINT_FACELET_ELEVATION = 0.5;
1248
1215
  var HINT_FACELET_SCALE = 0.85;
1249
- var BODY_MATERIAL_INDEX = 0;
1250
- var HINT_MATERIAL_INDEX = 1;
1251
1216
  var faceletMeshStickeringMasks = [
1252
1217
  "regular",
1253
1218
  "dim",
@@ -1470,7 +1435,6 @@ function newCubieShape(outwardFaces, slotWidth) {
1470
1435
  cubieBodyDimensions.roundingSegments
1471
1436
  );
1472
1437
  const bodyPositions = body.getAttribute("position").array;
1473
- const bodyNormals = body.getAttribute("normal").array;
1474
1438
  const bodyIndices = Array.from(body.getIndex().array);
1475
1439
  const faceVertexRanges = [];
1476
1440
  for (const group of body.groups) {
@@ -1482,9 +1446,8 @@ function newCubieShape(outwardFaces, slotWidth) {
1482
1446
  }
1483
1447
  faceVertexRanges[group.materialIndex] = { start, count: end - start };
1484
1448
  }
1485
- const positions = Array.from(bodyPositions);
1486
- const normals = Array.from(bodyNormals);
1487
- const indices = bodyIndices.slice();
1449
+ const hintPositions = [];
1450
+ const hintIndices = [];
1488
1451
  const hintVertexRanges = /* @__PURE__ */ new Map();
1489
1452
  const hintHalfWidth = HINT_FACELET_SCALE * slotWidth / 2;
1490
1453
  const hintDistance = slotWidth / 2 + HINT_FACELET_ELEVATION;
@@ -1495,7 +1458,7 @@ function newCubieShape(outwardFaces, slotWidth) {
1495
1458
  const normal2 = cubeFaceStyles[faceIdx].vector;
1496
1459
  u.set(normal2.y, normal2.z, normal2.x);
1497
1460
  v.crossVectors(normal2, u);
1498
- const start = positions.length / 3;
1461
+ const start = hintPositions.length / 3;
1499
1462
  for (const [su, sv] of [
1500
1463
  [-1, -1],
1501
1464
  [1, -1],
@@ -1503,44 +1466,39 @@ function newCubieShape(outwardFaces, slotWidth) {
1503
1466
  [-1, 1]
1504
1467
  ]) {
1505
1468
  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);
1469
+ hintPositions.push(corner.x, corner.y, corner.z);
1508
1470
  }
1509
- indices.push(start, start + 1, start + 2, start, start + 2, start + 3);
1471
+ hintIndices.push(start, start + 1, start + 2, start, start + 2, start + 3);
1510
1472
  hintVertexRanges.set(faceIdx, { start, count: 4 });
1511
1473
  }
1512
1474
  body.dispose();
1513
1475
  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,
1476
+ bodyPosition: new BufferAttribute3(bodyPositions.slice(), 3),
1477
+ bodyIndex: new BufferAttribute3(new Uint16Array(bodyIndices), 1),
1478
+ bodyVertexCount: bodyPositions.length / 3,
1479
+ hintPosition: new BufferAttribute3(new Float32Array(hintPositions), 3),
1480
+ hintIndex: new BufferAttribute3(new Uint16Array(hintIndices), 1),
1481
+ hintVertexCount: hintPositions.length / 3,
1520
1482
  faceVertexRanges,
1521
1483
  hintVertexRanges
1522
1484
  };
1523
1485
  }
1524
- function newCubiePiece(cubie, shape) {
1486
+ function newPieceMesh(position, index, vertexCount) {
1525
1487
  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
1533
- );
1488
+ geometry.setAttribute("position", position);
1489
+ geometry.setIndex(index);
1490
+ const colors = new BufferAttribute3(new Uint8Array(4 * vertexCount), 4, true);
1534
1491
  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);
1492
+ return { geometry, colors };
1493
+ }
1494
+ function newCubiePiece(cubie, shape) {
1495
+ const body = newPieceMesh(
1496
+ shape.bodyPosition,
1497
+ shape.bodyIndex,
1498
+ shape.bodyVertexCount
1499
+ );
1500
+ const hint = shape.hintVertexCount > 0 ? newPieceMesh(shape.hintPosition, shape.hintIndex, shape.hintVertexCount) : null;
1501
+ paintInternal(body.colors, shape.faceVertexRanges);
1544
1502
  const facelets = cubie.stickers.map((sticker) => ({
1545
1503
  ori: sticker.ori,
1546
1504
  faceStyle: sticker.faceIdx,
@@ -1553,8 +1511,8 @@ function newCubiePiece(cubie, shape) {
1553
1511
  orbit: cubie.orbit,
1554
1512
  ord: cubie.ord,
1555
1513
  home: new Matrix42().setPosition(cubie.center),
1556
- geometry,
1557
- colors,
1514
+ body,
1515
+ hint,
1558
1516
  facelets
1559
1517
  };
1560
1518
  }
@@ -1588,19 +1546,19 @@ import { DoubleSide as DoubleSide2, FrontSide as FrontSide2 } from "three/src/co
1588
1546
  import { BufferAttribute as BufferAttribute4 } from "three/src/core/BufferAttribute.js";
1589
1547
  import { BufferGeometry as BufferGeometry4 } from "three/src/core/BufferGeometry.js";
1590
1548
  import { Object3D as Object3D2 } from "three/src/core/Object3D.js";
1591
- import { MeshBasicMaterial as MeshBasicMaterial2 } from "three/src/materials/MeshBasicMaterial.js";
1549
+ import { MeshBasicMaterial as MeshBasicMaterial3 } from "three/src/materials/MeshBasicMaterial.js";
1592
1550
  import { Color as Color3 } from "three/src/math/Color.js";
1593
1551
  import { Vector3 as Vector35 } from "three/src/math/Vector3.js";
1594
1552
  import { Group as Group2 } from "three/src/objects/Group.js";
1595
1553
  import { Mesh as Mesh2 } from "three/src/objects/Mesh.js";
1596
- var foundationMaterial = new MeshBasicMaterial2({
1554
+ var foundationMaterial = new MeshBasicMaterial3({
1597
1555
  side: DoubleSide2,
1598
1556
  color: 0
1599
1557
  });
1600
- var invisMaterial = new MeshBasicMaterial2({
1558
+ var invisMaterial = new MeshBasicMaterial3({
1601
1559
  visible: false
1602
1560
  });
1603
- var basicStickerMaterial = new MeshBasicMaterial2({
1561
+ var basicStickerMaterial = new MeshBasicMaterial3({
1604
1562
  vertexColors: true
1605
1563
  });
1606
1564
  function dist(coords, a, b) {
@@ -1995,7 +1953,7 @@ var PG3D = class extends Object3D2 {
1995
1953
  if (stickerDat.stickers.length === 0) {
1996
1954
  throw Error("Reuse of stickerdat from pg; please don't do that.");
1997
1955
  }
1998
- this.hintMaterial = new MeshBasicMaterial2({
1956
+ this.hintMaterial = new MeshBasicMaterial3({
1999
1957
  vertexColors: true,
2000
1958
  transparent: true,
2001
1959
  opacity: 0.5
@@ -2414,7 +2372,7 @@ var PG3D = class extends Object3D2 {
2414
2372
  } else if (v === 1) {
2415
2373
  this.hintMaterial = this.stickerMaterial;
2416
2374
  } else {
2417
- this.hintMaterial = new MeshBasicMaterial2({
2375
+ this.hintMaterial = new MeshBasicMaterial3({
2418
2376
  vertexColors: true,
2419
2377
  transparent: true,
2420
2378
  opacity: v
@@ -2477,7 +2435,7 @@ var PG3D = class extends Object3D2 {
2477
2435
  this.stickerMaterialDisposable = false;
2478
2436
  }
2479
2437
  if (enabled) {
2480
- this.stickerMaterial = new MeshBasicMaterial2({
2438
+ this.stickerMaterial = new MeshBasicMaterial3({
2481
2439
  map: stickerTexture,
2482
2440
  side: FrontSide2,
2483
2441
  transparent: false
@@ -2491,7 +2449,7 @@ var PG3D = class extends Object3D2 {
2491
2449
  this.hintMaterialDisposable = false;
2492
2450
  }
2493
2451
  if (enabled) {
2494
- this.hintMaterial = new MeshBasicMaterial2({
2452
+ this.hintMaterial = new MeshBasicMaterial3({
2495
2453
  map: hintTexture,
2496
2454
  side: FrontSide2,
2497
2455
  transparent: true
@@ -2690,11 +2648,9 @@ function solidPieceGeometry(planes, options) {
2690
2648
  const withChamfers = options.chamfer > 0 ? working.concat(chamferPlanes(plain, working, options.chamfer, epsilon)) : working;
2691
2649
  const facets = (options.chamfer > 0 ? buildFacets(withChamfers, options.radius, epsilon) : plain) ?? plain;
2692
2650
  const positions = [];
2693
- const normals = [];
2694
2651
  const ranges = planes.map(() => []);
2695
2652
  const internalRanges = [];
2696
2653
  for (const facet of facets) {
2697
- const plane = withChamfers[facet.plane];
2698
2654
  const start = positions.length / 3;
2699
2655
  for (let i = 1; i < facet.vertices.length - 1; i++) {
2700
2656
  for (const vertex of [
@@ -2703,22 +2659,20 @@ function solidPieceGeometry(planes, options) {
2703
2659
  facet.vertices[i + 1]
2704
2660
  ]) {
2705
2661
  positions.push(vertex.x, vertex.y, vertex.z);
2706
- normals.push(plane.normal.x, plane.normal.y, plane.normal.z);
2707
2662
  }
2708
2663
  }
2709
2664
  const range = { start, count: positions.length / 3 - start };
2710
2665
  if (range.count === 0) {
2711
2666
  continue;
2712
2667
  }
2713
- if (plane.source === -1) {
2668
+ if (withChamfers[facet.plane].source === -1) {
2714
2669
  internalRanges.push(range);
2715
2670
  } else {
2716
- ranges[plane.source].push(range);
2671
+ ranges[withChamfers[facet.plane].source].push(range);
2717
2672
  }
2718
2673
  }
2719
2674
  return {
2720
2675
  positions: new Float32Array(positions),
2721
- normals: new Float32Array(normals),
2722
2676
  vertexCount: positions.length / 3,
2723
2677
  ranges,
2724
2678
  internalRanges
@@ -2805,6 +2759,8 @@ function solidPuzzlePlan(stickerDat) {
2805
2759
  return null;
2806
2760
  }
2807
2761
  let radius = 0;
2762
+ let faceletArea = 0;
2763
+ let faceletCount = 0;
2808
2764
  for (const sticker of stickers) {
2809
2765
  if (sticker.coords.length < 9 || sticker.coords.length % 3 !== 0) {
2810
2766
  return null;
@@ -2867,6 +2823,10 @@ function solidPuzzlePlan(stickerDat) {
2867
2823
  if (faceColors[sticker.face] !== sticker.color) {
2868
2824
  return null;
2869
2825
  }
2826
+ faceletCount++;
2827
+ 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;
2829
+ }
2870
2830
  piece.stickers.push({ sticker, polygon });
2871
2831
  for (let i = 0; i < polygon.length; i++) {
2872
2832
  const edgeKey = [
@@ -2885,16 +2845,30 @@ function solidPuzzlePlan(stickerDat) {
2885
2845
  if (!styles) {
2886
2846
  return null;
2887
2847
  }
2888
- const axes = stickerDat.axis.map(
2889
- (axis) => new Vector37(...axis.coordinates).normalize()
2848
+ const featureScale = Math.min(
2849
+ faceDistance,
2850
+ 1.5 * Math.sqrt(faceletArea / faceletCount)
2890
2851
  );
2852
+ const cuts = cutPlanes(
2853
+ order,
2854
+ edges,
2855
+ stickerDat.axis.map((axis) => new Vector37(...axis.coordinates).normalize()),
2856
+ quantum
2857
+ );
2858
+ if (!cuts) {
2859
+ return null;
2860
+ }
2891
2861
  const plans = [];
2892
2862
  for (const piece of order) {
2893
- const plan = newSolidPiece(piece, edges, axes, {
2894
- quantum,
2895
- radius,
2896
- faceDistance
2897
- });
2863
+ const plan = newSolidPiece(
2864
+ piece,
2865
+ { planes: cuts, edges },
2866
+ {
2867
+ quantum,
2868
+ radius,
2869
+ featureScale
2870
+ }
2871
+ );
2898
2872
  if (!plan) {
2899
2873
  return null;
2900
2874
  }
@@ -2902,11 +2876,74 @@ function solidPuzzlePlan(stickerDat) {
2902
2876
  }
2903
2877
  return { faceStyles: styles, pieces: plans, scale: PG_SCALE3 };
2904
2878
  }
2905
- function newSolidPiece(piece, edges, axes, sizes) {
2879
+ function cutPlanes(pieces, edges, axes, quantum) {
2880
+ const candidates = /* @__PURE__ */ new Map();
2881
+ const boundaryEdges = [];
2882
+ for (const piece of pieces) {
2883
+ const pieceKey = `${piece.orbit}/${piece.ord}`;
2884
+ for (const { polygon } of piece.stickers) {
2885
+ for (let i = 0; i < polygon.length; i++) {
2886
+ const a = polygon[i];
2887
+ const b = polygon[(i + 1) % polygon.length];
2888
+ const sharing = edges.get(
2889
+ [keyOf(a, quantum), keyOf(b, quantum)].sort().join("|")
2890
+ );
2891
+ if (!sharing || ![...sharing].some((key) => key !== pieceKey)) {
2892
+ continue;
2893
+ }
2894
+ boundaryEdges.push([a, b]);
2895
+ for (const axis of axes) {
2896
+ if (Math.abs(a.dot(axis) - b.dot(axis)) > quantum) {
2897
+ continue;
2898
+ }
2899
+ const offset = a.dot(axis);
2900
+ const key = `${keyOf(axis, 1e-4)}|${Math.round(offset / quantum)}`;
2901
+ const candidate = candidates.get(key);
2902
+ if (candidate) {
2903
+ candidate.uses++;
2904
+ } else {
2905
+ candidates.set(key, { normal: axis, offset, uses: 1 });
2906
+ }
2907
+ }
2908
+ }
2909
+ }
2910
+ }
2911
+ const cuts = [...candidates.values()].sort((a, b) => b.uses - a.uses);
2912
+ const kept = cuts.filter(({ normal: normal2, offset }) => {
2913
+ for (const piece of pieces) {
2914
+ let above = false;
2915
+ let below = false;
2916
+ for (const { polygon } of piece.stickers) {
2917
+ let onIt = true;
2918
+ for (const vertex of polygon) {
2919
+ above ||= vertex.dot(normal2) > offset + quantum;
2920
+ below ||= vertex.dot(normal2) < offset - quantum;
2921
+ onIt &&= Math.abs(vertex.dot(normal2) - offset) <= quantum;
2922
+ }
2923
+ if (onIt) {
2924
+ return false;
2925
+ }
2926
+ }
2927
+ if (above && below) {
2928
+ return false;
2929
+ }
2930
+ }
2931
+ return true;
2932
+ });
2933
+ for (const [a, b] of boundaryEdges) {
2934
+ const covered = kept.some(
2935
+ ({ normal: normal2, offset }) => Math.abs(a.dot(normal2) - offset) < quantum && Math.abs(b.dot(normal2) - offset) < quantum
2936
+ );
2937
+ if (!covered) {
2938
+ return null;
2939
+ }
2940
+ }
2941
+ return kept;
2942
+ }
2943
+ function newSolidPiece(piece, cuts, sizes) {
2906
2944
  if (piece.stickers.length === 0) {
2907
2945
  return null;
2908
2946
  }
2909
- const pieceKey = `${piece.orbit}/${piece.ord}`;
2910
2947
  const planes = [];
2911
2948
  const planeOfSticker = /* @__PURE__ */ new Map();
2912
2949
  const interior = new Vector37();
@@ -2914,7 +2951,7 @@ function newSolidPiece(piece, edges, axes, sizes) {
2914
2951
  for (const vertex of polygon) {
2915
2952
  interior.addScaledVector(
2916
2953
  vertex,
2917
- 0.5 / polygon.length / piece.stickers.length
2954
+ 1 / polygon.length / piece.stickers.length
2918
2955
  );
2919
2956
  }
2920
2957
  }
@@ -2927,88 +2964,88 @@ function newSolidPiece(piece, edges, axes, sizes) {
2927
2964
  color: sticker.face
2928
2965
  });
2929
2966
  }
2967
+ const pieceKey = `${piece.orbit}/${piece.ord}`;
2930
2968
  for (const { polygon } of piece.stickers) {
2931
2969
  for (let i = 0; i < polygon.length; i++) {
2932
2970
  const a = polygon[i];
2933
2971
  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)) {
2972
+ const sharing = cuts.edges.get(
2973
+ [keyOf(a, sizes.quantum), keyOf(b, sizes.quantum)].sort().join("|")
2974
+ );
2975
+ if (!sharing || ![...sharing].some((key) => key !== pieceKey)) {
2937
2976
  continue;
2938
2977
  }
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)) {
2978
+ const cut = cuts.planes.find(
2979
+ ({ normal: normal3, offset: offset2 }) => Math.abs(a.dot(normal3) - offset2) < sizes.quantum && Math.abs(b.dot(normal3) - offset2) < sizes.quantum
2980
+ );
2981
+ if (!cut) {
2948
2982
  return null;
2949
2983
  }
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
- }
2984
+ const outward = interior.dot(cut.normal) < cut.offset;
2985
+ const normal2 = outward ? cut.normal.clone() : cut.normal.clone().negate();
2986
+ const offset = outward ? cut.offset : -cut.offset;
2957
2987
  if (planes.some(
2958
- (plane) => plane.color === null && plane.normal.dot(normal2) > 0.9999 && Math.abs(plane.offset) < sizes.quantum
2988
+ (plane) => plane.color === null && plane.normal.dot(normal2) > 0.9999 && Math.abs(plane.offset - offset) < sizes.quantum
2959
2989
  )) {
2960
2990
  continue;
2961
2991
  }
2962
- planes.push({ normal: normal2, offset: 0, color: null });
2992
+ planes.push({ normal: normal2, offset, color: null });
2993
+ }
2994
+ }
2995
+ for (const cut of cuts.planes) {
2996
+ let touches = false;
2997
+ let above = false;
2998
+ let below = false;
2999
+ for (const { polygon } of piece.stickers) {
3000
+ for (const vertex of polygon) {
3001
+ const side = vertex.dot(cut.normal) - cut.offset;
3002
+ if (Math.abs(side) <= sizes.quantum) {
3003
+ touches = true;
3004
+ } else if (side > 0) {
3005
+ above = true;
3006
+ } else {
3007
+ below = true;
3008
+ }
3009
+ }
3010
+ }
3011
+ if (touches || above === below) {
3012
+ continue;
2963
3013
  }
3014
+ planes.push(
3015
+ above ? {
3016
+ normal: cut.normal.clone().negate(),
3017
+ offset: -cut.offset,
3018
+ color: null
3019
+ } : { normal: cut.normal.clone(), offset: cut.offset, color: null }
3020
+ );
2964
3021
  }
2965
3022
  const solid = solidPieceGeometry(planes, {
2966
- groove: SOLID_PIECE_GROOVE * sizes.faceDistance,
2967
- chamfer: SOLID_PIECE_CHAMFER * sizes.faceDistance,
3023
+ groove: SOLID_PIECE_GROOVE * sizes.featureScale,
3024
+ chamfer: SOLID_PIECE_CHAMFER * sizes.featureScale,
2968
3025
  radius: sizes.radius
2969
3026
  });
2970
3027
  if (!solid) {
2971
3028
  return null;
2972
3029
  }
2973
- const positions = Array.from(solid.positions);
2974
- const normals = Array.from(solid.normals);
3030
+ const hintPositions = [];
2975
3031
  const hintRanges = /* @__PURE__ */ new Map();
2976
3032
  for (const { sticker, polygon } of piece.stickers) {
2977
3033
  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);
3034
+ const hint2 = hintPolygon(polygon, normal2);
3035
+ const start = hintPositions.length / 3;
3036
+ for (let i = 1; i < hint2.length - 1; i++) {
3037
+ for (const vertex of [hint2[0], hint2[i], hint2[i + 1]]) {
3038
+ hintPositions.push(vertex.x, vertex.y, vertex.z);
2984
3039
  }
2985
3040
  }
2986
3041
  hintRanges.set(sticker, {
2987
3042
  start,
2988
- count: positions.length / 3 - start
3043
+ count: hintPositions.length / 3 - start
2989
3044
  });
2990
3045
  }
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
3009
- );
3010
- }
3011
- paintInternal(colors, solid.internalRanges);
3046
+ const body = newPieceMesh2(solid.positions);
3047
+ const hint = hintPositions.length > 0 ? newPieceMesh2(new Float32Array(hintPositions)) : null;
3048
+ paintInternal(body.colors, solid.internalRanges);
3012
3049
  const facelets = piece.stickers.map(({ sticker }) => ({
3013
3050
  ori: sticker.ori,
3014
3051
  faceStyle: sticker.face,
@@ -3028,18 +3065,29 @@ function newSolidPiece(piece, edges, axes, sizes) {
3028
3065
  ord: piece.ord,
3029
3066
  // The geometry is already where the piece belongs.
3030
3067
  home: new Matrix43(),
3031
- geometry,
3032
- colors,
3068
+ body,
3069
+ hint,
3033
3070
  facelets
3034
3071
  };
3035
3072
  }
3073
+ function newPieceMesh2(positions) {
3074
+ const geometry = new BufferGeometry5();
3075
+ geometry.setAttribute("position", new BufferAttribute5(positions, 3));
3076
+ const colors = new BufferAttribute5(
3077
+ new Uint8Array(4 * positions.length / 3),
3078
+ 4,
3079
+ true
3080
+ );
3081
+ geometry.setAttribute("color", colors);
3082
+ return { geometry, colors };
3083
+ }
3036
3084
 
3037
3085
  // src/cubing/twisty/views/3D/puzzles/Square1_3D.ts
3038
3086
  import { DoubleSide as DoubleSide3 } from "three/src/constants.js";
3039
3087
  import { BufferAttribute as BufferAttribute6 } from "three/src/core/BufferAttribute.js";
3040
3088
  import { BufferGeometry as BufferGeometry6 } from "three/src/core/BufferGeometry.js";
3041
3089
  import { Object3D as Object3D3 } from "three/src/core/Object3D.js";
3042
- import { MeshBasicMaterial as MeshBasicMaterial3 } from "three/src/materials/MeshBasicMaterial.js";
3090
+ import { MeshBasicMaterial as MeshBasicMaterial4 } from "three/src/materials/MeshBasicMaterial.js";
3043
3091
  import { Color as Color5 } from "three/src/math/Color.js";
3044
3092
  import { Quaternion as Quaternion2 } from "three/src/math/Quaternion.js";
3045
3093
  import { Vector3 as Vector38 } from "three/src/math/Vector3.js";
@@ -3297,11 +3345,10 @@ function stickerlessPieceGeometry(spec) {
3297
3345
  }
3298
3346
  const geometry = new BufferGeometry6();
3299
3347
  geometry.setAttribute("position", new BufferAttribute6(piece.positions, 3));
3300
- geometry.setAttribute("normal", new BufferAttribute6(piece.normals, 3));
3301
3348
  geometry.setAttribute("color", new BufferAttribute6(colors, 4, true));
3302
3349
  return geometry;
3303
3350
  }
3304
- var bodyMaterial = new MeshBasicMaterial3({
3351
+ var bodyMaterial = new MeshBasicMaterial4({
3305
3352
  color: new Color5(BODY_COLOR).convertLinearToSRGB(),
3306
3353
  side: DoubleSide3
3307
3354
  });
@@ -3313,7 +3360,7 @@ var stickerMaterialCache = /* @__PURE__ */ new Map();
3313
3360
  function stickerMaterial(color) {
3314
3361
  let material = stickerMaterialCache.get(color);
3315
3362
  if (!material) {
3316
- material = new MeshBasicMaterial3({
3363
+ material = new MeshBasicMaterial4({
3317
3364
  color: new Color5(color).convertLinearToSRGB(),
3318
3365
  side: DoubleSide3
3319
3366
  });
@@ -3468,9 +3515,6 @@ var Square1_3D = class extends Object3D3 {
3468
3515
  this.#equatorPieces.push(equator);
3469
3516
  this.add(equator);
3470
3517
  }
3471
- if (this.#stickerless) {
3472
- addCubieBodyLighting(this);
3473
- }
3474
3518
  this.scale.set(PUZZLE_SCALE, PUZZLE_SCALE, PUZZLE_SCALE);
3475
3519
  }
3476
3520
  scheduleRenderCallback;
@@ -3610,11 +3654,29 @@ import { BackSide as BackSide2 } from "three/src/constants.js";
3610
3654
  import { BufferAttribute as BufferAttribute7 } from "three/src/core/BufferAttribute.js";
3611
3655
  import { BufferGeometry as BufferGeometry7 } from "three/src/core/BufferGeometry.js";
3612
3656
  import { Object3D as Object3D4 } from "three/src/core/Object3D.js";
3613
- import { MeshBasicMaterial as MeshBasicMaterial4 } from "three/src/materials/MeshBasicMaterial.js";
3657
+ import { MeshBasicMaterial as MeshBasicMaterial5 } from "three/src/materials/MeshBasicMaterial.js";
3614
3658
  import { Matrix4 as Matrix44 } from "three/src/math/Matrix4.js";
3615
3659
  import { Vector3 as Vector39 } from "three/src/math/Vector3.js";
3660
+ import { BatchedMesh } from "three/src/objects/BatchedMesh.js";
3616
3661
  import { Mesh as Mesh4 } from "three/src/objects/Mesh.js";
3617
- var invisibleMaterial2 = new MeshBasicMaterial4({ visible: false });
3662
+ var invisibleMaterial2 = new MeshBasicMaterial5({ visible: false });
3663
+ function newBatch(meshes, material) {
3664
+ let vertices = 0;
3665
+ let indices = 0;
3666
+ for (const { geometry } of meshes) {
3667
+ vertices += geometry.getAttribute("position").count;
3668
+ indices += geometry.getIndex()?.count ?? 0;
3669
+ }
3670
+ return new BatchedMesh(meshes.length, vertices, indices, material);
3671
+ }
3672
+ function addToBatch(batch, mesh, home) {
3673
+ const geometryId = batch.addGeometry(mesh.geometry);
3674
+ const instance = batch.addInstance(geometryId);
3675
+ batch.setMatrixAt(instance, home);
3676
+ mesh.geometry.dispose();
3677
+ const range = batch.getGeometryRangeAt(geometryId);
3678
+ return { instance, vertexStart: range.vertexStart };
3679
+ }
3618
3680
  var Stickerless3D = class extends Object3D4 {
3619
3681
  constructor(scheduleRenderCallback, kpuzzle, stickerDat, plan, options = {}) {
3620
3682
  super();
@@ -3622,7 +3684,7 @@ var Stickerless3D = class extends Object3D4 {
3622
3684
  this.kpuzzle = kpuzzle;
3623
3685
  this.stickerDat = stickerDat;
3624
3686
  this.plan = plan;
3625
- this.#hintMaterial = new MeshBasicMaterial4({
3687
+ this.#hintMaterial = new MeshBasicMaterial5({
3626
3688
  vertexColors: true,
3627
3689
  transparent: true,
3628
3690
  // Hint facelets show the faces turned away from the camera, so they are
@@ -3630,20 +3692,35 @@ var Stickerless3D = class extends Object3D4 {
3630
3692
  side: BackSide2
3631
3693
  });
3632
3694
  this.#bodyMaterial = newVertexColorBodyMaterial();
3633
- this.#materials = [this.#bodyMaterial, this.#hintMaterial];
3634
- this.experimentalUpdateOptions({
3635
- hintFacelets: options.hintFacelets ?? "floating"
3636
- });
3637
3695
  for (const axis of stickerDat.axis) {
3638
3696
  this.#axesInfo[axis.quantumMove.family] = {
3639
3697
  axis: new Vector39(...axis.coordinates),
3640
3698
  order: axis.order
3641
3699
  };
3642
3700
  }
3701
+ this.#bodyBatch = newBatch(
3702
+ plan.pieces.map((piecePlan) => piecePlan.body),
3703
+ this.#bodyMaterial
3704
+ );
3705
+ this.#bodyBatch.sortObjects = false;
3706
+ this.#bodyBatch.perObjectFrustumCulled = false;
3707
+ this.add(this.#bodyBatch);
3708
+ const hints = plan.pieces.map((piecePlan) => piecePlan.hint).filter((hint) => hint !== null);
3709
+ if (hints.length > 0) {
3710
+ this.#hintBatch = newBatch(hints, this.#hintMaterial);
3711
+ this.#hintBatch.perObjectFrustumCulled = false;
3712
+ this.add(this.#hintBatch);
3713
+ }
3643
3714
  for (const piecePlan of plan.pieces) {
3644
3715
  this.#addPiece(piecePlan);
3645
3716
  }
3646
- addCubieBodyLighting(this);
3717
+ this.#bodyColors = this.#bodyBatch.geometry.getAttribute(
3718
+ "color"
3719
+ );
3720
+ this.#hintColors = this.#hintBatch?.geometry.getAttribute("color") ?? null;
3721
+ this.experimentalUpdateOptions({
3722
+ hintFacelets: options.hintFacelets ?? "floating"
3723
+ });
3647
3724
  for (const face2 of stickerDat.faces) {
3648
3725
  this.#addControlTarget(face2);
3649
3726
  }
@@ -3657,11 +3734,19 @@ var Stickerless3D = class extends Object3D4 {
3657
3734
  stickerDat;
3658
3735
  plan;
3659
3736
  #pieces = [];
3737
+ /**
3738
+ * One draw call for every piece of the puzzle, and one for every hint
3739
+ * facelet. A batch keeps a matrix per instance, so a move still moves the
3740
+ * pieces it sweeps and nothing else.
3741
+ */
3742
+ #bodyBatch;
3743
+ #hintBatch = null;
3744
+ #bodyColors;
3745
+ #hintColors = null;
3660
3746
  /** Indexed `[orbit][ori][ord]`, like the orbits of the `KPuzzle`. */
3661
3747
  #facelets = {};
3662
3748
  #axesInfo = {};
3663
3749
  #controlTargets = [];
3664
- #materials;
3665
3750
  #bodyMaterial;
3666
3751
  #hintMaterial;
3667
3752
  #appearances = /* @__PURE__ */ new Map();
@@ -3669,29 +3754,44 @@ var Stickerless3D = class extends Object3D4 {
3669
3754
  #turningPieces = [];
3670
3755
  /** Which pieces each quantum move sweeps, by the move's notation. */
3671
3756
  #turnedPieces = /* @__PURE__ */ new Map();
3672
- #dirtyColors = /* @__PURE__ */ new Set();
3757
+ /** The span of each color attribute that changed, so only it is uploaded. */
3758
+ #dirtyColors = /* @__PURE__ */ new Map();
3673
3759
  #lastPosition = null;
3674
3760
  #pendingStickeringUpdate = false;
3675
3761
  #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 };
3762
+ const bodyInstance = addToBatch(this.#bodyBatch, plan.body, plan.home);
3763
+ const hintInstance = plan.hint && this.#hintBatch ? addToBatch(this.#hintBatch, plan.hint, plan.home) : -1;
3764
+ const piece = {
3765
+ bodyInstance: bodyInstance.instance,
3766
+ hintInstance: hintInstance === -1 ? -1 : hintInstance.instance,
3767
+ home: plan.home,
3768
+ matrix: plan.home.clone(),
3769
+ turning: false
3770
+ };
3681
3771
  this.#pieces.push(piece);
3772
+ const shift = (ranges, offset) => ranges.map((range) => ({
3773
+ start: range.start + offset,
3774
+ count: range.count
3775
+ }));
3682
3776
  const orbitFacelets = this.#facelets[plan.orbit] ??= [];
3683
3777
  for (const facelet of plan.facelets) {
3684
3778
  (orbitFacelets[facelet.ori] ??= [])[plan.ord] = {
3685
3779
  piece,
3686
3780
  faceStyle: facelet.faceStyle,
3687
- body: facelet.body,
3688
- hint: facelet.hint,
3781
+ body: shift(facelet.body, bodyInstance.vertexStart),
3782
+ hint: hintInstance === -1 ? [] : shift(facelet.hint, hintInstance.vertexStart),
3689
3783
  appearanceKey: -1,
3690
3784
  mask: "regular",
3691
3785
  hintMask: "regular"
3692
3786
  };
3693
3787
  }
3694
3788
  }
3789
+ #setMatrix(piece) {
3790
+ this.#bodyBatch.setMatrixAt(piece.bodyInstance, piece.matrix);
3791
+ if (piece.hintInstance !== -1) {
3792
+ this.#hintBatch.setMatrixAt(piece.hintInstance, piece.matrix);
3793
+ }
3794
+ }
3695
3795
  #addControlTarget(face2) {
3696
3796
  const { coords } = face2;
3697
3797
  const triangles = coords.length / 3 - 2;
@@ -3716,9 +3816,10 @@ var Stickerless3D = class extends Object3D4 {
3716
3816
  this.#controlTargets.push(mesh);
3717
3817
  }
3718
3818
  dispose() {
3719
- for (const piece of this.#pieces) {
3720
- piece.mesh.geometry.dispose();
3721
- }
3819
+ this.#bodyBatch.dispose();
3820
+ this.#bodyBatch.geometry.dispose();
3821
+ this.#hintBatch?.dispose();
3822
+ this.#hintBatch?.geometry.dispose();
3722
3823
  this.#bodyMaterial.dispose();
3723
3824
  this.#hintMaterial.dispose();
3724
3825
  }
@@ -3809,8 +3910,8 @@ var Stickerless3D = class extends Object3D4 {
3809
3910
  }
3810
3911
  /** @deprecated */
3811
3912
  experimentalUpdateOptions(options) {
3812
- if (options.hintFacelets !== void 0) {
3813
- this.#materials[HINT_MATERIAL_INDEX] = options.hintFacelets === "none" ? invisibleMaterial2 : this.#hintMaterial;
3913
+ if (options.hintFacelets !== void 0 && this.#hintBatch) {
3914
+ this.#hintBatch.visible = options.hintFacelets !== "none";
3814
3915
  this.scheduleRenderCallback();
3815
3916
  }
3816
3917
  }
@@ -3829,10 +3930,11 @@ var Stickerless3D = class extends Object3D4 {
3829
3930
  this.#lastPosition = position;
3830
3931
  this.#pendingStickeringUpdate = false;
3831
3932
  }
3832
- for (const piece of this.#turningPieces) {
3833
- piece.mesh.matrix.copy(piece.home);
3933
+ const wereTurning = this.#turningPieces;
3934
+ for (const piece of wereTurning) {
3935
+ piece.turning = false;
3834
3936
  }
3835
- this.#turningPieces.length = 0;
3937
+ this.#turningPieces = [];
3836
3938
  const turnMatrix = new Matrix44();
3837
3939
  for (const moveProgress of position.movesInProgress) {
3838
3940
  const move = moveProgress.move;
@@ -3849,10 +3951,23 @@ var Stickerless3D = class extends Object3D4 {
3849
3951
  -smootherStep(moveProgress.fraction) * moveProgress.direction * unswizzled.amount * TAU / axisInfo.order
3850
3952
  );
3851
3953
  for (const piece of this.#piecesTurnedBy(move)) {
3852
- piece.mesh.matrix.premultiply(turnMatrix);
3853
- this.#turningPieces.push(piece);
3954
+ if (!piece.turning) {
3955
+ piece.turning = true;
3956
+ piece.matrix.copy(piece.home);
3957
+ this.#turningPieces.push(piece);
3958
+ }
3959
+ piece.matrix.premultiply(turnMatrix);
3854
3960
  }
3855
3961
  }
3962
+ for (const piece of wereTurning) {
3963
+ if (!piece.turning) {
3964
+ piece.matrix.copy(piece.home);
3965
+ this.#setMatrix(piece);
3966
+ }
3967
+ }
3968
+ for (const piece of this.#turningPieces) {
3969
+ this.#setMatrix(piece);
3970
+ }
3856
3971
  this.scheduleRenderCallback();
3857
3972
  }
3858
3973
  /**
@@ -3935,17 +4050,34 @@ var Stickerless3D = class extends Object3D4 {
3935
4050
  source.mask,
3936
4051
  source.hintMask
3937
4052
  );
3938
- writeColor(slot.piece.colors, slot.body, appearance);
3939
- writeColor(slot.piece.colors, slot.hint, appearance, 4);
3940
- this.#dirtyColors.add(slot.piece.colors);
4053
+ this.#paint(this.#bodyColors, slot.body, appearance, 0);
4054
+ if (this.#hintColors) {
4055
+ this.#paint(this.#hintColors, slot.hint, appearance, 4);
4056
+ }
3941
4057
  }
3942
4058
  }
3943
4059
  }
3944
- for (const colors of this.#dirtyColors) {
4060
+ for (const [colors, span] of this.#dirtyColors) {
4061
+ colors.addUpdateRange(4 * span.first, 4 * (span.last - span.first + 1));
3945
4062
  colors.needsUpdate = true;
3946
4063
  }
3947
4064
  this.#dirtyColors.clear();
3948
4065
  }
4066
+ #paint(colors, ranges, appearance, offset) {
4067
+ if (ranges.length === 0) {
4068
+ return;
4069
+ }
4070
+ writeColor(colors, ranges, appearance, offset);
4071
+ let span = this.#dirtyColors.get(colors);
4072
+ if (!span) {
4073
+ span = { first: Number.POSITIVE_INFINITY, last: 0 };
4074
+ this.#dirtyColors.set(colors, span);
4075
+ }
4076
+ for (const range of ranges) {
4077
+ span.first = Math.min(span.first, range.start);
4078
+ span.last = Math.max(span.last, range.start + range.count - 1);
4079
+ }
4080
+ }
3949
4081
  };
3950
4082
 
3951
4083
  // src/cubing/twisty/heavy-code-imports/dynamic-entries/twisty-dynamic-3d.ts
@@ -4033,4 +4165,4 @@ export {
4033
4165
  pg3dShim,
4034
4166
  square1_3DShim
4035
4167
  };
4036
- //# sourceMappingURL=twisty-dynamic-3d-SLKSUJTE.js.map
4168
+ //# sourceMappingURL=twisty-dynamic-3d-MF76FSDA.js.map