bep40 commited on
Commit
53ea1a7
·
verified ·
1 Parent(s): c5610dd

Upload index.html

Browse files
Files changed (1) hide show
  1. index.html +97 -105
index.html CHANGED
@@ -1,138 +1,130 @@
1
  <!DOCTYPE html>
2
- <html lang="vi">
3
  <head>
4
- <meta charset="UTF-8">
5
- <title>GLB Morph Target Tool</title>
6
- <script type="module" src="https://cdn.skypack.dev/@gltf-transform/core@4.1.0"></script>
7
  <style>
8
- body { font-family: system-ui, sans-serif; max-width: 600px; margin: 30px auto; padding: 20px; }
9
- .dropzone { border: 2px dashed #ccc; padding: 40px; text-align: center; margin: 20px 0; cursor: pointer; }
10
- .dropzone.dragover { border-color: #0066cc; background: #f0f8ff; }
11
- pre { background: #f5f5f5; padding: 10px; border-radius: 5px; max-height: 200px; overflow: auto; }
12
- button { padding: 10px 20px; background: #0066cc; color: white; border: none; border-radius: 5px; cursor: pointer; }
13
  </style>
14
  </head>
15
  <body>
16
- <h1>🔧 GLB Morph Target Tool</h1>
17
- <p>Thêm ARKit viseme morph targets vào file GLB</p>
18
 
19
- <div class="dropzone" id="dropzone">
20
- 📁 Drop files here hoặc click để chọn
21
- <input type="file" id="files" multiple accept=".glb,.gltf" style="display:none">
22
- </div>
23
 
24
- <div id="files-list"></div>
25
- <button onclick="process()" id="processBtn" style="display:none">Process & Download</button>
26
 
27
- <pre id="log"></pre>
28
- <a id="download" style="display:none; margin-top: 10px"></a>
 
 
29
 
30
  <script type="module">
31
  import { Document, NodeIO } from 'https://cdn.skypack.dev/@gltf-transform/core@4.1.0';
32
 
33
  const io = new NodeIO();
34
- let files = [];
35
-
36
- document.getElementById('dropzone').addEventListener('click', () => {
37
- document.getElementById('files').click();
38
- });
39
-
40
- document.getElementById('files').addEventListener('change', (e) => {
41
- files = Array.from(e.target.files);
42
- updateFilesList();
43
- });
44
 
45
- function updateFilesList() {
46
- const list = document.getElementById('files-list');
47
- list.innerHTML = '<h3>Files selected:</h3>' + files.map(f => '<div>• ' + f.name + '</div>').join('');
48
- document.getElementById('processBtn').style.display = files.length === 2 ? 'inline-block' : 'none';
49
  }
50
 
51
- function log(msg, isError) {
52
- document.getElementById('log').innerHTML += '<div style="color: ' + (isError ? 'red' : 'green') + '">' + msg + '</div>';
53
- }
54
-
55
- window.process = async () => {
56
- if (files.length < 2) return;
57
-
58
- const targetFile = files[0];
59
- const donorFile = files[1];
60
- log('\n📝 Loading...');
61
 
62
- const [targetBuf, donorBuf] = await Promise.all([
63
- targetFile.arrayBuffer(),
64
- donorFile.arrayBuffer()
65
- ]);
66
-
67
- const targetDoc = io.read(Buffer.from(targetBuf));
68
- const donorDoc = io.read(Buffer.from(donorBuf));
69
-
70
- // Find donor with morph targets
71
- let donorMesh = null, donorPrim = null;
72
- for (const mesh of donorDoc.getRoot().listMeshes()) {
73
- for (const prim of mesh.listPrimitives()) {
74
- if (prim.listTargets().length > 0) { donorMesh = mesh; donorPrim = prim; break; }
75
- }
76
- if (donorPrim) break;
77
  }
78
 
79
- if (!donorPrim) { log('❌ Donor has no morph targets!', true); return; }
80
 
81
- const donorCount = donorPrim.getAttribute('POSITION').getCount();
82
- const donorTargets = donorPrim.listTargets();
83
- log('✅ Donor: ' + donorMesh.getName() + ' - ' + donorCount + ' verts, ' + donorTargets.length + ' morphs');
84
-
85
- // Find recipient with same topology
86
- let recipientMesh = null, recipientPrim = null;
87
- for (const mesh of targetDoc.getRoot().listMeshes()) {
88
- for (const prim of mesh.listPrimitives()) {
89
- const pos = prim.getAttribute('POSITION');
90
- if (pos && pos.getCount() === donorCount) { recipientMesh = mesh; recipientPrim = prim; break; }
91
- }
92
- if (recipientPrim) break;
93
- }
94
-
95
- if (recipientPrim) {
96
- log('✅ SAME TOPOLOGY! Copying...');
97
- for (const t of donorTargets) {
98
- const newTarget = targetDoc.createPrimitiveTarget();
99
- newTarget.setName(t.getName());
100
- if (t.getDisplacement()) {
101
- newTarget.setDisplacement(new Float32Array(t.getDisplacement().getArray()));
102
  }
103
- recipientPrim.addTarget(newTarget);
104
  }
105
- recipientMesh.setWeights(new Array(donorTargets.length).fill(0));
106
- } else {
107
- log('❌ DIFFERENT TOPOLOGY - zero-displacement...');
108
- let bestMesh = null, bestPrim = null, bestCount = 0;
 
 
 
 
 
109
  for (const mesh of targetDoc.getRoot().listMeshes()) {
110
  for (const prim of mesh.listPrimitives()) {
111
  const pos = prim.getAttribute('POSITION');
112
- if (pos && pos.getCount() > bestCount) {
113
- bestCount = pos.getCount();
114
- bestMesh = mesh;
115
- bestPrim = prim;
116
- }
117
  }
 
118
  }
119
- for (const t of donorTargets) {
120
- const newTarget = targetDoc.createPrimitiveTarget();
121
- newTarget.setName(t.getName());
122
- newTarget.setDisplacement(new Float32Array(bestCount * 3));
123
- bestPrim.addTarget(newTarget);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  }
125
- bestMesh.setWeights(new Array(donorTargets.length).fill(0));
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  }
127
-
128
- const output = io.write(targetDoc);
129
- const blob = new Blob([output], { type: 'model/gltf-binary' });
130
- const url = URL.createObjectURL(blob);
131
- document.getElementById('download').href = url;
132
- document.getElementById('download').download = targetFile.name.replace('.glb', '-arkit.glb');
133
- document.getElementById('download').style.display = 'inline-block';
134
- document.getElementById('download').textContent = '📥 Download processed GLB';
135
- log('✅ Done! Click download link above.');
136
  };
137
  </script>
138
  </body>
 
1
  <!DOCTYPE html>
2
+ <html>
3
  <head>
4
+ <title>GLB Morph Tool</title>
5
+ <script type="module" crossorigin src="https://cdn.skypack.dev/@gltf-transform/core@4.1.0"></script>
 
6
  <style>
7
+ body { font-family: sans-serif; max-width: 600px; margin: 30px auto; padding: 20px; }
8
+ input { margin: 10px 0; }
9
+ button { padding: 10px 20px; }
10
+ #log { background: #f5f5f5; padding: 10px; margin-top: 10px; max-height: 200px; overflow: auto; }
 
11
  </style>
12
  </head>
13
  <body>
14
+ <h1>🔧 Add ARKit Morph Targets to GLB</h1>
 
15
 
16
+ <h3>Target GLB (lisamy.glb):</h3>
17
+ <input type="file" id="target" accept=".glb"><br>
 
 
18
 
19
+ <h3>Donor GLB (vuong.glb hoặc rpm1.glb):</h3>
20
+ <input type="file" id="donor" accept=".glb"><br>
21
 
22
+ <button onclick="run()">Process & Download</button>
23
+
24
+ <div id="log"></div>
25
+ <a id="dl" style="display:none">📥 Download</a>
26
 
27
  <script type="module">
28
  import { Document, NodeIO } from 'https://cdn.skypack.dev/@gltf-transform/core@4.1.0';
29
 
30
  const io = new NodeIO();
 
 
 
 
 
 
 
 
 
 
31
 
32
+ function log(msg, err) {
33
+ document.getElementById('log').innerHTML += '<div style="color:' + (err ? 'red' : 'green') + '">' + msg + '</div>';
 
 
34
  }
35
 
36
+ window.run = async () => {
37
+ const targetFile = document.getElementById('target').files[0];
38
+ const donorFile = document.getElementById('donor').files[0];
 
 
 
 
 
 
 
39
 
40
+ if (!targetFile || !donorFile) {
41
+ alert('Chọn cả 2 file!');
42
+ return;
 
 
 
 
 
 
 
 
 
 
 
 
43
  }
44
 
45
+ log('<br>📝 Loading files...');
46
 
47
+ try {
48
+ const [targetBuf, donorBuf] = await Promise.all([
49
+ targetFile.arrayBuffer(),
50
+ donorFile.arrayBuffer()
51
+ ]);
52
+
53
+ const targetDoc = io.read(Buffer.from(targetBuf));
54
+ const donorDoc = io.read(Buffer.from(donorBuf));
55
+
56
+ // Find donor with morph targets
57
+ let donorMesh, donorPrim;
58
+ for (const mesh of donorDoc.getRoot().listMeshes()) {
59
+ for (const prim of mesh.listPrimitives()) {
60
+ if (prim.listTargets().length > 0) { donorMesh = mesh; donorPrim = prim; break; }
 
 
 
 
 
 
 
61
  }
62
+ if (donorPrim) break;
63
  }
64
+
65
+ if (!donorPrim) { log('❌ Donor has NO morph targets!', true); return; }
66
+
67
+ const donorCount = donorPrim.getAttribute('POSITION').getCount();
68
+ const donorTargets = donorPrim.listTargets();
69
+ log('Donor: ' + donorMesh.getName() + ' - ' + donorTargets.length + ' morph targets');
70
+
71
+ // Find recipient with same topology
72
+ let recipientMesh, recipientPrim;
73
  for (const mesh of targetDoc.getRoot().listMeshes()) {
74
  for (const prim of mesh.listPrimitives()) {
75
  const pos = prim.getAttribute('POSITION');
76
+ if (pos && pos.getCount() === donorCount) { recipientMesh = mesh; recipientPrim = prim; break; }
 
 
 
 
77
  }
78
+ if (recipientPrim) break;
79
  }
80
+
81
+ if (recipientPrim) {
82
+ log('✅ SAME VERTEX COUNT! Copying morph targets...');
83
+ for (const t of donorTargets) {
84
+ const newT = targetDoc.createPrimitiveTarget();
85
+ newT.setName(t.getName());
86
+ if (t.getDisplacement()) {
87
+ newT.setDisplacement(new Float32Array(t.getDisplacement().getArray()));
88
+ }
89
+ recipientPrim.addTarget(newT);
90
+ }
91
+ recipientMesh.setWeights(new Array(donorTargets.length).fill(0));
92
+ } else {
93
+ log('❌ DIFFERENT topology - adding zero-displacement morphs');
94
+ let bestMesh, bestPrim, bestCount = 0;
95
+ for (const mesh of targetDoc.getRoot().listMeshes()) {
96
+ for (const prim of mesh.listPrimitives()) {
97
+ const pos = prim.getAttribute('POSITION');
98
+ if (pos && pos.getCount() > bestCount) {
99
+ bestCount = pos.getCount();
100
+ bestMesh = mesh;
101
+ bestPrim = prim;
102
+ }
103
+ }
104
+ }
105
+ for (const t of donorTargets) {
106
+ const newT = targetDoc.createPrimitiveTarget();
107
+ newT.setName(t.getName());
108
+ newT.setDisplacement(new Float32Array(bestCount * 3));
109
+ bestPrim.addTarget(newT);
110
+ }
111
+ bestMesh.setWeights(new Array(donorTargets.length).fill(0));
112
  }
113
+
114
+ // Save
115
+ const out = io.write(targetDoc);
116
+ const blob = new Blob([out], { type: 'model/gltf-binary' });
117
+ const url = URL.createObjectURL(blob);
118
+
119
+ document.getElementById('dl').href = url;
120
+ document.getElementById('dl').download = targetFile.name.replace('.glb', '-arkit.glb');
121
+ document.getElementById('dl').style.display = 'block';
122
+ log('<br>✅ Done! Click download link above.');
123
+
124
+ } catch (e) {
125
+ log('Error: ' + e.message, true);
126
+ console.error(e);
127
  }
 
 
 
 
 
 
 
 
 
128
  };
129
  </script>
130
  </body>