OSDN Git Service

ローカルスキーマ参照処理の共通化
[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.sfjp.mikutoga.bin.parser.CommonParser;
14 import jp.sfjp.mikutoga.bin.parser.MmdEofException;
15 import jp.sfjp.mikutoga.bin.parser.MmdFormatException;
16 import jp.sfjp.mikutoga.bin.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 = VmdUnifiedHandler.EMPTY;
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         if(basicHandler == null){
77             this.handler = VmdUnifiedHandler.EMPTY;
78         }else{
79             this.handler = basicHandler;
80         }
81
82         return;
83     }
84
85     /**
86      * 厳密なパース(Strict-mode)を行うか否か設定する。
87      * デフォルトではStrict-modeはオン。
88      * <p>Strict-mode下では、
89      * ボーンモーションの冗長な補間情報の一貫性チェックが行わる。
90      * @param mode Strict-modeに設定したければtrue
91      */
92     void setStrictMode(boolean mode){
93         this.strictMode = mode;
94         return;
95     }
96
97     /**
98      * 指定された最大バイト長に収まるゼロ終端(0x00)文字列を読み込む。
99      * 入力バイト列はwindows-31jエンコーディングとして解釈される。
100      * ゼロ終端以降のデータは無視されるが、
101      * IO入力は指定バイト数だけ読み進められる。
102      * ゼロ終端が見つからないまま指定バイト数が読み込み終わった場合、
103      * そこまでのデータから文字列を構成する。
104      * @param byteLen 読み込みバイト数
105      * @return デコードされた文字列
106      * @throws IOException IOエラー
107      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
108      * @throws MmdFormatException 不正な文字エンコーディングが検出された。
109      */
110     protected String parseVmdText(int byteLen)
111             throws IOException,
112                    MmdEofException,
113                    MmdFormatException {
114         String result = parseString(this.decoderWin31j, byteLen);
115         return result;
116     }
117
118     /**
119      * VMDファイル基本部のパースを開始する。
120      * @throws IOException IOエラー
121      * @throws MmdFormatException フォーマットエラー
122      */
123     void parse() throws IOException, MmdFormatException {
124         this.hasStageActName = false;
125
126         parseVmdHeader();
127         parseVmdModelName();
128         parseVmdBoneMotion();
129         parseVmdMorph();
130
131         return;
132     }
133
134     /**
135      * VMDファイルヘッダ部のパースと通知。
136      * @throws IOException IOエラー
137      * @throws MmdFormatException フォーマットエラー
138      */
139     private void parseVmdHeader() throws IOException, MmdFormatException{
140         byte[] header = new byte[VmdConst.HEADER_LENGTH];
141         parseByteArray(header);
142
143         if( ! VmdConst.startsWithMagic(header) ){
144             throw new MmdFormatException("unknown VMD-header type");
145         }
146
147         this.handler.vmdHeaderInfo(header);
148
149         return;
150     }
151
152     /**
153      * モデル名のパースと通知。
154      * @throws IOException IOエラー
155      * @throws MmdFormatException フォーマットエラー
156      */
157     private void parseVmdModelName() throws IOException, MmdFormatException{
158         String modelName = parseVmdText(VmdConst.MODELNAME_MAX);
159
160         if(VmdConst.isStageActName(modelName)){
161             this.hasStageActName = true;
162         }
163
164         this.handler.vmdModelName(modelName);
165
166         return;
167     }
168
169     /**
170      * ボーンモーションデータのパースと通知。
171      * @throws IOException IOエラー
172      * @throws MmdFormatException フォーマットエラー
173      */
174     private void parseVmdBoneMotion()
175             throws IOException, MmdFormatException{
176         int boneMotionNo = parseLeInt();
177
178         this.handler.loopStart(
179                 VmdBasicHandler.BONEMOTION_LIST, boneMotionNo);
180
181         for(int ct = 0; ct < boneMotionNo; ct++){
182             String boneName = parseVmdText(VmdConst.BONENAME_MAX);
183             int keyFrameNo = parseLeInt();
184             this.handler.vmdBoneMotion(boneName, keyFrameNo);
185
186             float xPos = parseLeFloat();
187             float yPos = parseLeFloat();
188             float zPos = parseLeFloat();
189             this.handler.vmdBonePosition(xPos, yPos, zPos);
190
191             float qx = parseLeFloat();
192             float qy = parseLeFloat();
193             float qz = parseLeFloat();
194             float qw = parseLeFloat();
195             this.handler.vmdBoneRotationQt(qx, qy, qz, qw);
196
197             parseVmdMotionInterpolation();
198
199             this.handler.loopNext(VmdBasicHandler.BONEMOTION_LIST);
200         }
201
202         this.handler.loopEnd(VmdBasicHandler.BONEMOTION_LIST);
203
204         return;
205     }
206
207     /**
208      * ボーンモーション補間データのパースと通知。
209      * @throws IOException IOエラー
210      * @throws MmdFormatException フォーマットエラー
211      */
212     private void parseVmdMotionInterpolation()
213             throws IOException, MmdFormatException{
214         parseByteArray(this.motionIntplt);
215
216         if(this.strictMode){
217             checkIntpltStrict();
218         }
219
220         int idx = 0;
221
222         byte xP1x = this.motionIntplt[idx++];
223         byte yP1x = this.motionIntplt[idx++];
224         byte zP1x = this.motionIntplt[idx++];
225         byte rP1x = this.motionIntplt[idx++];
226
227         byte xP1y = this.motionIntplt[idx++];
228         byte yP1y = this.motionIntplt[idx++];
229         byte zP1y = this.motionIntplt[idx++];
230         byte rP1y = this.motionIntplt[idx++];
231
232         byte xP2x = this.motionIntplt[idx++];
233         byte yP2x = this.motionIntplt[idx++];
234         byte zP2x = this.motionIntplt[idx++];
235         byte rP2x = this.motionIntplt[idx++];
236
237         byte xP2y = this.motionIntplt[idx++];
238         byte yP2y = this.motionIntplt[idx++];
239         byte zP2y = this.motionIntplt[idx++];
240         byte rP2y = this.motionIntplt[idx++];
241
242         assert idx == BZXYZR_SIZE;
243
244         this.handler.vmdBoneIntpltXpos(xP1x, xP1y, xP2x, xP2y);
245         this.handler.vmdBoneIntpltYpos(yP1x, yP1y, yP2x, yP2y);
246         this.handler.vmdBoneIntpltZpos(zP1x, zP1y, zP2x, zP2y);
247         this.handler.vmdBoneIntpltRot (rP1x, rP1y, rP2x, rP2y);
248
249         return;
250     }
251
252     /**
253      * 補間情報の冗長箇所の整合性チェックを行う。
254      * @throws MmdFormatException 冗長箇所の不整合を検出した。
255      */
256     private void checkIntpltStrict() throws MmdFormatException{
257         int lack = 1;
258         for(int ct = 1; ct < BZ_REDUNDANT; ct++){
259             int sourceIdx = 0 + lack;
260             int targetIdx = BZXYZR_SIZE * ct;
261             int span = BZXYZR_SIZE - lack;
262
263             for(int idx = 0; idx < span; idx++){
264                 byte sourceVal = this.motionIntplt[sourceIdx + idx];
265                 byte targetVal = this.motionIntplt[targetIdx + idx];
266                 if(sourceVal != targetVal){
267                     throw new MmdFormatException(ERRMSG_INVINTPLT,
268                                                  getPosition());
269                 }
270             }
271
272             int onePos = targetIdx + span;
273             if(this.motionIntplt[onePos] != (byte) 0x01){
274                 throw new MmdFormatException(ERRMSG_INVINTPLT, getPosition());
275             }
276
277             int zeroPosStart = onePos + 1;
278             int zeroPosEnd = targetIdx + BZXYZR_SIZE;
279             for(int idx = zeroPosStart; idx < zeroPosEnd; idx++){
280                 if(this.motionIntplt[idx] != (byte) 0x00){
281                     throw new MmdFormatException(ERRMSG_INVINTPLT,
282                                                  getPosition());
283                 }
284             }
285
286             lack++;
287         }
288
289         return;
290     }
291
292     /**
293      * モーフモーションデータのパースと通知。
294      * @throws IOException IOエラー
295      * @throws MmdFormatException フォーマットエラー
296      */
297     private void parseVmdMorph() throws IOException, MmdFormatException{
298         int morphMotionNo = parseLeInt();
299
300         this.handler.loopStart(
301                 VmdBasicHandler.MORPH_LIST, morphMotionNo);
302
303         for(int ct = 0; ct < morphMotionNo; ct++){
304             String morphName = parseVmdText(VmdConst.MORPHNAME_MAX);
305             int keyFrameNo = parseLeInt();
306             float flex = parseLeFloat();
307             this.handler.vmdMorphMotion(morphName, keyFrameNo, flex);
308
309             this.handler.loopNext(VmdBasicHandler.MORPH_LIST);
310         }
311
312         this.handler.loopEnd(VmdBasicHandler.MORPH_LIST);
313
314         return;
315     }
316
317 }