OSDN Git Service

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