three
Version:
JavaScript 3D library
2,319 lines (1,439 loc) • 97.2 kB
JavaScript
/**
* @author takahiro / https://github.com/takahirox
*
* Dependencies
* - charset-encoder-js https://github.com/takahirox/charset-encoder-js
* - THREE.TGALoader
*
*
* This loader loads and parses PMD/PMX and VMD binary files
* then creates mesh for Three.js.
*
* PMD/PMX is a model data format and VMD is a motion data format
* used in MMD(Miku Miku Dance).
*
* MMD is a 3D CG animation tool which is popular in Japan.
*
*
* MMD official site
* http://www.geocities.jp/higuchuu4/index_e.htm
*
* PMD, VMD format
* http://blog.goo.ne.jp/torisu_tetosuki/e/209ad341d3ece2b1b4df24abf619d6e4
*
* PMX format
* http://gulshan-i-raz.geo.jp/labs/2012/10/17/pmx-format1/
*
*
* TODO
* - light motion in vmd support.
* - SDEF support.
* - uv/material/bone morphing support.
* - more precise grant skinning support.
* - shadow support.
*/
THREE.MMDLoader = function ( showStatus, manager ) {
THREE.Loader.call( this, showStatus );
this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
this.defaultTexturePath = './models/default/';
};
THREE.MMDLoader.prototype = Object.create( THREE.Loader.prototype );
THREE.MMDLoader.prototype.constructor = THREE.MMDLoader;
THREE.MMDLoader.prototype.setDefaultTexturePath = function ( path ) {
this.defaultTexturePath = path;
};
THREE.MMDLoader.prototype.load = function ( modelUrl, vmdUrls, callback, onProgress, onError ) {
var scope = this;
this.loadModel( modelUrl, function ( mesh ) {
scope.loadVmds( vmdUrls, function ( vmd ) {
scope.pourVmdIntoModel( mesh, vmd );
callback( mesh );
}, onProgress, onError );
}, onProgress, onError );
};
THREE.MMDLoader.prototype.loadModel = function ( url, callback, onProgress, onError ) {
var scope = this;
var texturePath = this.extractUrlBase( url );
var modelExtension = this.extractExtension( url );
this.loadFileAsBuffer( url, function ( buffer ) {
callback( scope.createModel( buffer, modelExtension, texturePath ) );
}, onProgress, onError );
};
THREE.MMDLoader.prototype.createModel = function ( buffer, modelExtension, texturePath ) {
return this.createMesh( this.parseModel( buffer, modelExtension ), texturePath );
};
THREE.MMDLoader.prototype.loadVmd = function ( url, callback, onProgress, onError ) {
var scope = this;
this.loadFileAsBuffer( url, function ( buffer ) {
callback( scope.parseVmd( buffer ) );
}, onProgress, onError );
};
THREE.MMDLoader.prototype.loadVmds = function ( urls, callback, onProgress, onError ) {
var scope = this;
var vmds = [];
function run () {
var url = urls.shift();
scope.loadVmd( url, function ( vmd ) {
vmds.push( vmd );
if ( urls.length > 0 ) {
run();
} else {
callback( scope.mergeVmds( vmds ) );
}
}, onProgress, onError );
};
run();
};
THREE.MMDLoader.prototype.loadAudio = function ( url, callback, onProgress, onError ) {
var listener = new THREE.AudioListener();
var audio = new THREE.Audio( listener );
var loader = new THREE.AudioLoader( this.manager );
loader.load( url, function ( buffer ) {
audio.setBuffer( buffer );
callback( audio, listener );
}, onProgress, onError );
};
THREE.MMDLoader.prototype.loadVpd = function ( url, callback, onProgress, onError, params ) {
var scope = this;
var func = ( ( params && params.charcode === 'unicode' ) ? this.loadFileAsText : this.loadFileAsShiftJISText ).bind( this );
func( url, function ( text ) {
callback( scope.parseVpd( text ) );
}, onProgress, onError );
};
THREE.MMDLoader.prototype.mergeVmds = function ( vmds ) {
var v = {};
v.metadata = {};
v.metadata.name = vmds[ 0 ].metadata.name;
v.metadata.coordinateSystem = vmds[ 0 ].metadata.coordinateSystem;
v.metadata.motionCount = 0;
v.metadata.morphCount = 0;
v.metadata.cameraCount = 0;
v.motions = [];
v.morphs = [];
v.cameras = [];
for ( var i = 0; i < vmds.length; i++ ) {
var v2 = vmds[ i ];
v.metadata.motionCount += v2.metadata.motionCount;
v.metadata.morphCount += v2.metadata.morphCount;
v.metadata.cameraCount += v2.metadata.cameraCount;
for ( var j = 0; j < v2.metadata.motionCount; j++ ) {
v.motions.push( v2.motions[ j ] );
}
for ( var j = 0; j < v2.metadata.morphCount; j++ ) {
v.morphs.push( v2.morphs[ j ] );
}
for ( var j = 0; j < v2.metadata.cameraCount; j++ ) {
v.cameras.push( v2.cameras[ j ] );
}
}
return v;
};
THREE.MMDLoader.prototype.pourVmdIntoModel = function ( mesh, vmd, name ) {
this.createAnimation( mesh, vmd, name );
};
THREE.MMDLoader.prototype.pourVmdIntoCamera = function ( camera, vmd, name ) {
var helper = new THREE.MMDLoader.DataCreationHelper();
var initAnimation = function () {
var orderedMotions = helper.createOrderedMotionArray( vmd.cameras );
var q = new THREE.Quaternion();
var e = new THREE.Euler();
var pkeys = [];
var ckeys = [];
var ukeys = [];
var fkeys = [];
for ( var i = 0; i < orderedMotions.length; i++ ) {
var m = orderedMotions[ i ];
var t = m.frameNum / 30;
var p = m.position;
var r = m.rotation;
var d = m.distance;
var f = m.fov;
var position = new THREE.Vector3( 0, 0, -d );
var center = new THREE.Vector3( p[ 0 ], p[ 1 ], p[ 2 ] );
var up = new THREE.Vector3( 0, 1, 0 );
e.set( -r[ 0 ], -r[ 1 ], -r[ 2 ] );
q.setFromEuler( e );
position.add( center );
position.applyQuaternion( q );
up.applyQuaternion( q );
helper.pushAnimationKey( pkeys, t, position, true );
helper.pushAnimationKey( ckeys, t, center, true );
helper.pushAnimationKey( ukeys, t, up, true );
helper.pushAnimationKey( fkeys, t, f, true );
}
helper.insertAnimationKeyAtTimeZero( pkeys, new THREE.Vector3( 0, 0, 0 ) );
helper.insertAnimationKeyAtTimeZero( ckeys, new THREE.Vector3( 0, 0, 0 ) );
helper.insertAnimationKeyAtTimeZero( ukeys, new THREE.Vector3( 0, 0, 0 ) );
helper.insertAnimationKeyAtTimeZero( fkeys, 45 );
helper.insertStartAnimationKey( pkeys );
helper.insertStartAnimationKey( ckeys );
helper.insertStartAnimationKey( ukeys );
helper.insertStartAnimationKey( fkeys );
var tracks = [];
tracks.push( helper.generateTrackFromAnimationKeys( pkeys, 'VectorKeyframeTrackEx', '.position' ) );
tracks.push( helper.generateTrackFromAnimationKeys( ckeys, 'VectorKeyframeTrackEx', '.center' ) );
tracks.push( helper.generateTrackFromAnimationKeys( ukeys, 'VectorKeyframeTrackEx', '.up' ) );
tracks.push( helper.generateTrackFromAnimationKeys( fkeys, 'NumberKeyframeTrackEx', '.fov' ) );
camera.center = new THREE.Vector3( 0, 0, 0 );
if ( camera.animations === undefined ) {
camera.animations = [];
}
camera.animations.push( new THREE.AnimationClip( name === undefined ? THREE.Math.generateUUID() : name, -1, tracks ) );
};
this.leftToRightVmd( vmd );
initAnimation();
};
THREE.MMDLoader.prototype.extractExtension = function ( url ) {
var index = url.lastIndexOf( '.' );
if ( index < 0 ) {
return null;
}
return url.slice( index + 1 );
};
THREE.MMDLoader.prototype.loadFile = function ( url, onLoad, onProgress, onError, responseType ) {
var loader = new THREE.XHRLoader( this.manager );
loader.setResponseType( responseType );
var request = loader.load( url, function ( result ) {
onLoad( result );
}, onProgress, onError );
return request;
};
THREE.MMDLoader.prototype.loadFileAsBuffer = function ( url, onLoad, onProgress, onError ) {
this.loadFile( url, onLoad, onProgress, onError, 'arraybuffer' );
};
THREE.MMDLoader.prototype.loadFileAsText = function ( url, onLoad, onProgress, onError ) {
this.loadFile( url, onLoad, onProgress, onError, 'text' );
};
THREE.MMDLoader.prototype.loadFileAsShiftJISText = function ( url, onLoad, onProgress, onError ) {
var request = this.loadFile( url, onLoad, onProgress, onError, 'text' );
/*
* TODO: some browsers seem not support overrideMimeType
* so some workarounds for them may be necessary.
* Note: to set property of request after calling request.send(null)
* (it's called in THREE.XHRLoader.load()) could be a bad manner.
*/
request.overrideMimeType( 'text/plain; charset=shift_jis' );
};
THREE.MMDLoader.prototype.parseModel = function ( buffer, modelExtension ) {
// Should I judge from model data header?
switch( modelExtension.toLowerCase() ) {
case 'pmd':
return this.parsePmd( buffer );
case 'pmx':
return this.parsePmx( buffer );
default:
throw 'extension ' + modelExtension + ' is not supported.';
}
};
THREE.MMDLoader.prototype.parsePmd = function ( buffer ) {
var scope = this;
var pmd = {};
var dv = new THREE.MMDLoader.DataView( buffer );
var helper = new THREE.MMDLoader.DataCreationHelper();
pmd.metadata = {};
pmd.metadata.format = 'pmd';
pmd.metadata.coordinateSystem = 'left';
var parseHeader = function () {
var metadata = pmd.metadata;
metadata.magic = dv.getChars( 3 );
if ( metadata.magic !== 'Pmd' ) {
throw 'PMD file magic is not Pmd, but ' + metadata.magic;
}
metadata.version = dv.getFloat32();
metadata.modelName = dv.getSjisStringsAsUnicode( 20 );
metadata.comment = dv.getSjisStringsAsUnicode( 256 );
};
var parseVertices = function () {
var parseVertex = function () {
var p = {};
p.position = dv.getFloat32Array( 3 );
p.normal = dv.getFloat32Array( 3 );
p.uv = dv.getFloat32Array( 2 );
p.skinIndices = dv.getUint16Array( 2 );
p.skinWeights = [ dv.getUint8() / 100 ];
p.skinWeights.push( 1.0 - p.skinWeights[ 0 ] );
p.edgeFlag = dv.getUint8();
return p;
};
var metadata = pmd.metadata;
metadata.vertexCount = dv.getUint32();
pmd.vertices = [];
for ( var i = 0; i < metadata.vertexCount; i++ ) {
pmd.vertices.push( parseVertex() );
}
};
var parseFaces = function () {
var parseFace = function () {
var p = {};
p.indices = dv.getUint16Array( 3 );
return p;
};
var metadata = pmd.metadata;
metadata.faceCount = dv.getUint32() / 3;
pmd.faces = [];
for ( var i = 0; i < metadata.faceCount; i++ ) {
pmd.faces.push( parseFace() );
}
};
var parseMaterials = function () {
var parseMaterial = function () {
var p = {};
p.diffuse = dv.getFloat32Array( 4 );
p.shininess = dv.getFloat32();
p.specular = dv.getFloat32Array( 3 );
p.emissive = dv.getFloat32Array( 3 );
p.toonIndex = dv.getInt8();
p.edgeFlag = dv.getUint8();
p.faceCount = dv.getUint32() / 3;
p.fileName = dv.getSjisStringsAsUnicode( 20 );
return p;
};
var metadata = pmd.metadata;
metadata.materialCount = dv.getUint32();
pmd.materials = [];
for ( var i = 0; i < metadata.materialCount; i++ ) {
pmd.materials.push( parseMaterial() );
}
};
var parseBones = function () {
var parseBone = function () {
var p = {};
// Skinning animation doesn't work when bone name is Japanese Unicode in r73.
// So using charcode strings as workaround and keep original strings in .originalName.
p.originalName = dv.getSjisStringsAsUnicode( 20 );
p.name = helper.toCharcodeStrings( p.originalName );
p.parentIndex = dv.getInt16();
p.tailIndex = dv.getInt16();
p.type = dv.getUint8();
p.ikIndex = dv.getInt16();
p.position = dv.getFloat32Array( 3 );
return p;
};
var metadata = pmd.metadata;
metadata.boneCount = dv.getUint16();
pmd.bones = [];
for ( var i = 0; i < metadata.boneCount; i++ ) {
pmd.bones.push( parseBone() );
}
};
var parseIks = function () {
var parseIk = function () {
var p = {};
p.target = dv.getUint16();
p.effector = dv.getUint16();
p.linkCount = dv.getUint8();
p.iteration = dv.getUint16();
p.maxAngle = dv.getFloat32();
p.links = [];
for ( var i = 0; i < p.linkCount; i++ ) {
var link = {}
link.index = dv.getUint16();
p.links.push( link );
}
return p;
};
var metadata = pmd.metadata;
metadata.ikCount = dv.getUint16();
pmd.iks = [];
for ( var i = 0; i < metadata.ikCount; i++ ) {
pmd.iks.push( parseIk() );
}
};
var parseMorphs = function () {
var parseMorph = function () {
var p = {};
p.name = dv.getSjisStringsAsUnicode( 20 );
p.elementCount = dv.getUint32();
p.type = dv.getUint8();
p.elements = [];
for ( var i = 0; i < p.elementCount; i++ ) {
p.elements.push( {
index: dv.getUint32(),
position: dv.getFloat32Array( 3 )
} ) ;
}
return p;
};
var metadata = pmd.metadata;
metadata.morphCount = dv.getUint16();
pmd.morphs = [];
for ( var i = 0; i < metadata.morphCount; i++ ) {
pmd.morphs.push( parseMorph() );
}
};
var parseMorphFrames = function () {
var parseMorphFrame = function () {
var p = {};
p.index = dv.getUint16();
return p;
};
var metadata = pmd.metadata;
metadata.morphFrameCount = dv.getUint8();
pmd.morphFrames = [];
for ( var i = 0; i < metadata.morphFrameCount; i++ ) {
pmd.morphFrames.push( parseMorphFrame() );
}
};
var parseBoneFrameNames = function () {
var parseBoneFrameName = function () {
var p = {};
p.name = dv.getSjisStringsAsUnicode( 50 );
return p;
};
var metadata = pmd.metadata;
metadata.boneFrameNameCount = dv.getUint8();
pmd.boneFrameNames = [];
for ( var i = 0; i < metadata.boneFrameNameCount; i++ ) {
pmd.boneFrameNames.push( parseBoneFrameName() );
}
};
var parseBoneFrames = function () {
var parseBoneFrame = function () {
var p = {};
p.boneIndex = dv.getInt16();
p.frameIndex = dv.getUint8();
return p;
};
var metadata = pmd.metadata;
metadata.boneFrameCount = dv.getUint32();
pmd.boneFrames = [];
for ( var i = 0; i < metadata.boneFrameCount; i++ ) {
pmd.boneFrames.push( parseBoneFrame() );
}
};
var parseEnglishHeader = function () {
var metadata = pmd.metadata;
metadata.englishCompatibility = dv.getUint8();
if ( metadata.englishCompatibility > 0 ) {
metadata.englishModelName = dv.getSjisStringsAsUnicode( 20 );
metadata.englishComment = dv.getSjisStringsAsUnicode( 256 );
}
};
var parseEnglishBoneNames = function () {
var parseEnglishBoneName = function () {
var p = {};
p.name = dv.getSjisStringsAsUnicode( 20 );
return p;
};
var metadata = pmd.metadata;
if ( metadata.englishCompatibility === 0 ) {
return;
}
pmd.englishBoneNames = [];
for ( var i = 0; i < metadata.boneCount; i++ ) {
pmd.englishBoneNames.push( parseEnglishBoneName() );
}
};
var parseEnglishMorphNames = function () {
var parseEnglishMorphName = function () {
var p = {};
p.name = dv.getSjisStringsAsUnicode( 20 );
return p;
};
var metadata = pmd.metadata;
if ( metadata.englishCompatibility === 0 ) {
return;
}
pmd.englishMorphNames = [];
for ( var i = 0; i < metadata.morphCount - 1; i++ ) {
pmd.englishMorphNames.push( parseEnglishMorphName() );
}
};
var parseEnglishBoneFrameNames = function () {
var parseEnglishBoneFrameName = function () {
var p = {};
p.name = dv.getSjisStringsAsUnicode( 50 );
return p;
};
var metadata = pmd.metadata;
if ( metadata.englishCompatibility === 0 ) {
return;
}
pmd.englishBoneFrameNames = [];
for ( var i = 0; i < metadata.boneFrameNameCount; i++ ) {
pmd.englishBoneFrameNames.push( parseEnglishBoneFrameName() );
}
};
var parseToonTextures = function () {
var parseToonTexture = function () {
var p = {};
p.fileName = dv.getSjisStringsAsUnicode( 100 );
return p;
};
pmd.toonTextures = [];
for ( var i = 0; i < 10; i++ ) {
pmd.toonTextures.push( parseToonTexture() );
}
};
var parseRigidBodies = function () {
var parseRigidBody = function () {
var p = {};
p.name = dv.getSjisStringsAsUnicode( 20 );
p.boneIndex = dv.getInt16();
p.groupIndex = dv.getUint8();
p.groupTarget = dv.getUint16();
p.shapeType = dv.getUint8();
p.width = dv.getFloat32();
p.height = dv.getFloat32();
p.depth = dv.getFloat32();
p.position = dv.getFloat32Array( 3 );
p.rotation = dv.getFloat32Array( 3 );
p.weight = dv.getFloat32();
p.positionDamping = dv.getFloat32();
p.rotationDamping = dv.getFloat32();
p.restriction = dv.getFloat32();
p.friction = dv.getFloat32();
p.type = dv.getUint8();
return p;
};
var metadata = pmd.metadata;
metadata.rigidBodyCount = dv.getUint32();
pmd.rigidBodies = [];
for ( var i = 0; i < metadata.rigidBodyCount; i++ ) {
pmd.rigidBodies.push( parseRigidBody() );
}
};
var parseConstraints = function () {
var parseConstraint = function () {
var p = {};
p.name = dv.getSjisStringsAsUnicode( 20 );
p.rigidBodyIndex1 = dv.getUint32();
p.rigidBodyIndex2 = dv.getUint32();
p.position = dv.getFloat32Array( 3 );
p.rotation = dv.getFloat32Array( 3 );
p.translationLimitation1 = dv.getFloat32Array( 3 );
p.translationLimitation2 = dv.getFloat32Array( 3 );
p.rotationLimitation1 = dv.getFloat32Array( 3 );
p.rotationLimitation2 = dv.getFloat32Array( 3 );
p.springPosition = dv.getFloat32Array( 3 );
p.springRotation = dv.getFloat32Array( 3 );
return p;
};
var metadata = pmd.metadata;
metadata.constraintCount = dv.getUint32();
pmd.constraints = [];
for ( var i = 0; i < metadata.constraintCount; i++ ) {
pmd.constraints.push( parseConstraint() );
}
};
parseHeader();
parseVertices();
parseFaces();
parseMaterials();
parseBones();
parseIks();
parseMorphs();
parseMorphFrames();
parseBoneFrameNames();
parseBoneFrames();
parseEnglishHeader();
parseEnglishBoneNames();
parseEnglishMorphNames();
parseEnglishBoneFrameNames();
parseToonTextures();
parseRigidBodies();
parseConstraints();
// console.log( pmd ); // for console debug
return pmd;
};
THREE.MMDLoader.prototype.parsePmx = function ( buffer ) {
var scope = this;
var pmx = {};
var dv = new THREE.MMDLoader.DataView( buffer );
var helper = new THREE.MMDLoader.DataCreationHelper();
pmx.metadata = {};
pmx.metadata.format = 'pmx';
pmx.metadata.coordinateSystem = 'left';
var parseHeader = function () {
var metadata = pmx.metadata;
metadata.magic = dv.getChars( 4 );
// Note: don't remove the last blank space.
if ( metadata.magic !== 'PMX ' ) {
throw 'PMX file magic is not PMX , but ' + metadata.magic;
}
metadata.version = dv.getFloat32();
if ( metadata.version !== 2.0 && metadata.version !== 2.1 ) {
throw 'PMX version ' + metadata.version + ' is not supported.';
}
metadata.headerSize = dv.getUint8();
metadata.encoding = dv.getUint8();
metadata.additionalUvNum = dv.getUint8();
metadata.vertexIndexSize = dv.getUint8();
metadata.textureIndexSize = dv.getUint8();
metadata.materialIndexSize = dv.getUint8();
metadata.boneIndexSize = dv.getUint8();
metadata.morphIndexSize = dv.getUint8();
metadata.rigidBodyIndexSize = dv.getUint8();
metadata.modelName = dv.getTextBuffer();
metadata.englishModelName = dv.getTextBuffer();
metadata.comment = dv.getTextBuffer();
metadata.englishComment = dv.getTextBuffer();
};
var parseVertices = function () {
var parseVertex = function () {
var p = {};
p.position = dv.getFloat32Array( 3 );
p.normal = dv.getFloat32Array( 3 );
p.uv = dv.getFloat32Array( 2 );
p.auvs = [];
for ( var i = 0; i < pmx.metadata.additionalUvNum; i++ ) {
p.auvs.push( dv.getFloat32Array( 4 ) );
}
p.type = dv.getUint8();
var indexSize = metadata.boneIndexSize;
if ( p.type === 0 ) { // BDEF1
p.skinIndices = dv.getIndexArray( indexSize, 1 );
p.skinWeights = [ 1.0 ];
} else if ( p.type === 1 ) { // BDEF2
p.skinIndices = dv.getIndexArray( indexSize, 2 );
p.skinWeights = dv.getFloat32Array( 1 );
p.skinWeights.push( 1.0 - p.skinWeights[ 0 ] );
} else if ( p.type === 2 ) { // BDEF4
p.skinIndices = dv.getIndexArray( indexSize, 4 );
p.skinWeights = dv.getFloat32Array( 4 );
} else if ( p.type === 3 ) { // SDEF
p.skinIndices = dv.getIndexArray( indexSize, 2 );
p.skinWeights = dv.getFloat32Array( 1 );
p.skinWeights.push( 1.0 - p.skinWeights[ 0 ] );
p.skinC = dv.getFloat32Array( 3 );
p.skinR0 = dv.getFloat32Array( 3 );
p.skinR1 = dv.getFloat32Array( 3 );
// SDEF is not supported yet and is handled as BDEF2 so far.
// TODO: SDEF support
p.type = 1;
} else {
throw 'unsupport bone type ' + p.type + ' exception.';
}
p.edgeRatio = dv.getFloat32();
return p;
};
var metadata = pmx.metadata;
metadata.vertexCount = dv.getUint32();
pmx.vertices = [];
for ( var i = 0; i < metadata.vertexCount; i++ ) {
pmx.vertices.push( parseVertex() );
}
};
var parseFaces = function () {
var parseFace = function () {
var p = {};
p.indices = dv.getIndexArray( metadata.vertexIndexSize, 3 );
return p;
};
var metadata = pmx.metadata;
metadata.faceCount = dv.getUint32() / 3;
pmx.faces = [];
for ( var i = 0; i < metadata.faceCount; i++ ) {
pmx.faces.push( parseFace() );
}
};
var parseTextures = function () {
var parseTexture = function () {
return dv.getTextBuffer();
};
var metadata = pmx.metadata;
metadata.textureCount = dv.getUint32();
pmx.textures = [];
for ( var i = 0; i < metadata.textureCount; i++ ) {
pmx.textures.push( parseTexture() );
}
};
var parseMaterials = function () {
var parseMaterial = function () {
var p = {};
p.name = dv.getTextBuffer();
p.englishName = dv.getTextBuffer();
p.diffuse = dv.getFloat32Array( 4 );
p.specular = dv.getFloat32Array( 3 );
p.shininess = dv.getFloat32();
p.emissive = dv.getFloat32Array( 3 );
p.flag = dv.getUint8();
p.edgeColor = dv.getFloat32Array( 4 );
p.edgeSize = dv.getFloat32();
p.textureIndex = dv.getIndex( pmx.metadata.textureIndexSize );
p.envTextureIndex = dv.getIndex( pmx.metadata.textureIndexSize );
p.envFlag = dv.getUint8();
p.toonFlag = dv.getUint8();
if ( p.toonFlag === 0 ) {
p.toonIndex = dv.getIndex( pmx.metadata.textureIndexSize );
} else if ( p.toonFlag === 1 ) {
p.toonIndex = dv.getInt8();
} else {
throw 'unknown toon flag ' + p.toonFlag + ' exception.';
}
p.comment = dv.getTextBuffer();
p.faceCount = dv.getUint32() / 3;
return p;
};
var metadata = pmx.metadata;
metadata.materialCount = dv.getUint32();
pmx.materials = [];
for ( var i = 0; i < metadata.materialCount; i++ ) {
pmx.materials.push( parseMaterial() );
}
};
var parseBones = function () {
var parseBone = function () {
var p = {};
// Skinning animation doesn't work when bone name is Japanese Unicode in r73.
// So using charcode strings as workaround and keep original strings in .originalName.
p.originalName = dv.getTextBuffer();
p.name = helper.toCharcodeStrings( p.originalName );
p.englishName = dv.getTextBuffer();
p.position = dv.getFloat32Array( 3 );
p.parentIndex = dv.getIndex( pmx.metadata.boneIndexSize );
p.transformationClass = dv.getUint32();
p.flag = dv.getUint16();
if ( p.flag & 0x1 ) {
p.connectIndex = dv.getIndex( pmx.metadata.boneIndexSize );
} else {
p.offsetPosition = dv.getFloat32Array( 3 );
}
if ( p.flag & 0x100 || p.flag & 0x200 ) {
// Note: I don't think Grant is an appropriate name
// but I found that some English translated MMD tools use this term
// so I've named it Grant so far.
// I'd rename to more appropriate name from Grant later.
var grant = {};
grant.isLocal = ( p.flag & 0x80 ) !== 0 ? true : false;
grant.affectRotation = ( p.flag & 0x100 ) !== 0 ? true : false;
grant.affectPosition = ( p.flag & 0x200 ) !== 0 ? true : false;
grant.parentIndex = dv.getIndex( pmx.metadata.boneIndexSize );
grant.ratio = dv.getFloat32();
p.grant = grant;
}
if ( p.flag & 0x400 ) {
p.fixAxis = dv.getFloat32Array( 3 );
}
if ( p.flag & 0x800 ) {
p.localXVector = dv.getFloat32Array( 3 );
p.localZVector = dv.getFloat32Array( 3 );
}
if ( p.flag & 0x2000 ) {
p.key = dv.getUint32();
}
if ( p.flag & 0x20 ) {
var ik = {};
ik.effector = dv.getIndex( pmx.metadata.boneIndexSize );
ik.target = null;
ik.iteration = dv.getUint32();
ik.maxAngle = dv.getFloat32();
ik.linkCount = dv.getUint32();
ik.links = [];
for ( var i = 0; i < ik.linkCount; i++ ) {
var link = {};
link.index = dv.getIndex( pmx.metadata.boneIndexSize );
link.angleLimitation = dv.getUint8();
if ( link.angleLimitation === 1 ) {
link.lowerLimitationAngle = dv.getFloat32Array( 3 );
link.upperLimitationAngle = dv.getFloat32Array( 3 );
}
ik.links.push( link );
}
p.ik = ik;
}
return p;
};
var metadata = pmx.metadata;
metadata.boneCount = dv.getUint32();
pmx.bones = [];
for ( var i = 0; i < metadata.boneCount; i++ ) {
pmx.bones.push( parseBone() );
}
};
var parseMorphs = function () {
var parseMorph = function () {
var p = {};
p.name = dv.getTextBuffer();
p.englishName = dv.getTextBuffer();
p.panel = dv.getUint8();
p.type = dv.getUint8();
p.elementCount = dv.getUint32();
p.elements = [];
for ( var i = 0; i < p.elementCount; i++ ) {
if ( p.type === 0 ) { // group morph
var m = {};
m.index = dv.getIndex( pmx.metadata.morphIndexSize );
m.ratio = dv.getFloat32();
p.elements.push( m );
} else if ( p.type === 1 ) { // vertex morph
var m = {};
m.index = dv.getIndex( pmx.metadata.vertexIndexSize );
m.position = dv.getFloat32Array( 3 );
p.elements.push( m );
} else if ( p.type === 2 ) { // bone morph
var m = {};
m.index = dv.getIndex( pmx.metadata.boneIndexSize );
m.position = dv.getFloat32Array( 3 );
m.rotation = dv.getFloat32Array( 4 );
p.elements.push( m );
} else if ( p.type === 3 ) { // uv morph
var m = {};
m.index = dv.getIndex( pmx.metadata.vertexIndexSize );
m.uv = dv.getFloat32Array( 4 );
p.elements.push( m );
} else if ( p.type === 8 ) { // material morph
var m = {};
m.index = dv.getIndex( pmx.metadata.materialIndexSize );
m.type = dv.getUint8();
m.diffuse = dv.getFloat32Array( 4 );
m.specular = dv.getFloat32Array( 3 );
m.shininess = dv.getFloat32();
m.emissive = dv.getFloat32Array( 3 );
m.edgeColor = dv.getFloat32Array( 4 );
m.edgeSize = dv.getFloat32();
m.textureColor = dv.getFloat32Array( 4 );
m.sphereTextureColor = dv.getFloat32Array( 4 );
m.toonColor = dv.getFloat32Array( 4 );
p.elements.push( m );
}
}
return p;
};
var metadata = pmx.metadata;
metadata.morphCount = dv.getUint32();
pmx.morphs = [];
for ( var i = 0; i < metadata.morphCount; i++ ) {
pmx.morphs.push( parseMorph() );
}
};
var parseFrames = function () {
var parseFrame = function () {
var p = {};
p.name = dv.getTextBuffer();
p.englishName = dv.getTextBuffer();
p.type = dv.getUint8();
p.elementCount = dv.getUint32();
p.elements = [];
for ( var i = 0; i < p.elementCount; i++ ) {
var e = {};
e.target = dv.getUint8();
e.index = ( e.target === 0 ) ? dv.getIndex( pmx.metadata.boneIndexSize ) : dv.getIndex( pmx.metadata.morphIndexSize );
p.elements.push( e );
}
return p;
};
var metadata = pmx.metadata;
metadata.frameCount = dv.getUint32();
pmx.frames = [];
for ( var i = 0; i < metadata.frameCount; i++ ) {
pmx.frames.push( parseFrame() );
}
};
var parseRigidBodies = function () {
var parseRigidBody = function () {
var p = {};
p.name = dv.getTextBuffer();
p.englishName = dv.getTextBuffer();
p.boneIndex = dv.getIndex( pmx.metadata.boneIndexSize );
p.groupIndex = dv.getUint8();
p.groupTarget = dv.getUint16();
p.shapeType = dv.getUint8();
p.width = dv.getFloat32();
p.height = dv.getFloat32();
p.depth = dv.getFloat32();
p.position = dv.getFloat32Array( 3 );
p.rotation = dv.getFloat32Array( 3 );
p.weight = dv.getFloat32();
p.positionDamping = dv.getFloat32();
p.rotationDamping = dv.getFloat32();
p.restriction = dv.getFloat32();
p.friction = dv.getFloat32();
p.type = dv.getUint8();
return p;
};
var metadata = pmx.metadata;
metadata.rigidBodyCount = dv.getUint32();
pmx.rigidBodies = [];
for ( var i = 0; i < metadata.rigidBodyCount; i++ ) {
pmx.rigidBodies.push( parseRigidBody() );
}
};
var parseConstraints = function () {
var parseConstraint = function () {
var p = {};
p.name = dv.getTextBuffer();
p.englishName = dv.getTextBuffer();
p.type = dv.getUint8();
p.rigidBodyIndex1 = dv.getIndex( pmx.metadata.rigidBodyIndexSize );
p.rigidBodyIndex2 = dv.getIndex( pmx.metadata.rigidBodyIndexSize );
p.position = dv.getFloat32Array( 3 );
p.rotation = dv.getFloat32Array( 3 );
p.translationLimitation1 = dv.getFloat32Array( 3 );
p.translationLimitation2 = dv.getFloat32Array( 3 );
p.rotationLimitation1 = dv.getFloat32Array( 3 );
p.rotationLimitation2 = dv.getFloat32Array( 3 );
p.springPosition = dv.getFloat32Array( 3 );
p.springRotation = dv.getFloat32Array( 3 );
return p;
};
var metadata = pmx.metadata;
metadata.constraintCount = dv.getUint32();
pmx.constraints = [];
for ( var i = 0; i < metadata.constraintCount; i++ ) {
pmx.constraints.push( parseConstraint() );
}
};
parseHeader();
parseVertices();
parseFaces();
parseTextures();
parseMaterials();
parseBones();
parseMorphs();
parseFrames();
parseRigidBodies();
parseConstraints();
// console.log( pmx ); // for console debug
return pmx;
};
THREE.MMDLoader.prototype.parseVmd = function ( buffer ) {
var scope = this;
var vmd = {};
var dv = new THREE.MMDLoader.DataView( buffer );
var helper = new THREE.MMDLoader.DataCreationHelper();
vmd.metadata = {};
vmd.metadata.coordinateSystem = 'left';
var parseHeader = function () {
var metadata = vmd.metadata;
metadata.magic = dv.getChars( 30 );
if ( metadata.magic !== 'Vocaloid Motion Data 0002' ) {
throw 'VMD file magic is not Vocaloid Motion Data 0002, but ' + metadata.magic;
}
metadata.name = dv.getSjisStringsAsUnicode( 20 );
};
var parseMotions = function () {
var parseMotion = function () {
var p = {};
// Skinning animation doesn't work when bone name is Japanese Unicode in r73.
// So using charcode strings as workaround and keep original strings in .originalName.
p.originalBoneName = dv.getSjisStringsAsUnicode( 15 );
p.boneName = helper.toCharcodeStrings( p.originalBoneName );
p.frameNum = dv.getUint32();
p.position = dv.getFloat32Array( 3 );
p.rotation = dv.getFloat32Array( 4 );
p.interpolation = dv.getUint8Array( 64 );
return p;
};
var metadata = vmd.metadata;
metadata.motionCount = dv.getUint32();
vmd.motions = [];
for ( var i = 0; i < metadata.motionCount; i++ ) {
vmd.motions.push( parseMotion() );
}
};
var parseMorphs = function () {
var parseMorph = function () {
var p = {};
p.morphName = dv.getSjisStringsAsUnicode( 15 );
p.frameNum = dv.getUint32();
p.weight = dv.getFloat32();
return p;
};
var metadata = vmd.metadata;
metadata.morphCount = dv.getUint32();
vmd.morphs = [];
for ( var i = 0; i < metadata.morphCount; i++ ) {
vmd.morphs.push( parseMorph() );
}
};
var parseCameras = function () {
var parseCamera = function () {
var p = {};
p.frameNum = dv.getUint32();
p.distance = dv.getFloat32();
p.position = dv.getFloat32Array( 3 );
p.rotation = dv.getFloat32Array( 3 );
p.interpolation = dv.getUint8Array( 24 );
p.fov = dv.getUint32();
p.perspective = dv.getUint8();
return p;
};
var metadata = vmd.metadata;
metadata.cameraCount = dv.getUint32();
vmd.cameras = [];
for ( var i = 0; i < metadata.cameraCount; i++ ) {
vmd.cameras.push( parseCamera() );
}
};
parseHeader();
parseMotions();
parseMorphs();
parseCameras();
// console.log( vmd ); // for console debug
return vmd;
};
THREE.MMDLoader.prototype.parseVpd = function ( text ) {
var helper = new THREE.MMDLoader.DataCreationHelper();
var vpd = {};
vpd.metadata = {};
vpd.metadata.coordinateSystem = 'left';
vpd.bones = [];
var commentPatternG = /\/\/\w*(\r|\n|\r\n)/g;
var newlinePattern = /\r|\n|\r\n/;
var lines = text.replace( commentPatternG, '' ).split( newlinePattern );
function throwError () {
throw 'the file seems not vpd file.';
};
function checkMagic () {
if ( lines[ 0 ] !== 'Vocaloid Pose Data file' ) {
throwError();
}
};
function parseHeader () {
if ( lines.length < 4 ) {
throwError();
}
vpd.metadata.parentFile = lines[ 2 ];
vpd.metadata.boneCount = parseInt( lines[ 3 ] );
};
function parseBones () {
var boneHeaderPattern = /^\s*(Bone[0-9]+)\s*\{\s*(.*)$/;
var boneVectorPattern = /^\s*(-?[0-9]+\.[0-9]+)\s*,\s*(-?[0-9]+\.[0-9]+)\s*,\s*(-?[0-9]+\.[0-9]+)\s*;/;
var boneQuaternionPattern = /^\s*(-?[0-9]+\.[0-9]+)\s*,\s*(-?[0-9]+\.[0-9]+)\s*,\s*(-?[0-9]+\.[0-9]+)\s*,\s*(-?[0-9]+\.[0-9]+)\s*;/;
var boneFooterPattern = /^\s*}/;
var bones = vpd.bones;
var n = null;
var v = null;
var q = null;
var encoder = new CharsetEncoder();
for ( var i = 4; i < lines.length; i++ ) {
var line = lines[ i ];
var result;
result = line.match( boneHeaderPattern );
if ( result !== null ) {
if ( n !== null ) {
throwError();
}
n = result[ 2 ];
}
result = line.match( boneVectorPattern );
if ( result !== null ) {
if ( v !== null ) {
throwError();
}
v = [
parseFloat( result[ 1 ] ),
parseFloat( result[ 2 ] ),
parseFloat( result[ 3 ] )
];
}
result = line.match( boneQuaternionPattern );
if ( result !== null ) {
if ( q !== null ) {
throwError();
}
q = [
parseFloat( result[ 1 ] ),
parseFloat( result[ 2 ] ),
parseFloat( result[ 3 ] ),
parseFloat( result[ 4 ] )
];
}
result = line.match( boneFooterPattern );
if ( result !== null ) {
if ( n === null || v === null || q === null ) {
throwError();
}
bones.push( {
originalName: n,
name: helper.toCharcodeStrings( n ),
translation: v,
quaternion: q
} );
n = null;
v = null;
q = null;
}
}
if ( n !== null || v !== null || q !== null ) {
throwError();
}
};
checkMagic();
parseHeader();
parseBones();
this.leftToRightVpd( vpd );
// console.log( vpd ); // for console debug
return vpd;
};
THREE.MMDLoader.prototype.createMesh = function ( model, texturePath, onProgress, onError ) {
var scope = this;
var geometry = new THREE.Geometry();
var material = new THREE.MultiMaterial();
var helper = new THREE.MMDLoader.DataCreationHelper();
var initVartices = function () {
for ( var i = 0; i < model.metadata.vertexCount; i++ ) {
var v = model.vertices[ i ];
geometry.vertices.push(
new THREE.Vector3(
v.position[ 0 ],
v.position[ 1 ],
v.position[ 2 ]
)
);
geometry.skinIndices.push(
new THREE.Vector4(
v.skinIndices.length >= 1 ? v.skinIndices[ 0 ] : 0.0,
v.skinIndices.length >= 2 ? v.skinIndices[ 1 ] : 0.0,
v.skinIndices.length >= 3 ? v.skinIndices[ 2 ] : 0.0,
v.skinIndices.length >= 4 ? v.skinIndices[ 3 ] : 0.0
)
);
geometry.skinWeights.push(
new THREE.Vector4(
v.skinWeights.length >= 1 ? v.skinWeights[ 0 ] : 0.0,
v.skinWeights.length >= 2 ? v.skinWeights[ 1 ] : 0.0,
v.skinWeights.length >= 3 ? v.skinWeights[ 2 ] : 0.0,
v.skinWeights.length >= 4 ? v.skinWeights[ 3 ] : 0.0
)
);
}
};
var initFaces = function () {
for ( var i = 0; i < model.metadata.faceCount; i++ ) {
geometry.faces.push(
new THREE.Face3(
model.faces[ i ].indices[ 0 ],
model.faces[ i ].indices[ 1 ],
model.faces[ i ].indices[ 2 ]
)
);
for ( var j = 0; j < 3; j++ ) {
geometry.faces[ i ].vertexNormals[ j ] =
new THREE.Vector3(
model.vertices[ model.faces[ i ].indices[ j ] ].normal[ 0 ],
model.vertices[ model.faces[ i ].indices[ j ] ].normal[ 1 ],
model.vertices[ model.faces[ i ].indices[ j ] ].normal[ 2 ]
);
}
}
};
var initBones = function () {
var bones = [];
for ( var i = 0; i < model.metadata.boneCount; i++ ) {
var bone = {};
var b = model.bones[ i ];
bone.parent = b.parentIndex;
bone.name = b.name;
bone.pos = [ b.position[ 0 ], b.position[ 1 ], b.position[ 2 ] ];
bone.rotq = [ 0, 0, 0, 1 ];
bone.scl = [ 1, 1, 1 ];
if ( bone.parent !== -1 ) {
bone.pos[ 0 ] -= model.bones[ bone.parent ].position[ 0 ];
bone.pos[ 1 ] -= model.bones[ bone.parent ].position[ 1 ];
bone.pos[ 2 ] -= model.bones[ bone.parent ].position[ 2 ];
}
bones.push( bone );
}
geometry.bones = bones;
};
var initIKs = function () {
var iks = [];
// TODO: remove duplicated codes between PMD and PMX
if ( model.metadata.format === 'pmd' ) {
for ( var i = 0; i < model.metadata.ikCount; i++ ) {
var ik = model.iks[i];
var param = {};
param.target = ik.target;
param.effector = ik.effector;
param.iteration = ik.iteration;
param.maxAngle = ik.maxAngle * 4;
param.links = [];
for ( var j = 0; j < ik.links.length; j++ ) {
var link = {};
link.index = ik.links[ j ].index;
// Checking with .originalName, not .name.
// See parseBone() for the detail.
if ( model.bones[ link.index ].originalName.indexOf( 'ひざ' ) >= 0 ) {
link.limitation = new THREE.Vector3( 1.0, 0.0, 0.0 );
}
param.links.push( link );
}
iks.push( param );
}
} else {
for ( var i = 0; i < model.metadata.boneCount; i++ ) {
var b = model.bones[ i ];
var ik = b.ik;
if ( ik === undefined ) {
continue;
}
var param = {};
param.target = i;
param.effector = ik.effector;
param.iteration = ik.iteration;
param.maxAngle = ik.maxAngle;
param.links = [];
for ( var j = 0; j < ik.links.length; j++ ) {
var link = {};
link.index = ik.links[ j ].index;
if ( ik.links[ j ].angleLimitation === 1 ) {
link.limitation = new THREE.Vector3( 1.0, 0.0, 0.0 );
// TODO: use limitation angles
// link.lowerLimitationAngle;
// link.upperLimitationAngle;
}
param.links.push( link );
}
iks.push( param );
}
}
geometry.iks = iks;
};
var initGrants = function () {
if ( model.metadata.format === 'pmd' ) {
return;
}
var grants = [];
for ( var i = 0; i < model.metadata.boneCount; i++ ) {
var b = model.bones[ i ];
var grant = b.grant;
if ( grant === undefined ) {
continue;
}
var param = {};
param.index = i;
param.parentIndex = grant.parentIndex;
param.ratio = grant.ratio;
param.isLocal = grant.isLocal;
param.affectRotation = grant.affectRotation;
param.affectPosition = grant.affectPosition;
grants.push( param );
}
geometry.grants = grants;
};
var initMorphs = function () {
function updateVertex ( params, index, v, ratio ) {
params.vertices[ index ].x += v.position[ 0 ] * ratio;
params.vertices[ index ].y += v.position[ 1 ] * ratio;
params.vertices[ index ].z += v.position[ 2 ] * ratio;
};
function updateVertices ( params, m, ratio ) {
for ( var i = 0; i < m.elementCount; i++ ) {
var v = m.elements[ i ];
var index;
if ( model.metadata.format === 'pmd' ) {
index = model.morphs[ 0 ].elements[ v.index ].index;
} else {
index = v.index;
}
updateVertex( params, index, v, ratio );
}
};
for ( var i = 0; i < model.metadata.morphCount; i++ ) {
var m = model.morphs[ i ];
var params = {};
params.name = m.name;
params.vertices = [];
for ( var j = 0; j < model.metadata.vertexCount; j++ ) {
params.vertices[ j ] = new THREE.Vector3( 0, 0, 0 );
params.vertices[ j ].x = geometry.vertices[ j ].x;
params.vertices[ j ].y = geometry.vertices[ j ].y;
params.vertices[ j ].z = geometry.vertices[ j ].z;
}
if ( model.metadata.format === 'pmd' ) {
if ( i !== 0 ) {
updateVertices( params, m, 1.0 );
}
} else {
if ( m.type === 0 ) {
for ( var j = 0; j < m.elementCount; j++ ) {
var m2 = model.morphs[ m.elements[ j ].index ];
var ratio = m.elements[ j ].ratio;
if ( m2.type === 1 ) {
updateVertices( params, m2, ratio );
}
}
} else if ( m.type === 1 ) {
updateVertices( params, m, 1.0 );
}
}
// TODO: skip if this's non-vertex morphing of PMX to reduce CPU/Memory use
geometry.morphTargets.push( params );
}
};
var initMaterials = function () {
var textures = [];
var textureLoader = new THREE.TextureLoader( this.manager );
var tgaLoader = new THREE.TGALoader( this.manager );
var materialLoader = new THREE.MaterialLoader( this.manager );
var color = new THREE.Color();
var offset = 0;
var materialParams = [];
function loadTexture ( filePath, params ) {
if ( params === undefined ) {
params = {};
}
var directoryPath = ( params.defaultTexturePath === true ) ? scope.defaultTexturePath : texturePath;
var fullPath = directoryPath + filePath;
var loader = THREE.Loader.Handlers.get( fullPath );
if ( loader === null ) {
loader = ( filePath.indexOf( '.tga' ) >= 0 ) ? tgaLoader : textureLoader;
}
var texture = loader.load( fullPath, function ( t ) {
t.flipY = false;
t.wrapS = THREE.RepeatWrapping;
t.wrapT = THREE.RepeatWrapping;
if ( params.sphericalReflectionMapping === true ) {
t.mapping = THREE.SphericalReflectionMapping;
}
for ( var i = 0; i < texture.readyCallbacks.length; i++ ) {
texture.readyCallbacks[ i ]( texture );
}
} );
texture.readyCallbacks = [];
var uuid = THREE.Math.generateUUID();
textures[ uuid ] = texture;
return uuid;
};
for ( var i = 0; i < model.metadata.materialCount; i++ ) {
geometry.faceVertexUvs.push( [] );
}
for ( var i = 0; i < model.metadata.materialCount; i++ ) {
var m = model.materials[ i ];
var params = {
uuid: THREE.Math.generateUUID(),
type: 'MMDMaterial'
};
params.faceOffset = offset;
params.faceNum = m.faceCount;
for ( var j = 0; j < m.faceCount; j++ ) {
geometry.faces[ offset ].materialIndex = i;
var uvs = [];
for ( var k = 0; k < 3; k++ ) {
var v = model.vertices[ model.faces[ offset ].indices[ k ] ];
uvs.push( new THREE.Vector2( v.uv[ 0 ], v.uv[ 1 ] ) );
}
geometry.faceVertexUvs[ 0 ].push( uvs );
offset++;
}
params.name = m.name;
params.color = color.fromArray( [ m.diffuse[ 0 ], m.diffuse[ 1 ], m.diffuse[ 2 ] ] ).getHex();
params.opacity = m.diffuse[ 3 ];
params.specular = color.fromArray( [ m.specular[ 0 ], m.specular[ 1 ], m.specular[ 2 ] ] ).getHex();
params.shininess = m.shininess;
if ( params.opacity === 1.0 ) {
params.side = THREE.FrontSide;
params.transparent = false;
} else {
params.side = THREE.DoubleSide;
params.transparent = true;
}
if ( model.metadata.format === 'pmd' ) {
if ( m.fileName ) {
var fileName = m.fileName;
var fileNames = [];
var index = fileName.lastIndexOf( '*' );
if ( index >= 0 ) {
fileNames.push( fileName.slice( 0, index ) );
fileNames.push( fileName.slice( index + 1 ) );
} else {
fileNames.push( fileName );
}
for ( var j = 0; j < fileNames.length; j++ ) {
var n = fileNames[ j ];
if ( n.indexOf( '.sph' ) >= 0 || n.indexOf( '.spa' ) >= 0 ) {
params.envMap = loadTexture( n, { sphericalReflectionMapping: true } );
if ( n.indexOf( '.sph' ) >= 0 ) {
params.envMapType = THREE.MultiplyOperation;
} else {
params.envMapType = THREE.AddOperation;
}
} else {
params.map = loadTexture( n );
}
}
}
} else {
if ( m.textureIndex !== -1 ) {
var n = model.textures[ m.textureIndex ];
params.map = loadTexture( n );
}
// TODO: support m.envFlag === 3
if ( m.envTextureIndex !== -1 && ( m.envFlag === 1 || m.envFlag == 2 ) ) {
var n = model.textures[ m.envTextureIndex ];
params.envMap = loadTexture( n, { sphericalReflectionMapping: true } );
if ( m.envFlag === 1 ) {
params.envMapType = THREE.MultiplyOperation;
} else {
params.envMapType = THREE.AddOperation;
}
}
}
// TODO: check if this logic is right
if ( params.map === undefined /* && params.envMap === undefined */ ) {
params.emissive = color.fromArray( [ m.emissive[ 0 ], m.emissive[ 1 ], m.emissive[ 2 ] ] ).getHex();
}
var shader = THREE.ShaderLib[ 'mmd' ];
params.uniforms = THREE.UniformsUtils.clone( shader.uniforms );
params.vertexShader = shader.vertexShader;
params.fragmentShader = shader.fragmentShader;
materialParams.push( params );
}
materialLoader.setTextures( textures );
for ( var i = 0; i < materialParams.length; i++ ) {
var p = materialParams[ i ];
var p2 = model.materials[ i ];
var m = materialLoader.parse( p );
m.faceOffset = p.faceOffset;
m.faceNum = p.faceNum;
m.skinning = geometry.bones.length > 0 ? true : false;
m.morphTargets = geometry.morphTargets.length > 0 ? true : false;
m.lights = true;
m.blending = THREE.CustomBlending;
m.blendSrc = THREE.SrcAlphaFactor;
m.blendDst = THREE.OneMinusSrcAlphaFactor;
m.blendSrcAlpha = THREE.SrcAlphaFactor;
m.blendDstAlpha = THREE.DstAlphaFactor;
if ( m.map !== null ) {
// Check if this part of the texture image the material uses requires transparency
function checkTextureTransparency ( m ) {
m.map.readyCallbacks.push( function ( t ) {
// Is there any efficient ways?
function createImageData ( image ) {
var c = document.createElement( 'canvas' );
c.width = image.width;
c.height = image.height;
var ctx = c.getContext( '2d' );
ctx.drawImage( image, 0, 0 );
return ctx.getImageData( 0, 0, c.width, c.height );
};
function detectTextureTransparency ( image, uvs ) {
var width = image.width;
var height = image.height;
var data = image.data;
var threshold = 253;
if ( data.length / ( width * height ) !== 4 ) {
return false;
}
for ( var i = 0; i < uvs.length; i++ ) {
var centerUV = { x: 0.0, y: 0.0 };
for ( var j = 0; j < 3; j++ ) {
var uv = uvs[ i ][ j ];
if ( getAlphaByUv( image, uv ) < threshold ) {
return true;
}
centerUV.x += uv.x;
centerUV.y += uv.y;
}
centerUV.x /= 3;
centerUV.y /= 3;
if ( getAlphaByUv( image, centerUV ) < threshold ) {
return true;
}
}
return false;
};
/*
* This method expects
* t.flipY = false
* t.wrapS = THREE.RepeatWrapping
* t.wrapT = THREE.RepeatWrapping
* TODO: more precise
*/
function getAlphaByUv ( image, uv ) {
var width = image.width;
var height = image.height;
var x = Math.round( uv.x * width ) % width;
var y = Math.round( uv.y * height ) % height;