OSDN Git Service

175a827716c9d3e11d6dbf72aecab64836eb46b2
[mikutoga/Vmd2XML.git] / src / main / java / jp / sfjp / mikutoga / vmd / model / xml / VmdXmlExporter.java
1 /*
2  * vmd-xml exporter
3  *
4  * License : The MIT License
5  * Copyright(c) 2011 MikuToga Partners
6  */
7
8 package jp.sfjp.mikutoga.vmd.model.xml;
9
10 import java.io.IOException;
11 import java.text.MessageFormat;
12 import java.util.List;
13 import java.util.Map;
14 import jp.sfjp.mikutoga.math.EulerYXZ;
15 import jp.sfjp.mikutoga.math.MkPos3D;
16 import jp.sfjp.mikutoga.math.MkQuat;
17 import jp.sfjp.mikutoga.typical.TypicalBone;
18 import jp.sfjp.mikutoga.typical.TypicalMorph;
19 import jp.sfjp.mikutoga.vmd.IllegalVmdDataException;
20 import jp.sfjp.mikutoga.vmd.VmdUniq;
21 import jp.sfjp.mikutoga.vmd.model.BezierParam;
22 import jp.sfjp.mikutoga.vmd.model.BoneMotion;
23 import jp.sfjp.mikutoga.vmd.model.MorphMotion;
24 import jp.sfjp.mikutoga.vmd.model.PosCurve;
25 import jp.sfjp.mikutoga.vmd.model.VmdMotion;
26 import jp.sourceforge.mikutoga.xml.BasicXmlExporter;
27 import jp.sourceforge.mikutoga.xml.XmlResourceResolver;
28
29 /**
30  * VMDモーションデータをXMLへエクスポートする。
31  */
32 public class VmdXmlExporter extends BasicXmlExporter {
33
34     private static final String XML_VER = "1.0";
35     private static final String XML_ENC = "UTF-8";
36     private static final String XML_DECL =
37               "<?xml version=\"" + XML_VER
38             + "\" encoding=\"" + XML_ENC
39             + "\" ?>";
40
41     private static final String XSINS = "xsi";
42
43     private static final String MSG_MAYBE = "Perhaps : [{0}]";
44
45
46     private boolean isQuaternionMode = true;
47     private String generator = "";
48
49     private final CameraXmlExporter cameraXmlExporter;
50     private final LightingXmlExpoter lightingExporter;
51     private final ExtraXmlExporter extraExporter;
52
53
54     /**
55      * コンストラクタ。
56      */
57     public VmdXmlExporter(){
58         super();
59
60         this.cameraXmlExporter = new CameraXmlExporter(this);
61         this.lightingExporter  = new LightingXmlExpoter(this);
62         this.extraExporter     = new ExtraXmlExporter(this);
63
64         return;
65     }
66
67
68     /**
69      * ボーン回転量をクォータニオンで出力するか否か設定する。
70      * <p>デフォルトではtrue
71      * @param mode trueだとクォータニオン、falseだとオイラー角で出力される。
72      */
73     public void setQuaternionMode(boolean mode){
74         this.isQuaternionMode = mode;
75     }
76
77     /**
78      * ボーン回転量をクォータニオンで出力するか否か返す。
79      * @return クォータニオンで出力するならtrue
80      */
81     public boolean isQuaternionMode(){
82         return this.isQuaternionMode;
83     }
84
85     /**
86      * Generatorメタ情報を設定する。
87      * @param generatorArg Generatorメタ情報。nullならXML出力しない。
88      */
89     public void setGenerator(String generatorArg){
90         this.generator = generatorArg;
91         return;
92     }
93
94     /**
95      * Generatorメタ情報を取得する。
96      * @return Generatorメタ情報。XML出力しないならnullを返す。
97      */
98     public String getGenerator(){
99         return this.generator;
100     }
101
102     /**
103      * VMDモーションデータをXML形式で出力する。
104      * @param vmdMotion VMDモーションデータ
105      * @param xmlOut 出力先
106      * @throws IOException 出力エラー
107      * @throws IllegalVmdDataException 不正なモーションデータを検出
108      */
109     public void putVmdXml(VmdMotion vmdMotion, Appendable xmlOut)
110             throws IOException, IllegalVmdDataException{
111         setAppendable(xmlOut);
112
113         try{
114             putVmdXmlImpl(vmdMotion);
115         }finally{
116             flush();
117         }
118         return;
119     }
120
121     /**
122      * VMDモーションデータをXML形式で出力する。
123      * @param vmdMotion VMDモーションデータ
124      * @throws IOException 出力エラー
125      * @throws IllegalVmdDataException 不正なモーションデータを検出
126      */
127     private void putVmdXmlImpl(VmdMotion vmdMotion)
128             throws IOException, IllegalVmdDataException{
129         ind().putRawText(XML_DECL).ln(2);
130
131         ind().putBlockComment(XmlComment.TOP_COMMENT).ln(2);
132
133         ind().putOpenSTag(VmdTag.VMD_MOTION.tag()).ln();
134         pushNest();
135         ind().putAttr("xmlns", Schema110820.NS_VMDXML).ln();
136         ind().putAttr("xmlns:" + XSINS, XmlResourceResolver.NS_XSD).ln();
137
138         ind().putRawText(XSINS).putRawText(":schemaLocation=")
139              .putRawCh('"');
140         putRawText(Schema110820.NS_VMDXML).ln();
141         ind().sp(2).putRawText(Schema110820.SCHEMA_VMDXML)
142              .putRawCh('"')
143              .ln();
144
145         ind().putAttr(XmlAttr.ATTR_VERSION, Schema110820.VER_VMDXML).ln();
146         popNest();
147         putCloseSTag().ln(2);
148
149         putGenerator();
150
151         if(vmdMotion.isModelMotion()){
152             putModelName(vmdMotion);
153             putBoneMotionSequence(vmdMotion);
154             putMorphSequence(vmdMotion);
155         }else{
156             this.cameraXmlExporter.putCameraSequence(vmdMotion);
157             this.lightingExporter.putLuminousSequence(vmdMotion);
158             this.lightingExporter.putShadowSequence(vmdMotion);
159         }
160
161         ind().putETag(VmdTag.VMD_MOTION.tag()).ln(2);
162         ind().putLineComment("EOF").ln();
163
164         return;
165     }
166
167     /**
168      * ジェネレータ名を出力する。
169      * @throws IOException 出力エラー
170      */
171     private void putGenerator() throws IOException{
172         String genTxt = getGenerator();
173         if(genTxt == null) return;
174         if(genTxt.isEmpty()) return;
175
176         ind().putOpenSTag(VmdTag.META.tag()).sp();
177         putAttr(XmlAttr.ATTR_NAME, "generator").sp();
178         putAttr(XmlAttr.ATTR_CONTENT, genTxt).sp();
179         putCloseEmpty().ln(2);
180
181         return;
182     }
183
184     /**
185      * モデル名を出力する。
186      * @param vmdMotion モーションデータ
187      * @throws IOException 出力エラー
188      */
189     private void putModelName(VmdMotion vmdMotion)
190             throws IOException{
191         String modelName = vmdMotion.getModelName();
192
193         String modelComm;
194         if(modelName != null && modelName.length() > 0){
195             modelComm = modelName;
196         }else{
197             modelComm = "[NAMELESS]";
198         }
199
200         ind().putLineComment(modelComm).ln();
201         ind().putOpenSTag(VmdTag.MODEL_NAME.tag()).sp();
202         putAttr(XmlAttr.ATTR_NAME, modelName).sp();
203         putCloseEmpty().ln(2);
204
205         return;
206     }
207
208     /**
209      * ボーンモーションデータを出力する。
210      * @param vmdMotion モーションデータ
211      * @throws IOException 出力エラー
212      */
213     private void putBoneMotionSequence(VmdMotion vmdMotion)
214             throws IOException{
215         Map<String, List<BoneMotion>> boneMap = vmdMotion.getBonePartMap();
216
217         if( ! boneMap.isEmpty() ){
218             ind().putBlockComment(XmlComment.QUATERNION_COMMENT);
219             ind().putBlockComment(XmlComment.BEZIER_COMMENT);
220         }
221
222         ind().putSimpleSTag(VmdTag.BONE_M_SEQUENCE.tag()).ln();
223
224         pushNest();
225         if( ! boneMap.isEmpty() ) ln();
226         for(Map.Entry<String, List<BoneMotion>> entry : boneMap.entrySet()){
227             putBonePart(entry.getKey(), entry.getValue());
228         }
229         popNest();
230
231         ind().putETag(VmdTag.BONE_M_SEQUENCE.tag()).ln(2);
232
233         return;
234     }
235
236     /**
237      * ボーン別モーションデータを出力する。
238      * @param boneName ボーン名
239      * @param list ボーンモーションのリスト
240      * @throws IOException 出力エラー
241      */
242     private void putBonePart(String boneName, List<BoneMotion> list)
243             throws IOException{
244         ind().putLineComment(boneName);
245         String globalName = TypicalBone.primary2global(boneName);
246         if(globalName != null){
247             String gname =
248                     MessageFormat.format(MSG_MAYBE, globalName);
249             sp(2).putLineComment(gname);
250         }
251         ln();
252
253         ind().putOpenSTag(VmdTag.BONE_PART.tag()).sp();
254         putAttr(XmlAttr.ATTR_NAME, boneName).sp();
255         putCloseSTag().ln(2);
256
257         pushNest();
258         for(BoneMotion bone : list){
259             putBoneMotion(bone);
260         }
261         popNest();
262
263         ind().putETag(VmdTag.BONE_PART.tag()).ln(2);
264
265         return;
266     }
267
268     /**
269      * ボーンモーションを出力する。
270      * @param boneMotion ボーンモーション
271      * @throws IOException 出力エラー
272      */
273     private void putBoneMotion(BoneMotion boneMotion)
274             throws IOException{
275         ind().putOpenSTag(VmdTag.BONE_MOTION.tag()).sp();
276         int frameNo = boneMotion.getFrameNumber();
277         putIntAttr(XmlAttr.ATTR_FRAME, frameNo).sp();
278         putCloseSTag().ln();
279
280         pushNest();
281         putBonePosition(boneMotion);
282         if(isQuaternionMode()){
283             putBoneRotQuat(boneMotion);
284         }else{
285             putBoneRotEyxz(boneMotion);
286         }
287         popNest();
288
289         ind().putETag(VmdTag.BONE_MOTION.tag()).ln(2);
290
291         return;
292     }
293
294     /**
295      * ボーン位置を出力する。
296      * @param boneMotion ボーンモーション
297      * @throws IOException 出力エラー
298      */
299     private void putBonePosition(BoneMotion boneMotion)
300             throws IOException{
301         if(boneMotion.hasImplicitPosition()){
302             return;
303         }
304
305         ind().putOpenSTag(VmdTag.BONE_POSITION.tag()).sp();
306
307         MkPos3D position = boneMotion.getPosition();
308
309         float xPos = (float) position.getXpos();
310         float yPos = (float) position.getYpos();
311         float zPos = (float) position.getZpos();
312
313         putFloatAttr(XmlAttr.ATTR_X_POS, xPos).sp();
314         putFloatAttr(XmlAttr.ATTR_Y_POS, yPos).sp();
315         putFloatAttr(XmlAttr.ATTR_Z_POS, zPos).sp();
316
317         PosCurve posCurve = boneMotion.getPosCurve();
318         if(posCurve.isDefaultLinear()){
319             putCloseEmpty().ln();
320         }else{
321             putCloseSTag().ln();
322
323             pushNest();
324             this.extraExporter.putPositionCurve(posCurve);
325             popNest();
326
327             ind().putETag(VmdTag.BONE_POSITION.tag()).ln();
328         }
329
330         return;
331     }
332
333     /**
334      * ボーン回転を出力する。
335      * @param boneMotion ボーンモーション
336      * @throws IOException 出力エラー
337      */
338     private void putBoneRotQuat(BoneMotion boneMotion)
339             throws IOException{
340         MkQuat rotation = boneMotion.getRotation();
341         BezierParam rotCurve = boneMotion.getIntpltRotation();
342
343         ind().putOpenSTag(VmdTag.BONE_ROT_QUAT.tag()).ln();
344         pushNest();
345
346         float qx = (float) rotation.getQ1();
347         float qy = (float) rotation.getQ2();
348         float qz = (float) rotation.getQ3();
349         float qw = (float) rotation.getQW();
350
351         ind().putFloatAttr(XmlAttr.ATTR_QX, qx).ln();
352         ind().putFloatAttr(XmlAttr.ATTR_QY, qy).ln();
353         ind().putFloatAttr(XmlAttr.ATTR_QZ, qz).ln();
354         ind().putFloatAttr(XmlAttr.ATTR_QW, qw).ln();
355
356         popNest();
357         ind();
358
359         if(rotCurve.isDefaultLinear()){
360             putCloseEmpty().ln();
361         }else{
362             putCloseSTag().ln();
363             pushNest();
364             ind();
365             this.extraExporter.putBezierCurve(rotCurve);
366             ln();
367             popNest();
368             ind().putETag(VmdTag.BONE_ROT_QUAT.tag()).ln();
369         }
370
371         return;
372     }
373
374     /**
375      * ボーン回転を出力する。
376      * @param boneMotion ボーンモーション
377      * @throws IOException 出力エラー
378      */
379     private void putBoneRotEyxz(BoneMotion boneMotion)
380             throws IOException{
381         MkQuat rotation = boneMotion.getRotation();
382         BezierParam rotCurve = boneMotion.getIntpltRotation();
383
384         EulerYXZ euler = new EulerYXZ();
385         rotation.toEulerYXZ(euler);
386         float xDeg = (float)StrictMath.toDegrees(euler.getXRot());
387         float yDeg = (float)StrictMath.toDegrees(euler.getYRot());
388         float zDeg = (float)StrictMath.toDegrees(euler.getZRot());
389
390         ind().putOpenSTag(VmdTag.BONE_ROT_EYXZ.tag()).ln();
391         pushNest();
392         ind().putFloatAttr(XmlAttr.ATTR_X_DEG, xDeg).ln();
393         ind().putFloatAttr(XmlAttr.ATTR_Y_DEG, yDeg).ln();
394         ind().putFloatAttr(XmlAttr.ATTR_Z_DEG, zDeg).ln();
395         popNest();
396         ind();
397
398         if(rotCurve.isDefaultLinear()){
399             putCloseEmpty().ln();
400         }else{
401             putCloseSTag().ln();
402             pushNest();
403             ind();
404             this.extraExporter.putBezierCurve(rotCurve);
405             ln();
406             popNest();
407             ind().putETag(VmdTag.BONE_ROT_EYXZ.tag()).ln();
408         }
409
410         return;
411     }
412
413     /**
414      * モーフデータを出力する。
415      * @param vmdMotion モーションデータ
416      * @throws IOException 出力エラー
417      */
418     private void putMorphSequence(VmdMotion vmdMotion)
419             throws IOException{
420         ind().putSimpleSTag(VmdTag.MORPH_SEQUENCE.tag()).ln();
421
422         pushNest();
423         Map<String, List<MorphMotion>> listMap = vmdMotion.getMorphPartMap();
424         if( ! listMap.isEmpty() ) ln();
425         putMorphPartList(listMap);
426         popNest();
427
428         ind().putETag(VmdTag.MORPH_SEQUENCE.tag()).ln(2);
429
430         return;
431     }
432
433     /**
434      * 箇所別モーフデータを出力する。
435      * @param listMap モーフデータの名前付きリストマップ
436      * @throws IOException 出力エラー
437      */
438     private void putMorphPartList(Map<String, List<MorphMotion>> listMap)
439             throws IOException{
440         for(Map.Entry<String, List<MorphMotion>> entry : listMap.entrySet()){
441             String morphName       = entry.getKey();
442             List<MorphMotion> list = entry.getValue();
443
444             if(VmdUniq.isBaseMorphName(morphName)) continue;
445
446             ind().putLineComment(morphName);
447             String globalName = TypicalMorph.primary2global(morphName);
448             if(globalName != null){
449                 String gname =
450                         MessageFormat.format(MSG_MAYBE, globalName);
451                 sp(2).putLineComment(gname);
452             }
453             ln();
454
455             ind().putOpenSTag(VmdTag.MORPH_PART.tag()).sp();
456             putAttr(XmlAttr.ATTR_NAME, morphName).sp();
457             putCloseSTag().ln();
458
459             pushNest();
460             for(MorphMotion morph : list){
461                 putMorphMotion(morph);
462             }
463             popNest();
464
465             ind().putETag(VmdTag.MORPH_PART.tag()).ln(2);
466         }
467
468         return;
469     }
470
471     /**
472      * 個別のモーフモーションを出力する。
473      * @param morphMotion モーフモーション
474      * @throws IOException 出力エラー
475      */
476     private void putMorphMotion(MorphMotion morphMotion)
477             throws IOException{
478         ind().putOpenSTag(VmdTag.MORPH_MOTION.tag()).sp();
479
480         int frameNo = morphMotion.getFrameNumber();
481         float flex = morphMotion.getFlex();
482
483         putIntAttr(XmlAttr.ATTR_FRAME, frameNo).sp();
484         putFloatAttr(XmlAttr.ATTR_FLEX, flex).sp();
485
486         putCloseEmpty().ln();
487
488         return;
489     }
490
491 }