OSDN Git Service

XML出力改善
[mikutoga/TogaGem.git] / src / main / java / jp / sourceforge / mikutoga / pmd / parser / PmdParserBase.java
1 /*
2  * pmd file parser
3  *
4  * License : The MIT License
5  * Copyright(c) 2010 MikuToga Partners
6  */
7
8 package jp.sourceforge.mikutoga.pmd.parser;
9
10 import java.io.IOException;
11 import java.io.InputStream;
12 import java.nio.charset.Charset;
13 import java.util.Arrays;
14 import jp.sourceforge.mikutoga.parser.CommonParser;
15 import jp.sourceforge.mikutoga.parser.MmdEofException;
16 import jp.sourceforge.mikutoga.parser.MmdFormatException;
17 import jp.sourceforge.mikutoga.parser.TextDecoder;
18
19 /**
20  * PMDモデルファイルのパーサ基本部。
21  */
22 public class PmdParserBase extends CommonParser {
23
24     /**
25      * PMDで用いられる文字エンコーディング(windows-31j)。
26      * ほぼShift_JISのスーパーセットと思ってよい。
27      * デコード結果はUCS-2集合に収まるはず。
28      */
29     public static final Charset CS_WIN31J = Charset.forName("windows-31j");
30
31     /** 改行文字列 CR。 */
32     protected static final String CR = "\r";       // 0x0d
33     /** 改行文字列 LF。 */
34     protected static final String LF = "\n";       // 0x0a
35     /** 改行文字列 CRLF。 */
36     protected static final String CRLF = CR + LF;  // 0x0d, 0x0a
37
38     private static final int HEADER_LENGTH = 7;
39
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
43     };
44
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;
53
54
55     static{
56         assert MAGIC_BYTES.length == HEADER_LENGTH;
57     }
58
59
60     private final TextDecoder decoderWin31j  = new TextDecoder(CS_WIN31J);
61
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;
67
68     private int boneCount      = -1;
69     private int morphCount     = -1;
70     private int boneGroupCount = -1;
71
72
73     /**
74      * コンストラクタ。
75      * @param source 入力ソース
76      */
77     public PmdParserBase(InputStream source){
78         super(source);
79         this.decoderWin31j.setZeroChopMode(true);
80         return;
81     }
82
83     /**
84      * 文字列の最後がLF(0x0a)の場合削除する。
85      * @param name 文字列
86      * @return 末尾LFが削除された文字列
87      */
88     public static String chopLastLF(String name){
89         String result;
90
91         if(name.endsWith(LF)){
92             result = name.substring(0, name.length() - 1);
93         }else{
94             result = name;
95         }
96
97         return result;
98     }
99
100     /**
101      * シェーディング用ファイル情報から
102      * テクスチャファイル名とスフィアマップファイル名を分離する。
103      * @param shadingFile シェーディング用ファイル情報
104      * @return [0]:テクスチャファイル名 [1]:スフィアマップファイル名。
105      * 該当ファイル名が無い場合は空文字列。
106      */
107     public static String[] splitShadingFileInfo(String shadingFile){
108         String[] result;
109
110         result = shadingFile.split('\\'+"*", 2);
111         assert result.length == 1 || result.length == 2;
112
113         if(result.length == 1){
114             String onlyFile = result[0];
115             result = new String[2];
116             result[0] = "";
117             result[1] = "";
118             if(onlyFile.endsWith(".sph") || onlyFile.endsWith(".spa")){
119                 result[1] = onlyFile;
120             }else{
121                 result[0] = onlyFile;
122             }
123         }
124
125         assert result.length == 2;
126
127         return result;
128     }
129
130     /**
131      * 基本情報通知ハンドラを登録する。
132      * @param handler ハンドラ
133      */
134     public void setBasicHandler(PmdBasicHandler handler){
135         this.basicHandler = handler;
136         return;
137     }
138
139     /**
140      * 形状情報通知ハンドラを登録する。
141      * @param handler ハンドラ
142      */
143     public void setShapeHandler(PmdShapeHandler handler){
144         this.shapeHandler = handler;
145         return;
146     }
147
148     /**
149      * 材質情報通知ハンドラを登録する。
150      * @param handler ハンドラ
151      */
152     public void setMaterialHandler(PmdMaterialHandler handler){
153         this.materialHandler = handler;
154         return;
155     }
156
157     /**
158      * ボーン情報通知ハンドラを登録する。
159      * @param handler ハンドラ
160      */
161     public void setBoneHandler(PmdBoneHandler handler){
162         this.boneHandler = handler;
163         return;
164     }
165
166     /**
167      * モーフ情報通知ハンドラを登録する。
168      * @param handler ハンドラ
169      */
170     public void setMorphHandler(PmdMorphHandler handler){
171         this.morphHandler = handler;
172         return;
173     }
174
175     /**
176      * パースによって得られたボーン数を返す。
177      * @return ボーン数
178      */
179     protected int getBoneCount(){
180         return this.boneCount;
181     }
182
183     /**
184      * パースによって得られたモーフ数を返す。
185      * @return モーフ数
186      */
187     protected int getMorphCount(){
188         return this.morphCount;
189     }
190
191     /**
192      * パースによって得られたボーングループ数を返す。
193      * @return ボーングループ数
194      */
195     protected int getBoneGroupCount(){
196         return this.boneGroupCount;
197     }
198
199     /**
200      * 指定されたバイト長に収まるゼロ終端(0x00)文字列を読み込む。
201      * 入力バイト列はwindows-31jエンコーディングとして解釈される。
202      * ゼロ終端以降のデータは無視されるが、
203      * IO入力は指定バイト数だけ読み進められる。
204      * ゼロ終端が見つからないまま指定バイト数が読み込み終わった場合、
205      * そこまでのデータから文字列を構成する。
206      * @param byteLen 読み込みバイト数
207      * @return デコードされた文字列
208      * @throws IOException IOエラー
209      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
210      * @throws MmdFormatException 不正な文字エンコーディングが検出された。
211      */
212     protected String parsePmdText(int byteLen)
213             throws IOException,
214                    MmdEofException,
215                    MmdFormatException {
216         String result = parseString(this.decoderWin31j, byteLen);
217         return result;
218     }
219
220     /**
221      * PMDファイルのパースを開始する。
222      * @throws IOException IOエラー
223      * @throws MmdFormatException フォーマットエラー
224      */
225     public void parsePmd()
226             throws IOException, MmdFormatException {
227         if(this.basicHandler != null){
228             this.basicHandler.pmdParseStart();
229         }
230
231         parseBody();
232
233         boolean hasMoreData = hasMore();
234         if(this.basicHandler != null){
235             this.basicHandler.pmdParseEnd(hasMoreData);
236         }
237
238         return;
239     }
240
241     /**
242      * PMDファイル本体のパースを開始する。
243      * パーサを拡張する場合はこのメソッドをオーバーライドする。
244      * @throws IOException IOエラー
245      * @throws MmdFormatException フォーマットエラー
246      */
247     protected void parseBody() throws IOException, MmdFormatException{
248         parsePmdHeader();
249
250         parseVertexList();
251         parseSurfaceList();
252         parseMaterialList();
253         parseBoneList();
254         parseIKList();
255         parseMorphList();
256         parseMorphOrderList();
257         parseBoneGroupList();
258         parseGroupedBoneList();
259
260         return;
261     }
262
263     /**
264      * PMDファイルヘッダ部のパースと通知。
265      * @throws IOException IOエラー
266      * @throws MmdFormatException フォーマットエラー
267      */
268     private void parsePmdHeader() throws IOException, MmdFormatException{
269         byte[] header = new byte[HEADER_LENGTH];
270         parseByteArray(header);
271
272         if( ! Arrays.equals(header, MAGIC_BYTES) ){
273             throw new MmdFormatException("unknown PMD-header type");
274         }
275
276         String modelName   =
277                 parsePmdText(PmdLimits.MAXBYTES_MODELNAME);
278         String description =
279                 parsePmdText(PmdLimits.MAXBYTES_MODELDESC);
280         description = description.replace(CRLF, LF);
281
282         if(this.basicHandler != null){
283             this.basicHandler.pmdHeaderInfo(header);
284             this.basicHandler.pmdModelInfo(modelName, description);
285         }
286
287         return;
288     }
289
290     /**
291      * 頂点情報のパースと通知。
292      * @throws IOException IOエラー
293      * @throws MmdFormatException フォーマットエラー
294      */
295     private void parseVertexList() throws IOException, MmdFormatException{
296         int vertexNum = parseLeInt();
297
298         if(this.shapeHandler == null){
299             skip(VERTEX_DATA_SZ * vertexNum);
300             return;
301         }
302
303         this.shapeHandler.loopStart(PmdShapeHandler.VERTEX_LIST, vertexNum);
304
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);
310
311             float xVec = parseLeFloat();
312             float yVec = parseLeFloat();
313             float zVec = parseLeFloat();
314             this.shapeHandler.pmdVertexNormal(xVec, yVec, zVec);
315
316             float uVal = parseLeFloat();
317             float vVal = parseLeFloat();
318             this.shapeHandler.pmdVertexUV(uVal, vVal);
319
320             int boneId1 = parseLeUShortAsInt();
321             int boneId2 = parseLeUShortAsInt();
322             int weightForB1 = parseUByteAsInt();
323             this.shapeHandler.pmdVertexWeight(boneId1, boneId2, weightForB1);
324
325             boolean hideEdge = parseBoolean();
326             this.shapeHandler.pmdVertexEdge(hideEdge);
327
328             this.shapeHandler.loopNext(PmdShapeHandler.VERTEX_LIST);
329         }
330
331         this.shapeHandler.loopEnd(PmdShapeHandler.VERTEX_LIST);
332
333         return;
334     }
335
336     /**
337      * 面情報のパースと通知。
338      * @throws IOException IOエラー
339      * @throws MmdFormatException フォーマットエラー
340      */
341     private void parseSurfaceList() throws IOException, MmdFormatException{
342         int vertexNum = parseLeInt();
343         if(vertexNum % 3 != 0) throw new MmdFormatException();
344         int surfaceNum = vertexNum / 3;
345
346         if(this.shapeHandler == null){
347             skip(SURFACE_DATA_SZ * surfaceNum);
348             return;
349         }
350
351         this.shapeHandler.loopStart(PmdShapeHandler.SURFACE_LIST, surfaceNum);
352
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,
358                                                  vertexId2,
359                                                  vertexId3 );
360             this.shapeHandler.loopNext(PmdShapeHandler.SURFACE_LIST);
361         }
362
363         this.shapeHandler.loopEnd(PmdShapeHandler.SURFACE_LIST);
364
365         return;
366     }
367
368     /**
369      * 材質情報のパースと通知。
370      * @throws IOException IOエラー
371      * @throws MmdFormatException フォーマットエラー
372      */
373     private void parseMaterialList() throws IOException, MmdFormatException{
374         int materialNum = parseLeInt();
375
376         if(this.materialHandler == null){
377             skip(MATERIAL_DATA_SZ * materialNum);
378             return;
379         }
380
381         this.materialHandler.loopStart(PmdMaterialHandler.MATERIAL_LIST,
382                                        materialNum );
383
384         for(int ct = 0; ct < materialNum; ct++){
385             float red;
386             float green;
387             float blue;
388
389             red   = parseLeFloat();
390             green = parseLeFloat();
391             blue  = parseLeFloat();
392             float alpha = parseLeFloat();
393             this.materialHandler.pmdMaterialDiffuse(red, green, blue, alpha);
394
395             float shininess = parseLeFloat();
396             red   = parseLeFloat();
397             green = parseLeFloat();
398             blue  = parseLeFloat();
399             this.materialHandler.pmdMaterialSpecular(red, green, blue,
400                                                      shininess);
401
402             red   = parseLeFloat();
403             green = parseLeFloat();
404             blue  = parseLeFloat();
405             this.materialHandler.pmdMaterialAmbient(red, green, blue);
406
407             int toonidx = parseUByteAsInt();
408             boolean hasEdge = parseBoolean();
409             int surfaceCount = parseLeInt();
410             String shadingFile =
411                     parsePmdText(PmdLimits.MAXBYTES_TEXTUREFILENAME);
412             String[] splitted = splitShadingFileInfo(shadingFile);
413             String textureFile = splitted[0];
414             String sphereFile = splitted[1];
415
416             this.materialHandler.pmdMaterialShading(toonidx,
417                                                     textureFile, sphereFile );
418             this.materialHandler.pmdMaterialInfo(hasEdge, surfaceCount);
419
420             this.materialHandler.loopNext(PmdMaterialHandler.MATERIAL_LIST);
421         }
422
423         this.materialHandler.loopEnd(PmdMaterialHandler.MATERIAL_LIST);
424
425         return;
426     }
427
428     /**
429      * ボーン情報のパースと通知。
430      * @throws IOException IOエラー
431      * @throws MmdFormatException フォーマットエラー
432      */
433     private void parseBoneList() throws IOException, MmdFormatException{
434         this.boneCount = parseLeUShortAsInt();
435
436         if(this.boneHandler == null){
437             skip(BONE_DATA_SZ * this.boneCount);
438             return;
439         }
440
441         this.boneHandler.loopStart(PmdBoneHandler.BONE_LIST, this.boneCount);
442
443         for(int ct = 0; ct < this.boneCount; ct++){
444             String boneName =
445                     parsePmdText(PmdLimits.MAXBYTES_BONENAME);
446             int parentId = parseLeUShortAsInt();
447             int tailId = parseLeUShortAsInt();
448             byte boneKind = parseByte();
449             int ikId = parseLeUShortAsInt();
450
451             this.boneHandler.pmdBoneInfo(boneName, boneKind);
452             this.boneHandler.pmdBoneLink(parentId, tailId, ikId);
453
454             float xPos = parseLeFloat();
455             float yPos = parseLeFloat();
456             float zPos = parseLeFloat();
457
458             this.boneHandler.pmdBonePosition(xPos, yPos, zPos);
459
460             this.boneHandler.loopNext(PmdBoneHandler.BONE_LIST);
461         }
462
463         this.boneHandler.loopEnd(PmdBoneHandler.BONE_LIST);
464
465         return;
466     }
467
468     /**
469      * IKリスト情報のパースと通知。
470      * @throws IOException IOエラー
471      * @throws MmdFormatException フォーマットエラー
472      */
473     private void parseIKList() throws IOException, MmdFormatException{
474         int ikCount = parseLeUShortAsInt();
475
476         if(this.boneHandler != null){
477             this.boneHandler.loopStart(PmdBoneHandler.IK_LIST, ikCount);
478         }
479
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();
486
487             parseIKChainList(chainLength);
488
489             if(this.boneHandler != null){
490                 this.boneHandler.pmdIKInfo(boneId, targetId, depth, weight);
491                 this.boneHandler.loopNext(PmdBoneHandler.IK_LIST);
492             }
493         }
494
495         if(this.boneHandler != null){
496             this.boneHandler.loopEnd(PmdBoneHandler.IK_LIST);
497         }
498
499         return;
500     }
501
502     /**
503      * IKチェーン情報のパースと通知。
504      * @param chainLength チェーン長
505      * @throws IOException IOエラー
506      * @throws MmdFormatException フォーマットエラー
507      */
508     private void parseIKChainList(int chainLength)
509             throws IOException, MmdFormatException{
510         if(this.boneHandler != null){
511             this.boneHandler.loopStart(PmdBoneHandler.IKCHAIN_LIST,
512                                        chainLength);
513         }
514
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);
520             }
521         }
522
523         if(this.boneHandler != null){
524             this.boneHandler.loopEnd(PmdBoneHandler.IKCHAIN_LIST);
525         }
526
527         return;
528     }
529
530     /**
531      * モーフ情報のパースと通知。
532      * @throws IOException IOエラー
533      * @throws MmdFormatException フォーマットエラー
534      */
535     private void parseMorphList() throws IOException, MmdFormatException{
536         this.morphCount = parseLeUShortAsInt();
537
538         if(this.morphHandler != null){
539             this.morphHandler.loopStart(PmdMorphHandler.MORPH_LIST,
540                                         this.morphCount );
541         }
542
543         for(int ct = 0; ct < this.morphCount; ct++){
544             String morphName =
545                     parsePmdText(PmdLimits.MAXBYTES_MORPHNAME);
546             int vertexCount = parseLeInt();
547             byte morphType = parseByte();
548
549             if(this.morphHandler != null){
550                 this.morphHandler.pmdMorphInfo(morphName, morphType);
551             }
552
553             parseMorphVertexList(vertexCount);
554
555             if(this.morphHandler != null){
556                 this.morphHandler.loopNext(PmdMorphHandler.MORPH_LIST);
557             }
558         }
559
560         if(this.morphHandler != null){
561             this.morphHandler.loopEnd(PmdMorphHandler.MORPH_LIST);
562         }
563
564         return;
565     }
566
567     /**
568      * モーフ形状のパースと通知。
569      * @param vertexCount 頂点数
570      * @throws IOException IOエラー
571      * @throws MmdFormatException フォーマットエラー
572      */
573     private void parseMorphVertexList(int vertexCount)
574             throws IOException, MmdFormatException{
575         if(this.morphHandler == null){
576             skip(MORPHVERTEX_DATA_SZ * vertexCount);
577             return;
578         }
579
580         this.morphHandler.loopStart(PmdMorphHandler.MORPHVERTEX_LIST,
581                                     vertexCount );
582
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);
590         }
591
592         this.morphHandler.loopEnd(PmdMorphHandler.MORPHVERTEX_LIST);
593
594         return;
595     }
596
597     /**
598      * モーフGUI表示順のパースと通知。
599      * @throws IOException IOエラー
600      * @throws MmdFormatException フォーマットエラー
601      */
602     private void parseMorphOrderList()
603             throws IOException, MmdFormatException{
604         int morphOrderCount = parseUByteAsInt();
605
606         if(this.morphHandler == null){
607             skip(MORPHORDER_DATA_SZ * morphOrderCount);
608             return;
609         }
610
611         this.morphHandler.loopStart(PmdMorphHandler.MORPHORDER_LIST,
612                                     morphOrderCount );
613
614         for(int ct = 0; ct < morphOrderCount; ct++){
615             int morphId = parseLeUShortAsInt();
616             this.morphHandler.pmdMorphOrderInfo(morphId);
617
618             this.morphHandler.loopNext(PmdMorphHandler.MORPHORDER_LIST);
619         }
620
621         this.morphHandler.loopEnd(PmdMorphHandler.MORPHORDER_LIST);
622
623         return;
624     }
625
626     /**
627      * ボーングループ名のパースと通知。
628      * @throws IOException IOエラー
629      * @throws MmdFormatException フォーマットエラー
630      */
631     private void parseBoneGroupList()
632             throws IOException, MmdFormatException{
633         this.boneGroupCount = parseUByteAsInt();
634
635         if(this.boneHandler == null){
636             skip(BONEGROUP_DATA_SZ * this.boneGroupCount);
637             return;
638         }
639
640         this.boneHandler.loopStart(PmdBoneHandler.BONEGROUP_LIST,
641                                    this.boneGroupCount);
642
643         for(int ct = 0; ct < this.boneGroupCount; ct++){
644             String groupName =
645                     parsePmdText(PmdLimits.MAXBYTES_BONEGROUPNAME);
646             groupName = chopLastLF(groupName);
647             this.boneHandler.pmdBoneGroupInfo(groupName);
648
649             this.boneHandler.loopNext(PmdBoneHandler.BONEGROUP_LIST);
650         }
651
652         this.boneHandler.loopEnd(PmdBoneHandler.BONEGROUP_LIST);
653
654         return;
655     }
656
657     /**
658      * ボーングループ内訳のパースと通知。
659      * @throws IOException IOエラー
660      * @throws MmdFormatException フォーマットエラー
661      */
662     private void parseGroupedBoneList()
663             throws IOException, MmdFormatException{
664         int groupedBoneCount = parseLeInt();
665
666         if(this.boneHandler == null){
667             skip(GROUPEDBONE_DATA_SZ * groupedBoneCount);
668             return;
669         }
670
671         this.boneHandler.loopStart(PmdBoneHandler.GROUPEDBONE_LIST,
672                                    groupedBoneCount);
673
674         for(int ct = 0; ct < groupedBoneCount; ct++){
675             int boneId = parseLeUShortAsInt();
676             int groupId = parseUByteAsInt();
677             this.boneHandler.pmdGroupedBoneInfo(boneId, groupId);
678
679             this.boneHandler.loopNext(PmdBoneHandler.GROUPEDBONE_LIST);
680         }
681
682         this.boneHandler.loopEnd(PmdBoneHandler.GROUPEDBONE_LIST);
683
684         return;
685     }
686
687 }