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モーションファイルの基本部パーサ。
22 * <p>ボーンのモーション情報およびモーフモーション情報のパース処理を含む。
24 class VmdBasicParser extends ProxyParser{
27 * VMDで用いられる文字エンコーディング(windows-31j)。
28 * ほぼShift_JISのスーパーセットと思ってよい。
29 * デコード結果はUCS-2集合に収まるはず。
31 public static final Charset CS_WIN31J = Charset.forName("windows-31j");
32 private static final Charset CS_ASCII = Charset.forName("US-ASCII");
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;
39 private static final String ERRMSG_INVINTPLT =
40 "there is potential inconsistency in motion interpolation data. "
42 private static final String ERRMSG_UK_HEADER =
43 "unknown VMD-header type";
46 private final TextDecoder decoderWin31j = new TextDecoder(CS_WIN31J);
48 private final byte[] motionIntplt = new byte[BZTOTAL_SIZE];
50 private VmdBasicHandler handler = VmdUnifiedHandler.EMPTY;
52 private boolean hasStageActName = false;
53 private boolean redundantCheck = false;
58 * @param parser 委譲先パーサ
60 VmdBasicParser(BinParser parser){
62 this.decoderWin31j.setZeroChopMode(true);
68 * パースしたモデル名がカメラ及びライティング用モデル名だったか判定する。
69 * @return カメラ及びライティング用モデル名だったらtrue
71 boolean hasStageActName(){
72 return this.hasStageActName;
77 * @param basicHandler ハンドラ
79 void setBasicHandler(VmdBasicHandler basicHandler){
80 if(basicHandler == null){
81 this.handler = VmdUnifiedHandler.EMPTY;
83 this.handler = basicHandler;
90 * ボーンモーション補間情報冗長部のチェックを行うか否か設定する。
92 * @param mode チェックさせたければtrue
94 void setRedundantCheck(boolean mode){
95 this.redundantCheck = mode;
100 * 指定された最大バイト長に収まるゼロ終端(0x00)文字列を読み込む。
101 * 入力バイト列はwindows-31jエンコーディングとして解釈される。
103 * IO入力は指定バイト数だけ読み進められる。
104 * ゼロ終端が見つからないまま指定バイト数が読み込み終わった場合、
105 * そこまでのデータから文字列を構成する。
106 * @param byteLen 読み込みバイト数
108 * @throws IOException IOエラー
109 * @throws MmdEofException 読み込む途中でストリーム終端に達した。
110 * @throws MmdFormatException 不正な文字エンコーディングが検出された。
112 private String parseVmdText(int byteLen)
116 String result = parseString(this.decoderWin31j, byteLen);
121 * VMDファイル基本部のパースを開始する。
122 * @throws IOException IOエラー
123 * @throws MmdFormatException フォーマットエラー
125 void parse() throws IOException, MmdFormatException {
126 this.hasStageActName = false;
130 parseVmdBoneMotion();
137 * VMDファイルヘッダ部のパースと通知。
138 * @throws IOException IOエラー
139 * @throws MmdFormatException フォーマットエラー
141 private void parseVmdHeader() throws IOException, MmdFormatException{
142 byte[] header = new byte[VmdConst.HEADER_LENGTH];
143 parseByteArray(header);
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);
152 this.handler.vmdHeaderInfo(header);
159 * @throws IOException IOエラー
160 * @throws MmdFormatException フォーマットエラー
162 private void parseVmdModelName() throws IOException, MmdFormatException{
163 String modelName = parseVmdText(VmdConst.MODELNAME_MAX);
165 if(VmdUniq.isStageActName(modelName)){
166 this.hasStageActName = true;
169 this.handler.vmdModelName(modelName);
175 * ボーンモーションデータのパースと通知。
176 * @throws IOException IOエラー
177 * @throws MmdFormatException フォーマットエラー
179 private void parseVmdBoneMotion()
180 throws IOException, MmdFormatException{
181 int boneMotionNo = parseLeInt();
183 this.handler.loopStart(
184 VmdBasicHandler.BONEMOTION_LIST, boneMotionNo);
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);
191 float xPos = parseLeFloat();
192 float yPos = parseLeFloat();
193 float zPos = parseLeFloat();
194 this.handler.vmdBonePosition(xPos, yPos, zPos);
196 float qx = parseLeFloat();
197 float qy = parseLeFloat();
198 float qz = parseLeFloat();
199 float qw = parseLeFloat();
200 this.handler.vmdBoneRotationQt(qx, qy, qz, qw);
202 parseVmdMotionInterpolation();
204 this.handler.loopNext(VmdBasicHandler.BONEMOTION_LIST);
207 this.handler.loopEnd(VmdBasicHandler.BONEMOTION_LIST);
213 * ボーンモーション補間データのパースと通知。
214 * @throws IOException IOエラー
215 * @throws MmdFormatException フォーマットエラー
217 private void parseVmdMotionInterpolation()
218 throws IOException, MmdFormatException{
219 parseByteArray(this.motionIntplt);
221 if(this.redundantCheck){
227 byte xP1x = this.motionIntplt[idx++];
228 byte yP1x = this.motionIntplt[idx++];
229 byte zP1x = this.motionIntplt[idx++];
230 byte rP1x = this.motionIntplt[idx++];
232 byte xP1y = this.motionIntplt[idx++];
233 byte yP1y = this.motionIntplt[idx++];
234 byte zP1y = this.motionIntplt[idx++];
235 byte rP1y = this.motionIntplt[idx++];
237 byte xP2x = this.motionIntplt[idx++];
238 byte yP2x = this.motionIntplt[idx++];
239 byte zP2x = this.motionIntplt[idx++];
240 byte rP2x = this.motionIntplt[idx++];
242 byte xP2y = this.motionIntplt[idx++];
243 byte yP2y = this.motionIntplt[idx++];
244 byte zP2y = this.motionIntplt[idx++];
245 byte rP2y = this.motionIntplt[idx++];
247 assert idx == BZXYZR_SIZE;
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);
258 * 補間情報の冗長箇所の整合性チェックを行う。
259 * <p>※ MMDの版数によって微妙に詳細が異なる場合がある。
260 * @throws MmdFormatException 冗長箇所の不整合を検出した。
262 private void checkIntpltStrict() throws MmdFormatException{
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;
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,
278 int onePos = targetIdx + span;
279 if(this.motionIntplt[onePos] != (byte) 0x01){
280 throw new MmdFormatException(ERRMSG_INVINTPLT, getPosition());
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,
299 * モーフモーションデータのパースと通知。
300 * @throws IOException IOエラー
301 * @throws MmdFormatException フォーマットエラー
303 private void parseVmdMorph() throws IOException, MmdFormatException{
304 int morphMotionNo = parseLeInt();
306 this.handler.loopStart(
307 VmdBasicHandler.MORPH_LIST, morphMotionNo);
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);
315 this.handler.loopNext(VmdBasicHandler.MORPH_LIST);
318 this.handler.loopEnd(VmdBasicHandler.MORPH_LIST);