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 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.beardType;
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
- // scene.background = null; // Transparent background.
2613
- scene.background = new THREE.Color('white');
2614
- // (You DO NOT need to add any lights to the scene,
2615
- // unless you are using a Three.js built-in material.
2616
- // If you are, look at demo-basic.js "addLightsToScene".)
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
- if (mesh.isMesh && mesh.material.side === THREE.FrontSide)
2645
- // Fix triangle winding by changing the culling (side).
2646
- mesh.material.side = THREE.BackSide;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "miijs",
3
- "version": "2.1.3",
3
+ "version": "2.2.0",
4
4
  "description": "Work with Mii characters in every possible way needed for your project.",
5
5
  "main": "index.js",
6
6
  "scripts": {
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
- }