Files
2025-07-31 11:29:18 +02:00

815 lines
18 KiB
JavaScript

/**
* instance of classes in this file should be created and
* their fields should be set by VMDFileParser.
*/
function VMD() {
this.header = null;
this.motionCount = null;
this.faceCount = null;
this.cameraCount = null;
this.lightCount = null;
this.motions = [];
this.faces = [];
this.cameras = [];
this.lights = [];
this.frame = 0;
this.orderedMotions = [];
this.orderedFaces = [];
this.orderedCameras = [];
this.orderedLights = [];
this.cameraIndex = -1;
this.lightIndex = -1;
// TODO: rename
this.stepMotions = [];
this.stepFaces = [];
this.stepCamera = {location: [0, 0, 0],
rotation: [0, 0, 0],
length: 0,
angle: 0,
available: true};
this.stepLight = {color: [0, 0, 0],
location: [0, 0, 0],
available: true};
};
// for reference
VMD.prototype.Object = Object;
VMD.prototype.Math = Math;
VMD.prototype.vec3 = vec3;
VMD.prototype.quat4 = quat4;
VMD.prototype.valid = function() {
return this.header.valid();
};
VMD.prototype.supply = function() {
for(var i = 0; i < this.motionCount; i++)
this.motions[i].supply();
for(var i = 0; i < this.faceCount; i++)
this.faces[i].supply();
for(var i = 0; i < this.cameraCount; i++)
this.cameras[i].supply();
for(var i = 0; i < this.lightCount; i++)
this.lights[i].supply();
};
/**
* TODO: temporal
*/
VMD.prototype.clone = function() {
var v = new VMD();
v.motionCount = this.motionCount;
v.faceCount = this.faceCount;
v.cameraCount = this.cameraCount;
v.lightCount = this.lightCount;
for(var i = 0; i < this.motionCount; i++) {
v.motions[i] = this.motions[i];
}
for(var i = 0; i < this.faceCount; i++) {
v.faces[i] = this.faces[i];
}
for(var i = 0; i < this.cameraCount; i++) {
v.cameras[i] = this.cameras[i];
}
for(var i = 0; i < this.lightCount; i++) {
v.lights[i] = this.lights[i];
}
return v;
};
VMD.prototype.setup = function(pmd) {
this.frame = 0;
this.cameraIndex = -1;
this.lightIndex = -1;
if(pmd) {
this._setupMotions(pmd);
this._setupFaces(pmd);
}
this._setupCameras();
this._setupLights();
// TODO: temporal
this.step(1);
};
/**
* TODO: optimize
*/
VMD.prototype._setupMotions = function(pmd) {
var arrays = {};
for(var i = 0; i < this.motionCount; i++) {
var m = this.motions[i];
// Note: remove unnecessary element for PMD
if(pmd.bonesHash[m.boneName] === undefined)
continue;
if(arrays[m.boneName] === undefined) {
arrays[m.boneName] = {};
arrays[m.boneName].motions = [];
arrays[m.boneName].index = -1;
}
arrays[m.boneName].motions.push(m);
}
for(var key in arrays) {
arrays[key].motions.sort(function(a, b) {
return a.frameNum - b.frameNum;
});
}
this.orderedMotions.length = 0;
var motionKeys = this.Object.keys(arrays);
for(var i = 0; i < motionKeys.length; i++) {
this.orderedMotions[i] = arrays[motionKeys[i]];
}
this.stepMotions.length = 0;
for(var i = 0; i < pmd.boneCount; i++) {
var a = {};
a.location = [0, 0, 0];
a.rotation = [0, 0, 0, 1];
this._clearVec3(a.location); // just in case
this._clearQuat4(a.rotation); // just in case
this.stepMotions[i] = a;
}
var boneNames = pmd.getBoneNames();
var tmp = 0;
for(var i = 0; i < pmd.bones.length; i++) {
var p = pmd.bones[i];
p.motionIndex = motionKeys.indexOf(p.name);
if(p.motionIndex == -1) {
p.motionIndex = motionKeys.length + tmp;
tmp++;
}
}
};
VMD.prototype._setupFaces = function(pmd) {
var arrays = {};
for(var i = 0; i < this.faceCount; i++) {
var f = this.faces[i];
if(pmd.facesHash[f.name] === undefined)
continue;
if(arrays[f.name] === undefined) {
arrays[f.name] = {};
arrays[f.name].faces = [];
arrays[f.name].index = -1;
}
arrays[f.name].faces.push(f);
}
for(var key in arrays) {
arrays[key].faces.sort(function(a, b) {
return a.frameNum - b.frameNum;
});
}
this.orderedFaces.length = 0;
var faceKeys = this.Object.keys(arrays);
for(var i = 0; i < faceKeys.length; i++) {
this.orderedFaces[i] = arrays[faceKeys[i]];
}
this.stepFaces.length = 0;
for(var i = 0; i < pmd.faceCount; i++) {
var a = {};
a.weight = 0;
a.available = true;
this.stepFaces[i] = a;
}
var faceNames = pmd.getFaceNames();
var tmp = 0;
for(var i = 0; i < pmd.faces.length; i++) {
var p = pmd.faces[i];
p.motionIndex = faceKeys.indexOf(p.name);
if(p.motionIndex == -1) {
p.motionIndex = faceKeys.length + tmp;
this.stepFaces[p.motionIndex].available = false;
tmp++;
}
}
};
VMD.prototype._setupCameras = function() {
this.orderedCameras.length = 0;
for(var i = 0; i < this.cameraCount; i++) {
this.orderedCameras[i] = this.cameras[i];
}
this.orderedCameras.sort(function(a, b) {
return a.frameNum - b.frameNum;
});
};
VMD.prototype._setupLights = function() {
this.orderedLights.length = 0;
for(var i = 0; i < this.lightCount; i++) {
this.orderedLights[i] = {};
this.orderedLights[i].light = this.lights[i];
}
this.orderedLights.sort(function(a, b) {
return a.light.frameNum - b.light.frameNum;
});
};
VMD.prototype.step = function(dframe) {
this._stepMotion();
this._stepFace();
this._stepCamera();
this._stepLight();
// this.frame++;
this.frame += dframe;
};
/**
* TODO: check the logic.
*/
VMD.prototype._stepMotion = function() {
for(var i = 0; i < this.orderedMotions.length; i++) {
var m = this.orderedMotions[i];
while(m.index+1 < m.motions.length &&
m.motions[m.index+1].frameNum <= this.frame) {
m.index++;
}
}
};
/**
* TODO: check the logic.
*/
VMD.prototype._stepFace = function() {
for(var i = 0; i < this.orderedFaces.length; i++) {
var f = this.orderedFaces[i];
while(f.index+1 < f.faces.length &&
f.faces[f.index+1].frameNum <= this.frame) {
f.index++;
}
}
};
/**
* TODO: check the logic.
*/
VMD.prototype._stepCamera = function() {
while(this.cameraIndex+1 < this.cameras.length &&
this.orderedCameras[this.cameraIndex+1].frameNum <= this.frame) {
this.cameraIndex++;
}
};
/**
* TODO: check the logic.
*/
VMD.prototype._stepLight = function() {
while(this.lightIndex+1 < this.lights.length &&
this.orderedLights[this.lightIndex+1].light.frameNum <= this.frame) {
this.lightIndex++;
}
};
VMD.prototype.merge = function(v) {
this.motionCount += v.motionCount;
this.faceCount += v.faceCount;
this.cameraCount += v.cameraCount;
this.lightCount += v.lightCount;
for(var i = 0; i < v.motionCount; i++) {
this.motions.push(v.motions[i]);
}
for(var i = 0; i < v.faceCount; i++) {
this.faces.push(v.faces[i]);
}
for(var i = 0; i < v.cameraCount; i++) {
this.cameras.push(v.cameras[i]);
}
for(var i = 0; i < v.lightCount; i++) {
this.lights.push(v.lights[i]);
}
};
VMD.prototype.addOffset = function(o) {
for(var i = 0; i < this.motionCount; i++) {
this.motions[i].frameNum += o;
}
for(var i = 0; i < this.faceCount; i++) {
this.faces[i].frameNum += o;
}
for(var i = 0; i < this.cameraCount; i++) {
this.cameras[i].frameNum += o;
}
for(var i = 0; i < this.lightCount; i++) {
this.lights[i].frameNum += o;
}
};
/**
* TODO: temporal
* TODO: calculate next frameNum at setup phase?
* TODO: check the logic
*/
VMD.prototype.loadMotion = function() {
for(var i = 0; i < this.orderedMotions.length; i++) {
var m = this.orderedMotions[i];
if(m.index == -1)
continue;
var m1 = m.motions[m.index];
var m2 = m.motions[m.index+1];
var m3 = this.stepMotions[i];
if(m1.frameNum == this.frame
|| m2 === undefined
|| m2.frameNum - m1.frameNum <= 2) {
this._setVec3(m1.location, m3.location);
this._setQuat4(m1.rotation, m3.rotation);
} else {
// Note: linear interpolation so far
var d = m2.frameNum - m1.frameNum;
var d2 = this.frame - m1.frameNum;
var r = d2/d;
this._slerpQuat4(m1.rotation, m2.rotation, r, m3.rotation);
this._lerpVec3(m1.location, m2.location, r, m3.location);
}
}
for(var i = this.orderedMotions.length;
i < this.stepMotions.length;
i++) {
var s = this.stepMotions[i];
this._clearVec3(s.location);
this._clearQuat4(s.rotation);
}
};
/**
* TODO: temporal
* TODO: any ways to avoid update all morph Buffer?
* TODO: check the logic.
*/
VMD.prototype.loadFace = function() {
for(var i = 0; i < this.orderedFaces.length; i++) {
var f = this.orderedFaces[i];
if(f.index == -1)
continue;
var f1 = f.faces[f.index];
var f2 = f.faces[f.index+1];
var f3 = this.stepFaces[i];
if(f1.frameNum == this.frameNum
|| f2 === undefined
|| f2.frameNum - f1.frameNum <= 2) {
f3.weight = f1.weight;
} else {
var d = f2.frameNum - f1.frameNum;
var d2 = this.frame - f1.frameNum;
var r = d2/d;
f3.weight = this._lerp(f1.weight, f2.weight, r);
}
}
};
/**
* TODO: check the logic
*/
VMD.prototype.loadCamera = function() {
var ocs = this.orderedCameras;
var index = this.cameraIndex;
this.stepCamera.available = false;
if(index == -1)
return;
this.stepCamera.available = true;
var c1 = ocs[index];
var c2 = ocs[index+1];
if(c1.frameNum == this.frame
|| c2 === undefined
|| c2.frameNum - c1.frameNum <= 2) {
this._setVec3(c1.location, this.stepCamera.location);
this._setVec3(c1.rotation, this.stepCamera.rotation);
this.stepCamera.length = c1.length;
this.stepCamera.angle = c1.angle;
} else {
// Note: linear interpolation so far
var d = c2.frameNum - c1.frameNum;
var d2 = this.frame - c1.frameNum;
var r = d2/d;
this._lerpVec3(c1.location, c2.location, r, this.stepCamera.location);
this._lerpVec3(c1.rotation, c2.rotation, r, this.stepCamera.rotation);
this.stepCamera.length = this._lerp(c1.length, c2.length, r);
this.stepCamera.angle = this._lerp(c1.angle, c2.angle, r);
}
};
/**
* TODO: check the logic.
* TODO: implement correctly
*/
VMD.prototype.loadLight = function() {
var ols = this.orderedLights;
var index = this.lightIndex;
this.stepLight.available = false;
if(index == -1)
return;
var light = ols[index].light;
this.stepLight.available = true;
this._setVec3(light.color, this.stepLight.color);
this._setVec3(light.location, this.stepLight.location);
};
VMD.prototype._setVec3 = function(a, b) {
b[0] = a[0];
b[1] = a[1];
b[2] = a[2];
};
VMD.prototype._setQuat4 = function(a, b) {
b[0] = a[0];
b[1] = a[1];
b[2] = a[2];
b[3] = a[3];
};
VMD.prototype._clearVec3 = function(a) {
a[0] = 0;
a[1] = 0;
a[2] = 0;
};
VMD.prototype._clearQuat4 = function(a) {
a[0] = 0;
a[1] = 0;
a[2] = 0;
a[3] = 1;
};
VMD.prototype._lerp = function(a, b, c) {
return a * (1-c) + b * c;
};
VMD.prototype._lerpVec3 = function(a, b, c, d) {
d[0] = this._lerp(a[0], b[0], c);
d[1] = this._lerp(a[1], b[1], c);
d[2] = this._lerp(a[2], b[2], c);
};
/**
* copied from somewhere so far
* TODO: move this logic to general matrix class or somewhere
*/
VMD.prototype._slerpQuat4 = function(q, r, t, p) {
var cosHalfTheta = q[0]*r[0] + q[1]*r[1] + q[2]*r[2] + q[3]*r[3];
if(cosHalfTheta < 0) {
p[0] = -r[0];
p[1] = -r[1];
p[2] = -r[2];
p[3] = -r[3];
cosHalfTheta = -cosHalfTheta;
} else {
p[0] = r[0];
p[1] = r[1];
p[2] = r[2];
p[3] = r[3];
}
if(this.Math.abs(cosHalfTheta) >= 1.0) {
p[0] = q[0];
p[1] = q[1];
p[2] = q[2];
p[3] = q[3];
return p;
}
var halfTheta = this.Math.acos(cosHalfTheta);
var sinHalfTheta = this.Math.sqrt(1.0 - cosHalfTheta * cosHalfTheta);
if(this.Math.abs(sinHalfTheta) < 0.001) {
p[0] = 0.5 * (q[0]+r[0]);
p[1] = 0.5 * (q[1]+r[1]);
p[2] = 0.5 * (q[2]+r[2]);
p[3] = 0.5 * (q[3]+r[3]);
return p;
}
var ratioA = this.Math.sin((1-t) * halfTheta) / sinHalfTheta;
var ratioB = this.Math.sin(t * halfTheta) / sinHalfTheta;
p[0] = (q[0] * ratioA + p[0] * ratioB);
p[1] = (q[1] * ratioA + p[1] * ratioB);
p[2] = (q[2] * ratioA + p[2] * ratioB);
p[3] = (q[3] * ratioA + p[3] * ratioB);
return p;
};
/**
* just copied from MMD.js so far
*/
vec3.rotateX = function(vec, angle, dest) {
var rotation = mat4.rotateX(mat4.identity(mat4.create()), angle);
return mat4.multiplyVec3(rotation, vec, dest);
};
vec3.rotateY = function(vec, angle, dest) {
var rotation = mat4.rotateY(mat4.identity(mat4.create()), angle);
return mat4.multiplyVec3(rotation, vec, dest);
};
vec3.rotateZ = function(vec, angle, dest) {
var rotation = mat4.rotateZ(mat4.identity(mat4.create()), angle);
return mat4.multiplyVec3(rotation, vec, dest);
};
VMD.prototype.getBoneMotion = function(bone) {
return this.stepMotions[bone.motionIndex];
};
VMD.prototype.getFace = function(face) {
return this.stepFaces[face.motionIndex];
};
VMD.prototype.getCamera = function() {
return this.stepCamera;
};
VMD.prototype.getLight = function() {
return this.stepLight;
};
/**
* TODO: rename
*/
VMD.prototype.getCalculatedCameraParams = function(eye, center, up) {
var yOffset = 0.0;
var camera = this.getCamera();
center[0] = camera.location[0];
center[1] = camera.location[1]+yOffset;
center[2] = camera.location[2];
eye[0] = 0;
eye[1] = 0+yOffset;
eye[2] = camera.length;
up[0] = 0;
up[1] = 1;
up[2] = 0;
this.vec3.rotateX(eye, camera.rotation[0], eye);
this.vec3.rotateY(eye, camera.rotation[1], eye);
this.vec3.rotateZ(eye, camera.rotation[2], eye);
this.vec3.add(eye, camera.location, eye);
this.vec3.rotateX(up, camera.rotation[0], up);
this.vec3.rotateY(up, camera.rotation[1], up);
this.vec3.rotateZ(up, camera.rotation[2], up);
};
VMD.prototype.dump = function() {
var str = '';
str += 'motionCount: ' + this.motionCount + '\n';
str += 'faceCount: ' + this.faceCount + '\n';
str += 'cameraCount: ' + this.cameraCount + '\n';
str += 'lightCount: ' + this.lightCount + '\n';
str += this._dumpMotions();
str += this._dumpFaces();
str += this._dumpCameras();
str += this._dumpLights();
return str;
};
VMD.prototype._dumpMotions = function() {
var str = '';
str += '-- Motions --\n';
for(var i = 0; i < this.motionCount; i++) {
str += this.motions[i].dump();
}
str += '\n';
return str;
};
VMD.prototype._dumpFaces = function() {
var str = '';
str += '-- Faces --\n';
for(var i = 0; i < this.faceCount; i++) {
str += this.faces[i].dump();
}
str += '\n';
return str;
};
VMD.prototype._dumpCameras = function() {
var str = '';
str += '-- Cameras --\n';
for(var i = 0; i < this.cameraCount; i++) {
str += this.cameras[i].dump();
}
str += '\n';
return str;
};
VMD.prototype._dumpLights = function() {
var str = '';
str += '-- Lights --\n';
for(var i = 0; i < this.lightCount; i++) {
str += this.lights[i].dump();
}
str += '\n';
return str;
};
function VMDHeader() {
this.magic = null;
this.modelName = null;
};
VMDHeader.prototype.valid = function() {
return (this.magic == 'Vocaloid Motion Data 0002');
};
VMDHeader.prototype.dump = function() {
var str = '';
str += 'magic: ' + this.magic + '\n';
str += 'modelName: ' + this.modelName + '\n';
return str;
};
function VMDMotion(id) {
this.id = id;
this.boneName = null;
this.frameNum = null;
this.location = null;
this.rotation = null;
this.interpolation = null;
};
VMDMotion.prototype.supply = function() {
this.frameNum *= 2;
};
VMDMotion.prototype.dump = function() {
var str = '';
str += 'id: ' + this.id + '\n';
str += 'boneName: ' + this.boneName + '\n';
str += 'frameNum: ' + this.frameNum + '\n';
str += 'location: ' + this.location + '\n';
str += 'rotation: ' + this.rotation + '\n';
str += 'interpolation: ' + this.interpolation + '\n';
return str;
};
function VMDFace(id) {
this.id = id;
this.name = null;
this.frameNum = null;
this.weight = null;
};
VMDFace.prototype.supply = function() {
this.frameNum *= 2;
};
VMDFace.prototype.dump = function() {
var str = '';
str += 'id: ' + this.id + '\n';
str += 'name: ' + this.name + '\n';
str += 'frameNum: ' + this.frameNum + '\n';
str += 'weight: ' + this.weight + '\n';
return str;
};
function VMDCamera(id) {
this.id = id;
this.frameNum = null;
this.length = null;
this.location = null;
this.rotation = null;
this.interpolation = null;
this.angle = null;
this.perspective = null;
};
VMDCamera.prototype.supply = function() {
this.frameNum *= 2;
};
VMDCamera.prototype.dump = function() {
var str = '';
str += 'id: ' + this.id + '\n';
str += 'frameNum: ' + this.frameNum + '\n';
str += 'length: ' + this.length + '\n';
str += 'location: ' + this.location + '\n';
str += 'rotation: ' + this.rotation + '\n';
str += 'interpolation: ' + this.interpolation + '\n';
str += 'angle: ' + this.angle + '\n';
str += 'perspective: ' + this.perspective + '\n';
return str;
};
function VMDLight(id) {
this.id = id;
this.frameNum = null;
this.color = null;
this.location = null;
};
VMDLight.prototype.supply = function() {
this.frameNum *= 2;
};
VMDLight.prototype.dump = function() {
var str = '';
str += 'id: ' + this.id + '\n';
str += 'frameNum: ' + this.frameNum + '\n';
str += 'color: ' + this.color + '\n';
str += 'location: ' + this.location + '\n';
return str;
};