OSDN Git Service

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