metamaker-for-three 0.1.8-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.
Files changed (51) hide show
  1. package/.env.dev +1 -0
  2. package/.env.mlib +1 -0
  3. package/.gitlab-ci.yml +1 -0
  4. package/.prettierrc.js +50 -0
  5. package/README.md +207 -0
  6. package/babel.config.js +3 -0
  7. package/code.jpg +0 -0
  8. package/examples/example.ts +624 -0
  9. package/libs/metamaker-for-three.js +1 -0
  10. package/package.json +89 -0
  11. package/public/SSSLUT.png +0 -0
  12. package/public/f9d25cc22be065191dca0f2ac7b248fd.zip +0 -0
  13. package/public/favicon.ico +0 -0
  14. package/public/index.html +31 -0
  15. package/public/models/gltf/xj/-43190.jpg +0 -0
  16. package/public/models/gltf/xj/-43240.jpg +0 -0
  17. package/public/models/gltf/xj/-43256.png +0 -0
  18. package/public/models/gltf/xj/-44804.jpg +0 -0
  19. package/public/models/gltf/xj/22344.jpg +0 -0
  20. package/public/models/gltf/xj/37430.jpg +0 -0
  21. package/public/models/gltf/xj/37432.jpg +0 -0
  22. package/public/models/gltf/xj/character.bin +0 -0
  23. package/public/models/gltf/xj/character.glb +0 -0
  24. package/public/models/gltf/xj/character.gltf +12110 -0
  25. package/public/models/gltf/xj/character.zip +0 -0
  26. package/src/assets/SSSLUT.png +0 -0
  27. package/src/assets/metacrypto.wasm +0 -0
  28. package/src/lib/core/index.ts +103 -0
  29. package/src/lib/core/utils/GLTFLoader.d.ts +319 -0
  30. package/src/lib/core/utils/GLTFLoader.js +3827 -0
  31. package/src/lib/core/utils/ResetMaterial.ts +264 -0
  32. package/src/lib/core/utils/convert.ts +124 -0
  33. package/src/lib/core/utils/downloadAnimation.ts +117 -0
  34. package/src/lib/core/utils/downloadData.ts +20 -0
  35. package/src/lib/core/utils/index.ts +59 -0
  36. package/src/lib/core/utils/metacrypto.js +49 -0
  37. package/src/lib/globals.d.ts +7 -0
  38. package/src/lib/index.ts +7 -0
  39. package/tsconfig.dist.json +7 -0
  40. package/tsconfig.json +33 -0
  41. package/types/core/index.d.ts +9 -0
  42. package/types/core/utils/GLTFLoader.d.ts +17 -0
  43. package/types/core/utils/ResetMaterial.d.ts +2 -0
  44. package/types/core/utils/convert.d.ts +13 -0
  45. package/types/core/utils/downloadAnimation.d.ts +10 -0
  46. package/types/core/utils/downloadData.d.ts +2 -0
  47. package/types/core/utils/index.d.ts +13 -0
  48. package/types/core/utils/metacrypto.d.ts +12 -0
  49. package/types/index.d.ts +5 -0
  50. package/vue.config.js +49 -0
  51. package/vue.config.lib.js +61 -0
