4 * License : The MIT License
5 * Copyright(c) 2011 MikuToga Partners
8 package jp.sourceforge.mikutoga.vmd.parser;
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;
20 * VMDモーションファイルの基本部パーサ。
21 * <p>ボーンのモーション情報およびモーフモーション情報のパース処理を含む。
23 class VmdBasicParser extends CommonParser{
26 * VMDで用いられる文字エンコーディング(windows-31j)。
27 * ほぼShift_JISのスーパーセットと思ってよい。
28 * デコード結果はUCS-2集合に収まるはず。
30 public static final Charset CS_WIN31J = Charset.forName("windows-31j");
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;
37 private static final String ERRMSG_INVINTPLT =
38 "there is potential inconsistency in motion interpolation data. "
42 private final TextDecoder decoderWin31j = new TextDecoder(CS_WIN31J);
44 private final byte[] motionIntplt = new byte[BZTOTAL_SIZE];
46 private VmdBasicHandler handler = null;
48 private boolean hasStageActName = false;
49 private boolean strictMode = true;
56 VmdBasicParser(InputStream source){
58 this.decoderWin31j.setZeroChopMode(true);
64 * パースしたモデル名がカメラ及びライティング用モデル名だったか判定する。
65 * @return カメラ及びライティング用モデル名だったらtrue
67 boolean hasStageActName(){
68 return this.hasStageActName;
73 * @param basicHandler ハンドラ
75 void setBasicHandler(VmdBasicHandler basicHandler){
76 this.handler = basicHandler;
81 * 厳密なパース(Strict-mode)を行うか否か設定する。
82 * デフォルトではStrict-modeはオン。
84 * ボーンモーションの冗長な補間情報の一貫性チェックが行わる。
85 * @param mode Strict-modeに設定したければtrue
87 void setStrictMode(boolean mode){
88 this.strictMode = mode;
93 * 指定された最大バイト長に収まるゼロ終端(0x00)文字列を読み込む。
94 * 入力バイト列はwindows-31jエンコーディングとして解釈される。
96 * IO入力は指定バイト数だけ読み進められる。
97 * ゼロ終端が見つからないまま指定バイト数が読み込み終わった場合、
99 * @param byteLen 読み込みバイト数
101 * @throws IOException IOエラー
102 * @throws MmdEofException 読み込む途中でストリーム終端に達した。
103 * @throws MmdFormatException 不正な文字エンコーディングが検出された。
105 protected String parseVmdText(int byteLen)
109 String result = parseString(this.decoderWin31j, byteLen);
114 * VMDファイル基本部のパースを開始する。
115 * @throws IOException IOエラー
116 * @throws MmdFormatException フォーマットエラー
118 void parse() throws IOException, MmdFormatException {
119 this.hasStageActName = false;
123 parseVmdBoneMotion();
130 * VMDファイルヘッダ部のパースと通知。
131 * @throws IOException IOエラー
132 * @throws MmdFormatException フォーマットエラー
134 private void parseVmdHeader() throws IOException, MmdFormatException{
135 byte[] header = new byte[VmdConst.HEADER_LENGTH];
136 parseByteArray(header);
138 if( ! VmdConst.startsWithMagic(header) ){
139 throw new MmdFormatException("unknown VMD-header type");
142 if(this.handler != null){
143 this.handler.vmdHeaderInfo(header);
151 * @throws IOException IOエラー
152 * @throws MmdFormatException フォーマットエラー
154 private void parseVmdModelName() throws IOException, MmdFormatException{
155 String modelName = parseVmdText(VmdConst.MODELNAME_MAX);
157 if(VmdConst.isStageActName(modelName)){
158 this.hasStageActName = true;
161 if(this.handler != null){
162 this.handler.vmdModelName(modelName);
169 * ボーンモーションデータのパースと通知。
170 * @throws IOException IOエラー
171 * @throws MmdFormatException フォーマットエラー
173 private void parseVmdBoneMotion()
174 throws IOException, MmdFormatException{
175 int boneMotionNo = parseLeInt();
177 if(this.handler == null){
178 skip(VmdConst.BONEMOTION_DATA_SZ * boneMotionNo);
182 this.handler.loopStart(
183 VmdBasicHandler.BONEMOTION_LIST, boneMotionNo);
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);
190 float xPos = parseLeFloat();
191 float yPos = parseLeFloat();
192 float zPos = parseLeFloat();
193 this.handler.vmdBonePosition(xPos, yPos, zPos);
195 float qx = parseLeFloat();
196 float qy = parseLeFloat();
197 float qz = parseLeFloat();
198 float qw = parseLeFloat();
199 this.handler.vmdBoneRotationQt(qx, qy, qz, qw);
201 parseVmdMotionInterpolation();
203 this.handler.loopNext(VmdBasicHandler.BONEMOTION_LIST);
206 this.handler.loopEnd(VmdBasicHandler.BONEMOTION_LIST);
212 * ボーンモーション補間データのパースと通知。
213 * @throws IOException IOエラー
214 * @throws MmdFormatException フォーマットエラー
216 private void parseVmdMotionInterpolation()
217 throws IOException, MmdFormatException{
218 if(this.handler == null){
219 skip(this.motionIntplt.length);
223 parseByteArray(this.motionIntplt);
231 byte xP1x = this.motionIntplt[idx++];
232 byte yP1x = this.motionIntplt[idx++];
233 byte zP1x = this.motionIntplt[idx++];
234 byte rP1x = this.motionIntplt[idx++];
236 byte xP1y = this.motionIntplt[idx++];
237 byte yP1y = this.motionIntplt[idx++];
238 byte zP1y = this.motionIntplt[idx++];
239 byte rP1y = this.motionIntplt[idx++];
241 byte xP2x = this.motionIntplt[idx++];
242 byte yP2x = this.motionIntplt[idx++];
243 byte zP2x = this.motionIntplt[idx++];
244 byte rP2x = this.motionIntplt[idx++];
246 byte xP2y = this.motionIntplt[idx++];
247 byte yP2y = this.motionIntplt[idx++];
248 byte zP2y = this.motionIntplt[idx++];
249 byte rP2y = this.motionIntplt[idx++];
251 assert idx == BZXYZR_SIZE;
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);
262 * 補間情報の冗長箇所の整合性チェックを行う。
263 * @throws MmdFormatException 冗長箇所の不整合を検出した。
265 private void checkIntpltStrict() throws MmdFormatException{
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;
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,
281 int onePos = targetIdx + span;
282 if(this.motionIntplt[onePos] != (byte) 0x01){
283 throw new MmdFormatException(ERRMSG_INVINTPLT, getPosition());
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,
302 * モーフモーションデータのパースと通知。
303 * @throws IOException IOエラー
304 * @throws MmdFormatException フォーマットエラー
306 private void parseVmdMorph() throws IOException, MmdFormatException{
307 int morphMotionNo = parseLeInt();
309 if(this.handler == null){
310 skip(VmdConst.MORPH_DATA_SZ * morphMotionNo);
314 this.handler.loopStart(
315 VmdBasicHandler.MORPH_LIST, morphMotionNo);
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);
323 this.handler.loopNext(VmdBasicHandler.MORPH_LIST);
326 this.handler.loopEnd(VmdBasicHandler.MORPH_LIST);