OSDN Git Service

XML出力改善
[mikutoga/TogaGem.git] / src / main / java / jp / sourceforge / mikutoga / vmd / parser / VmdBasicParser.java
1 /*
2  * VMD basic parser
3  *
4  * License : The MIT License
5  * Copyright(c) 2011 MikuToga Partners
6  */
7
8 package jp.sourceforge.mikutoga.vmd.parser;
9
10 import java.io.IOException;
11 import java.io.InputStream;
12 import java.nio.charset.Charset;
13 import jp.sourceforge.mikutoga.parser.CommonParser;
14 import jp.sourceforge.mikutoga.parser.MmdEofException;
15 import jp.sourceforge.mikutoga.parser.MmdFormatException;
16 import jp.sourceforge.mikutoga.parser.TextDecoder;
17 import jp.sourceforge.mikutoga.vmd.VmdConst;
18
19 /**
20  * VMDモーションファイルの基本部パーサ。
21  * <p>ボーンのモーション情報およびモーフモーション情報のパース処理を含む。
22  */
23 class VmdBasicParser extends CommonParser{
24
25     /**
26      * VMDで用いられる文字エンコーディング(windows-31j)。
27      * ほぼShift_JISのスーパーセットと思ってよい。
28      * デコード結果はUCS-2集合に収まるはず。
29      */
30     public static final Charset CS_WIN31J = Charset.forName("windows-31j");
31
32     private static final int BZ_SIZE = 4;           // 4byte Bezier parameter
33     private static final int BZXYZR_SIZE = BZ_SIZE * 4; // XYZR Bezier
34     private static final int BZ_REDUNDANT = 4;          // redundant spare
35     private static final int BZTOTAL_SIZE = BZXYZR_SIZE * BZ_REDUNDANT;
36
37     private static final String ERRMSG_INVINTPLT =
38             "there is potential inconsistency in motion interpolation data. "
39             +"(Strict-mode)";
40
41
42     private final TextDecoder decoderWin31j  = new TextDecoder(CS_WIN31J);
43
44     private final byte[] motionIntplt = new byte[BZTOTAL_SIZE];
45
46     private VmdBasicHandler handler = null;
47
48     private boolean hasStageActName = false;
49     private boolean strictMode = true;
50
51
52     /**
53      * コンストラクタ。
54      * @param source 入力ソース
55      */
56     VmdBasicParser(InputStream source){
57         super(source);
58         this.decoderWin31j.setZeroChopMode(true);
59         return;
60     }
61
62
63     /**
64      * パースしたモデル名がカメラ及びライティング用モデル名だったか判定する。
65      * @return カメラ及びライティング用モデル名だったらtrue
66      */
67     boolean hasStageActName(){
68         return this.hasStageActName;
69     }
70
71     /**
72      * 基本情報通知用ハンドラを登録する。
73      * @param basicHandler ハンドラ
74      */
75     void setBasicHandler(VmdBasicHandler basicHandler){
76         this.handler = basicHandler;
77         return;
78     }
79
80     /**
81      * 厳密なパース(Strict-mode)を行うか否か設定する。
82      * デフォルトではStrict-modeはオン。
83      * <p>Strict-mode下では、
84      * ボーンモーションの冗長な補間情報の一貫性チェックが行わる。
85      * @param mode Strict-modeに設定したければtrue
86      */
87     void setStrictMode(boolean mode){
88         this.strictMode = mode;
89         return;
90     }
91
92     /**
93      * 指定された最大バイト長に収まるゼロ終端(0x00)文字列を読み込む。
94      * 入力バイト列はwindows-31jエンコーディングとして解釈される。
95      * ゼロ終端以降のデータは無視されるが、
96      * IO入力は指定バイト数だけ読み進められる。
97      * ゼロ終端が見つからないまま指定バイト数が読み込み終わった場合、
98      * そこまでのデータから文字列を構成する。
99      * @param byteLen 読み込みバイト数
100      * @return デコードされた文字列
101      * @throws IOException IOエラー
102      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
103      * @throws MmdFormatException 不正な文字エンコーディングが検出された。
104      */
105     protected String parseVmdText(int byteLen)
106             throws IOException,
107                    MmdEofException,
108                    MmdFormatException {
109         String result = parseString(this.decoderWin31j, byteLen);
110         return result;
111     }
112
113     /**
114      * VMDファイル基本部のパースを開始する。
115      * @throws IOException IOエラー
116      * @throws MmdFormatException フォーマットエラー
117      */
118     void parse() throws IOException, MmdFormatException {
119         this.hasStageActName = false;
120
121         parseVmdHeader();
122         parseVmdModelName();
123         parseVmdBoneMotion();
124         parseVmdMorph();
125
126         return;
127     }
128
129     /**
130      * VMDファイルヘッダ部のパースと通知。
131      * @throws IOException IOエラー
132      * @throws MmdFormatException フォーマットエラー
133      */
134     private void parseVmdHeader() throws IOException, MmdFormatException{
135         byte[] header = new byte[VmdConst.HEADER_LENGTH];
136         parseByteArray(header);
137
138         if( ! VmdConst.startsWithMagic(header) ){
139             throw new MmdFormatException("unknown VMD-header type");
140         }
141
142         if(this.handler != null){
143             this.handler.vmdHeaderInfo(header);
144         }
145
146         return;
147     }
148
149     /**
150      * モデル名のパースと通知。
151      * @throws IOException IOエラー
152      * @throws MmdFormatException フォーマットエラー
153      */
154     private void parseVmdModelName() throws IOException, MmdFormatException{
155         String modelName = parseVmdText(VmdConst.MODELNAME_MAX);
156
157         if(VmdConst.isStageActName(modelName)){
158             this.hasStageActName = true;
159         }
160
161         if(this.handler != null){
162             this.handler.vmdModelName(modelName);
163         }
164
165         return;
166     }
167
168     /**
169      * ボーンモーションデータのパースと通知。
170      * @throws IOException IOエラー
171      * @throws MmdFormatException フォーマットエラー
172      */
173     private void parseVmdBoneMotion()
174             throws IOException, MmdFormatException{
175         int boneMotionNo = parseLeInt();
176
177         if(this.handler == null){
178             skip(VmdConst.BONEMOTION_DATA_SZ * boneMotionNo);
179             return;
180         }
181
182         this.handler.loopStart(
183                 VmdBasicHandler.BONEMOTION_LIST, boneMotionNo);
184
185         for(int ct = 0; ct < boneMotionNo; ct++){
186             String boneName = parseVmdText(VmdConst.BONENAME_MAX);
187             int keyFrameNo = parseLeInt();
188             this.handler.vmdBoneMotion(boneName, keyFrameNo);
189
190             float xPos = parseLeFloat();
191             float yPos = parseLeFloat();
192             float zPos = parseLeFloat();
193             this.handler.vmdBonePosition(xPos, yPos, zPos);
194
195             float qx = parseLeFloat();
196             float qy = parseLeFloat();
197             float qz = parseLeFloat();
198             float qw = parseLeFloat();
199             this.handler.vmdBoneRotationQt(qx, qy, qz, qw);
200
201             parseVmdMotionInterpolation();
202
203             this.handler.loopNext(VmdBasicHandler.BONEMOTION_LIST);
204         }
205
206         this.handler.loopEnd(VmdBasicHandler.BONEMOTION_LIST);
207
208         return;
209     }
210
211     /**
212      * ボーンモーション補間データのパースと通知。
213      * @throws IOException IOエラー
214      * @throws MmdFormatException フォーマットエラー
215      */
216     private void parseVmdMotionInterpolation()
217             throws IOException, MmdFormatException{
218         if(this.handler == null){
219             skip(this.motionIntplt.length);
220             return;
221         }
222
223         parseByteArray(this.motionIntplt);
224
225         if(this.strictMode){
226             checkIntpltStrict();
227         }
228
229         int idx = 0;
230
231         byte xP1x = this.motionIntplt[idx++];
232         byte yP1x = this.motionIntplt[idx++];
233         byte zP1x = this.motionIntplt[idx++];
234         byte rP1x = this.motionIntplt[idx++];
235
236         byte xP1y = this.motionIntplt[idx++];
237         byte yP1y = this.motionIntplt[idx++];
238         byte zP1y = this.motionIntplt[idx++];
239         byte rP1y = this.motionIntplt[idx++];
240
241         byte xP2x = this.motionIntplt[idx++];
242         byte yP2x = this.motionIntplt[idx++];
243         byte zP2x = this.motionIntplt[idx++];
244         byte rP2x = this.motionIntplt[idx++];
245
246         byte xP2y = this.motionIntplt[idx++];
247         byte yP2y = this.motionIntplt[idx++];
248         byte zP2y = this.motionIntplt[idx++];
249         byte rP2y = this.motionIntplt[idx++];
250
251         assert idx == BZXYZR_SIZE;
252
253         this.handler.vmdBoneIntpltXpos(xP1x, xP1y, xP2x, xP2y);
254         this.handler.vmdBoneIntpltYpos(yP1x, yP1y, yP2x, yP2y);
255         this.handler.vmdBoneIntpltZpos(zP1x, zP1y, zP2x, zP2y);
256         this.handler.vmdBoneIntpltRot (rP1x, rP1y, rP2x, rP2y);
257
258         return;
259     }
260
261     /**
262      * 補間情報の冗長箇所の整合性チェックを行う。
263      * @throws MmdFormatException 冗長箇所の不整合を検出した。
264      */
265     private void checkIntpltStrict() throws MmdFormatException{
266         int lack = 1;
267         for(int ct = 1; ct < BZ_REDUNDANT; ct++){
268             int sourceIdx = 0 + lack;
269             int targetIdx = BZXYZR_SIZE * ct;
270             int span = BZXYZR_SIZE - lack;
271
272             for(int idx = 0; idx < span; idx++){
273                 byte sourceVal = this.motionIntplt[sourceIdx + idx];
274                 byte targetVal = this.motionIntplt[targetIdx + idx];
275                 if(sourceVal != targetVal){
276                     throw new MmdFormatException(ERRMSG_INVINTPLT,
277                                                  getPosition());
278                 }
279             }
280
281             int onePos = targetIdx + span;
282             if(this.motionIntplt[onePos] != (byte) 0x01){
283                 throw new MmdFormatException(ERRMSG_INVINTPLT, getPosition());
284             }
285
286             int zeroPosStart = onePos + 1;
287             int zeroPosEnd = targetIdx + BZXYZR_SIZE;
288             for(int idx = zeroPosStart; idx < zeroPosEnd; idx++){
289                 if(this.motionIntplt[idx] != (byte) 0x00){
290                     throw new MmdFormatException(ERRMSG_INVINTPLT,
291                                                  getPosition());
292                 }
293             }
294
295             lack++;
296         }
297
298         return;
299     }
300
301     /**
302      * モーフモーションデータのパースと通知。
303      * @throws IOException IOエラー
304      * @throws MmdFormatException フォーマットエラー
305      */
306     private void parseVmdMorph() throws IOException, MmdFormatException{
307         int morphMotionNo = parseLeInt();
308
309         if(this.handler == null){
310             skip(VmdConst.MORPH_DATA_SZ * morphMotionNo);
311             return;
312         }
313
314         this.handler.loopStart(
315                 VmdBasicHandler.MORPH_LIST, morphMotionNo);
316
317         for(int ct = 0; ct < morphMotionNo; ct++){
318             String morphName = parseVmdText(VmdConst.MORPHNAME_MAX);
319             int keyFrameNo = parseLeInt();
320             float flex = parseLeFloat();
321             this.handler.vmdMorphMotion(morphName, keyFrameNo, flex);
322
323             this.handler.loopNext(VmdBasicHandler.MORPH_LIST);
324         }
325
326         this.handler.loopEnd(VmdBasicHandler.MORPH_LIST);
327
328         return;
329     }
330
331 }