@@ -0,0 +1,624 @@
1
+ import GUI from "lil-gui";
2
+ import * as THREE from "three";
3
+ import MMFT from "@/lib";
4
+ import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
5
+ import Stats from "three/examples/jsm/libs/stats.module.js";
6
+ import * as fflate from "fflate";
7
+ import CryptoJS from "crypto-js";
8
+
9
+ import qs from "qs";
10
+ let renderer;
11
+ let scene;
12
+ let camera: THREE.PerspectiveCamera;
13
+ let controls;
14
+ let idol: THREE.Group;
15
+ let gui: GUI;
16
+ let stats;
17
+ let lookTarget;
18
+ let mixer: THREE.AnimationMixer;
19
+ let ttsAuth: string;
20
+
21
+ const canvasRect: {
22
+ width: number;
23
+ height: number;
24
+ } = {
25
+ width: 0,
26
+ height: 0,
27
+ };
28
+
29
+ const params = {
30
+ name: "虚拟人物女性",
31
+ url: "./f9d25cc22be065191dca0f2ac7b248fd.zip",
32
+ 自定义模型地址: "",
33
+ pose: "",
34
+ poseEmo: "",
35
+ fadeIn: 0,
36
+ fadeOut: 0,
37
+ ttsText: "",
38
+ audioURL: "",
39
+ teethAnimURL: "",
40
+ emoAnimURL: "",
41
+ appKey: "",
42
+ appSecret: "",
43
+ loop: THREE.LoopRepeat,
44
+ 发送TTS请求: async function () {
45
+ // todo
46
+ const [audio, teeth, emo] = await fetchTTSToAnim(params.ttsText);
47
+ handleTTS(audio, teeth, emo);
48
+ },
49
+ /**
50
+ * 加载本地的数字人Zip包
51
+ */
52
+ 加载GLBZip包: async function () {
53
+ // todo
54
+ const input = document.createElement("input");
55
+ input.setAttribute("type", "file");
56
+ input.addEventListener("change", async (e) => {
57
+ console.log(e);
58
+ if (input.files.length) {
59
+ const glbBuffer = await uncompressZipFile(input.files[0]);
60
+ replaceIdol(glbBuffer);
61
+ }
62
+ input.remove();
63
+ });
64
+ input.click();
65
+ },
66
+ 播放口型动画: async () => {
67
+ clearTTSResource();
68
+ const tclip = await MMFT.core.loadTTSTeethAnimation(params.teethAnimURL);
69
+ const eclip = await MMFT.core.loadTTSEmoAnimation(params.emoAnimURL);
70
+ activeTTSResource.teeth = mixer.clipAction(tclip);
71
+ activeTTSResource.emo = mixer.clipAction(eclip);
72
+ activeTTSResource.teeth.play();
73
+ activeTTSResource.emo.play();
74
+ },
75
+ };
76
+ const activeActions = [];
77
+ const activeTTSResource = {
78
+ audio: null,
79
+ teeth: null,
80
+ emo: null,
81
+ };
82
+
83
+ const animations = {
84
+ "anim/220515_daiji": "anim/220515_daiji",
85
+ "anim/Stand_Idel": "anim/Stand_Idel",
86
+ "anim/Talking_BGY_F0": "anim/Talking_BGY_F0",
87
+ "anim/anim_220415_F34": "anim/anim_220415_F34",
88
+ "anim/Anim_220422_F5311": "anim/Anim_220422_F5311",
89
+ "anim/ABS_Fxiang_shuangren_16_M0": "anim/ABS_Fxiang_shuangren_16_M0",
90
+ "anim/BaseAnim/Anim_walk_M01": "anim/BaseAnim/Anim_walk_M01",
91
+ "anim/BaseAnim/Anim_run_F01": "anim/BaseAnim/Anim_run_F01",
92
+ "anim/Anim_220705_F26": "anim/Anim_220705_F26",
93
+ "anim/BD_xl0011_01_F0": "anim/BD_xl0011_01_F0",
94
+ "anim/Anim_220808_F37": "anim/Anim_220808_F37",
95
+ "anim/Anim_220808_F38": "anim/Anim_220808_F38",
96
+ };
97
+
98
+ window.onload = async () => {
99
+ const app = document.querySelector("#app");
100
+ function onResize() {
101
+ console.log(`resize`);
102
+ canvasRect.width = window.innerWidth;
103
+ canvasRect.height = window.innerHeight;
104
+ camera.aspect = canvasRect.width / canvasRect.height;
105
+ camera.updateProjectionMatrix();
106
+ renderer.setSize(window.innerWidth, window.innerHeight);
107
+ }
108
+ window.addEventListener("resize", onResize);
109
+
110
+ // 创建renderer
111
+ renderer = new THREE.WebGLRenderer({
112
+ antialias: true,
113
+ alpha: true,
114
+ logarithmicDepthBuffer: false,
115
+ });
116
+
117
+ canvasRect.width = window.innerWidth;
118
+ canvasRect.height = window.innerHeight;
119
+ renderer.setPixelRatio(window.devicePixelRatio);
120
+ renderer.setSize(canvasRect.width, canvasRect.height);
121
+ renderer.outputEncoding = THREE.sRGBEncoding;
122
+ app.appendChild(renderer.domElement);
123
+ // 创建Scene
124
+ scene = new THREE.Scene();
125
+ // 创建Camera
126
+ camera = new THREE.PerspectiveCamera(23, window.innerWidth / window.innerHeight, 0.1, 10000);
127
+ camera.rotation.set(-0.17, 0, 0);
128
+ camera.position.set(0, 1.2, 1.3);
129
+ lookTarget = new THREE.Vector3(0, 1, 0);
130
+ camera.lookAt(lookTarget);
131
+
132
+ // 创建Controls
133
+ controls = new OrbitControls(camera, renderer.domElement);
134
+ controls.enablePan = true;
135
+ controls.enableZoom = true;
136
+ controls.target.set(0, 1, 0);
137
+ controls.update();
138
+ // 创建Idol
139
+ controls.addEventListener("end", () => {
140
+ MMFT.core.resetPolygonOffset(idol, camera);
141
+ });
142
+
143
+ await replaceIdol(params.url);
144
+ MMFT.core.resetPolygonOffset(idol, camera);
145
+
146
+ mixer = new THREE.AnimationMixer(idol);
147
+ scene.add(idol);
148
+ addDefaultLights(scene);
149
+ const clock = new THREE.Clock();
150
+
151
+ function animate() {
152
+ const delta = clock.getDelta();
153
+ try {
154
+ mixer && mixer.update(delta);
155
+ gui && gui.controllersRecursive().forEach((controller) => controller.updateDisplay());
156
+ } catch (e) {
157
+ console.error(e);
158
+ } finally {
159
+ requestAnimationFrame(animate);
160
+ render();
161
+ }
162
+ }
163
+ function render() {
164
+ // todo
165
+
166
+ stats && stats.update();
167
+ renderer.render(scene, camera);
168
+ }
169
+ stats = Stats();
170
+ app.appendChild(stats.dom);
171
+ animate();
172
+
173
+ addGui();
174
+ };
175
+
176
+ /**
177
+ *
178
+ * @param scene
179
+ * 创建场景的灯光,根据实际需求,创建合适灯光
180
+ */
181
+ function addDefaultLights(scene: THREE.Scene) {
182
+ const dirLight = new THREE.DirectionalLight();
183
+ dirLight.color = new THREE.Color(0xffffff);
184
+ dirLight.intensity = 0.4;
185
+ dirLight.position.set(-1.45, 1, 3.57);
186
+ scene.add(dirLight);
187
+
188
+ const hemiLight = new THREE.HemisphereLight(0xffffff);
189
+ hemiLight.visible = true;
190
+ hemiLight.intensity = 0.15;
191
+ hemiLight.position.set(0, 0, 20);
192
+ scene.add(hemiLight);
193
+
194
+ let spot = new THREE.SpotLight(0xffffff);
195
+ spot.color = new THREE.Color(0xffffff);
196
+ spot.visible = true;
197
+ spot.distance = 0;
198
+ spot.intensity = 0.28;
199
+ spot.penumbra = 0;
200
+ spot.decay = 2;
201
+ spot.position.set(-1.44, 1.59, 3.57);
202
+ spot.angle = 1;
203
+ scene.add(spot);
204
+ scene.add(spot.target);
205
+
206
+ spot = new THREE.SpotLight(0xffffff);
207
+ spot.intensity = 0.35;
208
+ spot.angle = 1;
209
+ spot.penumbra = 0;
210
+ spot.distance = 0;
211
+ spot.position.set(3.68, 0.15, 7.93);
212
+ scene.add(spot);
213
+ scene.add(spot.target);
214
+
215
+ spot = new THREE.SpotLight(0xffffff);
216
+ spot.intensity = 0.9;
217
+ spot.distance = 0;
218
+ spot.angle = 1;
219
+ spot.penumbra = 0;
220
+ spot.decay = 2;
221
+ spot.position.set(0.39, 1.19, -0.91);
222
+ scene.add(spot);
223
+ scene.add(spot.target);
224
+ }
225
+
226
+ /**
227
+ * @desc 添加右侧的编辑窗体
228
+ */
229
+ function addGui() {
230
+ gui = new GUI();
231
+ const humanGui = gui.addFolder("Meta Human");
232
+ humanGui.add(idol.position, "x", -10, 10, 0.01);
233
+ humanGui.add(idol.position, "y", -10, 10, 0.01);
234
+ humanGui.add(idol.position, "z", -10, 10, 0.01);
235
+ humanGui.onChange((e) => {
236
+ const value = e.object as any;
237
+ if (e.property == "x") {
238
+ idol.position.x = value.x;
239
+ } else if (e.property == "y") {
240
+ idol.position.y = value.y;
241
+ } else if (e.property == "z") {
242
+ idol.position.z = value.z;
243
+ }
244
+ });
245
+ const canvasGui = gui.addFolder("Canvas");
246
+ canvasGui.add(canvasRect, "width", 0, 20000, 1).onChange((value) => {
247
+ canvasRect.width = value;
248
+ renderer.setSize(canvasRect.width, canvasRect.height);
249
+ camera.aspect = canvasRect.width / canvasRect.height;
250
+ camera.updateProjectionMatrix();
251
+ });
252
+ canvasGui.add(canvasRect, "height", 0, 20000, 1).onChange((value) => {
253
+ canvasRect.height = value;
254
+ renderer.setSize(canvasRect.width, canvasRect.height);
255
+ camera.aspect = canvasRect.width / canvasRect.height;
256
+ camera.updateProjectionMatrix();
257
+ });
258
+ const cameraGui = gui.addFolder("Camera ");
259
+ cameraGui.add(camera.position, "x", -100, 100, 0.01);
260
+ cameraGui.add(camera.position, "y", -100, 100, 0.01);
261
+ cameraGui.add(camera.position, "z", -100, 100, 0.01);
262
+ cameraGui.add(camera, "near", 0, 10000, 0.1);
263
+ cameraGui.add(camera, "far", 0, 100000, 0.1);
264
+ cameraGui.add(camera, "fov", 0, 180, 0.1);
265
+ cameraGui.add(camera, "aspect", 0, 2, 0.1).onChange(() => {
266
+ camera.updateMatrix();
267
+ camera.updateProjectionMatrix();
268
+ });
269
+ cameraGui.onChange(() => {
270
+ camera.updateMatrix();
271
+ camera.updateProjectionMatrix();
272
+ });
273
+
274
+ const cameraRotationGui = gui.addFolder("Camera Rotation Euler");
275
+ cameraRotationGui.add(camera.rotation, "x", -Math.PI, Math.PI, 0.01);
276
+ cameraRotationGui.add(camera.rotation, "y", -Math.PI, Math.PI, 0.01);
277
+ cameraRotationGui.add(camera.rotation, "z", -Math.PI, Math.PI, 0.01);
278
+ cameraRotationGui.onChange(() => {
279
+ camera.updateMatrix();
280
+ camera.updateMatrixWorld();
281
+ });
282
+ const lookAtGui = gui.addFolder("Camera lookAt");
283
+ lookAtGui.add(lookTarget, "x", -10, 10, 0.01);
284
+ lookAtGui.add(lookTarget, "y", -10, 10, 0.01);
285
+ lookAtGui.add(lookTarget, "z", -10, 10, 0.01);
286
+ lookAtGui.onChange(() => {
287
+ camera.lookAt(lookTarget.x, lookTarget.y, lookTarget.z);
288
+ });
289
+
290
+ const idolGui = gui.addFolder("替换人物");
291
+
292
+ idolGui.add(params, "自定义模型地址").onChange(replaceIdol);
293
+
294
+ const animateGui = gui.addFolder("Pose Animate");
295
+ animateGui.add(params, "fadeIn", 0, 10, 0.01);
296
+ animateGui.add(params, "fadeOut", 0, 10, 0.01);
297
+ animateGui.add(params, "pose", animations).onChange(handleChangePose);
298
+ animateGui.add(params, "pose").onChange(handleChangePose);
299
+ animateGui.add(params, "loop", { LoopOnce: THREE.LoopOnce, LoopRepeat: THREE.LoopRepeat });
300
+
301
+ const emoGui = gui.addFolder("Emo Animate");
302
+ emoGui.add(params, "poseEmo").onChange(handleChangeEmo);
303
+
304
+ const ttsGui = gui.addFolder("tts");
305
+ ttsGui.add(params, "appKey").onChange(() => {
306
+ makeSignCode();
307
+ });
308
+ ttsGui.add(params, "appSecret").onChange(() => {
309
+ makeSignCode();
310
+ });
311
+ ttsGui.add(params, "ttsText");
312
+ ttsGui.add(params, "发送TTS请求");
313
+ ttsGui.add(params, "audioURL").onChange(async (value) => {
314
+ // todo
315
+ const audioBuffer = await loadAudio(value);
316
+ const listener = new THREE.AudioListener();
317
+ const audio = new THREE.Audio(listener);
318
+ audio.onEnded = () => {
319
+ console.log(`播放结束`);
320
+ clearTTSResource();
321
+ };
322
+ audio.setBuffer(audioBuffer);
323
+ audio.setLoop(false);
324
+ audio.setVolume(0.5);
325
+ activeTTSResource.audio = audio;
326
+ audio.play();
327
+ });
328
+ ttsGui.add(params, "teethAnimURL");
329
+ ttsGui.add(params, "emoAnimURL");
330
+ ttsGui.add(params, "播放口型动画");
331
+
332
+ const zipLoaderGui = gui.addFolder("ZipGlbLoader");
333
+ zipLoaderGui.add(params, "加载GLBZip包");
334
+ }
335
+
336
+ /**
337
+ *
338
+ * @param opts
339
+ * @desc
340
+ * 接受模型的地址:
341
+ * 用户通过数字人平台下载的数字人通常为一个zip包。将下载的zip包放到自己的开发服务器或者,OSS云服务器上,
342
+ * 可以直接使用地址进行加载。
343
+ */
344
+ async function replaceIdol(opts: string | Uint8Array) {
345
+ if (idol) {
346
+ scene.remove(idol);
347
+ idol.clear();
348
+ idol = null;
349
+ }
350
+
351
+ if (typeof opts == "string" && (opts.endsWith(".gltf") || opts.endsWith(".glb"))) {
352
+ idol = await MMFT.core.loadGLTFModel(opts);
353
+ } else if (typeof opts == "string") {
354
+ const response = await fetch(opts, { method: "get" });
355
+ const buffer = await response.arrayBuffer();
356
+ const idolBuffer = await uncompress(new Uint8Array(buffer));
357
+ idol = await MMFT.core.parseGLTFModel(idolBuffer.buffer);
358
+ } else {
359
+ idol = await MMFT.core.parseGLTFModel(opts.buffer);
360
+ }
361
+ MMFT.core.resetPolygonOffset(idol, camera);
362
+ mixer = new THREE.AnimationMixer(idol);
363
+ scene.add(idol);
364
+ }
365
+
366
+ /**
367
+ * @param value
368
+ * 用于gui直接修改当前播放的动画
369
+ * 通过动画名称加载的的动画资源,并进行播放
370
+ */
371
+ async function handleChangePose(value: string) {
372
+ const animateJSON = await MMFT.core.loadAnimationData(value);
373
+ const clip = MMFT.core.Convert(animateJSON);
374
+ console.warn(`pose clip `, clip);
375
+ const action = mixer.clipAction(clip);
376
+ while (activeActions.length) {
377
+ const action = activeActions.pop();
378
+ if (params.fadeOut) {
379
+ action.fadeOut(params.fadeOut);
380
+ }
381
+ setTimeout(() => {
382
+ action.paused = true;
383
+ action.stop();
384
+ }, params.fadeOut);
385
+ }
386
+
387
+ activeActions.push(action);
388
+ if (params.fadeIn) {
389
+ action.fadeIn(params.fadeIn);
390
+ }
391
+ action.loop = params.loop;
392
+ action.play();
393
+ }
394
+
395
+ async function handleChangeEmo(value: string) {
396
+ const animateJSON = await MMFT.core.loadAnimationData(value);
397
+ const clip = MMFT.core.Convert(animateJSON, true);
398
+ const action = mixer.clipAction(clip);
399
+ action.play();
400
+ }
401
+
402
+ /**
403
+ *
404
+ * @param audio
405
+ * @param teeth
406
+ * @param emo
407
+ * 播放threejs的相关动画与语音
408
+ */
409
+ async function handleTTS(audio, teeth, emo) {
410
+ const teethAction = mixer.clipAction(teeth);
411
+ const emoAction = mixer.clipAction(emo);
412
+ emoAction.loop = THREE.LoopOnce;
413
+ teethAction.loop = THREE.LoopOnce;
414
+ clearTTSResource();
415
+ audio.onEnded = () => {
416
+ console.log(`播放结束`);
417
+ clearTTSResource();
418
+ };
419
+
420
+ activeTTSResource.audio = audio;
421
+ activeTTSResource.teeth = teethAction;
422
+ activeTTSResource.emo = emoAction;
423
+
424
+ audio.play();
425
+ teethAction.play();
426
+ emoAction.play();
427
+ }
428
+
429
+ /**
430
+ *
431
+ * @param text
432
+ * @returns { [ THREE.Audio,THREE.AnimationAction,THREE.AnimationAction ] }
433
+ * 通过文字,以及tts的配置信息,获得用于threejs的口型动画与音频信息
434
+ */
435
+ async function fetchTTSToAnim(text: string) {
436
+ const tts = {
437
+ voice_name: "zh-CN-XiaoxiaoNeural",
438
+ speed: 42,
439
+ volume: 100,
440
+ };
441
+ let response: any = await fetch("//open.metamaker.cn/api/openmm/v1/text_to_anim", {
442
+ method: "post",
443
+ headers: {
444
+ "Content-Type": "application/x-www-form-urlencoded",
445
+ Authorization: ttsAuth,
446
+ },
447
+ body: qs.stringify({
448
+ text: text,
449
+ tts_args: JSON.stringify(tts),
450
+ audio_type: "wav",
451
+ storage_type: "cloud",
452
+ }),
453
+ mode: "cors",
454
+ });
455
+
456
+ response = await response.json();
457
+ if (response.err_code !== 0) {
458
+ throw new Error("fetch tts failed");
459
+ }
460
+ params.audioURL = response.ret.audio;
461
+ params.teethAnimURL = response.ret.teeth_anim;
462
+ params.emoAnimURL = response.ret.expression_anim;
463
+ const data = await Promise.all([
464
+ loadAudio(response.ret.audio),
465
+ MMFT.core.loadTTSTeethAnimation(response.ret.teeth_anim),
466
+ MMFT.core.loadTTSEmoAnimation(response.ret.expression_anim),
467
+ ]);
468
+ const audioBuffer = data[0];
469
+ const listener = new THREE.AudioListener();
470
+ const audio = new THREE.Audio(listener);
471
+ audio.setBuffer(audioBuffer);
472
+ audio.setLoop(false);
473
+ audio.setVolume(0.5);
474
+ return [audio, data[1], data[2]];
475
+ }
476
+
477
+ /**
478
+ *
479
+ * @param url
480
+ * @returns {Promise<AudioBuffer>}
481
+ * @desc 通过url获得音频buffer
482
+ */
483
+ async function loadAudio(url): Promise<AudioBuffer> {
484
+ return new Promise((resolve, reject) => {
485
+ const audioLoader = new THREE.AudioLoader();
486
+ audioLoader.load(url, (buffer) => {
487
+ resolve(buffer);
488
+ });
489
+ });
490
+ }
491
+
492
+ /**
493
+ * @desc 清除语音动画资源以及停止播放音频
494
+ */
495
+ function clearTTSResource() {
496
+ if (activeTTSResource.teeth) {
497
+ activeTTSResource.teeth.paused = true;
498
+ activeTTSResource.teeth.stop();
499
+ mixer && mixer.uncacheAction(activeTTSResource.teeth.getClip());
500
+ }
501
+ if (activeTTSResource.emo) {
502
+ activeTTSResource.emo.paused = true;
503
+ activeTTSResource.emo.stop();
504
+ mixer && mixer.uncacheAction(activeTTSResource.emo.getClip());
505
+ }
506
+ if (activeTTSResource.audio) {
507
+ const audio = activeTTSResource.audio;
508
+ audio.source && audio.stop();
509
+ }
510
+ }
511
+
512
+ /**
513
+ *
514
+ * @param file
515
+ * @returns
516
+ * @desc 解压zip包,提取glb文件
517
+ */
518
+ function uncompressZipFile(file: File): Promise<Uint8Array> {
519
+ return new Promise((resolve, reject) => {
520
+ const fileReader = new FileReader();
521
+ fileReader.onload = async (e) => {
522
+ const result = e.target.result as ArrayBuffer;
523
+ console.log(`读取完毕`);
524
+ const glbBuffer: Uint8Array = await new Promise((resolve) => {
525
+ const unzipper = new fflate.Unzip();
526
+ unzipper.register(fflate.UnzipInflate);
527
+ unzipper.onfile = (file) => {
528
+ // file.name is a string, file is a stream
529
+ if (!(file.name as string).endsWith(".glb")) {
530
+ return;
531
+ }
532
+ file.ondata = (err, dat, final) => {
533
+ // Stream output here
534
+ resolve(dat);
535
+ };
536
+ console.log("Reading:", file.name);
537
+
538
+ file.start();
539
+ };
540
+ unzipper.push(new Uint8Array(result), true);
541
+ });
542
+ resolve(glbBuffer);
543
+ };
544
+ fileReader.readAsArrayBuffer(file);
545
+ });
546
+ }
547
+
548
+ /**
549
+ *
550
+ * @param buffer
551
+ * @returns
552
+ * @desc 解压zip文件获得数据
553
+ */
554
+ function uncompress(buffer: ArrayBuffer): Promise<Uint8Array> {
555
+ return new Promise((resolve) => {
556
+ const unzipper = new fflate.Unzip();
557
+ unzipper.register(fflate.UnzipInflate);
558
+ unzipper.onfile = (file) => {
559
+ // file.name is a string, file is a stream
560
+ if (!(file.name as string).endsWith(".glb")) {
561
+ return;
562
+ }
563
+ file.ondata = (err, dat, final) => {
564
+ // Stream output here
565
+ resolve(dat);
566
+ };
567
+ console.log("Reading:", file.name);
568
+
569
+ file.start();
570
+ };
571
+ unzipper.push(new Uint8Array(buffer), true);
572
+ });
573
+ }
574
+
575
+ /**
576
+ * @desc
577
+ * 用于tts(语音播报)请求的鉴权代码,
578
+ * 在不使用tts的情况,可以不用理会该段代码。
579
+
580
+ */
581
+ function makeSignCode() {
582
+ const convertTextToUint8Array = (text: string) => {
583
+ return Array.from(text).map((letter) => letter.charCodeAt(0));
584
+ };
585
+ const convertWordArrayToUint8Array = (wordArray) => {
586
+ const len = wordArray.words.length;
587
+ const uint8Array = new Uint8Array(len << 2);
588
+ let offset = 0;
589
+ let word;
590
+ for (let i = 0; i < len; i++) {
591
+ word = wordArray.words[i];
592
+ uint8Array[offset++] = word >> 24;
593
+ uint8Array[offset++] = (word >> 16) & 0xff;
594
+ uint8Array[offset++] = (word >> 8) & 0xff;
595
+ uint8Array[offset++] = word & 0xff;
596
+ }
597
+ return uint8Array;
598
+ };
599
+
600
+ const appKey = params.appKey;
601
+ const appSecret = params.appSecret;
602
+ const timestamp = Math.floor(new Date().getTime() / 1000);
603
+ console.log(`timestamp:`, timestamp);
604
+ const message = `${timestamp}:${appKey}`;
605
+ const wordsArray = CryptoJS.HmacSHA256(message, appSecret);
606
+ const hashSuffix = convertWordArrayToUint8Array(wordsArray);
607
+ const hashPrefix = convertTextToUint8Array(`${timestamp}:`);
608
+ const totalArray = new Uint8Array(hashPrefix.length + hashSuffix.length);
609
+ totalArray.set(hashPrefix);
610
+ totalArray.set(hashSuffix, hashPrefix.length);
611
+ const tempstr = String.fromCharCode.apply(null, totalArray);
612
+ console.log(`temp str`, tempstr);
613
+ const base64 = btoa(tempstr);
614
+ ttsAuth = `AW ${appKey}:${base64}`;
615
+ }
616
+
617
+ makeSignCode();
618
+
619
+ /**
620
+ * 每10分钟更新一次鉴权
621
+ */
622
+ setInterval(() => {
623
+ makeSignCode();
624
+ }, 60 * 1000 * 10);