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.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;
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 = VmdUnifiedHandler.EMPTY;
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 if(basicHandler == null){
77 this.handler = VmdUnifiedHandler.EMPTY;
79 this.handler = basicHandler;
86 * 厳密なパース(Strict-mode)を行うか否か設定する。
87 * デフォルトではStrict-modeはオン。
89 * ボーンモーションの冗長な補間情報の一貫性チェックが行わる。
90 * @param mode Strict-modeに設定したければtrue
92 void setStrictMode(boolean mode){
93 this.strictMode = mode;
98 * 指定された最大バイト長に収まるゼロ終端(0x00)文字列を読み込む。
99 * 入力バイト列はwindows-31jエンコーディングとして解釈される。
101 * IO入力は指定バイト数だけ読み進められる。
102 * ゼロ終端が見つからないまま指定バイト数が読み込み終わった場合、
103 * そこまでのデータから文字列を構成する。
104 * @param byteLen 読み込みバイト数
106 * @throws IOException IOエラー
107 * @throws MmdEofException 読み込む途中でストリーム終端に達した。
108 * @throws MmdFormatException 不正な文字エンコーディングが検出された。
110 protected String parseVmdText(int byteLen)
114 String result = parseString(this.decoderWin31j, byteLen);
119 * VMDファイル基本部のパースを開始する。
120 * @throws IOException IOエラー
121 * @throws MmdFormatException フォーマットエラー
123 void parse() throws IOException, MmdFormatException {
124 this.hasStageActName = false;
128 parseVmdBoneMotion();
135 * VMDファイルヘッダ部のパースと通知。
136 * @throws IOException IOエラー
137 * @throws MmdFormatException フォーマットエラー
139 private void parseVmdHeader() throws IOException, MmdFormatException{
140 byte[] header = new byte[VmdConst.HEADER_LENGTH];
141 parseByteArray(header);
143 if( ! VmdConst.startsWithMagic(header) ){
144 throw new MmdFormatException("unknown VMD-header type");
147 this.handler.vmdHeaderInfo(header);
154 * @throws IOException IOエラー
155 * @throws MmdFormatException フォーマットエラー
157 private void parseVmdModelName() throws IOException, MmdFormatException{
158 String modelName = parseVmdText(VmdConst.MODELNAME_MAX);
160 if(VmdConst.isStageActName(modelName)){
161 this.hasStageActName = true;
164 this.handler.vmdModelName(modelName);
170 * ボーンモーションデータのパースと通知。
171 * @throws IOException IOエラー
172 * @throws MmdFormatException フォーマットエラー
174 private void parseVmdBoneMotion()
175 throws IOException, MmdFormatException{
176 int boneMotionNo = parseLeInt();
178 this.handler.loopStart(
179 VmdBasicHandler.BONEMOTION_LIST, boneMotionNo);
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);
186 float xPos = parseLeFloat();
187 float yPos = parseLeFloat();
188 float zPos = parseLeFloat();
189 this.handler.vmdBonePosition(xPos, yPos, zPos);
191 float qx = parseLeFloat();
192 float qy = parseLeFloat();
193 float qz = parseLeFloat();
194 float qw = parseLeFloat();
195 this.handler.vmdBoneRotationQt(qx, qy, qz, qw);
197 parseVmdMotionInterpolation();
199 this.handler.loopNext(VmdBasicHandler.BONEMOTION_LIST);
202 this.handler.loopEnd(VmdBasicHandler.BONEMOTION_LIST);
208 * ボーンモーション補間データのパースと通知。
209 * @throws IOException IOエラー
210 * @throws MmdFormatException フォーマットエラー
212 private void parseVmdMotionInterpolation()
213 throws IOException, MmdFormatException{
214 parseByteArray(this.motionIntplt);
222 byte xP1x = this.motionIntplt[idx++];
223 byte yP1x = this.motionIntplt[idx++];
224 byte zP1x = this.motionIntplt[idx++];
225 byte rP1x = this.motionIntplt[idx++];
227 byte xP1y = this.motionIntplt[idx++];
228 byte yP1y = this.motionIntplt[idx++];
229 byte zP1y = this.motionIntplt[idx++];
230 byte rP1y = this.motionIntplt[idx++];
232 byte xP2x = this.motionIntplt[idx++];
233 byte yP2x = this.motionIntplt[idx++];
234 byte zP2x = this.motionIntplt[idx++];
235 byte rP2x = this.motionIntplt[idx++];
237 byte xP2y = this.motionIntplt[idx++];
238 byte yP2y = this.motionIntplt[idx++];
239 byte zP2y = this.motionIntplt[idx++];
240 byte rP2y = this.motionIntplt[idx++];
242 assert idx == BZXYZR_SIZE;
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);
253 * 補間情報の冗長箇所の整合性チェックを行う。
254 * @throws MmdFormatException 冗長箇所の不整合を検出した。
256 private void checkIntpltStrict() throws MmdFormatException{
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;
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,
272 int onePos = targetIdx + span;
273 if(this.motionIntplt[onePos] != (byte) 0x01){
274 throw new MmdFormatException(ERRMSG_INVINTPLT, getPosition());
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,
293 * モーフモーションデータのパースと通知。
294 * @throws IOException IOエラー
295 * @throws MmdFormatException フォーマットエラー
297 private void parseVmdMorph() throws IOException, MmdFormatException{
298 int morphMotionNo = parseLeInt();
300 this.handler.loopStart(
301 VmdBasicHandler.MORPH_LIST, morphMotionNo);
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);
309 this.handler.loopNext(VmdBasicHandler.MORPH_LIST);
312 this.handler.loopEnd(VmdBasicHandler.MORPH_LIST);