@broberg/bodymap 0.2.8 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/three.d.ts CHANGED
@@ -138,6 +138,21 @@ interface BodyMap3DUiLabels {
138
138
  female: string;
139
139
  hoverHint: string;
140
140
  }
141
+ /**
142
+ * How much of the RUNNER-UP region bleeds into this vertex (F052.21).
143
+ *
144
+ * Takes SQUARED distances — what the assignment loop already has.
145
+ *
146
+ * ratio = d1 / (d1 + d2) → 0 at the anchor · 0.5 where two anchors are equidistant
147
+ *
148
+ * Returns 0 well inside a region and rises to 0.5 exactly at the seam, so the
149
+ * nearest region ALWAYS dominates and a vertex can never be painted mostly as
150
+ * its neighbour. Smoothstepped, so the fade has no visible start line of its own.
151
+ *
152
+ * This is a look, not a contract: hit-testing never sees this number, and the
153
+ * PainReport is unchanged.
154
+ */
155
+ declare function seamBlend(nearestSq: number, secondSq: number): number;
141
156
  interface BodyMap3DProps {
142
157
  /** URLs of the male/female body GLBs (you host them; reference GLBs ship under `@broberg/bodymap/models/`). */
143
158
  models: BodyMap3DModels;
@@ -171,4 +186,4 @@ interface BodyMap3DProps {
171
186
  }
172
187
  declare function BodyMap3D(props: BodyMap3DProps): react.JSX.Element;
173
188
 
174
- export { BodyMap3D, type BodyMap3DModels, type BodyMap3DProps, type BodyMap3DSex, type BodyMap3DUiLabels, serializeReport };
189
+ export { BodyMap3D, type BodyMap3DModels, type BodyMap3DProps, type BodyMap3DSex, type BodyMap3DUiLabels, seamBlend, serializeReport };
package/dist/three.js CHANGED
@@ -214,6 +214,17 @@ var ANCHORS = {
214
214
  foot_left: [-0.1, 0.02, 0.11],
215
215
  foot_right: [0.1, 0.02, 0.11]
216
216
  };
217
+ var SEAM = 0.18;
218
+ function seamBlend(nearestSq, secondSq) {
219
+ const d1 = Math.sqrt(nearestSq), d2 = Math.sqrt(secondSq);
220
+ const sum = d1 + d2;
221
+ if (!(sum > 0)) return 0;
222
+ const ratio = d1 / sum;
223
+ const t = (ratio - (0.5 - SEAM)) / SEAM;
224
+ if (t <= 0) return 0;
225
+ if (t >= 1) return 0.5;
226
+ return t * t * (3 - 2 * t) * 0.5;
227
+ }
217
228
  var ANCHOR_KEYS = Object.keys(ANCHORS);
218
229
  for (const k of ANCHOR_KEYS) ANCHORS[k][0] = -ANCHORS[k][0];
219
230
  function mergeLabels(locale, overrides) {
@@ -356,6 +367,8 @@ function BodyMap3D(props) {
356
367
  let modelRoot = null;
357
368
  let bodyMesh = null;
358
369
  let vertexRegion = [];
370
+ let vertexNeighbour = [];
371
+ let vertexBlend = new Float32Array(0);
359
372
  let colorAttr = null;
360
373
  let hovered = null;
361
374
  const loader = new GLTFLoader();
@@ -367,14 +380,32 @@ function BodyMap3D(props) {
367
380
  if (selectedRef.current === key) return paletteRef.current.selected;
368
381
  return baseColorFor(key, paletteRef.current);
369
382
  };
370
- const colorRegion = (key, hex) => {
383
+ const tmp2 = new THREE.Color();
384
+ const colourOf = (key) => hovered === key ? paletteRef.current.hover : restingHex(key);
385
+ const paint = () => {
371
386
  if (!colorAttr) return;
372
- tmp.set(hex);
373
- for (let i = 0; i < vertexRegion.length; i++) if (vertexRegion[i] === key) colorAttr.setXYZ(i, tmp.r, tmp.g, tmp.b);
387
+ const cache = /* @__PURE__ */ new Map();
388
+ const col = (k) => {
389
+ let c = cache.get(k);
390
+ if (!c) {
391
+ c = new THREE.Color(colourOf(k));
392
+ cache.set(k, c);
393
+ }
394
+ return c;
395
+ };
396
+ for (let i = 0; i < vertexRegion.length; i++) {
397
+ tmp.copy(col(vertexRegion[i]));
398
+ const w = vertexBlend[i];
399
+ if (w > 0) {
400
+ tmp2.copy(col(vertexNeighbour[i]));
401
+ tmp.lerp(tmp2, w);
402
+ }
403
+ colorAttr.setXYZ(i, tmp.r, tmp.g, tmp.b);
404
+ }
374
405
  colorAttr.needsUpdate = true;
375
406
  };
376
407
  const refresh = () => {
377
- for (const k of ANCHOR_KEYS) colorRegion(k, hovered === k ? paletteRef.current.hover : restingHex(k));
408
+ paint();
378
409
  renderFrame();
379
410
  };
380
411
  const loadModel = (which) => {
@@ -403,18 +434,27 @@ function BodyMap3D(props) {
403
434
  const n = pos.count;
404
435
  const cols = new Float32Array(n * 3);
405
436
  vertexRegion = new Array(n);
437
+ vertexNeighbour = new Array(n);
438
+ vertexBlend = new Float32Array(n);
406
439
  const v = new THREE.Vector3();
407
440
  for (let i = 0; i < n; i++) {
408
441
  v.fromBufferAttribute(pos, i).applyMatrix4(bodyMesh.matrixWorld);
409
- let best = 0, bd = Infinity;
442
+ let best = 0, second = 0, bd = Infinity, sd = Infinity;
410
443
  for (let a = 0; a < anchorVecs.length; a++) {
411
444
  const d = v.distanceToSquared(anchorVecs[a]);
412
445
  if (d < bd) {
446
+ sd = bd;
447
+ second = best;
413
448
  bd = d;
414
449
  best = a;
450
+ } else if (d < sd) {
451
+ sd = d;
452
+ second = a;
415
453
  }
416
454
  }
417
455
  vertexRegion[i] = ANCHOR_KEYS[best];
456
+ vertexNeighbour[i] = ANCHOR_KEYS[second];
457
+ vertexBlend[i] = seamBlend(bd, sd);
418
458
  }
419
459
  colorAttr = new THREE.BufferAttribute(cols, 3);
420
460
  geo.setAttribute("color", colorAttr);
@@ -474,10 +514,8 @@ function BodyMap3D(props) {
474
514
  if ((e.buttons || 0) !== 0) return;
475
515
  const k = pick(e.clientX, e.clientY);
476
516
  if (k === hovered) return;
477
- const prev = hovered;
478
517
  hovered = k;
479
- if (prev) colorRegion(prev, restingHex(prev));
480
- if (k) colorRegion(k, paletteRef.current.hover);
518
+ paint();
481
519
  canvas.style.cursor = k ? "pointer" : "default";
482
520
  renderFrame();
483
521
  };
@@ -586,6 +624,6 @@ function BodyMap3D(props) {
586
624
  ] });
587
625
  }
588
626
 
589
- export { BodyMap3D, serializeReport };
627
+ export { BodyMap3D, seamBlend, serializeReport };
590
628
  //# sourceMappingURL=three.js.map
591
629
  //# sourceMappingURL=three.js.map