OSDN Git Service

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