bep40 commited on
Commit
3a3c80c
·
verified ·
1 Parent(s): 6d9ce55

Upload add-arkit-browser.html

Browse files
Files changed (1) hide show
  1. add-arkit-browser.html +183 -0
add-arkit-browser.html ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Add ARKit Viseme to GLB - Browser Tool</title>
6
+ <script type="module">
7
+ // Import glTF-Transform từ CDN
8
+ import { Document, NodeIO } from 'https://cdn.skypack.dev/@gltf-transform/core';
9
+ import { dedup, prune } from 'https://cdn.skypack.dev/@gltf-transform/functions';
10
+
11
+ // ARKit 52 blendshape names
12
+ const ARKIT_NAMES = [
13
+ 'jawOpen', 'jawForward', 'jawLeft', 'jawRight',
14
+ 'mouthClose', 'mouthDimpleLeft', 'mouthDimpleRight', 'mouthFrownLeft',
15
+ 'mouthFrownRight', 'mouthFunnel', 'mouthLeft', 'mouthRight',
16
+ 'mouthRollLower', 'mouthRollUpper', 'mouthShrugLower', 'mouthShrugUpper',
17
+ 'mouthSmileLeft', 'mouthSmileRight', 'mouthStretchLeft', 'mouthStretchRight',
18
+ 'mouthUpperUpLeft', 'mouthUpperUpRight', 'mouthLowerDownLeft', 'mouthLowerDownRight',
19
+ 'tongueOut',
20
+ 'eyeBlinkLeft', 'eyeBlinkRight', 'eyeSquintLeft', 'eyeSquintRight',
21
+ 'eyeWideLeft', 'eyeWideRight'
22
+ ];
23
+
24
+ // Viseme chính cho lip-sync
25
+ const VISEME_NAMES = [
26
+ 'sil', 'PP', 'FF', 'TH', 'DD', 'kk', 'kk', 'CH', 'SS', 'nn',
27
+ 'RR', 'aa', 'E', 'ih', 'oh', 'oo'
28
+ ];
29
+
30
+ window.runTool = async function() {
31
+ const statusEl = document.getElementById('status');
32
+ const outputEl = document.getElementById('output');
33
+
34
+ try {
35
+ statusEl.textContent = 'Select lisamy.glb file...';
36
+ const lisamyFile = document.getElementById('lisamy-input').files[0];
37
+ if (!lisamyFile) throw new Error('Chưa chọn file lisamy.glb');
38
+
39
+ statusEl.textContent = 'Select donor GLB file (vuong.glb hoặc ready_player_me)...';
40
+ const donorFile = document.getElementById('donor-input').files[0];
41
+ if (!donorFile) throw new Error('Chưa chọn file donor');
42
+
43
+ const io = new NodeIO();
44
+
45
+ // Load lisamy
46
+ const lisamyBuffer = await lisamyFile.arrayBuffer();
47
+ const docLisa = io.read(lisamyBuffer);
48
+
49
+ // Load donor
50
+ const donorBuffer = await donorFile.arrayBuffer();
51
+ const docDonor = io.read(donorBuffer);
52
+
53
+ // Find donor morph targets
54
+ let donorMesh = null, donorPrim = null;
55
+ for (const mesh of docDonor.getRoot().listMeshes()) {
56
+ for (const prim of mesh.listPrimitives()) {
57
+ if (prim.listTargets().length > 0) {
58
+ donorMesh = mesh; donorPrim = prim; break;
59
+ }
60
+ }
61
+ if (donorPrim) break;
62
+ }
63
+
64
+ if (!donorPrim) throw new Error('Donor không có morph targets');
65
+
66
+ const donorPos = donorPrim.getAttribute('POSITION');
67
+ const donorCount = donorPos.getCount();
68
+ const donorTargets = donorPrim.listTargets();
69
+
70
+ // Find recipient with same topology
71
+ let recipientMesh = null, recipientPrim = null;
72
+ for (const mesh of docLisa.getRoot().listMeshes()) {
73
+ for (const prim of mesh.listPrimitives()) {
74
+ const pos = prim.getAttribute('POSITION');
75
+ if (pos && pos.getCount() === donorCount) {
76
+ recipientMesh = mesh; recipientPrim = prim; break;
77
+ }
78
+ }
79
+ if (recipientPrim) break;
80
+ }
81
+
82
+ if (recipientPrim) {
83
+ // Copy morph targets
84
+ for (const srcTarget of donorTargets) {
85
+ const name = srcTarget.getName();
86
+ const disp = srcTarget.getDisplacement();
87
+ if (!disp) continue;
88
+
89
+ const newTarget = docLisa.createPrimitiveTarget();
90
+ newTarget.setName(name);
91
+ newTarget.setDisplacement(new Float32Array(disp.getArray()));
92
+
93
+ const normals = srcTarget.getNormal();
94
+ if (normals) newTarget.setNormal(new Float32Array(normals.getArray()));
95
+
96
+ recipientPrim.addTarget(newTarget);
97
+ }
98
+ recipientMesh.setWeights(new Array(donorTargets.length).fill(0));
99
+ statusEl.textContent = `Copied ${donorTargets.length} morph targets (same topology)`;
100
+ } else {
101
+ // Add empty morph targets
102
+ let bestMesh = null, bestPrim = null, bestCount = 0;
103
+ for (const mesh of docLisa.getRoot().listMeshes()) {
104
+ for (const prim of mesh.listPrimitives()) {
105
+ const pos = prim.getAttribute('POSITION');
106
+ if (pos && pos.getCount() > bestCount) {
107
+ bestCount = pos.getCount();
108
+ bestMesh = mesh;
109
+ bestPrim = prim;
110
+ }
111
+ }
112
+ }
113
+
114
+ if (!bestPrim) throw new Error('No mesh found in lisamy.glb');
115
+
116
+ for (const t of donorTargets) {
117
+ const target = docLisa.createPrimitiveTarget();
118
+ target.setName(t.getName());
119
+ target.setDisplacement(new Float32Array(bestCount * 3));
120
+ bestPrim.addTarget(target);
121
+ }
122
+ bestMesh.setWeights(new Array(donorTargets.length).fill(0));
123
+ statusEl.textContent = `Added ${donorTargets.length} zero-displacement morph targets`;
124
+ }
125
+
126
+ // Save and download
127
+ const outputBuffer = await io.writeBuffer(docLisa);
128
+ const blob = new Blob([outputBuffer], { type: 'model/gltf-binary' });
129
+ const url = URL.createObjectURL(blob);
130
+
131
+ const link = document.createElement('a');
132
+ link.href = url;
133
+ link.download = 'lisamy_arkit.glb';
134
+ link.textContent = '⬇️ Download lisamy_arkit.glb';
135
+ link.style.display = 'block';
136
+ link.style.marginTop = '10px';
137
+
138
+ outputEl.innerHTML = '';
139
+ outputEl.appendChild(link);
140
+
141
+ } catch (err) {
142
+ statusEl.textContent = 'Lỗi: ' + err.message;
143
+ console.error(err);
144
+ }
145
+ };
146
+
147
+ window.loadDonorPresets = function() {
148
+ // Pre-load donor files
149
+ document.getElementById('donor-presets').innerHTML = `
150
+ <p>Donor options (download one):</p>
151
+ <a href="https://huggingface.co/spaces/bep40/gemma-avatar/resolve/main/public/avatars/vuong.glb" download>vuong.glb</a> |
152
+ <a href="https://huggingface.co/spaces/bep40/gemma-avatar/resolve/main/public/avatars/ready_player_me_1.glb" download>ready_player_me_1.glb</a>
153
+ `;
154
+ };
155
+ </script>
156
+ </head>
157
+ <body onload="loadDonorPresets()">
158
+ <h1>Thêm ARKit Viseme Morph Targets vào GLB</h1>
159
+ <p><b>Tool chạy trên browser</b> - không cần cài đặt</p>
160
+
161
+ <h3>Step 1: Chọn file lisamy.glb</h3>
162
+ <input type="file" id="lisamy-input" accept=".glb,.gltf">
163
+
164
+ <h3>Step 2: Chọn donor file (có morph targets)</h3>
165
+ <div id="donor-presets"></div>
166
+ <input type="file" id="donor-input" accept=".glb,.gltf">
167
+ <p><small>Donor: để copy morph targets - vuong.glb hoặc ready_player_me_1.glb</small></p>
168
+
169
+ <h3>Step 3: Chạy</h3>
170
+ <button onclick="runTool()">Thêm Morph Targets</button>
171
+
172
+ <p id="status">Sẵn sàng...</p>
173
+ <div id="output"></div>
174
+
175
+ <hr>
176
+ <h3>Lưu ý:</h3>
177
+ <ul>
178
+ <li>Nếu donor có cùng số vertices → copy chính xác morph targets</li>
179
+ <li>Nếu khác → thêm morph targets tên đúng nhưng displacement=0 (cần Blender để sculpt)</li>
180
+ <li>Kết quả download file <b>lisamy_arkit.glb</b></li>
181
+ </ul>
182
+ </body>
183
+ </html>