UNPKG

three

Version:

JavaScript 3D library

2,319 lines (1,439 loc) 97.2 kB
/** * @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;