PMX Compatible MMD Renderer. (pressing a stage link the second time, messes up query strings. reload page then)
Adapted from: https://github.com/Momijinn/SampleWebMMD
Changes: https://github.com/Momijinn/SampleWebMMD/issues/1 Download
Download: https://ry3yr.github.io/SampleWebMMD-master.zip
Src: https://github.com/Momijinn/SampleWebMMD
====main.js===
let scene, renderer, camera, mesh, mesh2;
let hasLoaded = false;
let mixer1, mixer2, clock, cameraAnimation;
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
function getQueryStringValue(key) {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get(key);
}
const pmx = getQueryStringValue('pmx');
const vmdpath = getQueryStringValue('vmd') || "bts-bestofme";
const pmx2 = getQueryStringValue('pmx2') || "AoiZaizen/AoiZaizen.pmx";
const cameraId = getQueryStringValue('camera');
const stage = getQueryStringValue('stage') || "/sorclair/sorclair.pmx";
let Pmx;
let Pmx2;
if (pmx) {
Pmx = `./pmx/pronama/${pmx.trim()}`;
console.log(`PMX: ${pmx.trim()}`);
} else {
console.log("No PMX selected.");
}
if (pmx2) {
Pmx2 = `./pmx/pronama/${pmx2.trim()}`;
console.log(`PMX2: ${pmx2.trim()}`);
} else {
console.log("No PMX2 selected.");
}
let StagePath = stage ? `./stages/${stage.trim()}` : './stages/sorclair/sorclair.pmx';
if (pmx) {
Pmx = `./pmx/pronama/${pmx.trim()}`;console.log(`PMX: ${pmx.trim()}`);} else {console.log("No PMX selected.");}
if (pmx2) {Pmx2 = `./pmx/pronama/${pmx2.trim()}`;console.log(`PMX2: ${pmx2.trim()}`);} else {console.log("No PMX2 selected.");}
if (StagePath) {StagePath = `./stages${stage.trim()}`;} else {StagePath = './stages/sorclair/sorclair.pmx';}
console.log('StagePath:', StagePath);
if (StagePath) {
const loader = new THREE.MMDLoader();
const lastIndex = StagePath.lastIndexOf("/");
const basePath = StagePath.substring(0, lastIndex);
const vmd1Path = `${basePath}/001.vmd`;
const vmd2Path = `${basePath}/002.vmd`;
loader.load(StagePath, (stageObject) => {
var ambientLight = new THREE.AmbientLight(0xffffff, 1.0); //hardcoded
scene.add(ambientLight);
scene.add(stageObject);
const mixer = new THREE.AnimationMixer(stageObject);
loader.loadAnimation(vmd1Path, stageObject, (vmd1Clip) => {
vmd1Clip.name = "001";
console.log(`Loaded VMD: ${vmd1Path}`);
const motionObject1 = MotionObjects.find(obj => obj.id === "001");
if (motionObject1) {
motionObject1.VmdClip = vmd1Clip;
const action1 = mixer.clipAction(vmd1Clip);
action1.play();
} else {
console.warn(`Motion object with id "001" not found.`);
}
}, onProgress, onError);
loader.loadAnimation(vmd2Path, stageObject, (vmd2Clip) => {
vmd2Clip.name = "002";
console.log(`Loaded VMD: ${vmd2Path}`);
const motionObject2 = MotionObjects.find(obj => obj.id === "002");
if (motionObject2) {
motionObject2.VmdClip = vmd2Clip;
const action2 = mixer.clipAction(vmd2Clip);
action2.play();
} else {
console.warn(`Motion object with id "002" not found.`);
}
}, onProgress, onError);
const clock = new THREE.Clock();
const animate = () => {
requestAnimationFrame(animate);
const delta = clock.getDelta();
mixer.update(delta);
renderer.render(scene, camera);
};
animate();
}, onProgress, onError);
} else {console.warn('No valid stage path found.');}
//if (!Stage) {Stage = stage ? `stages${stage}` : '/sorclair/sorclair.pmx';}
if (!Pmx) {Pmx = `./pmx/pronama/AoiZaizen/AoiZaizen.pmx`;}
console.log('StagePath:', StagePath);
const MotionObjects = [
{ id: "001", pose: "001", VmdClip: null, AudioClip: false },
];
window.onload = () => {
Init();
LoadStage().then(() => {
LoadModels().then(() => {
});
});
};
function Init() {
document.getElementById("moveLeftButton").addEventListener("click", () => { camera.position.x -= 1; });
document.getElementById("moveRightButton").addEventListener("click", () => { camera.position.x += 1; });
document.getElementById("moveUpButton").addEventListener("click", () => { camera.position.y += 1; });
document.getElementById("moveDownButton").addEventListener("click", () => { camera.position.y -= 1; });
document.getElementById("rotaterightButton").addEventListener("click", () => { mesh.rotateY(Math.PI / 4); });
document.getElementById("rotateleftButton").addEventListener("click", () => { mesh.rotateY(-Math.PI / 4); });
scene = new THREE.Scene();
renderer = new THREE.WebGLRenderer({ alpha: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
camera = new THREE.PerspectiveCamera(100, windowWidth / windowHeight, 1, 1000);
camera.position.set(0, 19, 20);
clock = new THREE.Clock();
}
function LoadStage() {
return new Promise((resolve, reject) => {
const loader = new THREE.MMDLoader();
loader.load(StagePath, (stageObject) => {
resolve();
}, onProgress, reject);
});
}
let animate;
function startAnimation() {
document.getElementById('readystate').innerHTML = 'Camera(localstorage): ready - ' + localStorage.getItem('vmd') + ' Reload';
if (!animate) {
console.error('Animation function not initialized.');
return;
}
animate(); // Start the animation loop
}
document.getElementById('play').addEventListener('click', async () => {
const urlParams = new URLSearchParams(window.location.search);
const vmdValue = urlParams.get('vmd') || "bts-bestofme";
if (!vmdValue) {
console.log('No vmd parameter found in the URL');
return false;}
console.log('vmdValue from URL:', vmdValue);
const audioPath = `audio/${vmdValue}.mp3`;
const audioListener = new THREE.AudioListener();
const audio = new THREE.Audio(audioListener);
const audioLoader = new THREE.AudioLoader();
try {
const audioBuffer = await new Promise((resolve, reject) => {
audioLoader.load(audioPath, resolve, onAudioLoadProgress, reject);
});
audio.setBuffer(audioBuffer);
audio.setLoop(true); // Set to true if audio should loop
audio.setVolume(1.0); // Adjust volume as needed
audio.play();
console.log('Audio loaded and playing:', audioPath);
} catch (error) {
console.error('Error loading audio:', error);
document.getElementById('readystate').textContent = "Error loading Audio";
return false;
}
function onAudioLoadProgress(xhr) {
if (xhr.lengthComputable) {
const percentComplete = (xhr.loaded / xhr.total) * 100;
console.log('Audio load progress:', percentComplete.toFixed(2) + '%');
}}
try {
startAnimation();
} catch (error) {
console.error('Error loading models:', error);
}
});
async function LoadModels() {
const loader = new THREE.MMDLoader();
function LoadPMX(path) {
return new Promise(resolve => {
loader.load(path, (object) => {
resolve(object);
}, onProgress, onError);
});
}
async function LoadVMDAnimation(mesh, id) {
function getQueryStringParameter(name) {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get(name);
}
const vmdId = getQueryStringParameter('vmd') || 'bts-bestofme';
const vmdPath = `./vmd/${vmdId}.vmd`;
localStorage.setItem('vmd', vmdId);
return new Promise((resolve, reject) => {
loader.loadAnimation(vmdPath, mesh, (vmdClip) => {
vmdClip.name = vmdId;
resolve(vmdClip);
}, onProgress, reject);
});
}
async function LoadCameraAnimation(camera) {
let camid;
if (new URLSearchParams(window.location.search).has('camera')) {
camid = new URLSearchParams(window.location.search).get('camera');
} else if (new URLSearchParams(window.location.search).has('vmd')) {
camid = new URLSearchParams(window.location.search).get('vmd');
} else {
camid = localStorage.getItem('camid');
if (!camid) {
camid = 'bts-bestofme';
}
}
const cameraVmdPath = "./camera/" + camid + ".vmd";
try {
const vmdClip = await new Promise((resolve, reject) => {
loader.loadAnimation(cameraVmdPath, camera, (vmdClip) => {
vmdClip.name = camid; // Set the name to the loaded camid
resolve(vmdClip);
}, onProgress, reject);
});
return vmdClip;
} catch (error) {
console.error('Error loading camera animation:', error);
throw error; // Re-throw the error to propagate it
}
}
async function LoadModel1() {
const mesh = await LoadPMX(Pmx);
scene.add(mesh);
const vmdClip = await LoadVMDAnimation(mesh, "001");
const helper = new THREE.MMDAnimationHelper({ afterglow: 1.0 });
const mmd = { mesh: mesh, animation: vmdClip };
helper.add(mmd.mesh, {
animation: mmd.animation,
physics: true
});
return { mesh: mesh, helper: helper };
}
async function LoadModel2() {
if (Pmx2) {
const mesh2 = await LoadPMX(Pmx2);
mesh2.position.x += 15;
scene.add(mesh2);
const vmdClip = await LoadVMDAnimation(mesh2, "002");
const helper = new THREE.MMDAnimationHelper({ afterglow: 1.0 });
const mmd = { mesh: mesh2, animation: vmdClip };
helper.add(mmd.mesh, {
animation: mmd.animation,
physics: true
});
return { mesh: mesh2, helper: helper };
}
}
const { mesh: mesh1, helper: helper1 } = await LoadModel1();
const { mesh: mesh2, helper: helper2 } = await LoadModel2();
const fov = 45; // Define the field of view
const aspect = window.innerWidth / window.innerHeight; // Define the aspect ratio
const near = 1; // Define the near clipping plane
const far = 1000; // Define the far clipping plane
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
const cameraVmdClip = await LoadCameraAnimation(camera);
const cameraHelper = new THREE.MMDAnimationHelper();
cameraHelper.add(camera, {
animation: cameraVmdClip
});
const clock = new THREE.Clock();
animate = () => {
requestAnimationFrame(animate);
const delta = clock.getDelta();
helper1.update(delta);
if (helper2) helper2.update(delta);
cameraHelper.update(delta); // Update camera animation
renderer.render(scene, camera);
};
}
function onProgress(xhr) {
if (xhr.lengthComputable) {
const percentComplete = xhr.loaded / xhr.total * 100;
console.log(Math.round(percentComplete) + '% downloaded');
document.getElementById('readystate').innerHTML = Math.round(percentComplete) + '% downloaded (if stuck, click here) ' + 'Camera: ready - ' + localStorage.getItem('camid');
}
}
function onError(xhr) {
console.error("Error loading resource:", xhr);
document.getElementById('readystate').textContent = "Error loading resource: " + xhr.statusText;
}
fullscreenButton.addEventListener('click', () => {
if (!document.fullscreenElement) {
//document.body.requestFullscreen();
renderer.domElement.requestFullscreen();
} else {
if (document.exitFullscreen) {
document.exitFullscreen();
}
}
});
===========index.htm.======
...If vmd will cause keychar err, use https://www.mediafire.com/file/9olqch9pazq3fzd/AnimationSmoother.rar...