@broberg/bodymap 0.7.1 → 0.8.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 +37 -0
- package/dist/index.cjs +3 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -2
- package/dist/index.d.ts +25 -2
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +15 -1
- package/dist/react.d.ts +15 -1
- package/dist/react.js.map +1 -1
- package/dist/three.cjs +33 -6
- package/dist/three.cjs.map +1 -1
- package/dist/three.d.cts +50 -2
- package/dist/three.d.ts +50 -2
- package/dist/three.js +32 -7
- package/dist/three.js.map +1 -1
- package/package.json +1 -1
package/dist/three.d.cts
CHANGED
|
@@ -97,8 +97,22 @@ interface BodymapUiColors {
|
|
|
97
97
|
text?: string;
|
|
98
98
|
/** Secondary text — section labels, the empty-state hint. */
|
|
99
99
|
mutedText?: string;
|
|
100
|
-
/** Panel background. */
|
|
100
|
+
/** Panel background — the card, its buttons, the empty hint. */
|
|
101
101
|
panelBg?: string;
|
|
102
|
+
/**
|
|
103
|
+
* THE STAGE: the fullscreen backdrop, the canvas frame, and the three.js
|
|
104
|
+
* scene behind the body. A different surface from `panelBg` with an opposite
|
|
105
|
+
* need — see F052.29, where one field paints both and no value is right for
|
|
106
|
+
* either.
|
|
107
|
+
*
|
|
108
|
+
* 3D only. The 2D renderer has no canvas and no fullscreen, so it ignores
|
|
109
|
+
* this; it is declared here because a stage is a shared idea, not because
|
|
110
|
+
* every renderer has one.
|
|
111
|
+
*
|
|
112
|
+
* Defaults to {@link STAGE_BG}. Pass `'#fff'` for the pre-0.8.0 white
|
|
113
|
+
* fullscreen backdrop.
|
|
114
|
+
*/
|
|
115
|
+
stageBg?: string;
|
|
102
116
|
/** Panel + control borders. */
|
|
103
117
|
border?: string;
|
|
104
118
|
/** Background behind the region-code badge. */
|
|
@@ -193,6 +207,40 @@ interface BodyMap3DUiLabels {
|
|
|
193
207
|
* PainReport is unchanged.
|
|
194
208
|
*/
|
|
195
209
|
declare function seamBlend(nearestSq: number, secondSq: number): number;
|
|
210
|
+
/**
|
|
211
|
+
* F052.23 — how much of the NEIGHBOUR region's colour a vertex takes.
|
|
212
|
+
*
|
|
213
|
+
* Extracted from the paint loop for one reason: happy-dom cannot run WebGL, so
|
|
214
|
+
* the fix that put the seam blend behind an opt-in had no automated guard at
|
|
215
|
+
* all. A regression here is a WCAG failure on a public-health surface (measured:
|
|
216
|
+
* 4.71:1 without the blend, 3.59:1 with it, averaged over a small mark), which
|
|
217
|
+
* is not the kind of thing that should rely on someone re-measuring by hand.
|
|
218
|
+
*
|
|
219
|
+
* The invariant worth asserting is not "off returns 0 for this blend value" —
|
|
220
|
+
* it is that off returns 0 for EVERY blend value, so the shipped default can
|
|
221
|
+
* never mix body colour into a mark.
|
|
222
|
+
*/
|
|
223
|
+
declare function seamWeight(seamEnabled: boolean, blend: number): number;
|
|
224
|
+
/**
|
|
225
|
+
* F052.25 — recompute vertex normals ONLY when the file shipped none.
|
|
226
|
+
*
|
|
227
|
+
* Same reason as above: this single condition WAS the "lines on the body" bug,
|
|
228
|
+
* and it lived on the one code path the unit suite cannot execute. Takes a
|
|
229
|
+
* structural slice rather than a THREE.BufferGeometry so it is testable without
|
|
230
|
+
* a GL context.
|
|
231
|
+
*
|
|
232
|
+
* It performs the call as well as the check, deliberately. A predicate alone
|
|
233
|
+
* would let a test pass while the call site did the opposite with it — and what
|
|
234
|
+
* the acceptance criterion asks for is the EFFECT: a model that ships normals
|
|
235
|
+
* must come out the other side with those same normals, untouched.
|
|
236
|
+
*
|
|
237
|
+
* Returns true when it computed (the model had none, and would otherwise render
|
|
238
|
+
* flat), false when it left the authored normals alone.
|
|
239
|
+
*/
|
|
240
|
+
declare function ensureNormals(geo: {
|
|
241
|
+
getAttribute(name: string): unknown;
|
|
242
|
+
computeVertexNormals(): void;
|
|
243
|
+
}): boolean;
|
|
196
244
|
interface BodyMap3DProps {
|
|
197
245
|
/** URLs of the male/female body GLBs (you host them; reference GLBs ship under `@broberg/bodymap/models/`). */
|
|
198
246
|
models: BodyMap3DModels;
|
|
@@ -264,4 +312,4 @@ interface BodyMap3DProps {
|
|
|
264
312
|
}
|
|
265
313
|
declare function BodyMap3D(props: BodyMap3DProps): react.JSX.Element;
|
|
266
314
|
|
|
267
|
-
export { BodyMap3D, type BodyMap3DModels, type BodyMap3DProps, type BodyMap3DSex, type BodyMap3DUiLabels, seamBlend, serializeReport };
|
|
315
|
+
export { BodyMap3D, type BodyMap3DModels, type BodyMap3DProps, type BodyMap3DSex, type BodyMap3DUiLabels, ensureNormals, seamBlend, seamWeight, serializeReport };
|
package/dist/three.d.ts
CHANGED
|
@@ -97,8 +97,22 @@ interface BodymapUiColors {
|
|
|
97
97
|
text?: string;
|
|
98
98
|
/** Secondary text — section labels, the empty-state hint. */
|
|
99
99
|
mutedText?: string;
|
|
100
|
-
/** Panel background. */
|
|
100
|
+
/** Panel background — the card, its buttons, the empty hint. */
|
|
101
101
|
panelBg?: string;
|
|
102
|
+
/**
|
|
103
|
+
* THE STAGE: the fullscreen backdrop, the canvas frame, and the three.js
|
|
104
|
+
* scene behind the body. A different surface from `panelBg` with an opposite
|
|
105
|
+
* need — see F052.29, where one field paints both and no value is right for
|
|
106
|
+
* either.
|
|
107
|
+
*
|
|
108
|
+
* 3D only. The 2D renderer has no canvas and no fullscreen, so it ignores
|
|
109
|
+
* this; it is declared here because a stage is a shared idea, not because
|
|
110
|
+
* every renderer has one.
|
|
111
|
+
*
|
|
112
|
+
* Defaults to {@link STAGE_BG}. Pass `'#fff'` for the pre-0.8.0 white
|
|
113
|
+
* fullscreen backdrop.
|
|
114
|
+
*/
|
|
115
|
+
stageBg?: string;
|
|
102
116
|
/** Panel + control borders. */
|
|
103
117
|
border?: string;
|
|
104
118
|
/** Background behind the region-code badge. */
|
|
@@ -193,6 +207,40 @@ interface BodyMap3DUiLabels {
|
|
|
193
207
|
* PainReport is unchanged.
|
|
194
208
|
*/
|
|
195
209
|
declare function seamBlend(nearestSq: number, secondSq: number): number;
|
|
210
|
+
/**
|
|
211
|
+
* F052.23 — how much of the NEIGHBOUR region's colour a vertex takes.
|
|
212
|
+
*
|
|
213
|
+
* Extracted from the paint loop for one reason: happy-dom cannot run WebGL, so
|
|
214
|
+
* the fix that put the seam blend behind an opt-in had no automated guard at
|
|
215
|
+
* all. A regression here is a WCAG failure on a public-health surface (measured:
|
|
216
|
+
* 4.71:1 without the blend, 3.59:1 with it, averaged over a small mark), which
|
|
217
|
+
* is not the kind of thing that should rely on someone re-measuring by hand.
|
|
218
|
+
*
|
|
219
|
+
* The invariant worth asserting is not "off returns 0 for this blend value" —
|
|
220
|
+
* it is that off returns 0 for EVERY blend value, so the shipped default can
|
|
221
|
+
* never mix body colour into a mark.
|
|
222
|
+
*/
|
|
223
|
+
declare function seamWeight(seamEnabled: boolean, blend: number): number;
|
|
224
|
+
/**
|
|
225
|
+
* F052.25 — recompute vertex normals ONLY when the file shipped none.
|
|
226
|
+
*
|
|
227
|
+
* Same reason as above: this single condition WAS the "lines on the body" bug,
|
|
228
|
+
* and it lived on the one code path the unit suite cannot execute. Takes a
|
|
229
|
+
* structural slice rather than a THREE.BufferGeometry so it is testable without
|
|
230
|
+
* a GL context.
|
|
231
|
+
*
|
|
232
|
+
* It performs the call as well as the check, deliberately. A predicate alone
|
|
233
|
+
* would let a test pass while the call site did the opposite with it — and what
|
|
234
|
+
* the acceptance criterion asks for is the EFFECT: a model that ships normals
|
|
235
|
+
* must come out the other side with those same normals, untouched.
|
|
236
|
+
*
|
|
237
|
+
* Returns true when it computed (the model had none, and would otherwise render
|
|
238
|
+
* flat), false when it left the authored normals alone.
|
|
239
|
+
*/
|
|
240
|
+
declare function ensureNormals(geo: {
|
|
241
|
+
getAttribute(name: string): unknown;
|
|
242
|
+
computeVertexNormals(): void;
|
|
243
|
+
}): boolean;
|
|
196
244
|
interface BodyMap3DProps {
|
|
197
245
|
/** URLs of the male/female body GLBs (you host them; reference GLBs ship under `@broberg/bodymap/models/`). */
|
|
198
246
|
models: BodyMap3DModels;
|
|
@@ -264,4 +312,4 @@ interface BodyMap3DProps {
|
|
|
264
312
|
}
|
|
265
313
|
declare function BodyMap3D(props: BodyMap3DProps): react.JSX.Element;
|
|
266
314
|
|
|
267
|
-
export { BodyMap3D, type BodyMap3DModels, type BodyMap3DProps, type BodyMap3DSex, type BodyMap3DUiLabels, seamBlend, serializeReport };
|
|
315
|
+
export { BodyMap3D, type BodyMap3DModels, type BodyMap3DProps, type BodyMap3DSex, type BodyMap3DUiLabels, ensureNormals, seamBlend, seamWeight, serializeReport };
|
package/dist/three.js
CHANGED
|
@@ -81,10 +81,12 @@ function emitFeedback(outcome, region, opts = {}) {
|
|
|
81
81
|
if (opts.haptics === false) return "skipped";
|
|
82
82
|
return requestVibration(VIBRATION_PATTERNS[outcome], opts.nav);
|
|
83
83
|
}
|
|
84
|
+
var STAGE_BG = "#0e1424";
|
|
84
85
|
var defaultUi = {
|
|
85
86
|
text: "#1e293b",
|
|
86
87
|
mutedText: "#475569",
|
|
87
88
|
panelBg: "#fff",
|
|
89
|
+
stageBg: STAGE_BG,
|
|
88
90
|
border: "#e2e8f0",
|
|
89
91
|
badgeBg: "#f1f5f9",
|
|
90
92
|
danger: "#dc2626"
|
|
@@ -246,6 +248,14 @@ function seamBlend(nearestSq, secondSq) {
|
|
|
246
248
|
if (t >= 1) return 0.5;
|
|
247
249
|
return t * t * (3 - 2 * t) * 0.5;
|
|
248
250
|
}
|
|
251
|
+
function seamWeight(seamEnabled, blend) {
|
|
252
|
+
return seamEnabled ? blend : 0;
|
|
253
|
+
}
|
|
254
|
+
function ensureNormals(geo) {
|
|
255
|
+
if (geo.getAttribute("normal")) return false;
|
|
256
|
+
geo.computeVertexNormals();
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
249
259
|
var ANCHOR_KEYS = Object.keys(ANCHORS);
|
|
250
260
|
for (const k of ANCHOR_KEYS) ANCHORS[k][0] = -ANCHORS[k][0];
|
|
251
261
|
function mergeLabels(locale, overrides) {
|
|
@@ -331,6 +341,8 @@ function BodyMap3D(props) {
|
|
|
331
341
|
setReadyRef.current = setReady;
|
|
332
342
|
const seamRef = useRef(seam);
|
|
333
343
|
seamRef.current = seam;
|
|
344
|
+
const stageRef = useRef(chrome.stageBg);
|
|
345
|
+
stageRef.current = chrome.stageBg;
|
|
334
346
|
const [internalFs, setInternalFs] = useState(false);
|
|
335
347
|
const isFullscreen = fullscreenProp ?? internalFs;
|
|
336
348
|
const setFullscreen = (v) => {
|
|
@@ -347,7 +359,7 @@ function BodyMap3D(props) {
|
|
|
347
359
|
if (!el) return;
|
|
348
360
|
let W = el.clientWidth || 520, H = el.clientHeight || 600;
|
|
349
361
|
const scene = new THREE.Scene();
|
|
350
|
-
scene.background = new THREE.Color(
|
|
362
|
+
scene.background = new THREE.Color(stageRef.current);
|
|
351
363
|
const camera = new THREE.PerspectiveCamera(32, W / H, 0.1, 100);
|
|
352
364
|
camera.position.set(0, 1.05, 4.4);
|
|
353
365
|
let renderer;
|
|
@@ -430,7 +442,7 @@ function BodyMap3D(props) {
|
|
|
430
442
|
};
|
|
431
443
|
for (let i = 0; i < vertexRegion.length; i++) {
|
|
432
444
|
tmp.copy(col(vertexRegion[i]));
|
|
433
|
-
const w = seamRef.current
|
|
445
|
+
const w = seamWeight(seamRef.current, vertexBlend[i]);
|
|
434
446
|
if (w > 0) {
|
|
435
447
|
tmp2.copy(col(vertexNeighbour[i]));
|
|
436
448
|
tmp.lerp(tmp2, w);
|
|
@@ -440,6 +452,7 @@ function BodyMap3D(props) {
|
|
|
440
452
|
colorAttr.needsUpdate = true;
|
|
441
453
|
};
|
|
442
454
|
const refresh = () => {
|
|
455
|
+
scene.background = new THREE.Color(stageRef.current);
|
|
443
456
|
paint();
|
|
444
457
|
renderFrame();
|
|
445
458
|
};
|
|
@@ -464,7 +477,7 @@ function BodyMap3D(props) {
|
|
|
464
477
|
});
|
|
465
478
|
if (bodyMesh) {
|
|
466
479
|
const geo = bodyMesh.geometry;
|
|
467
|
-
|
|
480
|
+
ensureNormals(geo);
|
|
468
481
|
const pos = geo.getAttribute("position");
|
|
469
482
|
const n = pos.count;
|
|
470
483
|
const cols = new Float32Array(n * 3);
|
|
@@ -662,7 +675,12 @@ function BodyMap3D(props) {
|
|
|
662
675
|
position: "fixed",
|
|
663
676
|
inset: 0,
|
|
664
677
|
zIndex: 2147483e3,
|
|
665
|
-
|
|
678
|
+
// THE STAGE, not the panel. This used to be `chrome.panelBg`,
|
|
679
|
+
// the same field that paints the picker card — so a consumer with
|
|
680
|
+
// a dark canvas got a white void around a dark body in fullscreen,
|
|
681
|
+
// and could not fix it: darkening panelBg hid the 0-10 buttons.
|
|
682
|
+
// One field, two surfaces, opposite needs. (F052.29)
|
|
683
|
+
background: chrome.stageBg,
|
|
666
684
|
// F052.27 — a fixed inset:0 surface in a standalone/Capacitor
|
|
667
685
|
// webview covers the STATUS BAR too, so a control laid out at the
|
|
668
686
|
// top lands on the clock and the battery. Measured by fd-sundhed
|
|
@@ -705,7 +723,14 @@ function BodyMap3D(props) {
|
|
|
705
723
|
}
|
|
706
724
|
) }),
|
|
707
725
|
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 18, alignItems: "flex-start", flexWrap: "wrap", ...isFullscreen ? { flex: "1 1 auto", minHeight: 0 } : null }, children: [
|
|
708
|
-
unsupported ? /* @__PURE__ */ jsx(
|
|
726
|
+
unsupported ? /* @__PURE__ */ jsx(
|
|
727
|
+
"div",
|
|
728
|
+
{
|
|
729
|
+
"data-testid": "bodymap3d-unsupported",
|
|
730
|
+
style: { flex: "1 1 520px", minWidth: 320, height: canvasH, borderRadius: 16, background: chrome.panelBg, color: chrome.mutedText, border: `1px solid ${chrome.border}`, display: "flex", alignItems: "center", justifyContent: "center", padding: 24, textAlign: "center", fontSize: 14 },
|
|
731
|
+
children: "3D kr\xE6ver WebGL, som ikke er tilg\xE6ngeligt her."
|
|
732
|
+
}
|
|
733
|
+
) : /* @__PURE__ */ jsx(
|
|
709
734
|
"div",
|
|
710
735
|
{
|
|
711
736
|
ref: mountRef,
|
|
@@ -720,7 +745,7 @@ function BodyMap3D(props) {
|
|
|
720
745
|
minHeight: isFullscreen ? 0 : void 0,
|
|
721
746
|
borderRadius: 16,
|
|
722
747
|
overflow: "hidden",
|
|
723
|
-
background:
|
|
748
|
+
background: chrome.stageBg,
|
|
724
749
|
touchAction: "none"
|
|
725
750
|
}
|
|
726
751
|
}
|
|
@@ -756,6 +781,6 @@ function BodyMap3D(props) {
|
|
|
756
781
|
);
|
|
757
782
|
}
|
|
758
783
|
|
|
759
|
-
export { BodyMap3D, seamBlend, serializeReport };
|
|
784
|
+
export { BodyMap3D, ensureNormals, seamBlend, seamWeight, serializeReport };
|
|
760
785
|
//# sourceMappingURL=three.js.map
|
|
761
786
|
//# sourceMappingURL=three.js.map
|