miijs 2.2.0 → 2.2.1
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 +30 -27
- 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
|
}
|
|
@@ -3054,7 +3054,7 @@ async function write3DSQR(miiJson, outPath, fflRes = getFFLRes()) {
|
|
|
3054
3054
|
main_img.blit(canvas, 212 - 100 / 2, 212 - 100 / 2);
|
|
3055
3055
|
const font = await Jimp.loadFont(Jimp.FONT_SANS_16_BLACK)
|
|
3056
3056
|
|
|
3057
|
-
main_img.print(font, 0,
|
|
3057
|
+
main_img.print(font, 0, 70, {
|
|
3058
3058
|
text: miiJson.meta.name,
|
|
3059
3059
|
alignmentX: Jimp.HORIZONTAL_ALIGN_CENTER,
|
|
3060
3060
|
alignmentY: Jimp.VERTICAL_ALIGN_MIDDLE
|
|
@@ -3077,61 +3077,64 @@ async function write3DSQR(miiJson, outPath, fflRes = getFFLRes()) {
|
|
|
3077
3077
|
return imageBuffer;
|
|
3078
3078
|
}
|
|
3079
3079
|
function make3DSChild(dad,mom,options={}){
|
|
3080
|
-
if(!["3ds","wii u"].includes(dad.console?.toLowerCase())){
|
|
3080
|
+
if(!["3ds","wii u"].includes(dad.meta.console?.toLowerCase())){
|
|
3081
3081
|
dad=convertMii(dad,"wii");
|
|
3082
3082
|
}
|
|
3083
|
-
if(!["3ds","wii u"].includes(mom.console?.toLowerCase())){
|
|
3083
|
+
if(!["3ds","wii u"].includes(mom.meta.console?.toLowerCase())){
|
|
3084
3084
|
mom=convertMii(dad,"wii");
|
|
3085
3085
|
}
|
|
3086
|
-
var g=options.gender||Math.floor(Math.random()*2)
|
|
3086
|
+
var g=options.gender||Math.floor(Math.random()*2);
|
|
3087
3087
|
var child={
|
|
3088
|
-
"
|
|
3088
|
+
"general":{
|
|
3089
3089
|
"birthMonth":new Date().getMonth()+1,
|
|
3090
3090
|
"birthday":new Date().getDay(),
|
|
3091
3091
|
"height":64,
|
|
3092
3092
|
"weight":64,
|
|
3093
|
-
"creatorName":"",
|
|
3094
3093
|
"gender":g,
|
|
3095
|
-
"name":options.name||kidNames[g][Math.floor(Math.random()*kidNames[g].length)],
|
|
3096
3094
|
"favColor":options.favColor||favCols[Math.floor(Math.random()*favCols.length)]
|
|
3097
3095
|
},
|
|
3096
|
+
"meta":{
|
|
3097
|
+
"name":options.name||kidNames[g][Math.floor(Math.random()*kidNames[g].length)],
|
|
3098
|
+
"creatorName":"",
|
|
3099
|
+
},
|
|
3098
3100
|
"perms":{
|
|
3099
3101
|
"sharing":true,
|
|
3100
3102
|
"copying":true
|
|
3101
3103
|
},
|
|
3102
3104
|
"hair":{
|
|
3103
|
-
"
|
|
3104
|
-
"
|
|
3105
|
+
"page":8,//Hardcoded, needs to be generated
|
|
3106
|
+
"type":3,// ^
|
|
3107
|
+
"color":Math.floor(Math.random()*2)===1?dad.hair.color:mom.hair.color,
|
|
3105
3108
|
"flipped":Math.floor(Math.random()*2)===0?true:false
|
|
3106
3109
|
},
|
|
3107
3110
|
"face":{
|
|
3108
3111
|
"shape":Math.floor(Math.random()*2)===1?dad.face.shape:mom.face.shape,
|
|
3109
3112
|
"feature":Math.floor(Math.random()*2)===1?dad.face.feature:mom.face.feature,
|
|
3110
|
-
"makeup":g===
|
|
3113
|
+
"makeup":g===0?0:Math.floor(Math.random()*2)===1?dad.face.makeup:mom.face.makeup
|
|
3111
3114
|
},
|
|
3112
3115
|
"eyes":Math.floor(Math.random()*2)===1?dad.eyes:mom.eyes,
|
|
3113
3116
|
"eyebrows":Math.floor(Math.random()*2)===1?dad.eyebrows:mom.eyebrows,
|
|
3114
3117
|
"nose":Math.floor(Math.random()*2)===1?dad.nose:mom.nose,
|
|
3115
3118
|
"mouth":Math.floor(Math.random()*2)===1?dad.mouth:mom.mouth,
|
|
3116
|
-
"
|
|
3117
|
-
"
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3119
|
+
"beard":{//Beards can never be generated for children allegedly, confirm before finishing
|
|
3120
|
+
"mustache":{
|
|
3121
|
+
"type":0,
|
|
3122
|
+
"mustacheSize": 4,
|
|
3123
|
+
"mustacheYPos": 10
|
|
3124
|
+
},
|
|
3125
|
+
"type": 0,
|
|
3126
|
+
"color": 0
|
|
3127
|
+
},
|
|
3123
3128
|
"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":""
|
|
3129
|
+
"mole":Math.floor(Math.random()*2)===1?dad.mole:mom.mole
|
|
3126
3130
|
};
|
|
3127
|
-
child.eyebrows.
|
|
3128
|
-
var c=[
|
|
3131
|
+
child.eyebrows.color=child.hair.color;
|
|
3132
|
+
var c=[mom.face.color,dad.face.color];
|
|
3129
3133
|
if(c[0]>c[1]){
|
|
3130
3134
|
c[1]=c[0];
|
|
3131
|
-
c[0]=
|
|
3135
|
+
c[0]=dad.face.color;
|
|
3132
3136
|
}
|
|
3133
|
-
child.face.
|
|
3134
|
-
child.name=child.info.name;
|
|
3137
|
+
child.face.color=c[0]+Math.round((c[1]-c[0])/2);
|
|
3135
3138
|
child.type="3DS";
|
|
3136
3139
|
return child;
|
|
3137
3140
|
}
|