miijs 2.1.3 → 2.2.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 +1 -0
- package/index.js +77 -14
- package/mii-body.glb +0 -0
- package/package.json +1 -1
- package/defaultMii.json +0 -188
package/README.md
CHANGED
|
@@ -93,3 +93,4 @@ You can find FFLResHigh using a Wii U with an FTP program installed at `sys/titl
|
|
|
93
93
|
# Credits
|
|
94
94
|
- **[kazuki-4ys' MiiInfoEditorCTR](https://github.com/kazuki-4ys/kazuki-4ys.github.io/tree/master/web_apps/MiiInfoEditorCTR)** - I repurposed how to decrypt and reencrypt the QR codes from here, including repurposing the asmCrypto.js file in its entirety with very small modifications (it has since been stripped down to only include the functions this library uses). I believe I also modified the code for rendering the Mii using Nintendo's Mii Studio from here as well, though I do not remember for certain.
|
|
95
95
|
- **[ariankordi's FFL.js](https://github.com/ariankordi/FFL.js/)** - Rendering Miis locally would not be possible without this library. Instructions for finding FFLResHigh are also learned from [ariankordi's FFL-Testing repository](https://github.com/ariankordi/FFL-Testing).
|
|
96
|
+
- **[Models Resource](https://models.spriters-resource.com/3ds/systembios/asset/306260/)** - For the bodies used in Mii rendering
|
package/index.js
CHANGED
|
@@ -5,6 +5,7 @@ const { createCanvas, loadImage, ImageData } = nodeCanvas;
|
|
|
5
5
|
const jsQR = require('jsqr');
|
|
6
6
|
const Jimp = require('jimp');
|
|
7
7
|
const THREE = require('three');
|
|
8
|
+
var GLTFLoader=null;
|
|
8
9
|
const QRCodeStyling = require("qr-code-styling");
|
|
9
10
|
const { JSDOM } = require("jsdom");
|
|
10
11
|
const httpsLib = require('https');
|
|
@@ -2263,7 +2264,7 @@ function convertMiiToStudio(jsonIn) {
|
|
|
2263
2264
|
studioMii[0x23] = mii.mouth.squash;
|
|
2264
2265
|
studioMii[0x27] = mii.mouth.yPosition;
|
|
2265
2266
|
studioMii[0x29] = mii.beard.mustache.type;
|
|
2266
|
-
studioMii[1] = mii.beard.
|
|
2267
|
+
studioMii[1] = mii.beard.type;
|
|
2267
2268
|
studioMii[0] = mii.beard.color;
|
|
2268
2269
|
if (!studioMii[0]) studioMii[0] = 8;
|
|
2269
2270
|
studioMii[0x28] = mii.beard.mustache.size;
|
|
@@ -2569,7 +2570,7 @@ async function renderMiiWithStudio(jsonIn){
|
|
|
2569
2570
|
var studioMii=convertMiiToStudio(jsonIn);
|
|
2570
2571
|
return await downloadImage('https://studio.mii.nintendo.com/miis/image.png?data=' + studioMii + "&width=270&type=face");
|
|
2571
2572
|
}
|
|
2572
|
-
async function createFFLMiiIcon(data, width, height, fflRes) {
|
|
2573
|
+
async function createFFLMiiIcon(data, width, height, useBody, shirtColor, fflRes) {
|
|
2573
2574
|
/**
|
|
2574
2575
|
* Creates a Mii face render using FFL.js/Three.js/gl-headless.
|
|
2575
2576
|
* @example
|
|
@@ -2609,11 +2610,18 @@ async function createFFLMiiIcon(data, width, height, fflRes) {
|
|
|
2609
2610
|
setIsWebGL1State(!renderer.capabilities.isWebGL2); // Tell FFL.js we are WebGL1
|
|
2610
2611
|
|
|
2611
2612
|
const scene = new THREE.Scene();
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2613
|
+
scene.background = null; // Transparent background.
|
|
2614
|
+
|
|
2615
|
+
if(useBody){
|
|
2616
|
+
// After: const scene = new THREE.Scene(); scene.background = null;
|
|
2617
|
+
const ambient = new THREE.AmbientLight(0xffffff, 0.15);
|
|
2618
|
+
scene.add(ambient);
|
|
2619
|
+
|
|
2620
|
+
const rim = new THREE.DirectionalLight(0xffffff, 3);
|
|
2621
|
+
rim.position.set(0.5, -7, -1.0);
|
|
2622
|
+
scene.add(rim);
|
|
2623
|
+
|
|
2624
|
+
}
|
|
2617
2625
|
|
|
2618
2626
|
let ffl, currentCharModel;
|
|
2619
2627
|
|
|
@@ -2629,11 +2637,61 @@ async function createFFLMiiIcon(data, width, height, fflRes) {
|
|
|
2629
2637
|
// Convert Uint8Array to Buffer for struct-fu compatibility
|
|
2630
2638
|
const studioBuffer = Buffer.from(studioRaw);
|
|
2631
2639
|
|
|
2632
|
-
currentCharModel = createCharModel(studioBuffer, null,
|
|
2633
|
-
FFLShaderMaterial, ffl.module);
|
|
2640
|
+
currentCharModel = createCharModel(studioBuffer, null, FFLShaderMaterial, ffl.module);
|
|
2634
2641
|
initCharModelTextures(currentCharModel, renderer); // Initialize fully
|
|
2635
2642
|
scene.add(currentCharModel.meshes); // Add to scene
|
|
2636
2643
|
|
|
2644
|
+
//Add body
|
|
2645
|
+
if (useBody) {
|
|
2646
|
+
if (typeof GLTFLoader === 'undefined' || !GLTFLoader) {
|
|
2647
|
+
const mod = await import('three/examples/jsm/loaders/GLTFLoader.js');
|
|
2648
|
+
GLTFLoader = mod.GLTFLoader;
|
|
2649
|
+
}
|
|
2650
|
+
//Read GLB from disk and parse (avoids URL/fetch issues)
|
|
2651
|
+
const absPath = path.resolve(__dirname, './mii-body.glb');
|
|
2652
|
+
const buf = fs.readFileSync(absPath);
|
|
2653
|
+
const arrayBuffer = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
2654
|
+
const loader = new GLTFLoader();
|
|
2655
|
+
const gltf = await new Promise((resolve, reject) => {
|
|
2656
|
+
loader.parse(
|
|
2657
|
+
arrayBuffer,
|
|
2658
|
+
path.dirname(absPath) + path.sep,
|
|
2659
|
+
resolve,
|
|
2660
|
+
reject
|
|
2661
|
+
);
|
|
2662
|
+
});
|
|
2663
|
+
|
|
2664
|
+
const body = gltf.scene;
|
|
2665
|
+
|
|
2666
|
+
body.position.y-=110;
|
|
2667
|
+
|
|
2668
|
+
//Recolor
|
|
2669
|
+
body.userData.isMiiBody = true;
|
|
2670
|
+
body.traverse(o => {
|
|
2671
|
+
if (o.isMesh) {
|
|
2672
|
+
if (!o.geometry.attributes.normal) {
|
|
2673
|
+
o.geometry.computeVertexNormals();
|
|
2674
|
+
}
|
|
2675
|
+
const isShirt = (o.name === 'mesh_1_');
|
|
2676
|
+
o.material?.dispose?.();
|
|
2677
|
+
o.material = new THREE.MeshLambertMaterial({
|
|
2678
|
+
//["Red", "Orange", "Yellow", "Lime", "Green", "Blue", "Cyan", "Pink", "Purple", "Brown", "White", "Black"]
|
|
2679
|
+
color: isShirt ? [0xFF2400,0xF08000,0xFFD700,0xAAFF00,0x008000,0x0000FF,0x00D7FF,0xFF69B4,0x7F00FF,0x6F4E37,0xFFFFFF,0x303030][shirtColor] : 0x808080,
|
|
2680
|
+
emissive: isShirt ? 0x330000 : 0x222222,
|
|
2681
|
+
emissiveIntensity: 0.0
|
|
2682
|
+
});
|
|
2683
|
+
o.material.side = THREE.DoubleSide;
|
|
2684
|
+
o.material.needsUpdate = true;
|
|
2685
|
+
}
|
|
2686
|
+
});
|
|
2687
|
+
|
|
2688
|
+
|
|
2689
|
+
|
|
2690
|
+
// (6) Add to scene
|
|
2691
|
+
scene.add(body);
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2694
|
+
|
|
2637
2695
|
// Use the camera for an icon pose.
|
|
2638
2696
|
const camera = getCameraForViewType(ViewType.MakeIcon);
|
|
2639
2697
|
|
|
@@ -2641,11 +2699,16 @@ async function createFFLMiiIcon(data, width, height, fflRes) {
|
|
|
2641
2699
|
camera.projectionMatrix.elements[5] *= -1; // Flip the camera Y axis.
|
|
2642
2700
|
// When flipping the camera, the triangles are in the wrong direction.
|
|
2643
2701
|
scene.traverse(mesh => {
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2702
|
+
if (
|
|
2703
|
+
mesh.isMesh &&
|
|
2704
|
+
mesh.material.side === THREE.FrontSide &&
|
|
2705
|
+
!mesh.userData.isMiiBody
|
|
2706
|
+
) {
|
|
2707
|
+
mesh.material.side = THREE.BackSide;
|
|
2708
|
+
}
|
|
2647
2709
|
});
|
|
2648
2710
|
|
|
2711
|
+
|
|
2649
2712
|
// Render the scene, and read the pixels into a buffer.
|
|
2650
2713
|
renderer.render(scene, camera);
|
|
2651
2714
|
const pixels = new Uint8Array(width * height * 4);
|
|
@@ -2682,7 +2745,7 @@ async function renderMii(jsonIn, fflRes=getFFLRes()){
|
|
|
2682
2745
|
const studioMii = convertMiiToStudio(jsonIn);
|
|
2683
2746
|
const width = height = 600;
|
|
2684
2747
|
|
|
2685
|
-
return createFFLMiiIcon(studioMii, width, height, fflRes);
|
|
2748
|
+
return createFFLMiiIcon(studioMii, width, height, true, jsonIn.general.favoriteColor, fflRes);
|
|
2686
2749
|
}
|
|
2687
2750
|
async function writeWiiBin(jsonIn, outPath) {
|
|
2688
2751
|
if (jsonIn.console?.toLowerCase() !== "wii") {
|
|
@@ -2992,7 +3055,7 @@ async function write3DSQR(miiJson, outPath, fflRes = getFFLRes()) {
|
|
|
2992
3055
|
const font = await Jimp.loadFont(Jimp.FONT_SANS_16_BLACK)
|
|
2993
3056
|
|
|
2994
3057
|
main_img.print(font, 0, 55, {
|
|
2995
|
-
text: miiJson.name,
|
|
3058
|
+
text: miiJson.meta.name,
|
|
2996
3059
|
alignmentX: Jimp.HORIZONTAL_ALIGN_CENTER,
|
|
2997
3060
|
alignmentY: Jimp.VERTICAL_ALIGN_MIDDLE
|
|
2998
3061
|
}, 424, 395);
|
package/mii-body.glb
ADDED
|
Binary file
|
package/package.json
CHANGED
package/defaultMii.json
DELETED
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"male":{
|
|
3
|
-
"general": {
|
|
4
|
-
"type":3,
|
|
5
|
-
"birthday": 17,
|
|
6
|
-
"birthMonth": 4,
|
|
7
|
-
"height": 0,
|
|
8
|
-
"weight": 0,
|
|
9
|
-
"gender": 1,
|
|
10
|
-
"favoriteColor": 7
|
|
11
|
-
},
|
|
12
|
-
"meta":{
|
|
13
|
-
"name": "",
|
|
14
|
-
"creatorName": "",
|
|
15
|
-
"console":"3ds",
|
|
16
|
-
"miiId":"148",
|
|
17
|
-
"systemId":"148"
|
|
18
|
-
},
|
|
19
|
-
"perms": {
|
|
20
|
-
"sharing": false,
|
|
21
|
-
"copying": true,
|
|
22
|
-
"fromCheckMiiOut": false,
|
|
23
|
-
"mingle": true
|
|
24
|
-
},
|
|
25
|
-
"hair": {
|
|
26
|
-
"page":0,
|
|
27
|
-
"type":7,
|
|
28
|
-
"color": 7,
|
|
29
|
-
"flipped": false
|
|
30
|
-
},
|
|
31
|
-
"face": {
|
|
32
|
-
"type": 5,
|
|
33
|
-
"color": 0,
|
|
34
|
-
"feature": 0,
|
|
35
|
-
"makeup": 0
|
|
36
|
-
},
|
|
37
|
-
"eyes": {
|
|
38
|
-
"page":0,
|
|
39
|
-
"type": 9,
|
|
40
|
-
"col": 4,
|
|
41
|
-
"size": 1,
|
|
42
|
-
"squash": 3,
|
|
43
|
-
"rotation": 4,
|
|
44
|
-
"distanceApart": 3,
|
|
45
|
-
"yPosition": 11
|
|
46
|
-
},
|
|
47
|
-
"eyebrows": {
|
|
48
|
-
"page":0,
|
|
49
|
-
"type":5,
|
|
50
|
-
"color":7,
|
|
51
|
-
"size": 2,
|
|
52
|
-
"squash": 4,
|
|
53
|
-
"rotation": 4,
|
|
54
|
-
"distanceApart": 4,
|
|
55
|
-
"yPosition": 6
|
|
56
|
-
},
|
|
57
|
-
"nose": {
|
|
58
|
-
"page":1,
|
|
59
|
-
"type":0,
|
|
60
|
-
"size": 0,
|
|
61
|
-
"yPosition": 5
|
|
62
|
-
},
|
|
63
|
-
"mouth": {
|
|
64
|
-
"page":1,
|
|
65
|
-
"type":6,
|
|
66
|
-
"color": 0,
|
|
67
|
-
"size": 2,
|
|
68
|
-
"squash": 3,
|
|
69
|
-
"yPosition": 10
|
|
70
|
-
},
|
|
71
|
-
"beard": {
|
|
72
|
-
"mustache":{
|
|
73
|
-
"type": 0,
|
|
74
|
-
"size": 4,
|
|
75
|
-
"yPosition": 10
|
|
76
|
-
},
|
|
77
|
-
"col": 0,
|
|
78
|
-
"type": 0
|
|
79
|
-
},
|
|
80
|
-
"glasses": {
|
|
81
|
-
"type": 0,
|
|
82
|
-
"color":0,
|
|
83
|
-
"size": 4,
|
|
84
|
-
"yPosition": 10
|
|
85
|
-
},
|
|
86
|
-
"mole": {
|
|
87
|
-
"on": false,
|
|
88
|
-
"size": 4,
|
|
89
|
-
"xPosition": 2,
|
|
90
|
-
"yPosition": 20
|
|
91
|
-
},
|
|
92
|
-
"name": "",
|
|
93
|
-
"creatorName": ""
|
|
94
|
-
},
|
|
95
|
-
"female":{
|
|
96
|
-
"general": {
|
|
97
|
-
"type":3,
|
|
98
|
-
"birthday": 17,
|
|
99
|
-
"birthMonth": 4,
|
|
100
|
-
"height": 0,
|
|
101
|
-
"weight": 0,
|
|
102
|
-
"gender": 1,
|
|
103
|
-
"favoriteColor": 7
|
|
104
|
-
},
|
|
105
|
-
"meta":{
|
|
106
|
-
"name": "",
|
|
107
|
-
"creatorName": "",
|
|
108
|
-
"console":"3ds",
|
|
109
|
-
"miiId":"148",
|
|
110
|
-
"systemId":"148"
|
|
111
|
-
},
|
|
112
|
-
"perms": {
|
|
113
|
-
"sharing": false,
|
|
114
|
-
"copying": true,
|
|
115
|
-
"fromCheckMiiOut": false,
|
|
116
|
-
"mingle": true
|
|
117
|
-
},
|
|
118
|
-
"hair": {
|
|
119
|
-
"page":0,
|
|
120
|
-
"type":7,
|
|
121
|
-
"color": 7,
|
|
122
|
-
"flipped": false
|
|
123
|
-
},
|
|
124
|
-
"face": {
|
|
125
|
-
"type": 5,
|
|
126
|
-
"color": 0,
|
|
127
|
-
"feature": 0,
|
|
128
|
-
"makeup": 0
|
|
129
|
-
},
|
|
130
|
-
"eyes": {
|
|
131
|
-
"page":0,
|
|
132
|
-
"type": 9,
|
|
133
|
-
"col": 4,
|
|
134
|
-
"size": 1,
|
|
135
|
-
"squash": 3,
|
|
136
|
-
"rotation": 4,
|
|
137
|
-
"distanceApart": 3,
|
|
138
|
-
"yPosition": 11
|
|
139
|
-
},
|
|
140
|
-
"eyebrows": {
|
|
141
|
-
"page":0,
|
|
142
|
-
"type":5,
|
|
143
|
-
"color":7,
|
|
144
|
-
"size": 2,
|
|
145
|
-
"squash": 4,
|
|
146
|
-
"rotation": 4,
|
|
147
|
-
"distanceApart": 4,
|
|
148
|
-
"yPosition": 6
|
|
149
|
-
},
|
|
150
|
-
"nose": {
|
|
151
|
-
"page":1,
|
|
152
|
-
"type":0,
|
|
153
|
-
"size": 0,
|
|
154
|
-
"yPosition": 5
|
|
155
|
-
},
|
|
156
|
-
"mouth": {
|
|
157
|
-
"page":1,
|
|
158
|
-
"type":6,
|
|
159
|
-
"color": 0,
|
|
160
|
-
"size": 2,
|
|
161
|
-
"squash": 3,
|
|
162
|
-
"yPosition": 10
|
|
163
|
-
},
|
|
164
|
-
"beard": {
|
|
165
|
-
"mustache":{
|
|
166
|
-
"type": 0,
|
|
167
|
-
"size": 4,
|
|
168
|
-
"yPosition": 10
|
|
169
|
-
},
|
|
170
|
-
"col": 0,
|
|
171
|
-
"type": 0
|
|
172
|
-
},
|
|
173
|
-
"glasses": {
|
|
174
|
-
"type": 0,
|
|
175
|
-
"color":0,
|
|
176
|
-
"size": 4,
|
|
177
|
-
"yPosition": 10
|
|
178
|
-
},
|
|
179
|
-
"mole": {
|
|
180
|
-
"on": false,
|
|
181
|
-
"size": 4,
|
|
182
|
-
"xPosition": 2,
|
|
183
|
-
"yPosition": 20
|
|
184
|
-
},
|
|
185
|
-
"name": "",
|
|
186
|
-
"creatorName": ""
|
|
187
|
-
}
|
|
188
|
-
}
|