4 * License : The MIT License
5 * Copyright(c) 2010 MikuToga Partners
8 package jp.sourceforge.mikutoga.pmd.parser;
10 import java.io.IOException;
11 import java.io.InputStream;
12 import java.nio.charset.Charset;
13 import java.util.Arrays;
14 import jp.sfjp.mikutoga.bin.parser.CommonParser;
15 import jp.sfjp.mikutoga.bin.parser.MmdEofException;
16 import jp.sfjp.mikutoga.bin.parser.MmdFormatException;
17 import jp.sfjp.mikutoga.bin.parser.TextDecoder;
22 public class PmdParserBase extends CommonParser {
25 * PMDで用いられる文字エンコーディング(windows-31j)。
26 * ほぼShift_JISのスーパーセットと思ってよい。
27 * デコード結果はUCS-2集合に収まるはず。
29 public static final Charset CS_WIN31J = Charset.forName("windows-31j");
32 protected static final String CR = "\r"; // 0x0d
34 protected static final String LF = "\n"; // 0x0a
36 protected static final String CRLF = CR + LF; // 0x0d, 0x0a
38 private static final int HEADER_LENGTH = 7;
40 private static final byte[] MAGIC_BYTES = {
41 (byte)0x50, (byte)0x6d, (byte)0x64, // "Pmd"
42 (byte)0x00, (byte)0x00, (byte)0x80, (byte)0x3f, // 1.0f
45 private static final int VERTEX_DATA_SZ = 38;
46 private static final int SURFACE_DATA_SZ = 6;
47 private static final int MATERIAL_DATA_SZ = 70;
48 private static final int BONE_DATA_SZ = 39;
49 private static final int MORPHVERTEX_DATA_SZ = 16;
50 private static final int MORPHORDER_DATA_SZ = 2;
51 private static final int BONEGROUP_DATA_SZ = 50;
52 private static final int GROUPEDBONE_DATA_SZ = 3;
56 assert MAGIC_BYTES.length == HEADER_LENGTH;
60 private final TextDecoder decoderWin31j = new TextDecoder(CS_WIN31J);
62 private PmdBasicHandler basicHandler = null;
63 private PmdShapeHandler shapeHandler = null;
64 private PmdMaterialHandler materialHandler = null;
65 private PmdBoneHandler boneHandler = null;
66 private PmdMorphHandler morphHandler = null;
68 private int boneCount = -1;
69 private int morphCount = -1;
70 private int boneGroupCount = -1;
77 public PmdParserBase(InputStream source){
79 this.decoderWin31j.setZeroChopMode(true);
84 * 文字列の最後がLF(0x0a)の場合削除する。
86 * @return 末尾LFが削除された文字列
88 public static String chopLastLF(String name){
91 if(name.endsWith(LF)){
92 result = name.substring(0, name.length() - 1);
102 * テクスチャファイル名とスフィアマップファイル名を分離する。
103 * @param shadingFile シェーディング用ファイル情報
104 * @return [0]:テクスチャファイル名 [1]:スフィアマップファイル名。
107 public static String[] splitShadingFileInfo(String shadingFile){
110 result = shadingFile.split('\\'+"*", 2);
111 assert result.length == 1 || result.length == 2;
113 if(result.length == 1){
114 String onlyFile = result[0];
115 result = new String[2];
118 if(onlyFile.endsWith(".sph") || onlyFile.endsWith(".spa")){
119 result[1] = onlyFile;
121 result[0] = onlyFile;
125 assert result.length == 2;
132 * @param handler ハンドラ
134 public void setBasicHandler(PmdBasicHandler handler){
135 this.basicHandler = handler;
141 * @param handler ハンドラ
143 public void setShapeHandler(PmdShapeHandler handler){
144 this.shapeHandler = handler;
150 * @param handler ハンドラ
152 public void setMaterialHandler(PmdMaterialHandler handler){
153 this.materialHandler = handler;
159 * @param handler ハンドラ
161 public void setBoneHandler(PmdBoneHandler handler){
162 this.boneHandler = handler;
168 * @param handler ハンドラ
170 public void setMorphHandler(PmdMorphHandler handler){
171 this.morphHandler = handler;
176 * パースによって得られたボーン数を返す。
179 protected int getBoneCount(){
180 return this.boneCount;
184 * パースによって得られたモーフ数を返す。
187 protected int getMorphCount(){
188 return this.morphCount;
192 * パースによって得られたボーングループ数を返す。
195 protected int getBoneGroupCount(){
196 return this.boneGroupCount;
200 * 指定されたバイト長に収まるゼロ終端(0x00)文字列を読み込む。
201 * 入力バイト列はwindows-31jエンコーディングとして解釈される。
203 * IO入力は指定バイト数だけ読み進められる。
204 * ゼロ終端が見つからないまま指定バイト数が読み込み終わった場合、
205 * そこまでのデータから文字列を構成する。
206 * @param byteLen 読み込みバイト数
208 * @throws IOException IOエラー
209 * @throws MmdEofException 読み込む途中でストリーム終端に達した。
210 * @throws MmdFormatException 不正な文字エンコーディングが検出された。
212 protected String parsePmdText(int byteLen)
216 String result = parseString(this.decoderWin31j, byteLen);
222 * @throws IOException IOエラー
223 * @throws MmdFormatException フォーマットエラー
225 public void parsePmd()
226 throws IOException, MmdFormatException {
227 if(this.basicHandler != null){
228 this.basicHandler.pmdParseStart();
233 boolean hasMoreData = hasMore();
234 if(this.basicHandler != null){
235 this.basicHandler.pmdParseEnd(hasMoreData);
242 * PMDファイル本体のパースを開始する。
243 * パーサを拡張する場合はこのメソッドをオーバーライドする。
244 * @throws IOException IOエラー
245 * @throws MmdFormatException フォーマットエラー
247 protected void parseBody() throws IOException, MmdFormatException{
256 parseMorphOrderList();
257 parseBoneGroupList();
258 parseGroupedBoneList();
264 * PMDファイルヘッダ部のパースと通知。
265 * @throws IOException IOエラー
266 * @throws MmdFormatException フォーマットエラー
268 private void parsePmdHeader() throws IOException, MmdFormatException{
269 byte[] header = new byte[HEADER_LENGTH];
270 parseByteArray(header);
272 if( ! Arrays.equals(header, MAGIC_BYTES) ){
273 throw new MmdFormatException("unknown PMD-header type");
277 parsePmdText(PmdLimits.MAXBYTES_MODELNAME);
279 parsePmdText(PmdLimits.MAXBYTES_MODELDESC);
280 description = description.replace(CRLF, LF);
282 if(this.basicHandler != null){
283 this.basicHandler.pmdHeaderInfo(header);
284 this.basicHandler.pmdModelInfo(modelName, description);
292 * @throws IOException IOエラー
293 * @throws MmdFormatException フォーマットエラー
295 private void parseVertexList() throws IOException, MmdFormatException{
296 int vertexNum = parseLeInt();
298 if(this.shapeHandler == null){
299 skip(VERTEX_DATA_SZ * vertexNum);
303 this.shapeHandler.loopStart(PmdShapeHandler.VERTEX_LIST, vertexNum);
305 for(int ct = 0; ct < vertexNum; ct++){
306 float xPos = parseLeFloat();
307 float yPos = parseLeFloat();
308 float zPos = parseLeFloat();
309 this.shapeHandler.pmdVertexPosition(xPos, yPos, zPos);
311 float xVec = parseLeFloat();
312 float yVec = parseLeFloat();
313 float zVec = parseLeFloat();
314 this.shapeHandler.pmdVertexNormal(xVec, yVec, zVec);
316 float uVal = parseLeFloat();
317 float vVal = parseLeFloat();
318 this.shapeHandler.pmdVertexUV(uVal, vVal);
320 int boneId1 = parseLeUShortAsInt();
321 int boneId2 = parseLeUShortAsInt();
322 int weightForB1 = parseUByteAsInt();
323 this.shapeHandler.pmdVertexWeight(boneId1, boneId2, weightForB1);
325 boolean hideEdge = parseBoolean();
326 this.shapeHandler.pmdVertexEdge(hideEdge);
328 this.shapeHandler.loopNext(PmdShapeHandler.VERTEX_LIST);
331 this.shapeHandler.loopEnd(PmdShapeHandler.VERTEX_LIST);
338 * @throws IOException IOエラー
339 * @throws MmdFormatException フォーマットエラー
341 private void parseSurfaceList() throws IOException, MmdFormatException{
342 int vertexNum = parseLeInt();
343 if(vertexNum % 3 != 0) throw new MmdFormatException();
344 int surfaceNum = vertexNum / 3;
346 if(this.shapeHandler == null){
347 skip(SURFACE_DATA_SZ * surfaceNum);
351 this.shapeHandler.loopStart(PmdShapeHandler.SURFACE_LIST, surfaceNum);
353 for(int ct = 0; ct < surfaceNum; ct++){
354 int vertexId1 = parseLeUShortAsInt();
355 int vertexId2 = parseLeUShortAsInt();
356 int vertexId3 = parseLeUShortAsInt();
357 this.shapeHandler.pmdSurfaceTriangle(vertexId1,
360 this.shapeHandler.loopNext(PmdShapeHandler.SURFACE_LIST);
363 this.shapeHandler.loopEnd(PmdShapeHandler.SURFACE_LIST);
370 * @throws IOException IOエラー
371 * @throws MmdFormatException フォーマットエラー
373 private void parseMaterialList() throws IOException, MmdFormatException{
374 int materialNum = parseLeInt();
376 if(this.materialHandler == null){
377 skip(MATERIAL_DATA_SZ * materialNum);
381 this.materialHandler.loopStart(PmdMaterialHandler.MATERIAL_LIST,
384 for(int ct = 0; ct < materialNum; ct++){
389 red = parseLeFloat();
390 green = parseLeFloat();
391 blue = parseLeFloat();
392 float alpha = parseLeFloat();
393 this.materialHandler.pmdMaterialDiffuse(red, green, blue, alpha);
395 float shininess = parseLeFloat();
396 red = parseLeFloat();
397 green = parseLeFloat();
398 blue = parseLeFloat();
399 this.materialHandler.pmdMaterialSpecular(red, green, blue,
402 red = parseLeFloat();
403 green = parseLeFloat();
404 blue = parseLeFloat();
405 this.materialHandler.pmdMaterialAmbient(red, green, blue);
407 int toonidx = parseUByteAsInt();
408 boolean hasEdge = parseBoolean();
409 int surfaceCount = parseLeInt();
411 parsePmdText(PmdLimits.MAXBYTES_TEXTUREFILENAME);
412 String[] splitted = splitShadingFileInfo(shadingFile);
413 String textureFile = splitted[0];
414 String sphereFile = splitted[1];
416 this.materialHandler.pmdMaterialShading(toonidx,
417 textureFile, sphereFile );
418 this.materialHandler.pmdMaterialInfo(hasEdge, surfaceCount);
420 this.materialHandler.loopNext(PmdMaterialHandler.MATERIAL_LIST);
423 this.materialHandler.loopEnd(PmdMaterialHandler.MATERIAL_LIST);
430 * @throws IOException IOエラー
431 * @throws MmdFormatException フォーマットエラー
433 private void parseBoneList() throws IOException, MmdFormatException{
434 this.boneCount = parseLeUShortAsInt();
436 if(this.boneHandler == null){
437 skip(BONE_DATA_SZ * this.boneCount);
441 this.boneHandler.loopStart(PmdBoneHandler.BONE_LIST, this.boneCount);
443 for(int ct = 0; ct < this.boneCount; ct++){
445 parsePmdText(PmdLimits.MAXBYTES_BONENAME);
446 int parentId = parseLeUShortAsInt();
447 int tailId = parseLeUShortAsInt();
448 byte boneKind = parseByte();
449 int srcId = parseLeUShortAsInt();
451 this.boneHandler.pmdBoneInfo(boneName, boneKind);
452 this.boneHandler.pmdBoneLink(parentId, tailId, srcId);
454 float xPos = parseLeFloat();
455 float yPos = parseLeFloat();
456 float zPos = parseLeFloat();
458 this.boneHandler.pmdBonePosition(xPos, yPos, zPos);
460 this.boneHandler.loopNext(PmdBoneHandler.BONE_LIST);
463 this.boneHandler.loopEnd(PmdBoneHandler.BONE_LIST);
470 * @throws IOException IOエラー
471 * @throws MmdFormatException フォーマットエラー
473 private void parseIKList() throws IOException, MmdFormatException{
474 int ikCount = parseLeUShortAsInt();
476 if(this.boneHandler != null){
477 this.boneHandler.loopStart(PmdBoneHandler.IK_LIST, ikCount);
480 for(int ct = 0; ct < ikCount; ct++){
481 int boneId = parseLeUShortAsInt();
482 int targetId = parseLeUShortAsInt();
483 int chainLength = parseUByteAsInt();
484 int depth = parseLeUShortAsInt();
485 float weight = parseLeFloat();
487 parseIKChainList(chainLength);
489 if(this.boneHandler != null){
490 this.boneHandler.pmdIKInfo(boneId, targetId, depth, weight);
491 this.boneHandler.loopNext(PmdBoneHandler.IK_LIST);
495 if(this.boneHandler != null){
496 this.boneHandler.loopEnd(PmdBoneHandler.IK_LIST);
504 * @param chainLength チェーン長
505 * @throws IOException IOエラー
506 * @throws MmdFormatException フォーマットエラー
508 private void parseIKChainList(int chainLength)
509 throws IOException, MmdFormatException{
510 if(this.boneHandler != null){
511 this.boneHandler.loopStart(PmdBoneHandler.IKCHAIN_LIST,
515 for(int ct = 0; ct < chainLength; ct++){
516 int childId = parseLeUShortAsInt();
517 if(this.boneHandler != null){
518 this.boneHandler.pmdIKChainInfo(childId);
519 this.boneHandler.loopNext(PmdBoneHandler.IKCHAIN_LIST);
523 if(this.boneHandler != null){
524 this.boneHandler.loopEnd(PmdBoneHandler.IKCHAIN_LIST);
532 * @throws IOException IOエラー
533 * @throws MmdFormatException フォーマットエラー
535 private void parseMorphList() throws IOException, MmdFormatException{
536 this.morphCount = parseLeUShortAsInt();
538 if(this.morphHandler != null){
539 this.morphHandler.loopStart(PmdMorphHandler.MORPH_LIST,
543 for(int ct = 0; ct < this.morphCount; ct++){
545 parsePmdText(PmdLimits.MAXBYTES_MORPHNAME);
546 int vertexCount = parseLeInt();
547 byte morphType = parseByte();
549 if(this.morphHandler != null){
550 this.morphHandler.pmdMorphInfo(morphName, morphType);
553 parseMorphVertexList(vertexCount);
555 if(this.morphHandler != null){
556 this.morphHandler.loopNext(PmdMorphHandler.MORPH_LIST);
560 if(this.morphHandler != null){
561 this.morphHandler.loopEnd(PmdMorphHandler.MORPH_LIST);
569 * @param vertexCount 頂点数
570 * @throws IOException IOエラー
571 * @throws MmdFormatException フォーマットエラー
573 private void parseMorphVertexList(int vertexCount)
574 throws IOException, MmdFormatException{
575 if(this.morphHandler == null){
576 skip(MORPHVERTEX_DATA_SZ * vertexCount);
580 this.morphHandler.loopStart(PmdMorphHandler.MORPHVERTEX_LIST,
583 for(int ct = 0; ct < vertexCount; ct++){
584 int vertexId = parseLeInt();
585 float xPos = parseLeFloat();
586 float yPos = parseLeFloat();
587 float zPos = parseLeFloat();
588 this.morphHandler.pmdMorphVertexInfo(vertexId, xPos, yPos, zPos);
589 this.morphHandler.loopNext(PmdMorphHandler.MORPHVERTEX_LIST);
592 this.morphHandler.loopEnd(PmdMorphHandler.MORPHVERTEX_LIST);
599 * @throws IOException IOエラー
600 * @throws MmdFormatException フォーマットエラー
602 private void parseMorphOrderList()
603 throws IOException, MmdFormatException{
604 int morphOrderCount = parseUByteAsInt();
606 if(this.morphHandler == null){
607 skip(MORPHORDER_DATA_SZ * morphOrderCount);
611 this.morphHandler.loopStart(PmdMorphHandler.MORPHORDER_LIST,
614 for(int ct = 0; ct < morphOrderCount; ct++){
615 int morphId = parseLeUShortAsInt();
616 this.morphHandler.pmdMorphOrderInfo(morphId);
618 this.morphHandler.loopNext(PmdMorphHandler.MORPHORDER_LIST);
621 this.morphHandler.loopEnd(PmdMorphHandler.MORPHORDER_LIST);
628 * @throws IOException IOエラー
629 * @throws MmdFormatException フォーマットエラー
631 private void parseBoneGroupList()
632 throws IOException, MmdFormatException{
633 this.boneGroupCount = parseUByteAsInt();
635 if(this.boneHandler == null){
636 skip(BONEGROUP_DATA_SZ * this.boneGroupCount);
640 this.boneHandler.loopStart(PmdBoneHandler.BONEGROUP_LIST,
641 this.boneGroupCount);
643 for(int ct = 0; ct < this.boneGroupCount; ct++){
645 parsePmdText(PmdLimits.MAXBYTES_BONEGROUPNAME);
646 groupName = chopLastLF(groupName);
647 this.boneHandler.pmdBoneGroupInfo(groupName);
649 this.boneHandler.loopNext(PmdBoneHandler.BONEGROUP_LIST);
652 this.boneHandler.loopEnd(PmdBoneHandler.BONEGROUP_LIST);
659 * @throws IOException IOエラー
660 * @throws MmdFormatException フォーマットエラー
662 private void parseGroupedBoneList()
663 throws IOException, MmdFormatException{
664 int groupedBoneCount = parseLeInt();
666 if(this.boneHandler == null){
667 skip(GROUPEDBONE_DATA_SZ * groupedBoneCount);
671 this.boneHandler.loopStart(PmdBoneHandler.GROUPEDBONE_LIST,
674 for(int ct = 0; ct < groupedBoneCount; ct++){
675 int boneId = parseLeUShortAsInt();
676 int groupId = parseUByteAsInt();
677 this.boneHandler.pmdGroupedBoneInfo(boneId, groupId);
679 this.boneHandler.loopNext(PmdBoneHandler.GROUPEDBONE_LIST);
682 this.boneHandler.loopEnd(PmdBoneHandler.GROUPEDBONE_LIST);