@broberg/bodymap 0.2.8 → 0.4.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/README.md +75 -0
- package/dist/index.cjs +22 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +70 -1
- package/dist/index.d.ts +70 -1
- package/dist/index.js +20 -1
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +32 -1
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +45 -1
- package/dist/react.d.ts +45 -1
- package/dist/react.js +32 -1
- package/dist/react.js.map +1 -1
- package/dist/three.cjs +69 -8
- package/dist/three.cjs.map +1 -1
- package/dist/three.d.cts +59 -1
- package/dist/three.d.ts +59 -1
- package/dist/three.js +69 -9
- package/dist/three.js.map +1 -1
- package/package.json +1 -1
package/dist/three.cjs
CHANGED
|
@@ -84,6 +84,25 @@ function isSelectable(key, config = {}) {
|
|
|
84
84
|
if (s?.visible === false) return false;
|
|
85
85
|
return s?.selectable ?? true;
|
|
86
86
|
}
|
|
87
|
+
var VIBRATION_PATTERNS = {
|
|
88
|
+
select: [12],
|
|
89
|
+
clear: [8, 40, 8],
|
|
90
|
+
ignore: []
|
|
91
|
+
};
|
|
92
|
+
function requestVibration(pattern, nav = globalThis.navigator) {
|
|
93
|
+
if (pattern.length === 0) return "skipped";
|
|
94
|
+
if (typeof nav?.vibrate !== "function") return "unsupported";
|
|
95
|
+
try {
|
|
96
|
+
return nav.vibrate([...pattern]) ? "requested" : "declined";
|
|
97
|
+
} catch {
|
|
98
|
+
return "declined";
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function emitFeedback(outcome, region, opts = {}) {
|
|
102
|
+
opts.onFeedback?.({ outcome, region });
|
|
103
|
+
if (opts.haptics === false) return "skipped";
|
|
104
|
+
return requestVibration(VIBRATION_PATTERNS[outcome], opts.nav);
|
|
105
|
+
}
|
|
87
106
|
var defaultUi = {
|
|
88
107
|
text: "#1e293b",
|
|
89
108
|
mutedText: "#475569",
|
|
@@ -236,6 +255,17 @@ var ANCHORS = {
|
|
|
236
255
|
foot_left: [-0.1, 0.02, 0.11],
|
|
237
256
|
foot_right: [0.1, 0.02, 0.11]
|
|
238
257
|
};
|
|
258
|
+
var SEAM = 0.18;
|
|
259
|
+
function seamBlend(nearestSq, secondSq) {
|
|
260
|
+
const d1 = Math.sqrt(nearestSq), d2 = Math.sqrt(secondSq);
|
|
261
|
+
const sum = d1 + d2;
|
|
262
|
+
if (!(sum > 0)) return 0;
|
|
263
|
+
const ratio = d1 / sum;
|
|
264
|
+
const t = (ratio - (0.5 - SEAM)) / SEAM;
|
|
265
|
+
if (t <= 0) return 0;
|
|
266
|
+
if (t >= 1) return 0.5;
|
|
267
|
+
return t * t * (3 - 2 * t) * 0.5;
|
|
268
|
+
}
|
|
239
269
|
var ANCHOR_KEYS = Object.keys(ANCHORS);
|
|
240
270
|
for (const k of ANCHOR_KEYS) ANCHORS[k][0] = -ANCHORS[k][0];
|
|
241
271
|
function mergeLabels(locale, overrides) {
|
|
@@ -276,6 +306,8 @@ function BodyMap3D(props) {
|
|
|
276
306
|
autoRotate = true,
|
|
277
307
|
canvasHeight = "60vh",
|
|
278
308
|
showRegionCode = true,
|
|
309
|
+
onFeedback,
|
|
310
|
+
haptics,
|
|
279
311
|
className
|
|
280
312
|
} = props;
|
|
281
313
|
const chrome = uiColors(palette);
|
|
@@ -378,6 +410,8 @@ function BodyMap3D(props) {
|
|
|
378
410
|
let modelRoot = null;
|
|
379
411
|
let bodyMesh = null;
|
|
380
412
|
let vertexRegion = [];
|
|
413
|
+
let vertexNeighbour = [];
|
|
414
|
+
let vertexBlend = new Float32Array(0);
|
|
381
415
|
let colorAttr = null;
|
|
382
416
|
let hovered = null;
|
|
383
417
|
const loader = new GLTFLoader_js.GLTFLoader();
|
|
@@ -389,14 +423,32 @@ function BodyMap3D(props) {
|
|
|
389
423
|
if (selectedRef.current === key) return paletteRef.current.selected;
|
|
390
424
|
return baseColorFor(key, paletteRef.current);
|
|
391
425
|
};
|
|
392
|
-
const
|
|
426
|
+
const tmp2 = new THREE__namespace.Color();
|
|
427
|
+
const colourOf = (key) => hovered === key ? paletteRef.current.hover : restingHex(key);
|
|
428
|
+
const paint = () => {
|
|
393
429
|
if (!colorAttr) return;
|
|
394
|
-
|
|
395
|
-
|
|
430
|
+
const cache = /* @__PURE__ */ new Map();
|
|
431
|
+
const col = (k) => {
|
|
432
|
+
let c = cache.get(k);
|
|
433
|
+
if (!c) {
|
|
434
|
+
c = new THREE__namespace.Color(colourOf(k));
|
|
435
|
+
cache.set(k, c);
|
|
436
|
+
}
|
|
437
|
+
return c;
|
|
438
|
+
};
|
|
439
|
+
for (let i = 0; i < vertexRegion.length; i++) {
|
|
440
|
+
tmp.copy(col(vertexRegion[i]));
|
|
441
|
+
const w = vertexBlend[i];
|
|
442
|
+
if (w > 0) {
|
|
443
|
+
tmp2.copy(col(vertexNeighbour[i]));
|
|
444
|
+
tmp.lerp(tmp2, w);
|
|
445
|
+
}
|
|
446
|
+
colorAttr.setXYZ(i, tmp.r, tmp.g, tmp.b);
|
|
447
|
+
}
|
|
396
448
|
colorAttr.needsUpdate = true;
|
|
397
449
|
};
|
|
398
450
|
const refresh = () => {
|
|
399
|
-
|
|
451
|
+
paint();
|
|
400
452
|
renderFrame();
|
|
401
453
|
};
|
|
402
454
|
const loadModel = (which) => {
|
|
@@ -425,18 +477,27 @@ function BodyMap3D(props) {
|
|
|
425
477
|
const n = pos.count;
|
|
426
478
|
const cols = new Float32Array(n * 3);
|
|
427
479
|
vertexRegion = new Array(n);
|
|
480
|
+
vertexNeighbour = new Array(n);
|
|
481
|
+
vertexBlend = new Float32Array(n);
|
|
428
482
|
const v = new THREE__namespace.Vector3();
|
|
429
483
|
for (let i = 0; i < n; i++) {
|
|
430
484
|
v.fromBufferAttribute(pos, i).applyMatrix4(bodyMesh.matrixWorld);
|
|
431
|
-
let best = 0, bd = Infinity;
|
|
485
|
+
let best = 0, second = 0, bd = Infinity, sd = Infinity;
|
|
432
486
|
for (let a = 0; a < anchorVecs.length; a++) {
|
|
433
487
|
const d = v.distanceToSquared(anchorVecs[a]);
|
|
434
488
|
if (d < bd) {
|
|
489
|
+
sd = bd;
|
|
490
|
+
second = best;
|
|
435
491
|
bd = d;
|
|
436
492
|
best = a;
|
|
493
|
+
} else if (d < sd) {
|
|
494
|
+
sd = d;
|
|
495
|
+
second = a;
|
|
437
496
|
}
|
|
438
497
|
}
|
|
439
498
|
vertexRegion[i] = ANCHOR_KEYS[best];
|
|
499
|
+
vertexNeighbour[i] = ANCHOR_KEYS[second];
|
|
500
|
+
vertexBlend[i] = seamBlend(bd, sd);
|
|
440
501
|
}
|
|
441
502
|
colorAttr = new THREE__namespace.BufferAttribute(cols, 3);
|
|
442
503
|
geo.setAttribute("color", colorAttr);
|
|
@@ -496,10 +557,8 @@ function BodyMap3D(props) {
|
|
|
496
557
|
if ((e.buttons || 0) !== 0) return;
|
|
497
558
|
const k = pick(e.clientX, e.clientY);
|
|
498
559
|
if (k === hovered) return;
|
|
499
|
-
const prev = hovered;
|
|
500
560
|
hovered = k;
|
|
501
|
-
|
|
502
|
-
if (k) colorRegion(k, paletteRef.current.hover);
|
|
561
|
+
paint();
|
|
503
562
|
canvas.style.cursor = k ? "pointer" : "default";
|
|
504
563
|
renderFrame();
|
|
505
564
|
};
|
|
@@ -569,6 +628,7 @@ function BodyMap3D(props) {
|
|
|
569
628
|
};
|
|
570
629
|
pickRef.current = (k) => {
|
|
571
630
|
const what = decidePick(k, reportRef.current, configRef.current ?? {});
|
|
631
|
+
emitFeedback(what, k, { onFeedback, haptics });
|
|
572
632
|
if (what === "clear") removePain(k);
|
|
573
633
|
else if (what === "select") setSelected(k);
|
|
574
634
|
return what;
|
|
@@ -609,6 +669,7 @@ function BodyMap3D(props) {
|
|
|
609
669
|
}
|
|
610
670
|
|
|
611
671
|
exports.BodyMap3D = BodyMap3D;
|
|
672
|
+
exports.seamBlend = seamBlend;
|
|
612
673
|
exports.serializeReport = serializeReport;
|
|
613
674
|
//# sourceMappingURL=three.cjs.map
|
|
614
675
|
//# sourceMappingURL=three.cjs.map
|