4 * License : The MIT License
5 * Copyright(c) 2011 MikuToga Partners
8 package jp.sfjp.mikutoga.vmd.parser;
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;
21 * VMDモーションファイルの基本部パーサ。
23 * <p>ボーンのモーション情報およびモーフモーション情報のパース処理を含む。
25 class VmdBasicParser extends ProxyParser{
28 * VMDで用いられる文字エンコーディング(windows-31j)。
29 * ほぼShift_JISのスーパーセットと思ってよい。
30 * デコード結果はUCS-2集合に収まるはず。
32 public static final Charset CS_WIN31J = Charset.forName("windows-31j");
33 private static final Charset CS_ASCII = Charset.forName("US-ASCII");
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;
40 private static final String ERRMSG_INVINTPLT =
41 "there is potential inconsistency in motion interpolation data. "
43 private static final String ERRMSG_UK_HEADER =
44 "unknown VMD-header type";
47 private final TextDecoder decoderWin31j = new TextDecoder(CS_WIN31J);
49 private final byte[] motionIntplt = new byte[BZTOTAL_SIZE];
51 private VmdBasicHandler handler = VmdUnifiedHandler.EMPTY;
53 private boolean hasStageActName = false;
54 private boolean redundantCheck = false;
59 * @param parser 委譲先パーサ
61 VmdBasicParser(BinParser parser){
63 this.decoderWin31j.setZeroChopMode(true);
69 * パースしたモデル名がカメラ及びライティング用モデル名だったか判定する。
70 * @return カメラ及びライティング用モデル名だったらtrue
72 boolean hasStageActName(){
73 return this.hasStageActName;
78 * @param basicHandler ハンドラ
80 void setBasicHandler(VmdBasicHandler basicHandler){
81 if(basicHandler == null){
82 this.handler = VmdUnifiedHandler.EMPTY;
84 this.handler = basicHandler;
91 * ボーンモーション補間情報冗長部のチェックを行うか否か設定する。
93 * @param mode チェックさせたければtrue
95 void setRedundantCheck(boolean mode){
96 this.redundantCheck = mode;
101 * 指定された最大バイト長に収まるゼロ終端(0x00)文字列を読み込む。
102 * 入力バイト列はwindows-31jエンコーディングとして解釈される。
104 * IO入力は指定バイト数だけ読み進められる。
105 * ゼロ終端が見つからないまま指定バイト数が読み込み終わった場合、
106 * そこまでのデータから文字列を構成する。
107 * @param byteLen 読み込みバイト数
109 * @throws IOException IOエラー
110 * @throws MmdEofException 読み込む途中でストリーム終端に達した。
111 * @throws MmdFormatException 不正な文字エンコーディングが検出された。
113 private String parseVmdText(int byteLen)
117 String result = parseString(this.decoderWin31j, byteLen);
122 * VMDファイル基本部のパースを開始する。
123 * @throws IOException IOエラー
124 * @throws MmdFormatException フォーマットエラー
126 void parse() throws IOException, MmdFormatException {
127 this.hasStageActName = false;
131 parseVmdBoneMotion();
138 * VMDファイルヘッダ部のパースと通知。
139 * @throws IOException IOエラー
140 * @throws MmdFormatException フォーマットエラー
142 private void parseVmdHeader() throws IOException, MmdFormatException{
143 byte[] header = new byte[VmdConst.HEADER_LENGTH];
144 parseByteArray(header);
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);
153 this.handler.vmdHeaderInfo(header);
160 * @throws IOException IOエラー
161 * @throws MmdFormatException フォーマットエラー
163 private void parseVmdModelName() throws IOException, MmdFormatException{
164 String modelName = parseVmdText(VmdConst.MODELNAME_MAX);
166 if(VmdUniq.isStageActName(modelName)){
167 this.hasStageActName = true;
170 this.handler.vmdModelName(modelName);
176 * ボーンモーションデータのパースと通知。
177 * @throws IOException IOエラー
178 * @throws MmdFormatException フォーマットエラー
180 private void parseVmdBoneMotion()
181 throws IOException, MmdFormatException{
182 int boneMotionNo = parseLeInt();
184 this.handler.loopStart(
185 VmdBasicHandler.BONEMOTION_LIST, boneMotionNo);
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);
192 float xPos = parseLeFloat();
193 float yPos = parseLeFloat();
194 float zPos = parseLeFloat();
195 this.handler.vmdBonePosition(xPos, yPos, zPos);
197 float qx = parseLeFloat();
198 float qy = parseLeFloat();
199 float qz = parseLeFloat();
200 float qw = parseLeFloat();
201 this.handler.vmdBoneRotationQt(qx, qy, qz, qw);
203 parseVmdMotionInterpolation();
205 this.handler.loopNext(VmdBasicHandler.BONEMOTION_LIST);
208 this.handler.loopEnd(VmdBasicHandler.BONEMOTION_LIST);
214 * ボーンモーション補間データのパースと通知。
215 * @throws IOException IOエラー
216 * @throws MmdFormatException フォーマットエラー
218 private void parseVmdMotionInterpolation()
219 throws IOException, MmdFormatException{
220 parseByteArray(this.motionIntplt);
222 if(this.redundantCheck){
228 byte xP1x = this.motionIntplt[idx++];
229 byte yP1x = this.motionIntplt[idx++];
230 byte zP1x = this.motionIntplt[idx++];
231 byte rP1x = this.motionIntplt[idx++];
233 byte xP1y = this.motionIntplt[idx++];
234 byte yP1y = this.motionIntplt[idx++];
235 byte zP1y = this.motionIntplt[idx++];
236 byte rP1y = this.motionIntplt[idx++];
238 byte xP2x = this.motionIntplt[idx++];
239 byte yP2x = this.motionIntplt[idx++];
240 byte zP2x = this.motionIntplt[idx++];
241 byte rP2x = this.motionIntplt[idx++];
243 byte xP2y = this.motionIntplt[idx++];
244 byte yP2y = this.motionIntplt[idx++];
245 byte zP2y = this.motionIntplt[idx++];
246 byte rP2y = this.motionIntplt[idx++];
248 assert idx == BZXYZR_SIZE;
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);
259 * 補間情報の冗長箇所の整合性チェックを行う。
261 * <p>※ MMDの版数によって微妙に詳細が異なる場合がある。
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 this.handler.loopStart(
310 VmdBasicHandler.MORPH_LIST, morphMotionNo);
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);
318 this.handler.loopNext(VmdBasicHandler.MORPH_LIST);
321 this.handler.loopEnd(VmdBasicHandler.MORPH_LIST);