let scene, renderer, camera, mesh, mesh2, helper, cameraAnimation; let ready = false; const windowWidth = window.innerWidth; const windowHeight = window.innerHeight; const clock = new THREE.Clock(); function getQueryStringValue(key) { const urlParams = new URLSearchParams(window.location.search); return urlParams.get(key); } const pmx = getQueryStringValue('pmx'); const stage = getQueryStringValue('stage'); const pmx2 = getQueryStringValue('pmx2'); let Pmx = pmx ? `./pmx/pronama/${pmx.trim()}` : `./pmx/pronama/AoiZaizen/AoiZaizen.pmx`; let Stage = stage ? `./stages${stage.trim()}` : "./stages/sorclair/sorclair.pmx"; let Pmx2 = pmx2 ? `./pmx/pronama/${pmx2.trim()}` : null; const MotionObjects = [ { id: "001", pose: "001", VmdClip: null, AudioClip: false }, { id: "002", pose: "002", VmdClip: null, AudioClip: false }, { id: "003", pose: "003", VmdClip: null, AudioClip: false }, { id: "004walknthink", pose: "004walknthink", VmdClip: null, AudioClip: false }, { id: "005hard-carry", pose: "005hard-carry", VmdClip: null, AudioClip: true }, { id: "006_girlwaving", pose: "008_girlwaving", VmdClip: null, AudioClip: false }, { id: "bts-bestofme", pose: "bts-bestofme", VmdClip: null, AudioClip: true }, { id: "seniorita", pose: "seniorita", VmdClip: null, AudioClip: true }, { id: "test", pose: "test", VmdClip: null, AudioClip: false }, { id: "lupin", pose: "lupin", VmdClip: null, AudioClip: true }, { id: "pillarmen", pose: "pillarmen", VmdClip: null, AudioClip: true }, ]; window.onload = () => { Init(); LoadModeler(); Render(); }; function Init() { document.getElementById("moveLeftButton").addEventListener("click", moveCameraLeft); document.getElementById("moveRightButton").addEventListener("click", moveCameraRight); document.getElementById("moveUpButton").addEventListener("click", moveCameraUp); document.getElementById("moveDownButton").addEventListener("click", moveCameraDown); document.getElementById("rotaterightButton").addEventListener("click", rotateCameraRight); document.getElementById("rotateleftButton").addEventListener("click", rotateCameraLeft); function moveCameraLeft() { camera.position.x -= 1; } function moveCameraRight() { camera.position.x += 1; } function moveCameraUp() { camera.position.y += 1; } function moveCameraDown() { camera.position.y -= 1; } function rotateCameraRight() { mesh.rotateY(Math.PI / 4); } function rotateCameraLeft() { mesh.rotateY(-Math.PI / 4); } scene = new THREE.Scene(); const ambient = new THREE.AmbientLight(0xeeeeee); scene.add(ambient); renderer = new THREE.WebGLRenderer({ alpha: true }); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setClearColor(0xcccccc, 0); document.body.appendChild(renderer.domElement); camera = new THREE.PerspectiveCamera(100, windowWidth / windowHeight, 1, 1000); camera.position.set(0, 19, 20); } async function LoadModeler() { const loader = new THREE.MMDLoader(); async function LoadPMX(url) { return new Promise(resolve => { loader.load(url, object => { scene.add(object); resolve(object); }); }); } await LoadAdditionalPMX(); mesh = await LoadPMX(Pmx); if (Pmx2) { await LoadPMX(Pmx2); } async function LoadVMD(id) { return new Promise(resolve => { const path = `./vmd/${id}.vmd`; const val = MotionObjects.findIndex(MotionObject => MotionObject.id === id); loader.loadAnimation(path, mesh, vmd => { vmd.name = id; MotionObjects[val].VmdClip = vmd; document.getElementById('readystate').innerHTML = `Checking motion vmd - ${id} (if stuck, click here)`; resolve(true); }); }); } async function LoadCamera() { const camid = getQueryStringValue('camera') || localStorage.getItem('camid') || 'bts-bestofme'; const path = `./camera/${camid}.vmd`; return new Promise((resolve, reject) => { loader.loadAnimation(path, camera, cameraAnimation => { cameraAnimation.name = path; resolve(cameraAnimation); }, onProgress, (xhr) => reject(new Error(`Failed to load Camera VMD file: ${path}`))); }); } async function LoadAudio(id) { return new Promise(resolve => { const path = `./audio/${id}.mp3`; const val = MotionObjects.findIndex(MotionObject => MotionObject.id === id); if (MotionObjects[val].AudioClip) { new THREE.AudioLoader().load(path, buffer => { const listener = new THREE.AudioListener(); const audio = new THREE.Audio(listener).setBuffer(buffer); MotionObjects[val].AudioClip = audio; document.getElementById('readystate').innerHTML = `Checking audio - ${id} (if stuck, click here)`; resolve(true); }); } else { resolve(false); } }); } async function LoadAdditionalPMX() { return new Promise(resolve => { loader.load(Stage, object => { scene.add(object); resolve(true); }); }); } await Promise.all(MotionObjects.map(async (MotionObject) => await LoadVMD(MotionObject.id))); await Promise.all(MotionObjects.map(async (MotionObject) => await LoadAudio(MotionObject.id))); cameraAnimation = await LoadCamera(); VmdControl("loop", true); } function VmdControl(id, loop) { const index = MotionObjects.findIndex(MotionObject => MotionObject.id === id); if (index === -1) { document.getElementById('readystate').textContent = `Camera: ready - ${localStorage.getItem('camid')}`; return; } ready = false; helper = new THREE.MMDAnimationHelper({ afterglow: 2.0, resetPhysicsOnLoop: true }); const enablePhysics = localStorage.getItem("physicsareon") === "true"; helper.add(mesh, { animation: MotionObjects[index].VmdClip, physics: enablePhysics }); if (MotionObjects[index].AudioClip) { MotionObjects[index].AudioClip.play(); } if (cameraAnimation) { helper.add(camera, { animation: cameraAnimation }); } const mixer = helper.objects.get(mesh).mixer; if (!loop) { mixer.existingAction(MotionObjects[index].VmdClip).setLoop(THREE.LoopOnce); } mixer.addEventListener("loop", () => { console.log("loop"); }); mixer.addEventListener("finished", () => { console.log("finished"); VmdControl("loop", true); }); ready = true; } function onProgress(xhr) { if (xhr.lengthComputable) { const percentComplete = xhr.loaded / xhr.total * 100; console.log(`${Math.round(percentComplete, 2)}% downloaded`); } } function Render() { requestAnimationFrame(Render); renderer.clear(); renderer.render(scene, camera); if (ready) { helper.update(clock.getDelta()); } } const generatePoseClickEvent = (motionObjects) => { const functionBody = motionObjects.map(motion => { return `case '${motion.pose}': VmdControl('${motion.id}', ${motion.pose === "pose1"}); break;`; }).join(""); return new Function("id", `switch (id) { ${functionBody} default: VmdControl('001', true); break; }`); }; const PoseClickEvent = generatePoseClickEvent(MotionObjects); console.log(PoseClickEvent);