├── .gitignore ├── src ├── CharsetEncoder.js ├── DataCreationHelper.js ├── DataViewEx.js └── Parser.js ├── index.js ├── rollup.config.js ├── LICENSE ├── package.json ├── Readme.md ├── index.html └── test └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /src/CharsetEncoder.js: -------------------------------------------------------------------------------- 1 | import { CharsetEncoder } from 'charset-encoder-js'; 2 | export { CharsetEncoder }; 3 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import { CharsetEncoder } from './src/CharsetEncoder' 2 | import { Parser } from './src/Parser' 3 | 4 | var MMDParser = { 5 | CharsetEncoder: CharsetEncoder, 6 | Parser: Parser 7 | }; 8 | 9 | export { MMDParser, CharsetEncoder, Parser }; 10 | 11 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from 'rollup-plugin-node-resolve'; 2 | import commonjs from 'rollup-plugin-commonjs'; 3 | 4 | export default { 5 | entry: 'index.js', 6 | plugins: [ 7 | resolve({ 8 | jsnext: true, 9 | main: true, 10 | bowser: true 11 | }), 12 | commonjs() 13 | ], 14 | targets: [ 15 | { 16 | format: 'umd', 17 | moduleName: 'MMDParser', 18 | dest: 'build/mmdparser.js' 19 | }, 20 | { 21 | format: 'es', 22 | dest: 'build/mmdparser.module.js' 23 | } 24 | ] 25 | }; 26 | -------------------------------------------------------------------------------- /src/DataCreationHelper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author takahiro / https://github.com/takahirox 3 | */ 4 | 5 | function DataCreationHelper () { 6 | }; 7 | 8 | DataCreationHelper.prototype = { 9 | 10 | constructor: DataCreationHelper, 11 | 12 | leftToRightVector3: function ( v ) { 13 | 14 | v[ 2 ] = -v[ 2 ]; 15 | 16 | }, 17 | 18 | leftToRightQuaternion: function ( q ) { 19 | 20 | q[ 0 ] = -q[ 0 ]; 21 | q[ 1 ] = -q[ 1 ]; 22 | 23 | }, 24 | 25 | leftToRightEuler: function ( r ) { 26 | 27 | r[ 0 ] = -r[ 0 ]; 28 | r[ 1 ] = -r[ 1 ]; 29 | 30 | }, 31 | 32 | leftToRightIndexOrder: function ( p ) { 33 | 34 | var tmp = p[ 2 ]; 35 | p[ 2 ] = p[ 0 ]; 36 | p[ 0 ] = tmp; 37 | 38 | }, 39 | 40 | leftToRightVector3Range: function ( v1, v2 ) { 41 | 42 | var tmp = -v2[ 2 ]; 43 | v2[ 2 ] = -v1[ 2 ]; 44 | v1[ 2 ] = tmp; 45 | 46 | }, 47 | 48 | leftToRightEulerRange: function ( r1, r2 ) { 49 | 50 | var tmp1 = -r2[ 0 ]; 51 | var tmp2 = -r2[ 1 ]; 52 | r2[ 0 ] = -r1[ 0 ]; 53 | r2[ 1 ] = -r1[ 1 ]; 54 | r1[ 0 ] = tmp1; 55 | r1[ 1 ] = tmp2; 56 | 57 | } 58 | 59 | }; 60 | 61 | export { DataCreationHelper } 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Takahiro 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mmd-parser", 3 | "version": "1.0.4", 4 | "description": "MMD parser", 5 | "main": "build/mmdparser.js", 6 | "jsnext:main": "build/mmdparser.module.js", 7 | "scripts": { 8 | "all": "npm run build && npm run build-uglify && npm run test", 9 | "build": "rollup -c", 10 | "build-uglify": "rollup -c && uglifyjs build/mmdparser.js -cm --preamble \"// https://github.com/takahirox/mmd-parser#readme\" > build/mmdparser.min.js", 11 | "dev": "rollup -c -w", 12 | "test": "node test/index.js" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/takahirox/mmd-parser.git" 17 | }, 18 | "author": "Takahiro (https://github.com/takahirox)", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/takahirox/mmd-parser/issues" 22 | }, 23 | "homepage": "https://github.com/takahirox/mmd-parser#readme", 24 | "devDependencies": { 25 | "charset-encoder-js": "^1.0.1", 26 | "rollup": "^0.36.3", 27 | "rollup-plugin-node-resolve": "^2.0.0", 28 | "rollup-plugin-commonjs": "^5.0.5", 29 | "uglify-js": "^2.7.4", 30 | "xhr2": "^0.1.3" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # MMD Parser 2 | 3 | mmd-parser parses MMD ArrayBuffer/Strings and generates Object. 4 | 5 | 6 | ## Browser 7 | 8 | ### How to use 9 | ``` 10 | 11 | 52 | ``` 53 | 54 | ### methods 55 | * MMDParser.Parser 56 | * parsePmd(buffer, leftToRight) 57 | * parsePmx(buffer, leftToRight) 58 | * parseVmd(buffer, leftToRight) 59 | * parseVpd(text, leftToRight) 60 | * mergeVmds(vmds) 61 | * leftToRightModel(model) 62 | * leftToRightVmd(vmd) 63 | * leftToRightVpd(vpd) 64 | 65 | 66 | ## NPM 67 | 68 | ### How to install 69 | ``` 70 | $ npm install mmd-parser 71 | ``` 72 | 73 | ### How to build 74 | ``` 75 | $ npm install 76 | $ npm run all 77 | ``` 78 | 79 | ### How to load 80 | ``` 81 | require('mmd-parser'); 82 | ``` 83 | 84 | 85 | ## Copyright 86 | 87 | You are allowed to use Crypton's Vocaloid(Hatsune Miku, Kagamine Rin, and so on) 88 | stuffs (MMD models, songs, and so on) only if you follow the guideline set by 89 | Crypton Future Media, INC. for the usage of its characters. 90 | 91 | For detail, see http://piapro.net/en_for_creators.html 92 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MMD Parser 6 | 7 | 8 | 79 | 80 | 81 | See JavaScript console. 82 | 83 | 84 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var MMDParser = require('../build/mmdparser'); 2 | var CharsetEncoder = require('charset-encoder-js').CharsetEncoder; 3 | var parser = new MMDParser.Parser(); 4 | var encoder = new CharsetEncoder(); 5 | var pmdUrl = 'https://cdn.rawgit.com/mrdoob/three.js/dev/examples/models/mmd/miku/miku_v2.pmd'; 6 | var vmdUrl = 'https://cdn.rawgit.com/mrdoob/three.js/dev/examples/models/mmd/vmds/wavefile_v2.vmd'; 7 | var vpdUrl = 'https://cdn.rawgit.com/mrdoob/three.js/dev/examples/models/mmd/vpds/01.vpd'; 8 | var XMLHttpRequest = require('xhr2').XMLHttpRequest; 9 | 10 | function load (url, responseType, mimeType, onLoad, onProgress, onError) { 11 | var request = new XMLHttpRequest(); 12 | request.open('GET', url, true); 13 | request.addEventListener('load', function (event) { 14 | var response = event.target.response; 15 | if (this.status === 200) { 16 | onLoad(response); 17 | } else if (this.status === 0) { 18 | console.warn('HTTP Status 0 received.'); 19 | onLoad(response); 20 | } else { 21 | console.warn('HTTP Status ' + this.status + ' received.'); 22 | onError(event); 23 | } 24 | }, false); 25 | if (onProgress !== undefined) request.addEventListener('progress', onProgress, false); 26 | if (onError !== undefined) request.addEventListener('error', onError, false); 27 | request.responseType = responseType; 28 | if (mimeType !== undefined) request.overrideMimeType(mimeType) 29 | request.send(null); 30 | console.log('downloading: ' + url); 31 | } 32 | 33 | function onProgress (event) { 34 | if (event.lengthComputable) { 35 | var percentComplete = event.loaded / event.total * 100; 36 | //console.log( Math.round(percentComplete, 2) + '% downloaded' ); 37 | } 38 | } 39 | 40 | function onError (event) { 41 | console.error('downloading has failed.'); 42 | console.log(event); 43 | } 44 | 45 | function testPmd () { 46 | console.log('PMD parse test'); 47 | load( 48 | pmdUrl, 49 | 'arraybuffer', 50 | undefined, 51 | function (buffer) { 52 | var pmd = parser.parsePmd(buffer); 53 | console.log(pmd.metadata); 54 | console.log(''); 55 | testVmd(); 56 | } 57 | ); 58 | } 59 | 60 | function testVmd () { 61 | console.log('VMD parse test'); 62 | load( 63 | vmdUrl, 64 | 'arraybuffer', 65 | undefined, 66 | function (buffer) { 67 | var vmd = parser.parseVmd(buffer); 68 | console.log(vmd.metadata); 69 | console.log(''); 70 | testVpd(); 71 | } 72 | ); 73 | } 74 | 75 | function testVpd () { 76 | console.log('VPD parse test'); 77 | load( 78 | vpdUrl, 79 | 'arraybuffer', 80 | undefined, 81 | function (buffer) { 82 | var text = encoder.s2u(new Uint8Array(buffer)); 83 | var vpd = parser.parseVpd(text); 84 | console.log(vpd.metadata); 85 | console.log(''); 86 | } 87 | ); 88 | } 89 | 90 | testPmd(); 91 | -------------------------------------------------------------------------------- /src/DataViewEx.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author takahiro / https://github.com/takahirox 3 | */ 4 | 5 | import { CharsetEncoder } from './CharsetEncoder'; 6 | 7 | function DataViewEx ( buffer, littleEndian ) { 8 | 9 | this.dv = new DataView( buffer ); 10 | this.offset = 0; 11 | this.littleEndian = ( littleEndian !== undefined ) ? littleEndian : true; 12 | this.encoder = new CharsetEncoder(); 13 | 14 | }; 15 | 16 | DataViewEx.prototype = { 17 | 18 | constructor: DataViewEx, 19 | 20 | getInt8: function () { 21 | 22 | var value = this.dv.getInt8( this.offset ); 23 | this.offset += 1; 24 | return value; 25 | 26 | }, 27 | 28 | getInt8Array: function ( size ) { 29 | 30 | var a = []; 31 | 32 | for ( var i = 0; i < size; i++ ) { 33 | 34 | a.push( this.getInt8() ); 35 | 36 | } 37 | 38 | return a; 39 | 40 | }, 41 | 42 | getUint8: function () { 43 | 44 | var value = this.dv.getUint8( this.offset ); 45 | this.offset += 1; 46 | return value; 47 | 48 | }, 49 | 50 | getUint8Array: function ( size ) { 51 | 52 | var a = []; 53 | 54 | for ( var i = 0; i < size; i++ ) { 55 | 56 | a.push( this.getUint8() ); 57 | 58 | } 59 | 60 | return a; 61 | 62 | }, 63 | 64 | 65 | getInt16: function () { 66 | 67 | var value = this.dv.getInt16( this.offset, this.littleEndian ); 68 | this.offset += 2; 69 | return value; 70 | 71 | }, 72 | 73 | getInt16Array: function ( size ) { 74 | 75 | var a = []; 76 | 77 | for ( var i = 0; i < size; i++ ) { 78 | 79 | a.push( this.getInt16() ); 80 | 81 | } 82 | 83 | return a; 84 | 85 | }, 86 | 87 | getUint16: function () { 88 | 89 | var value = this.dv.getUint16( this.offset, this.littleEndian ); 90 | this.offset += 2; 91 | return value; 92 | 93 | }, 94 | 95 | getUint16Array: function ( size ) { 96 | 97 | var a = []; 98 | 99 | for ( var i = 0; i < size; i++ ) { 100 | 101 | a.push( this.getUint16() ); 102 | 103 | } 104 | 105 | return a; 106 | 107 | }, 108 | 109 | getInt32: function () { 110 | 111 | var value = this.dv.getInt32( this.offset, this.littleEndian ); 112 | this.offset += 4; 113 | return value; 114 | 115 | }, 116 | 117 | getInt32Array: function ( size ) { 118 | 119 | var a = []; 120 | 121 | for ( var i = 0; i < size; i++ ) { 122 | 123 | a.push( this.getInt32() ); 124 | 125 | } 126 | 127 | return a; 128 | 129 | }, 130 | 131 | getUint32: function () { 132 | 133 | var value = this.dv.getUint32( this.offset, this.littleEndian ); 134 | this.offset += 4; 135 | return value; 136 | 137 | }, 138 | 139 | getUint32Array: function ( size ) { 140 | 141 | var a = []; 142 | 143 | for ( var i = 0; i < size; i++ ) { 144 | 145 | a.push( this.getUint32() ); 146 | 147 | } 148 | 149 | return a; 150 | 151 | }, 152 | 153 | getFloat32: function () { 154 | 155 | var value = this.dv.getFloat32( this.offset, this.littleEndian ); 156 | this.offset += 4; 157 | return value; 158 | 159 | }, 160 | 161 | getFloat32Array: function( size ) { 162 | 163 | var a = []; 164 | 165 | for ( var i = 0; i < size; i++ ) { 166 | 167 | a.push( this.getFloat32() ); 168 | 169 | } 170 | 171 | return a; 172 | 173 | }, 174 | 175 | getFloat64: function () { 176 | 177 | var value = this.dv.getFloat64( this.offset, this.littleEndian ); 178 | this.offset += 8; 179 | return value; 180 | 181 | }, 182 | 183 | getFloat64Array: function( size ) { 184 | 185 | var a = []; 186 | 187 | for ( var i = 0; i < size; i++ ) { 188 | 189 | a.push( this.getFloat64() ); 190 | 191 | } 192 | 193 | return a; 194 | 195 | }, 196 | 197 | getIndex: function ( type, isUnsigned ) { 198 | 199 | switch ( type ) { 200 | 201 | case 1: 202 | return ( isUnsigned === true ) ? this.getUint8() : this.getInt8(); 203 | 204 | case 2: 205 | return ( isUnsigned === true ) ? this.getUint16() : this.getInt16(); 206 | 207 | case 4: 208 | return this.getInt32(); // No Uint32 209 | 210 | default: 211 | throw 'unknown number type ' + type + ' exception.'; 212 | 213 | } 214 | 215 | }, 216 | 217 | getIndexArray: function ( type, size, isUnsigned ) { 218 | 219 | var a = []; 220 | 221 | for ( var i = 0; i < size; i++ ) { 222 | 223 | a.push( this.getIndex( type, isUnsigned ) ); 224 | 225 | } 226 | 227 | return a; 228 | 229 | }, 230 | 231 | getChars: function ( size ) { 232 | 233 | var str = ''; 234 | 235 | while ( size > 0 ) { 236 | 237 | var value = this.getUint8(); 238 | size--; 239 | 240 | if ( value === 0 ) { 241 | 242 | break; 243 | 244 | } 245 | 246 | str += String.fromCharCode( value ); 247 | 248 | } 249 | 250 | while ( size > 0 ) { 251 | 252 | this.getUint8(); 253 | size--; 254 | 255 | } 256 | 257 | return str; 258 | 259 | }, 260 | 261 | getSjisStringsAsUnicode: function ( size ) { 262 | 263 | var a = []; 264 | 265 | while ( size > 0 ) { 266 | 267 | var value = this.getUint8(); 268 | size--; 269 | 270 | if ( value === 0 ) { 271 | 272 | break; 273 | 274 | } 275 | 276 | a.push( value ); 277 | 278 | } 279 | 280 | while ( size > 0 ) { 281 | 282 | this.getUint8(); 283 | size--; 284 | 285 | } 286 | 287 | return this.encoder.s2u( new Uint8Array( a ) ); 288 | 289 | }, 290 | 291 | getUnicodeStrings: function ( size ) { 292 | 293 | var str = ''; 294 | 295 | while ( size > 0 ) { 296 | 297 | var value = this.getUint16(); 298 | size -= 2; 299 | 300 | if ( value === 0 ) { 301 | 302 | break; 303 | 304 | } 305 | 306 | str += String.fromCharCode( value ); 307 | 308 | } 309 | 310 | while ( size > 0 ) { 311 | 312 | this.getUint8(); 313 | size--; 314 | 315 | } 316 | 317 | return str; 318 | 319 | }, 320 | 321 | getTextBuffer: function () { 322 | 323 | var size = this.getUint32(); 324 | return this.getUnicodeStrings( size ); 325 | 326 | } 327 | 328 | }; 329 | 330 | export { DataViewEx }; 331 | -------------------------------------------------------------------------------- /src/Parser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author takahiro / https://github.com/takahirox 3 | */ 4 | 5 | import { DataViewEx } from './DataViewEx'; 6 | import { DataCreationHelper } from './DataCreationHelper'; 7 | 8 | function Parser() { 9 | }; 10 | 11 | Parser.prototype.parsePmd = function ( buffer, leftToRight ) { 12 | 13 | var pmd = {}; 14 | var dv = new DataViewEx( buffer ); 15 | 16 | pmd.metadata = {}; 17 | pmd.metadata.format = 'pmd'; 18 | pmd.metadata.coordinateSystem = 'left'; 19 | 20 | var parseHeader = function () { 21 | 22 | var metadata = pmd.metadata; 23 | metadata.magic = dv.getChars( 3 ); 24 | 25 | if ( metadata.magic !== 'Pmd' ) { 26 | 27 | throw 'PMD file magic is not Pmd, but ' + metadata.magic; 28 | 29 | } 30 | 31 | metadata.version = dv.getFloat32(); 32 | metadata.modelName = dv.getSjisStringsAsUnicode( 20 ); 33 | metadata.comment = dv.getSjisStringsAsUnicode( 256 ); 34 | 35 | }; 36 | 37 | var parseVertices = function () { 38 | 39 | var parseVertex = function () { 40 | 41 | var p = {}; 42 | p.position = dv.getFloat32Array( 3 ); 43 | p.normal = dv.getFloat32Array( 3 ); 44 | p.uv = dv.getFloat32Array( 2 ); 45 | p.skinIndices = dv.getUint16Array( 2 ); 46 | p.skinWeights = [ dv.getUint8() / 100 ]; 47 | p.skinWeights.push( 1.0 - p.skinWeights[ 0 ] ); 48 | p.edgeFlag = dv.getUint8(); 49 | return p; 50 | 51 | }; 52 | 53 | var metadata = pmd.metadata; 54 | metadata.vertexCount = dv.getUint32(); 55 | 56 | pmd.vertices = []; 57 | 58 | for ( var i = 0; i < metadata.vertexCount; i++ ) { 59 | 60 | pmd.vertices.push( parseVertex() ); 61 | 62 | } 63 | 64 | }; 65 | 66 | var parseFaces = function () { 67 | 68 | var parseFace = function () { 69 | 70 | var p = {}; 71 | p.indices = dv.getUint16Array( 3 ); 72 | return p; 73 | 74 | }; 75 | 76 | var metadata = pmd.metadata; 77 | metadata.faceCount = dv.getUint32() / 3; 78 | 79 | pmd.faces = []; 80 | 81 | for ( var i = 0; i < metadata.faceCount; i++ ) { 82 | 83 | pmd.faces.push( parseFace() ); 84 | 85 | } 86 | 87 | }; 88 | 89 | var parseMaterials = function () { 90 | 91 | var parseMaterial = function () { 92 | 93 | var p = {}; 94 | p.diffuse = dv.getFloat32Array( 4 ); 95 | p.shininess = dv.getFloat32(); 96 | p.specular = dv.getFloat32Array( 3 ); 97 | p.ambient = dv.getFloat32Array( 3 ); 98 | p.toonIndex = dv.getInt8(); 99 | p.edgeFlag = dv.getUint8(); 100 | p.faceCount = dv.getUint32() / 3; 101 | p.fileName = dv.getSjisStringsAsUnicode( 20 ); 102 | return p; 103 | 104 | }; 105 | 106 | var metadata = pmd.metadata; 107 | metadata.materialCount = dv.getUint32(); 108 | 109 | pmd.materials = []; 110 | 111 | for ( var i = 0; i < metadata.materialCount; i++ ) { 112 | 113 | pmd.materials.push( parseMaterial() ); 114 | 115 | } 116 | 117 | }; 118 | 119 | var parseBones = function () { 120 | 121 | var parseBone = function () { 122 | 123 | var p = {}; 124 | p.name = dv.getSjisStringsAsUnicode( 20 ); 125 | p.parentIndex = dv.getInt16(); 126 | p.tailIndex = dv.getInt16(); 127 | p.type = dv.getUint8(); 128 | p.ikIndex = dv.getInt16(); 129 | p.position = dv.getFloat32Array( 3 ); 130 | return p; 131 | 132 | }; 133 | 134 | var metadata = pmd.metadata; 135 | metadata.boneCount = dv.getUint16(); 136 | 137 | pmd.bones = []; 138 | 139 | for ( var i = 0; i < metadata.boneCount; i++ ) { 140 | 141 | pmd.bones.push( parseBone() ); 142 | 143 | } 144 | 145 | }; 146 | 147 | var parseIks = function () { 148 | 149 | var parseIk = function () { 150 | 151 | var p = {}; 152 | p.target = dv.getUint16(); 153 | p.effector = dv.getUint16(); 154 | p.linkCount = dv.getUint8(); 155 | p.iteration = dv.getUint16(); 156 | p.maxAngle = dv.getFloat32(); 157 | 158 | p.links = []; 159 | for ( var i = 0; i < p.linkCount; i++ ) { 160 | 161 | var link = {} 162 | link.index = dv.getUint16(); 163 | p.links.push( link ); 164 | 165 | } 166 | 167 | return p; 168 | 169 | }; 170 | 171 | var metadata = pmd.metadata; 172 | metadata.ikCount = dv.getUint16(); 173 | 174 | pmd.iks = []; 175 | 176 | for ( var i = 0; i < metadata.ikCount; i++ ) { 177 | 178 | pmd.iks.push( parseIk() ); 179 | 180 | } 181 | 182 | }; 183 | 184 | var parseMorphs = function () { 185 | 186 | var parseMorph = function () { 187 | 188 | var p = {}; 189 | p.name = dv.getSjisStringsAsUnicode( 20 ); 190 | p.elementCount = dv.getUint32(); 191 | p.type = dv.getUint8(); 192 | 193 | p.elements = []; 194 | for ( var i = 0; i < p.elementCount; i++ ) { 195 | 196 | p.elements.push( { 197 | index: dv.getUint32(), 198 | position: dv.getFloat32Array( 3 ) 199 | } ) ; 200 | 201 | } 202 | 203 | return p; 204 | 205 | }; 206 | 207 | var metadata = pmd.metadata; 208 | metadata.morphCount = dv.getUint16(); 209 | 210 | pmd.morphs = []; 211 | 212 | for ( var i = 0; i < metadata.morphCount; i++ ) { 213 | 214 | pmd.morphs.push( parseMorph() ); 215 | 216 | } 217 | 218 | 219 | }; 220 | 221 | var parseMorphFrames = function () { 222 | 223 | var parseMorphFrame = function () { 224 | 225 | var p = {}; 226 | p.index = dv.getUint16(); 227 | return p; 228 | 229 | }; 230 | 231 | var metadata = pmd.metadata; 232 | metadata.morphFrameCount = dv.getUint8(); 233 | 234 | pmd.morphFrames = []; 235 | 236 | for ( var i = 0; i < metadata.morphFrameCount; i++ ) { 237 | 238 | pmd.morphFrames.push( parseMorphFrame() ); 239 | 240 | } 241 | 242 | }; 243 | 244 | var parseBoneFrameNames = function () { 245 | 246 | var parseBoneFrameName = function () { 247 | 248 | var p = {}; 249 | p.name = dv.getSjisStringsAsUnicode( 50 ); 250 | return p; 251 | 252 | }; 253 | 254 | var metadata = pmd.metadata; 255 | metadata.boneFrameNameCount = dv.getUint8(); 256 | 257 | pmd.boneFrameNames = []; 258 | 259 | for ( var i = 0; i < metadata.boneFrameNameCount; i++ ) { 260 | 261 | pmd.boneFrameNames.push( parseBoneFrameName() ); 262 | 263 | } 264 | 265 | }; 266 | 267 | var parseBoneFrames = function () { 268 | 269 | var parseBoneFrame = function () { 270 | 271 | var p = {}; 272 | p.boneIndex = dv.getInt16(); 273 | p.frameIndex = dv.getUint8(); 274 | return p; 275 | 276 | }; 277 | 278 | var metadata = pmd.metadata; 279 | metadata.boneFrameCount = dv.getUint32(); 280 | 281 | pmd.boneFrames = []; 282 | 283 | for ( var i = 0; i < metadata.boneFrameCount; i++ ) { 284 | 285 | pmd.boneFrames.push( parseBoneFrame() ); 286 | 287 | } 288 | 289 | }; 290 | 291 | var parseEnglishHeader = function () { 292 | 293 | var metadata = pmd.metadata; 294 | metadata.englishCompatibility = dv.getUint8(); 295 | 296 | if ( metadata.englishCompatibility > 0 ) { 297 | 298 | metadata.englishModelName = dv.getSjisStringsAsUnicode( 20 ); 299 | metadata.englishComment = dv.getSjisStringsAsUnicode( 256 ); 300 | 301 | } 302 | 303 | }; 304 | 305 | var parseEnglishBoneNames = function () { 306 | 307 | var parseEnglishBoneName = function () { 308 | 309 | var p = {}; 310 | p.name = dv.getSjisStringsAsUnicode( 20 ); 311 | return p; 312 | 313 | }; 314 | 315 | var metadata = pmd.metadata; 316 | 317 | if ( metadata.englishCompatibility === 0 ) { 318 | 319 | return; 320 | 321 | } 322 | 323 | pmd.englishBoneNames = []; 324 | 325 | for ( var i = 0; i < metadata.boneCount; i++ ) { 326 | 327 | pmd.englishBoneNames.push( parseEnglishBoneName() ); 328 | 329 | } 330 | 331 | }; 332 | 333 | var parseEnglishMorphNames = function () { 334 | 335 | var parseEnglishMorphName = function () { 336 | 337 | var p = {}; 338 | p.name = dv.getSjisStringsAsUnicode( 20 ); 339 | return p; 340 | 341 | }; 342 | 343 | var metadata = pmd.metadata; 344 | 345 | if ( metadata.englishCompatibility === 0 ) { 346 | 347 | return; 348 | 349 | } 350 | 351 | pmd.englishMorphNames = []; 352 | 353 | for ( var i = 0; i < metadata.morphCount - 1; i++ ) { 354 | 355 | pmd.englishMorphNames.push( parseEnglishMorphName() ); 356 | 357 | } 358 | 359 | }; 360 | 361 | var parseEnglishBoneFrameNames = function () { 362 | 363 | var parseEnglishBoneFrameName = function () { 364 | 365 | var p = {}; 366 | p.name = dv.getSjisStringsAsUnicode( 50 ); 367 | return p; 368 | 369 | }; 370 | 371 | var metadata = pmd.metadata; 372 | 373 | if ( metadata.englishCompatibility === 0 ) { 374 | 375 | return; 376 | 377 | } 378 | 379 | pmd.englishBoneFrameNames = []; 380 | 381 | for ( var i = 0; i < metadata.boneFrameNameCount; i++ ) { 382 | 383 | pmd.englishBoneFrameNames.push( parseEnglishBoneFrameName() ); 384 | 385 | } 386 | 387 | }; 388 | 389 | var parseToonTextures = function () { 390 | 391 | var parseToonTexture = function () { 392 | 393 | var p = {}; 394 | p.fileName = dv.getSjisStringsAsUnicode( 100 ); 395 | return p; 396 | 397 | }; 398 | 399 | pmd.toonTextures = []; 400 | 401 | for ( var i = 0; i < 10; i++ ) { 402 | 403 | pmd.toonTextures.push( parseToonTexture() ); 404 | 405 | } 406 | 407 | }; 408 | 409 | var parseRigidBodies = function () { 410 | 411 | var parseRigidBody = function () { 412 | 413 | var p = {}; 414 | p.name = dv.getSjisStringsAsUnicode( 20 ); 415 | p.boneIndex = dv.getInt16(); 416 | p.groupIndex = dv.getUint8(); 417 | p.groupTarget = dv.getUint16(); 418 | p.shapeType = dv.getUint8(); 419 | p.width = dv.getFloat32(); 420 | p.height = dv.getFloat32(); 421 | p.depth = dv.getFloat32(); 422 | p.position = dv.getFloat32Array( 3 ); 423 | p.rotation = dv.getFloat32Array( 3 ); 424 | p.weight = dv.getFloat32(); 425 | p.positionDamping = dv.getFloat32(); 426 | p.rotationDamping = dv.getFloat32(); 427 | p.restitution = dv.getFloat32(); 428 | p.friction = dv.getFloat32(); 429 | p.type = dv.getUint8(); 430 | return p; 431 | 432 | }; 433 | 434 | var metadata = pmd.metadata; 435 | metadata.rigidBodyCount = dv.getUint32(); 436 | 437 | pmd.rigidBodies = []; 438 | 439 | for ( var i = 0; i < metadata.rigidBodyCount; i++ ) { 440 | 441 | pmd.rigidBodies.push( parseRigidBody() ); 442 | 443 | } 444 | 445 | }; 446 | 447 | var parseConstraints = function () { 448 | 449 | var parseConstraint = function () { 450 | 451 | var p = {}; 452 | p.name = dv.getSjisStringsAsUnicode( 20 ); 453 | p.rigidBodyIndex1 = dv.getUint32(); 454 | p.rigidBodyIndex2 = dv.getUint32(); 455 | p.position = dv.getFloat32Array( 3 ); 456 | p.rotation = dv.getFloat32Array( 3 ); 457 | p.translationLimitation1 = dv.getFloat32Array( 3 ); 458 | p.translationLimitation2 = dv.getFloat32Array( 3 ); 459 | p.rotationLimitation1 = dv.getFloat32Array( 3 ); 460 | p.rotationLimitation2 = dv.getFloat32Array( 3 ); 461 | p.springPosition = dv.getFloat32Array( 3 ); 462 | p.springRotation = dv.getFloat32Array( 3 ); 463 | return p; 464 | 465 | }; 466 | 467 | var metadata = pmd.metadata; 468 | metadata.constraintCount = dv.getUint32(); 469 | 470 | pmd.constraints = []; 471 | 472 | for ( var i = 0; i < metadata.constraintCount; i++ ) { 473 | 474 | pmd.constraints.push( parseConstraint() ); 475 | 476 | } 477 | 478 | }; 479 | 480 | parseHeader(); 481 | parseVertices(); 482 | parseFaces(); 483 | parseMaterials(); 484 | parseBones(); 485 | parseIks(); 486 | parseMorphs(); 487 | parseMorphFrames(); 488 | parseBoneFrameNames(); 489 | parseBoneFrames(); 490 | parseEnglishHeader(); 491 | parseEnglishBoneNames(); 492 | parseEnglishMorphNames(); 493 | parseEnglishBoneFrameNames(); 494 | parseToonTextures(); 495 | parseRigidBodies(); 496 | parseConstraints(); 497 | 498 | if ( leftToRight === true ) this.leftToRightModel( pmd ); 499 | 500 | // console.log( pmd ); // for console debug 501 | 502 | return pmd; 503 | 504 | }; 505 | 506 | Parser.prototype.parsePmx = function ( buffer, leftToRight ) { 507 | 508 | var pmx = {}; 509 | var dv = new DataViewEx( buffer ); 510 | 511 | pmx.metadata = {}; 512 | pmx.metadata.format = 'pmx'; 513 | pmx.metadata.coordinateSystem = 'left'; 514 | 515 | var parseHeader = function () { 516 | 517 | var metadata = pmx.metadata; 518 | metadata.magic = dv.getChars( 4 ); 519 | 520 | // Note: don't remove the last blank space. 521 | if ( metadata.magic !== 'PMX ' ) { 522 | 523 | throw 'PMX file magic is not PMX , but ' + metadata.magic; 524 | 525 | } 526 | 527 | metadata.version = dv.getFloat32(); 528 | 529 | if ( metadata.version !== 2.0 && metadata.version !== 2.1 ) { 530 | 531 | throw 'PMX version ' + metadata.version + ' is not supported.'; 532 | 533 | } 534 | 535 | metadata.headerSize = dv.getUint8(); 536 | metadata.encoding = dv.getUint8(); 537 | metadata.additionalUvNum = dv.getUint8(); 538 | metadata.vertexIndexSize = dv.getUint8(); 539 | metadata.textureIndexSize = dv.getUint8(); 540 | metadata.materialIndexSize = dv.getUint8(); 541 | metadata.boneIndexSize = dv.getUint8(); 542 | metadata.morphIndexSize = dv.getUint8(); 543 | metadata.rigidBodyIndexSize = dv.getUint8(); 544 | metadata.modelName = dv.getTextBuffer(); 545 | metadata.englishModelName = dv.getTextBuffer(); 546 | metadata.comment = dv.getTextBuffer(); 547 | metadata.englishComment = dv.getTextBuffer(); 548 | 549 | }; 550 | 551 | var parseVertices = function () { 552 | 553 | var parseVertex = function () { 554 | 555 | var p = {}; 556 | p.position = dv.getFloat32Array( 3 ); 557 | p.normal = dv.getFloat32Array( 3 ); 558 | p.uv = dv.getFloat32Array( 2 ); 559 | 560 | p.auvs = []; 561 | 562 | for ( var i = 0; i < pmx.metadata.additionalUvNum; i++ ) { 563 | 564 | p.auvs.push( dv.getFloat32Array( 4 ) ); 565 | 566 | } 567 | 568 | p.type = dv.getUint8(); 569 | 570 | var indexSize = metadata.boneIndexSize; 571 | 572 | if ( p.type === 0 ) { // BDEF1 573 | 574 | p.skinIndices = dv.getIndexArray( indexSize, 1 ); 575 | p.skinWeights = [ 1.0 ]; 576 | 577 | } else if ( p.type === 1 ) { // BDEF2 578 | 579 | p.skinIndices = dv.getIndexArray( indexSize, 2 ); 580 | p.skinWeights = dv.getFloat32Array( 1 ); 581 | p.skinWeights.push( 1.0 - p.skinWeights[ 0 ] ); 582 | 583 | } else if ( p.type === 2 ) { // BDEF4 584 | 585 | p.skinIndices = dv.getIndexArray( indexSize, 4 ); 586 | p.skinWeights = dv.getFloat32Array( 4 ); 587 | 588 | } else if ( p.type === 3 ) { // SDEF 589 | 590 | p.skinIndices = dv.getIndexArray( indexSize, 2 ); 591 | p.skinWeights = dv.getFloat32Array( 1 ); 592 | p.skinWeights.push( 1.0 - p.skinWeights[ 0 ] ); 593 | 594 | p.skinC = dv.getFloat32Array( 3 ); 595 | p.skinR0 = dv.getFloat32Array( 3 ); 596 | p.skinR1 = dv.getFloat32Array( 3 ); 597 | 598 | // SDEF is not supported yet and is handled as BDEF2 so far. 599 | // TODO: SDEF support 600 | p.type = 1; 601 | 602 | } else { 603 | 604 | throw 'unsupport bone type ' + p.type + ' exception.'; 605 | 606 | } 607 | 608 | p.edgeRatio = dv.getFloat32(); 609 | return p; 610 | 611 | }; 612 | 613 | var metadata = pmx.metadata; 614 | metadata.vertexCount = dv.getUint32(); 615 | 616 | pmx.vertices = []; 617 | 618 | for ( var i = 0; i < metadata.vertexCount; i++ ) { 619 | 620 | pmx.vertices.push( parseVertex() ); 621 | 622 | } 623 | 624 | }; 625 | 626 | var parseFaces = function () { 627 | 628 | var parseFace = function () { 629 | 630 | var p = {}; 631 | p.indices = dv.getIndexArray( metadata.vertexIndexSize, 3, true ); 632 | return p; 633 | 634 | }; 635 | 636 | var metadata = pmx.metadata; 637 | metadata.faceCount = dv.getUint32() / 3; 638 | 639 | pmx.faces = []; 640 | 641 | for ( var i = 0; i < metadata.faceCount; i++ ) { 642 | 643 | pmx.faces.push( parseFace() ); 644 | 645 | } 646 | 647 | }; 648 | 649 | var parseTextures = function () { 650 | 651 | var parseTexture = function () { 652 | 653 | return dv.getTextBuffer(); 654 | 655 | }; 656 | 657 | var metadata = pmx.metadata; 658 | metadata.textureCount = dv.getUint32(); 659 | 660 | pmx.textures = []; 661 | 662 | for ( var i = 0; i < metadata.textureCount; i++ ) { 663 | 664 | pmx.textures.push( parseTexture() ); 665 | 666 | } 667 | 668 | }; 669 | 670 | var parseMaterials = function () { 671 | 672 | var parseMaterial = function () { 673 | 674 | var p = {}; 675 | p.name = dv.getTextBuffer(); 676 | p.englishName = dv.getTextBuffer(); 677 | p.diffuse = dv.getFloat32Array( 4 ); 678 | p.specular = dv.getFloat32Array( 3 ); 679 | p.shininess = dv.getFloat32(); 680 | p.ambient = dv.getFloat32Array( 3 ); 681 | p.flag = dv.getUint8(); 682 | p.edgeColor = dv.getFloat32Array( 4 ); 683 | p.edgeSize = dv.getFloat32(); 684 | p.textureIndex = dv.getIndex( pmx.metadata.textureIndexSize ); 685 | p.envTextureIndex = dv.getIndex( pmx.metadata.textureIndexSize ); 686 | p.envFlag = dv.getUint8(); 687 | p.toonFlag = dv.getUint8(); 688 | 689 | if ( p.toonFlag === 0 ) { 690 | 691 | p.toonIndex = dv.getIndex( pmx.metadata.textureIndexSize ); 692 | 693 | } else if ( p.toonFlag === 1 ) { 694 | 695 | p.toonIndex = dv.getInt8(); 696 | 697 | } else { 698 | 699 | throw 'unknown toon flag ' + p.toonFlag + ' exception.'; 700 | 701 | } 702 | 703 | p.comment = dv.getTextBuffer(); 704 | p.faceCount = dv.getUint32() / 3; 705 | return p; 706 | 707 | }; 708 | 709 | var metadata = pmx.metadata; 710 | metadata.materialCount = dv.getUint32(); 711 | 712 | pmx.materials = []; 713 | 714 | for ( var i = 0; i < metadata.materialCount; i++ ) { 715 | 716 | pmx.materials.push( parseMaterial() ); 717 | 718 | } 719 | 720 | }; 721 | 722 | var parseBones = function () { 723 | 724 | var parseBone = function () { 725 | 726 | var p = {}; 727 | p.name = dv.getTextBuffer(); 728 | p.englishName = dv.getTextBuffer(); 729 | p.position = dv.getFloat32Array( 3 ); 730 | p.parentIndex = dv.getIndex( pmx.metadata.boneIndexSize ); 731 | p.transformationClass = dv.getUint32(); 732 | p.flag = dv.getUint16(); 733 | 734 | if ( p.flag & 0x1 ) { 735 | 736 | p.connectIndex = dv.getIndex( pmx.metadata.boneIndexSize ); 737 | 738 | } else { 739 | 740 | p.offsetPosition = dv.getFloat32Array( 3 ); 741 | 742 | } 743 | 744 | if ( p.flag & 0x100 || p.flag & 0x200 ) { 745 | 746 | // Note: I don't think Grant is an appropriate name 747 | // but I found that some English translated MMD tools use this term 748 | // so I've named it Grant so far. 749 | // I'd rename to more appropriate name from Grant later. 750 | var grant = {}; 751 | 752 | grant.isLocal = ( p.flag & 0x80 ) !== 0 ? true : false; 753 | grant.affectRotation = ( p.flag & 0x100 ) !== 0 ? true : false; 754 | grant.affectPosition = ( p.flag & 0x200 ) !== 0 ? true : false; 755 | grant.parentIndex = dv.getIndex( pmx.metadata.boneIndexSize ); 756 | grant.ratio = dv.getFloat32(); 757 | 758 | p.grant = grant; 759 | 760 | } 761 | 762 | if ( p.flag & 0x400 ) { 763 | 764 | p.fixAxis = dv.getFloat32Array( 3 ); 765 | 766 | } 767 | 768 | if ( p.flag & 0x800 ) { 769 | 770 | p.localXVector = dv.getFloat32Array( 3 ); 771 | p.localZVector = dv.getFloat32Array( 3 ); 772 | 773 | } 774 | 775 | if ( p.flag & 0x2000 ) { 776 | 777 | p.key = dv.getUint32(); 778 | 779 | } 780 | 781 | if ( p.flag & 0x20 ) { 782 | 783 | var ik = {}; 784 | 785 | ik.effector = dv.getIndex( pmx.metadata.boneIndexSize ); 786 | ik.target = null; 787 | ik.iteration = dv.getUint32(); 788 | ik.maxAngle = dv.getFloat32(); 789 | ik.linkCount = dv.getUint32(); 790 | ik.links = []; 791 | 792 | for ( var i = 0; i < ik.linkCount; i++ ) { 793 | 794 | var link = {}; 795 | link.index = dv.getIndex( pmx.metadata.boneIndexSize ); 796 | link.angleLimitation = dv.getUint8(); 797 | 798 | if ( link.angleLimitation === 1 ) { 799 | 800 | link.lowerLimitationAngle = dv.getFloat32Array( 3 ); 801 | link.upperLimitationAngle = dv.getFloat32Array( 3 ); 802 | 803 | } 804 | 805 | ik.links.push( link ); 806 | 807 | } 808 | 809 | p.ik = ik; 810 | } 811 | 812 | return p; 813 | 814 | }; 815 | 816 | var metadata = pmx.metadata; 817 | metadata.boneCount = dv.getUint32(); 818 | 819 | pmx.bones = []; 820 | 821 | for ( var i = 0; i < metadata.boneCount; i++ ) { 822 | 823 | pmx.bones.push( parseBone() ); 824 | 825 | } 826 | 827 | }; 828 | 829 | var parseMorphs = function () { 830 | 831 | var parseMorph = function () { 832 | 833 | var p = {}; 834 | p.name = dv.getTextBuffer(); 835 | p.englishName = dv.getTextBuffer(); 836 | p.panel = dv.getUint8(); 837 | p.type = dv.getUint8(); 838 | p.elementCount = dv.getUint32(); 839 | p.elements = []; 840 | 841 | for ( var i = 0; i < p.elementCount; i++ ) { 842 | 843 | if ( p.type === 0 ) { // group morph 844 | 845 | var m = {}; 846 | m.index = dv.getIndex( pmx.metadata.morphIndexSize ); 847 | m.ratio = dv.getFloat32(); 848 | p.elements.push( m ); 849 | 850 | } else if ( p.type === 1 ) { // vertex morph 851 | 852 | var m = {}; 853 | m.index = dv.getIndex( pmx.metadata.vertexIndexSize, true ); 854 | m.position = dv.getFloat32Array( 3 ); 855 | p.elements.push( m ); 856 | 857 | } else if ( p.type === 2 ) { // bone morph 858 | 859 | var m = {}; 860 | m.index = dv.getIndex( pmx.metadata.boneIndexSize ); 861 | m.position = dv.getFloat32Array( 3 ); 862 | m.rotation = dv.getFloat32Array( 4 ); 863 | p.elements.push( m ); 864 | 865 | } else if ( p.type === 3 ) { // uv morph 866 | 867 | var m = {}; 868 | m.index = dv.getIndex( pmx.metadata.vertexIndexSize, true ); 869 | m.uv = dv.getFloat32Array( 4 ); 870 | p.elements.push( m ); 871 | 872 | } else if ( p.type === 4 ) { // additional uv1 873 | 874 | // TODO: implement 875 | 876 | } else if ( p.type === 5 ) { // additional uv2 877 | 878 | // TODO: implement 879 | 880 | } else if ( p.type === 6 ) { // additional uv3 881 | 882 | // TODO: implement 883 | 884 | } else if ( p.type === 7 ) { // additional uv4 885 | 886 | // TODO: implement 887 | 888 | } else if ( p.type === 8 ) { // material morph 889 | 890 | var m = {}; 891 | m.index = dv.getIndex( pmx.metadata.materialIndexSize ); 892 | m.type = dv.getUint8(); 893 | m.diffuse = dv.getFloat32Array( 4 ); 894 | m.specular = dv.getFloat32Array( 3 ); 895 | m.shininess = dv.getFloat32(); 896 | m.ambient = dv.getFloat32Array( 3 ); 897 | m.edgeColor = dv.getFloat32Array( 4 ); 898 | m.edgeSize = dv.getFloat32(); 899 | m.textureColor = dv.getFloat32Array( 4 ); 900 | m.sphereTextureColor = dv.getFloat32Array( 4 ); 901 | m.toonColor = dv.getFloat32Array( 4 ); 902 | p.elements.push( m ); 903 | 904 | } 905 | 906 | } 907 | 908 | return p; 909 | 910 | }; 911 | 912 | var metadata = pmx.metadata; 913 | metadata.morphCount = dv.getUint32(); 914 | 915 | pmx.morphs = []; 916 | 917 | for ( var i = 0; i < metadata.morphCount; i++ ) { 918 | 919 | pmx.morphs.push( parseMorph() ); 920 | 921 | } 922 | 923 | }; 924 | 925 | var parseFrames = function () { 926 | 927 | var parseFrame = function () { 928 | 929 | var p = {}; 930 | p.name = dv.getTextBuffer(); 931 | p.englishName = dv.getTextBuffer(); 932 | p.type = dv.getUint8(); 933 | p.elementCount = dv.getUint32(); 934 | p.elements = []; 935 | 936 | for ( var i = 0; i < p.elementCount; i++ ) { 937 | 938 | var e = {}; 939 | e.target = dv.getUint8(); 940 | e.index = ( e.target === 0 ) ? dv.getIndex( pmx.metadata.boneIndexSize ) : dv.getIndex( pmx.metadata.morphIndexSize ); 941 | p.elements.push( e ); 942 | 943 | } 944 | 945 | return p; 946 | 947 | }; 948 | 949 | var metadata = pmx.metadata; 950 | metadata.frameCount = dv.getUint32(); 951 | 952 | pmx.frames = []; 953 | 954 | for ( var i = 0; i < metadata.frameCount; i++ ) { 955 | 956 | pmx.frames.push( parseFrame() ); 957 | 958 | } 959 | 960 | }; 961 | 962 | var parseRigidBodies = function () { 963 | 964 | var parseRigidBody = function () { 965 | 966 | var p = {}; 967 | p.name = dv.getTextBuffer(); 968 | p.englishName = dv.getTextBuffer(); 969 | p.boneIndex = dv.getIndex( pmx.metadata.boneIndexSize ); 970 | p.groupIndex = dv.getUint8(); 971 | p.groupTarget = dv.getUint16(); 972 | p.shapeType = dv.getUint8(); 973 | p.width = dv.getFloat32(); 974 | p.height = dv.getFloat32(); 975 | p.depth = dv.getFloat32(); 976 | p.position = dv.getFloat32Array( 3 ); 977 | p.rotation = dv.getFloat32Array( 3 ); 978 | p.weight = dv.getFloat32(); 979 | p.positionDamping = dv.getFloat32(); 980 | p.rotationDamping = dv.getFloat32(); 981 | p.restitution = dv.getFloat32(); 982 | p.friction = dv.getFloat32(); 983 | p.type = dv.getUint8(); 984 | return p; 985 | 986 | }; 987 | 988 | var metadata = pmx.metadata; 989 | metadata.rigidBodyCount = dv.getUint32(); 990 | 991 | pmx.rigidBodies = []; 992 | 993 | for ( var i = 0; i < metadata.rigidBodyCount; i++ ) { 994 | 995 | pmx.rigidBodies.push( parseRigidBody() ); 996 | 997 | } 998 | 999 | }; 1000 | 1001 | var parseConstraints = function () { 1002 | 1003 | var parseConstraint = function () { 1004 | 1005 | var p = {}; 1006 | p.name = dv.getTextBuffer(); 1007 | p.englishName = dv.getTextBuffer(); 1008 | p.type = dv.getUint8(); 1009 | p.rigidBodyIndex1 = dv.getIndex( pmx.metadata.rigidBodyIndexSize ); 1010 | p.rigidBodyIndex2 = dv.getIndex( pmx.metadata.rigidBodyIndexSize ); 1011 | p.position = dv.getFloat32Array( 3 ); 1012 | p.rotation = dv.getFloat32Array( 3 ); 1013 | p.translationLimitation1 = dv.getFloat32Array( 3 ); 1014 | p.translationLimitation2 = dv.getFloat32Array( 3 ); 1015 | p.rotationLimitation1 = dv.getFloat32Array( 3 ); 1016 | p.rotationLimitation2 = dv.getFloat32Array( 3 ); 1017 | p.springPosition = dv.getFloat32Array( 3 ); 1018 | p.springRotation = dv.getFloat32Array( 3 ); 1019 | return p; 1020 | 1021 | }; 1022 | 1023 | var metadata = pmx.metadata; 1024 | metadata.constraintCount = dv.getUint32(); 1025 | 1026 | pmx.constraints = []; 1027 | 1028 | for ( var i = 0; i < metadata.constraintCount; i++ ) { 1029 | 1030 | pmx.constraints.push( parseConstraint() ); 1031 | 1032 | } 1033 | 1034 | }; 1035 | 1036 | parseHeader(); 1037 | parseVertices(); 1038 | parseFaces(); 1039 | parseTextures(); 1040 | parseMaterials(); 1041 | parseBones(); 1042 | parseMorphs(); 1043 | parseFrames(); 1044 | parseRigidBodies(); 1045 | parseConstraints(); 1046 | 1047 | if ( leftToRight === true ) this.leftToRightModel( pmx ); 1048 | 1049 | // console.log( pmx ); // for console debug 1050 | 1051 | return pmx; 1052 | 1053 | }; 1054 | 1055 | Parser.prototype.parseVmd = function ( buffer, leftToRight ) { 1056 | 1057 | var vmd = {}; 1058 | var dv = new DataViewEx( buffer ); 1059 | 1060 | vmd.metadata = {}; 1061 | vmd.metadata.coordinateSystem = 'left'; 1062 | 1063 | var parseHeader = function () { 1064 | 1065 | var metadata = vmd.metadata; 1066 | metadata.magic = dv.getChars( 30 ); 1067 | 1068 | if ( metadata.magic !== 'Vocaloid Motion Data 0002' ) { 1069 | 1070 | throw 'VMD file magic is not Vocaloid Motion Data 0002, but ' + metadata.magic; 1071 | 1072 | } 1073 | 1074 | metadata.name = dv.getSjisStringsAsUnicode( 20 ); 1075 | 1076 | }; 1077 | 1078 | var parseMotions = function () { 1079 | 1080 | var parseMotion = function () { 1081 | 1082 | var p = {}; 1083 | p.boneName = dv.getSjisStringsAsUnicode( 15 ); 1084 | p.frameNum = dv.getUint32(); 1085 | p.position = dv.getFloat32Array( 3 ); 1086 | p.rotation = dv.getFloat32Array( 4 ); 1087 | p.interpolation = dv.getUint8Array( 64 ); 1088 | return p; 1089 | 1090 | }; 1091 | 1092 | var metadata = vmd.metadata; 1093 | metadata.motionCount = dv.getUint32(); 1094 | 1095 | vmd.motions = []; 1096 | for ( var i = 0; i < metadata.motionCount; i++ ) { 1097 | 1098 | vmd.motions.push( parseMotion() ); 1099 | 1100 | } 1101 | 1102 | }; 1103 | 1104 | var parseMorphs = function () { 1105 | 1106 | var parseMorph = function () { 1107 | 1108 | var p = {}; 1109 | p.morphName = dv.getSjisStringsAsUnicode( 15 ); 1110 | p.frameNum = dv.getUint32(); 1111 | p.weight = dv.getFloat32(); 1112 | return p; 1113 | 1114 | }; 1115 | 1116 | var metadata = vmd.metadata; 1117 | metadata.morphCount = dv.getUint32(); 1118 | 1119 | vmd.morphs = []; 1120 | for ( var i = 0; i < metadata.morphCount; i++ ) { 1121 | 1122 | vmd.morphs.push( parseMorph() ); 1123 | 1124 | } 1125 | 1126 | }; 1127 | 1128 | var parseCameras = function () { 1129 | 1130 | var parseCamera = function () { 1131 | 1132 | var p = {}; 1133 | p.frameNum = dv.getUint32(); 1134 | p.distance = dv.getFloat32(); 1135 | p.position = dv.getFloat32Array( 3 ); 1136 | p.rotation = dv.getFloat32Array( 3 ); 1137 | p.interpolation = dv.getUint8Array( 24 ); 1138 | p.fov = dv.getUint32(); 1139 | p.perspective = dv.getUint8(); 1140 | return p; 1141 | 1142 | }; 1143 | 1144 | var metadata = vmd.metadata; 1145 | metadata.cameraCount = dv.getUint32(); 1146 | 1147 | vmd.cameras = []; 1148 | for ( var i = 0; i < metadata.cameraCount; i++ ) { 1149 | 1150 | vmd.cameras.push( parseCamera() ); 1151 | 1152 | } 1153 | 1154 | }; 1155 | 1156 | parseHeader(); 1157 | parseMotions(); 1158 | parseMorphs(); 1159 | parseCameras(); 1160 | 1161 | if ( leftToRight === true ) this.leftToRightVmd( vmd ); 1162 | 1163 | // console.log( vmd ); // for console debug 1164 | 1165 | return vmd; 1166 | 1167 | }; 1168 | 1169 | Parser.prototype.parseVpd = function ( text, leftToRight ) { 1170 | 1171 | var vpd = {}; 1172 | 1173 | vpd.metadata = {}; 1174 | vpd.metadata.coordinateSystem = 'left'; 1175 | 1176 | vpd.bones = []; 1177 | 1178 | var commentPatternG = /\/\/\w*(\r|\n|\r\n)/g; 1179 | var newlinePattern = /\r|\n|\r\n/; 1180 | 1181 | var lines = text.replace( commentPatternG, '' ).split( newlinePattern ); 1182 | 1183 | function throwError () { 1184 | 1185 | throw 'the file seems not vpd file.'; 1186 | 1187 | }; 1188 | 1189 | function checkMagic () { 1190 | 1191 | if ( lines[ 0 ] !== 'Vocaloid Pose Data file' ) { 1192 | 1193 | throwError(); 1194 | 1195 | } 1196 | 1197 | }; 1198 | 1199 | function parseHeader () { 1200 | 1201 | if ( lines.length < 4 ) { 1202 | 1203 | throwError(); 1204 | 1205 | } 1206 | 1207 | vpd.metadata.parentFile = lines[ 2 ]; 1208 | vpd.metadata.boneCount = parseInt( lines[ 3 ] ); 1209 | 1210 | }; 1211 | 1212 | function parseBones () { 1213 | 1214 | var boneHeaderPattern = /^\s*(Bone[0-9]+)\s*\{\s*(.*)$/; 1215 | var boneVectorPattern = /^\s*(-?[0-9]+\.[0-9]+)\s*,\s*(-?[0-9]+\.[0-9]+)\s*,\s*(-?[0-9]+\.[0-9]+)\s*;/; 1216 | 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*;/; 1217 | var boneFooterPattern = /^\s*}/; 1218 | 1219 | var bones = vpd.bones; 1220 | var n = null; 1221 | var v = null; 1222 | var q = null; 1223 | 1224 | for ( var i = 4; i < lines.length; i++ ) { 1225 | 1226 | var line = lines[ i ]; 1227 | 1228 | var result; 1229 | 1230 | result = line.match( boneHeaderPattern ); 1231 | 1232 | if ( result !== null ) { 1233 | 1234 | if ( n !== null ) { 1235 | 1236 | throwError(); 1237 | 1238 | } 1239 | 1240 | n = result[ 2 ]; 1241 | 1242 | } 1243 | 1244 | result = line.match( boneVectorPattern ); 1245 | 1246 | if ( result !== null ) { 1247 | 1248 | if ( v !== null ) { 1249 | 1250 | throwError(); 1251 | 1252 | } 1253 | 1254 | v = [ 1255 | 1256 | parseFloat( result[ 1 ] ), 1257 | parseFloat( result[ 2 ] ), 1258 | parseFloat( result[ 3 ] ) 1259 | 1260 | ]; 1261 | 1262 | } 1263 | 1264 | result = line.match( boneQuaternionPattern ); 1265 | 1266 | if ( result !== null ) { 1267 | 1268 | if ( q !== null ) { 1269 | 1270 | throwError(); 1271 | 1272 | } 1273 | 1274 | q = [ 1275 | 1276 | parseFloat( result[ 1 ] ), 1277 | parseFloat( result[ 2 ] ), 1278 | parseFloat( result[ 3 ] ), 1279 | parseFloat( result[ 4 ] ) 1280 | 1281 | ]; 1282 | 1283 | 1284 | } 1285 | 1286 | result = line.match( boneFooterPattern ); 1287 | 1288 | if ( result !== null ) { 1289 | 1290 | if ( n === null || v === null || q === null ) { 1291 | 1292 | throwError(); 1293 | 1294 | } 1295 | 1296 | bones.push( { 1297 | 1298 | name: n, 1299 | translation: v, 1300 | quaternion: q 1301 | 1302 | } ); 1303 | 1304 | n = null; 1305 | v = null; 1306 | q = null; 1307 | 1308 | } 1309 | 1310 | } 1311 | 1312 | if ( n !== null || v !== null || q !== null ) { 1313 | 1314 | throwError(); 1315 | 1316 | } 1317 | 1318 | }; 1319 | 1320 | checkMagic(); 1321 | parseHeader(); 1322 | parseBones(); 1323 | 1324 | if ( leftToRight === true ) this.leftToRightVpd( vpd ); 1325 | 1326 | // console.log( vpd ); // for console debug 1327 | 1328 | return vpd; 1329 | 1330 | }; 1331 | 1332 | Parser.prototype.mergeVmds = function ( vmds ) { 1333 | 1334 | var v = {}; 1335 | v.metadata = {}; 1336 | v.metadata.name = vmds[ 0 ].metadata.name; 1337 | v.metadata.coordinateSystem = vmds[ 0 ].metadata.coordinateSystem; 1338 | v.metadata.motionCount = 0; 1339 | v.metadata.morphCount = 0; 1340 | v.metadata.cameraCount = 0; 1341 | v.motions = []; 1342 | v.morphs = []; 1343 | v.cameras = []; 1344 | 1345 | for ( var i = 0; i < vmds.length; i++ ) { 1346 | 1347 | var v2 = vmds[ i ]; 1348 | 1349 | v.metadata.motionCount += v2.metadata.motionCount; 1350 | v.metadata.morphCount += v2.metadata.morphCount; 1351 | v.metadata.cameraCount += v2.metadata.cameraCount; 1352 | 1353 | for ( var j = 0; j < v2.metadata.motionCount; j++ ) { 1354 | 1355 | v.motions.push( v2.motions[ j ] ); 1356 | 1357 | } 1358 | 1359 | for ( var j = 0; j < v2.metadata.morphCount; j++ ) { 1360 | 1361 | v.morphs.push( v2.morphs[ j ] ); 1362 | 1363 | } 1364 | 1365 | for ( var j = 0; j < v2.metadata.cameraCount; j++ ) { 1366 | 1367 | v.cameras.push( v2.cameras[ j ] ); 1368 | 1369 | } 1370 | 1371 | } 1372 | 1373 | return v; 1374 | 1375 | }; 1376 | 1377 | Parser.prototype.leftToRightModel = function ( model ) { 1378 | 1379 | if ( model.metadata.coordinateSystem === 'right' ) { 1380 | 1381 | return; 1382 | 1383 | } 1384 | 1385 | model.metadata.coordinateSystem = 'right'; 1386 | 1387 | var helper = new DataCreationHelper(); 1388 | 1389 | for ( var i = 0; i < model.metadata.vertexCount; i++ ) { 1390 | 1391 | helper.leftToRightVector3( model.vertices[ i ].position ); 1392 | helper.leftToRightVector3( model.vertices[ i ].normal ); 1393 | 1394 | } 1395 | 1396 | for ( var i = 0; i < model.metadata.faceCount; i++ ) { 1397 | 1398 | helper.leftToRightIndexOrder( model.faces[ i ].indices ); 1399 | 1400 | } 1401 | 1402 | for ( var i = 0; i < model.metadata.boneCount; i++ ) { 1403 | 1404 | helper.leftToRightVector3( model.bones[ i ].position ); 1405 | 1406 | } 1407 | 1408 | // TODO: support other morph for PMX 1409 | for ( var i = 0; i < model.metadata.morphCount; i++ ) { 1410 | 1411 | var m = model.morphs[ i ]; 1412 | 1413 | if ( model.metadata.format === 'pmx' && m.type !== 1 ) { 1414 | 1415 | // TODO: implement 1416 | continue; 1417 | 1418 | } 1419 | 1420 | for ( var j = 0; j < m.elements.length; j++ ) { 1421 | 1422 | helper.leftToRightVector3( m.elements[ j ].position ); 1423 | 1424 | } 1425 | 1426 | } 1427 | 1428 | for ( var i = 0; i < model.metadata.rigidBodyCount; i++ ) { 1429 | 1430 | helper.leftToRightVector3( model.rigidBodies[ i ].position ); 1431 | helper.leftToRightEuler( model.rigidBodies[ i ].rotation ); 1432 | 1433 | } 1434 | 1435 | for ( var i = 0; i < model.metadata.constraintCount; i++ ) { 1436 | 1437 | helper.leftToRightVector3( model.constraints[ i ].position ); 1438 | helper.leftToRightEuler( model.constraints[ i ].rotation ); 1439 | helper.leftToRightVector3Range( model.constraints[ i ].translationLimitation1, model.constraints[ i ].translationLimitation2 ); 1440 | helper.leftToRightEulerRange( model.constraints[ i ].rotationLimitation1, model.constraints[ i ].rotationLimitation2 ); 1441 | 1442 | } 1443 | 1444 | }; 1445 | 1446 | Parser.prototype.leftToRightVmd = function ( vmd ) { 1447 | 1448 | if ( vmd.metadata.coordinateSystem === 'right' ) { 1449 | 1450 | return; 1451 | 1452 | } 1453 | 1454 | vmd.metadata.coordinateSystem = 'right'; 1455 | 1456 | var helper = new DataCreationHelper(); 1457 | 1458 | for ( var i = 0; i < vmd.metadata.motionCount; i++ ) { 1459 | 1460 | helper.leftToRightVector3( vmd.motions[ i ].position ); 1461 | helper.leftToRightQuaternion( vmd.motions[ i ].rotation ); 1462 | 1463 | } 1464 | 1465 | for ( var i = 0; i < vmd.metadata.cameraCount; i++ ) { 1466 | 1467 | helper.leftToRightVector3( vmd.cameras[ i ].position ); 1468 | helper.leftToRightEuler( vmd.cameras[ i ].rotation ); 1469 | 1470 | } 1471 | 1472 | }; 1473 | 1474 | Parser.prototype.leftToRightVpd = function ( vpd ) { 1475 | 1476 | if ( vpd.metadata.coordinateSystem === 'right' ) { 1477 | 1478 | return; 1479 | 1480 | } 1481 | 1482 | vpd.metadata.coordinateSystem = 'right'; 1483 | 1484 | var helper = new DataCreationHelper(); 1485 | 1486 | for ( var i = 0; i < vpd.bones.length; i++ ) { 1487 | 1488 | helper.leftToRightVector3( vpd.bones[ i ].translation ); 1489 | helper.leftToRightQuaternion( vpd.bones[ i ].quaternion ); 1490 | 1491 | } 1492 | 1493 | }; 1494 | 1495 | export { Parser } 1496 | --------------------------------------------------------------------------------