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);