miijs 2.2.0 → 2.2.2
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/heightWeightConversion.js +123 -0
- package/index.js +136 -30
- package/package.json +1 -1
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// ------------------------------
|
|
2
|
+
// Helpers
|
|
3
|
+
// ------------------------------
|
|
4
|
+
const clamp = (v, min, max) => Math.min(max, Math.max(min, v));
|
|
5
|
+
|
|
6
|
+
// ------------------------------
|
|
7
|
+
// Height (0..127) <-> feet/inches
|
|
8
|
+
// Calibrated so height=64 -> 67 inches exactly.
|
|
9
|
+
// Range: 36" (3'0") .. 84" (7'0").
|
|
10
|
+
// ------------------------------
|
|
11
|
+
const HEIGHT_MIN_IN = 36;
|
|
12
|
+
const HEIGHT_MAX_IN = 84;
|
|
13
|
+
const HEIGHT_RANGE_IN = HEIGHT_MAX_IN - HEIGHT_MIN_IN;
|
|
14
|
+
|
|
15
|
+
// Calibration target: slider mid (64) should be 67"
|
|
16
|
+
const HEIGHT_MID_SLIDER = 64;
|
|
17
|
+
const HEIGHT_MID_IN = 67;
|
|
18
|
+
|
|
19
|
+
// Compute exponent k so that x0^k = y0 (bias curve, invertible)
|
|
20
|
+
const x0 = HEIGHT_MID_SLIDER / 127; // ~0.50394
|
|
21
|
+
const y0 = (HEIGHT_MID_IN - HEIGHT_MIN_IN) / HEIGHT_RANGE_IN; // 31/48 ≈ 0.64583
|
|
22
|
+
const HEIGHT_EXP = Math.log(y0) / Math.log(x0); // ≈ 0.638 (computed once)
|
|
23
|
+
|
|
24
|
+
/** 0..127 -> { feet, inches, totalInches } */
|
|
25
|
+
function miiHeightToFeetInches(heightValue) {
|
|
26
|
+
const hv = clamp(heightValue | 0, 0, 127);
|
|
27
|
+
const x = hv / 127;
|
|
28
|
+
const s = Math.pow(x, HEIGHT_EXP); // calibrated bias
|
|
29
|
+
const inches = HEIGHT_MIN_IN + s * HEIGHT_RANGE_IN;
|
|
30
|
+
|
|
31
|
+
const totalInches = Math.round(inches);
|
|
32
|
+
const feet = Math.floor(totalInches / 12);
|
|
33
|
+
const inchesR = totalInches % 12;
|
|
34
|
+
|
|
35
|
+
return { feet, inches: inchesR, totalInches };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** inches -> 0..127 (inverse of the calibrated mapping) */
|
|
39
|
+
function inchesToMiiHeight(targetInches) {
|
|
40
|
+
const y = clamp((targetInches - HEIGHT_MIN_IN) / HEIGHT_RANGE_IN, 0, 1);
|
|
41
|
+
const x = Math.pow(y, 1 / HEIGHT_EXP);
|
|
42
|
+
return Math.round(x * 127);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ------------------------------
|
|
46
|
+
// Weight (0..127) + height(0..127) <-> pounds
|
|
47
|
+
// Baseline by BMI; composition slider is nonlinear & invertible.
|
|
48
|
+
// Center (64) -> multiplier 1.0.
|
|
49
|
+
// ------------------------------
|
|
50
|
+
const BMI_AVG = 23.5; // gives ~150 lb at 67"
|
|
51
|
+
const WEIGHT_SPREAD = 0.35; // ±35% around baseline at extremes
|
|
52
|
+
const WEIGHT_GAMMA = 1.6; // steeper near ends, softer center
|
|
53
|
+
|
|
54
|
+
/** weight slider + height slider -> pounds */
|
|
55
|
+
function miiWeightToPounds(weightValue, heightValue) {
|
|
56
|
+
const wv = clamp(weightValue | 0, 0, 127);
|
|
57
|
+
const hv = clamp(heightValue | 0, 0, 127);
|
|
58
|
+
|
|
59
|
+
// Height in inches using the calibrated mapping
|
|
60
|
+
const { totalInches: H } = miiHeightToFeetInches(hv);
|
|
61
|
+
|
|
62
|
+
// BMI baseline
|
|
63
|
+
const base = (BMI_AVG * H * H) / 703;
|
|
64
|
+
|
|
65
|
+
// Nonlinear symmetric multiplier around 1.0 (64 -> 0 offset)
|
|
66
|
+
const d = clamp((wv - 64) / 63, -1, 1);
|
|
67
|
+
const multiplier = 1 + Math.sign(d) * WEIGHT_SPREAD * Math.pow(Math.abs(d), WEIGHT_GAMMA);
|
|
68
|
+
|
|
69
|
+
return Math.round(base * multiplier);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** pounds + height slider -> weight slider (inverse of above) */
|
|
73
|
+
function poundsToMiiWeight(pounds, heightValue) {
|
|
74
|
+
const hv = clamp(heightValue | 0, 0, 127);
|
|
75
|
+
const { totalInches: H } = miiHeightToFeetInches(hv);
|
|
76
|
+
const base = (BMI_AVG * H * H) / 703;
|
|
77
|
+
|
|
78
|
+
// Guard against tiny numeric drift outside the spread
|
|
79
|
+
const mult = clamp(pounds / base, 1 - WEIGHT_SPREAD * 1.0001, 1 + WEIGHT_SPREAD * 1.0001);
|
|
80
|
+
|
|
81
|
+
let d;
|
|
82
|
+
if (Math.abs(mult - 1) < 1e-9) {
|
|
83
|
+
d = 0;
|
|
84
|
+
} else if (mult > 1) {
|
|
85
|
+
d = Math.pow((mult - 1) / WEIGHT_SPREAD, 1 / WEIGHT_GAMMA);
|
|
86
|
+
} else {
|
|
87
|
+
d = -Math.pow((1 - mult) / WEIGHT_SPREAD, 1 / WEIGHT_GAMMA);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const wv = Math.round(64 + 63 * clamp(d, -1, 1));
|
|
91
|
+
return clamp(wv, 0, 127);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ------------------------------
|
|
95
|
+
// Quick checks (should match your expectations)
|
|
96
|
+
// ------------------------------
|
|
97
|
+
// Average points
|
|
98
|
+
// console.log(miiHeightToFeetInches(64), miiWeightToPounds(64, 64)); // ~5'7", ~150 lb
|
|
99
|
+
// Extremes
|
|
100
|
+
// console.log(miiHeightToFeetInches(0), miiWeightToPounds(0, 0)); // 3'0", ~28 lb
|
|
101
|
+
// console.log(miiHeightToFeetInches(127), miiWeightToPounds(127, 127)); // 7'0", ~318 lb
|
|
102
|
+
// Inverses
|
|
103
|
+
// const h = miiHeightToFeetInches(64);
|
|
104
|
+
// console.log(inchesToMiiHeight(h.totalInches)); // ~64
|
|
105
|
+
// const lbs = miiWeightToPounds(96, 64);
|
|
106
|
+
// console.log(lbs); // ~135
|
|
107
|
+
// console.log(poundsToMiiWeight(lbs, 64)); // ~96
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
// ------------------------------
|
|
111
|
+
// Quick sanity checks
|
|
112
|
+
// ------------------------------
|
|
113
|
+
// Average points
|
|
114
|
+
console.log(miiHeightToFeetInches(64), miiWeightToPounds(64, 64)); // ~5'0", ~150 lb
|
|
115
|
+
// // Extremes
|
|
116
|
+
console.log(miiHeightToFeetInches(0), miiWeightToPounds(0, 0));//3'0, 28 lb
|
|
117
|
+
console.log(miiHeightToFeetInches(127), miiWeightToPounds(127, 127));//7'0 318 lb
|
|
118
|
+
// // Inverses
|
|
119
|
+
const h = miiHeightToFeetInches(64);
|
|
120
|
+
console.log(inchesToMiiHeight(h.totalInches)); // ~64
|
|
121
|
+
const lbs = miiWeightToPounds(96, 64);
|
|
122
|
+
console.log(lbs);//168
|
|
123
|
+
console.log(poundsToMiiWeight(lbs, 64)); // ~96
|
package/index.js
CHANGED
|
@@ -2179,11 +2179,11 @@ function convertMii(jsonIn,typeTo){
|
|
|
2179
2179
|
}
|
|
2180
2180
|
miiTo.nose.type=convTables.nose3DSToWii[mii.nose.page][mii.nose.type];
|
|
2181
2181
|
miiTo.mouth.type=convTables.mouth3DSToWii[mii.mouth.page][mii.mouth.type];
|
|
2182
|
-
miiTo.mouth.color=mii.mouth.
|
|
2182
|
+
miiTo.mouth.color=mii.mouth.color>2?0:mii.mouth.color;
|
|
2183
2183
|
miiTo.hair.type=convTables.hair3DSToWii[mii.hair.page][mii.hair.type];
|
|
2184
2184
|
miiTo.eyebrows.type=convTables.eyebrows3DSToWii[mii.eyebrows.page][mii.eyebrows.type];
|
|
2185
2185
|
miiTo.eyes.type=convTables.eyes3DSToWii[mii.eyes.page][mii.eyes.type];
|
|
2186
|
-
miiTo.glasses.
|
|
2186
|
+
miiTo.glasses.color=mii.glasses.color;
|
|
2187
2187
|
if(miiTo.beard.mustache.type===4){
|
|
2188
2188
|
miiTo.beard.mustache.type=2;
|
|
2189
2189
|
}
|
|
@@ -2211,7 +2211,7 @@ function convertMii(jsonIn,typeTo){
|
|
|
2211
2211
|
}
|
|
2212
2212
|
miiTo.eyes.squash=3;
|
|
2213
2213
|
miiTo.eyebrows.squash=3;
|
|
2214
|
-
miiTo.mouth.
|
|
2214
|
+
miiTo.mouth.color=mii.mouth.color;
|
|
2215
2215
|
miiTo.mouth.squash=3;
|
|
2216
2216
|
miiTo.console="3ds";
|
|
2217
2217
|
}
|
|
@@ -2288,9 +2288,13 @@ function convertMiiToStudio(jsonIn) {
|
|
|
2288
2288
|
}
|
|
2289
2289
|
async function readWiiBin(binOrPath) {
|
|
2290
2290
|
let data;
|
|
2291
|
-
if
|
|
2291
|
+
if(Buffer.isBuffer(binOrPath)){
|
|
2292
|
+
data=binOrPath;
|
|
2293
|
+
}
|
|
2294
|
+
else if (/[^01]/ig.test(binOrPath)) {
|
|
2292
2295
|
data = await fs.promises.readFile(binOrPath);
|
|
2293
|
-
}
|
|
2296
|
+
}
|
|
2297
|
+
else{
|
|
2294
2298
|
data = Buffer.from(binOrPath);
|
|
2295
2299
|
}
|
|
2296
2300
|
var thisMii={
|
|
@@ -3054,7 +3058,7 @@ async function write3DSQR(miiJson, outPath, fflRes = getFFLRes()) {
|
|
|
3054
3058
|
main_img.blit(canvas, 212 - 100 / 2, 212 - 100 / 2);
|
|
3055
3059
|
const font = await Jimp.loadFont(Jimp.FONT_SANS_16_BLACK)
|
|
3056
3060
|
|
|
3057
|
-
main_img.print(font, 0,
|
|
3061
|
+
main_img.print(font, 0, 70, {
|
|
3058
3062
|
text: miiJson.meta.name,
|
|
3059
3063
|
alignmentX: Jimp.HORIZONTAL_ALIGN_CENTER,
|
|
3060
3064
|
alignmentY: Jimp.VERTICAL_ALIGN_MIDDLE
|
|
@@ -3077,61 +3081,64 @@ async function write3DSQR(miiJson, outPath, fflRes = getFFLRes()) {
|
|
|
3077
3081
|
return imageBuffer;
|
|
3078
3082
|
}
|
|
3079
3083
|
function make3DSChild(dad,mom,options={}){
|
|
3080
|
-
if(!["3ds","wii u"].includes(dad.console?.toLowerCase())){
|
|
3084
|
+
if(!["3ds","wii u"].includes(dad.meta.console?.toLowerCase())){
|
|
3081
3085
|
dad=convertMii(dad,"wii");
|
|
3082
3086
|
}
|
|
3083
|
-
if(!["3ds","wii u"].includes(mom.console?.toLowerCase())){
|
|
3087
|
+
if(!["3ds","wii u"].includes(mom.meta.console?.toLowerCase())){
|
|
3084
3088
|
mom=convertMii(dad,"wii");
|
|
3085
3089
|
}
|
|
3086
|
-
var g=options.gender||Math.floor(Math.random()*2)
|
|
3090
|
+
var g=options.gender||Math.floor(Math.random()*2);
|
|
3087
3091
|
var child={
|
|
3088
|
-
"
|
|
3092
|
+
"general":{
|
|
3089
3093
|
"birthMonth":new Date().getMonth()+1,
|
|
3090
3094
|
"birthday":new Date().getDay(),
|
|
3091
3095
|
"height":64,
|
|
3092
3096
|
"weight":64,
|
|
3093
|
-
"creatorName":"",
|
|
3094
3097
|
"gender":g,
|
|
3095
|
-
"name":options.name||kidNames[g][Math.floor(Math.random()*kidNames[g].length)],
|
|
3096
3098
|
"favColor":options.favColor||favCols[Math.floor(Math.random()*favCols.length)]
|
|
3097
3099
|
},
|
|
3100
|
+
"meta":{
|
|
3101
|
+
"name":options.name||kidNames[g][Math.floor(Math.random()*kidNames[g].length)],
|
|
3102
|
+
"creatorName":"",
|
|
3103
|
+
},
|
|
3098
3104
|
"perms":{
|
|
3099
3105
|
"sharing":true,
|
|
3100
3106
|
"copying":true
|
|
3101
3107
|
},
|
|
3102
3108
|
"hair":{
|
|
3103
|
-
"
|
|
3104
|
-
"
|
|
3109
|
+
"page":8,//Hardcoded, needs to be generated
|
|
3110
|
+
"type":3,// ^
|
|
3111
|
+
"color":Math.floor(Math.random()*2)===1?dad.hair.color:mom.hair.color,
|
|
3105
3112
|
"flipped":Math.floor(Math.random()*2)===0?true:false
|
|
3106
3113
|
},
|
|
3107
3114
|
"face":{
|
|
3108
3115
|
"shape":Math.floor(Math.random()*2)===1?dad.face.shape:mom.face.shape,
|
|
3109
3116
|
"feature":Math.floor(Math.random()*2)===1?dad.face.feature:mom.face.feature,
|
|
3110
|
-
"makeup":g===
|
|
3117
|
+
"makeup":g===0?0:Math.floor(Math.random()*2)===1?dad.face.makeup:mom.face.makeup
|
|
3111
3118
|
},
|
|
3112
3119
|
"eyes":Math.floor(Math.random()*2)===1?dad.eyes:mom.eyes,
|
|
3113
3120
|
"eyebrows":Math.floor(Math.random()*2)===1?dad.eyebrows:mom.eyebrows,
|
|
3114
3121
|
"nose":Math.floor(Math.random()*2)===1?dad.nose:mom.nose,
|
|
3115
3122
|
"mouth":Math.floor(Math.random()*2)===1?dad.mouth:mom.mouth,
|
|
3116
|
-
"
|
|
3117
|
-
"
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
+
"beard":{//Beards can never be generated for children allegedly, confirm before finishing
|
|
3124
|
+
"mustache":{
|
|
3125
|
+
"type":0,
|
|
3126
|
+
"mustacheSize": 4,
|
|
3127
|
+
"mustacheYPos": 10
|
|
3128
|
+
},
|
|
3129
|
+
"type": 0,
|
|
3130
|
+
"color": 0
|
|
3131
|
+
},
|
|
3123
3132
|
"glasses":Math.floor(Math.random()*2)===1?dad.glasses:mom.glasses,
|
|
3124
|
-
"mole":Math.floor(Math.random()*2)===1?dad.mole:mom.mole
|
|
3125
|
-
"creatorName":""
|
|
3133
|
+
"mole":Math.floor(Math.random()*2)===1?dad.mole:mom.mole
|
|
3126
3134
|
};
|
|
3127
|
-
child.eyebrows.
|
|
3128
|
-
var c=[
|
|
3135
|
+
child.eyebrows.color=child.hair.color;
|
|
3136
|
+
var c=[mom.face.color,dad.face.color];
|
|
3129
3137
|
if(c[0]>c[1]){
|
|
3130
3138
|
c[1]=c[0];
|
|
3131
|
-
c[0]=
|
|
3139
|
+
c[0]=dad.face.color;
|
|
3132
3140
|
}
|
|
3133
|
-
child.face.
|
|
3134
|
-
child.name=child.info.name;
|
|
3141
|
+
child.face.color=c[0]+Math.round((c[1]-c[0])/2);
|
|
3135
3142
|
child.type="3DS";
|
|
3136
3143
|
return child;
|
|
3137
3144
|
}
|
|
@@ -3254,6 +3261,100 @@ function generateInstructions(mii,full){
|
|
|
3254
3261
|
}
|
|
3255
3262
|
}
|
|
3256
3263
|
|
|
3264
|
+
function miiHeightToFeetInches(value) {
|
|
3265
|
+
const minInches = 36; // 3'0"
|
|
3266
|
+
const midInches = 69; // 5'9"
|
|
3267
|
+
const maxInches = 84; // 7'0"
|
|
3268
|
+
const midPoint = 64;
|
|
3269
|
+
|
|
3270
|
+
let totalInches;
|
|
3271
|
+
if (value <= midPoint) {
|
|
3272
|
+
// Lower half: 0–64 maps to 36–69
|
|
3273
|
+
totalInches = minInches + (value / midPoint) * (midInches - minInches);
|
|
3274
|
+
}
|
|
3275
|
+
else {
|
|
3276
|
+
// Upper half: 64–127 maps to 69–84
|
|
3277
|
+
totalInches = midInches + ((value - midPoint) / (127 - midPoint)) * (maxInches - midInches);
|
|
3278
|
+
}
|
|
3279
|
+
|
|
3280
|
+
const feet = Math.floor(totalInches / 12);
|
|
3281
|
+
const inches = Math.round(totalInches % 12);
|
|
3282
|
+
return { feet, inches, totalInches };
|
|
3283
|
+
}
|
|
3284
|
+
function inchesToMiiHeight(totalInches) {
|
|
3285
|
+
const minInches = 36;
|
|
3286
|
+
const midInches = 69;
|
|
3287
|
+
const maxInches = 84;
|
|
3288
|
+
const midPoint = 64;
|
|
3289
|
+
|
|
3290
|
+
let value;
|
|
3291
|
+
if (totalInches <= midInches) {
|
|
3292
|
+
// Below or equal to midpoint
|
|
3293
|
+
value = ((totalInches - minInches) / (midInches - minInches)) * midPoint;
|
|
3294
|
+
} else {
|
|
3295
|
+
// Above midpoint
|
|
3296
|
+
value = midPoint + ((totalInches - midInches) / (maxInches - midInches)) * (127 - midPoint);
|
|
3297
|
+
}
|
|
3298
|
+
|
|
3299
|
+
return Math.round(Math.max(0, Math.min(value, 127)));
|
|
3300
|
+
}
|
|
3301
|
+
// ---- Tunable anchors (BMI breakpoints) ----
|
|
3302
|
+
const BMI_MIN = 16; // maps to Mii weight 0
|
|
3303
|
+
const BMI_MID = 23; // maps to Mii weight 64 (average look)
|
|
3304
|
+
const BMI_MAX = 40; // maps to Mii weight 127
|
|
3305
|
+
|
|
3306
|
+
// Convenience: clamp helper
|
|
3307
|
+
const clamp = (x, lo, hi) => Math.max(lo, Math.min(hi, x));
|
|
3308
|
+
|
|
3309
|
+
/**
|
|
3310
|
+
* Convert real-world height & weight to Mii weight (0–127) using BMI.
|
|
3311
|
+
* @param {number} heightInches - Total height in inches
|
|
3312
|
+
* @param {number} weightLbs - Weight in pounds
|
|
3313
|
+
* @returns {number} - Mii weight index (0–127)
|
|
3314
|
+
*/
|
|
3315
|
+
function heightWeightToMiiWeight(heightInches, weightLbs) {
|
|
3316
|
+
if (!heightInches || heightInches <= 0) throw new Error("heightInches must be > 0");
|
|
3317
|
+
const bmi = (703 * weightLbs) / (heightInches * heightInches);
|
|
3318
|
+
|
|
3319
|
+
let v;
|
|
3320
|
+
if (bmi <= BMI_MID) {
|
|
3321
|
+
// Map BMI_MIN..BMI_MID -> 0..64
|
|
3322
|
+
const t = (clamp(bmi, BMI_MIN, BMI_MID) - BMI_MIN) / (BMI_MID - BMI_MIN);
|
|
3323
|
+
v = 0 + t * 64;
|
|
3324
|
+
} else {
|
|
3325
|
+
// Map BMI_MID..BMI_MAX -> 64..127
|
|
3326
|
+
const t = (clamp(bmi, BMI_MID, BMI_MAX) - BMI_MID) / (BMI_MAX - BMI_MID);
|
|
3327
|
+
v = 64 + t * (127 - 64);
|
|
3328
|
+
}
|
|
3329
|
+
return Math.round(clamp(v, 0, 127));
|
|
3330
|
+
}
|
|
3331
|
+
|
|
3332
|
+
/**
|
|
3333
|
+
* Convert Mii weight (0–127) back to a realistic real-world weight (lbs)
|
|
3334
|
+
* for a given height, by inverting the BMI mapping above.
|
|
3335
|
+
* @param {number} heightInches - Total height in inches
|
|
3336
|
+
* @param {number} miiWeight - 0..127
|
|
3337
|
+
* @returns {{ pounds:number, bmi:number }}
|
|
3338
|
+
*/
|
|
3339
|
+
function miiWeightToRealWeight(heightInches, miiWeight) {
|
|
3340
|
+
if (!heightInches || heightInches <= 0) heightInches=0;
|
|
3341
|
+
const v = clamp(miiWeight, 0, 127);
|
|
3342
|
+
|
|
3343
|
+
let bmi;
|
|
3344
|
+
if (v <= 64) {
|
|
3345
|
+
// Invert 0..64 -> BMI_MIN..BMI_MID
|
|
3346
|
+
const t = v / 64;
|
|
3347
|
+
bmi = BMI_MIN + t * (BMI_MID - BMI_MIN);
|
|
3348
|
+
} else {
|
|
3349
|
+
// Invert 64..127 -> BMI_MID..BMI_MAX
|
|
3350
|
+
const t = (v - 64) / (127 - 64);
|
|
3351
|
+
bmi = BMI_MID + t * (BMI_MAX - BMI_MID);
|
|
3352
|
+
}
|
|
3353
|
+
|
|
3354
|
+
const pounds = (bmi * heightInches * heightInches) / 703;
|
|
3355
|
+
return { pounds, bmi };
|
|
3356
|
+
}
|
|
3357
|
+
|
|
3257
3358
|
|
|
3258
3359
|
|
|
3259
3360
|
module.exports = {
|
|
@@ -3275,5 +3376,10 @@ module.exports = {
|
|
|
3275
3376
|
|
|
3276
3377
|
//make3DSChild, //WIP
|
|
3277
3378
|
|
|
3278
|
-
generateInstructions
|
|
3379
|
+
generateInstructions,
|
|
3380
|
+
|
|
3381
|
+
miiHeightToFeetInches,
|
|
3382
|
+
inchesToMiiHeight,
|
|
3383
|
+
heightWeightToMiiWeight,
|
|
3384
|
+
miiWeightToRealWeight
|
|
3279
3385
|
}
|