3D-AI-Avatar / static /app.js
SS-2005's picture
Create app.js
c8673d9 verified
Raw
History Blame Contribute Delete
2.66 kB
import * as THREE from "three";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import { KTX2Loader } from "three/addons/loaders/KTX2Loader.js";
import { MeshoptDecoder } from "three/addons/libs/meshopt_decoder.module.js";
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(45, 1, 0.1, 100);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth * 0.66, window.innerHeight);
document.getElementById("viewer").appendChild(renderer.domElement);
scene.add(new THREE.AmbientLight(0xffffff, 0.6));
const light = new THREE.DirectionalLight(0xffffff, 0.8);
light.position.set(0, 1, 2);
scene.add(light);
const ktx2 = new KTX2Loader()
.setTranscoderPath("https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/basis/")
.detectSupport(renderer);
const loader = new GLTFLoader();
loader.setKTX2Loader(ktx2);
loader.setMeshoptDecoder(MeshoptDecoder);
let mesh;
loader.load("facecap.glb", gltf => {
scene.add(gltf.scene);
gltf.scene.traverse(o => {
if (o.morphTargetDictionary) mesh = o;
});
const box = new THREE.Box3().setFromObject(gltf.scene);
const size = box.getSize(new THREE.Vector3()).length();
const center = box.getCenter(new THREE.Vector3());
gltf.scene.position.sub(center);
camera.position.z = size * 0.7;
});
let analyser, data;
async function speak(text) {
const res = await fetch("/speak", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text })
});
const { audio } = await res.json();
const audioEl = new Audio(audio);
const ctx = new AudioContext();
const src = ctx.createMediaElementSource(audioEl);
analyser = ctx.createAnalyser();
analyser.fftSize = 1024;
data = new Uint8Array(analyser.frequencyBinCount);
src.connect(analyser);
analyser.connect(ctx.destination);
audioEl.play();
audioEl.onended = () => {
if (mesh?.morphTargetDictionary.mouthSmile_L !== undefined) {
mesh.morphTargetInfluences[mesh.morphTargetDictionary.mouthSmile_L] = 0.7;
mesh.morphTargetInfluences[mesh.morphTargetDictionary.mouthSmile_R] = 0.7;
}
};
}
document.getElementById("speak").onclick = () => {
speak(document.getElementById("text").value);
};
function animate() {
requestAnimationFrame(animate);
if (mesh && analyser) {
analyser.getByteFrequencyData(data);
const energy = data.reduce((a,b)=>a+b,0)/data.length;
const jaw = mesh.morphTargetDictionary.jawOpen;
if (jaw !== undefined) {
mesh.morphTargetInfluences[jaw] = Math.min(energy / 180, 1);
}
}
renderer.render(scene, camera);
}
animate();