OSDN Git Service

Upgrade ToaGem to 3.122.2
[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.sfjp.mikutoga.xml.BasicXmlExporter;
27 import jp.sfjp.mikutoga.xml.SchemaUtil;
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 XmlMotionFileType xmlType = XmlMotionFileType.XML_110820;
47
48     private boolean isQuaternionMode = true;
49     private String generator = "";
50
51     private final CameraXmlExporter cameraXmlExporter;
52     private final LightingXmlExpoter lightingExporter;
53     private final ExtraXmlExporter extraExporter;
54     private final FlagXmlExporter flagXmlExporter;
55
56
57     /**
58      * コンストラクタ。
59      */
60     public VmdXmlExporter(){
61         super();
62
63         this.cameraXmlExporter = new CameraXmlExporter(this);
64         this.lightingExporter  = new LightingXmlExpoter(this);
65         this.extraExporter     = new ExtraXmlExporter(this);
66         this.flagXmlExporter   = new FlagXmlExporter(this);
67
68         return;
69     }
70
71
72     /**
73      * 出力XMLファイル種別を設定する。
74      * @param type ファイル種別
75      */
76     public void setXmlFileType(XmlMotionFileType type){
77         switch(type){
78         case XML_110820:
79         case XML_130609:
80             this.xmlType = type;
81             break;
82         case XML_AUTO:
83             this.xmlType = XmlMotionFileType.XML_130609;
84             break;
85         default:
86             assert false;
87             throw new AssertionError();
88         }
89
90         assert this.xmlType == XmlMotionFileType.XML_110820
91             || this.xmlType == XmlMotionFileType.XML_130609;
92
93         return;
94     }
95
96     /**
97      * 出力XMLファイル種別を返す。
98      * @return ファイル種別
99      */
100     public XmlMotionFileType getXmlFileType(){
101         return this.xmlType;
102     }
103
104     /**
105      * ボーン回転量をクォータニオンで出力するか否か設定する。
106      * <p>デフォルトではtrue
107      * @param mode trueだとクォータニオン、falseだとオイラー角で出力される。
108      */
109     public void setQuaternionMode(boolean mode){
110         this.isQuaternionMode = mode;
111     }
112
113     /**
114      * ボーン回転量をクォータニオンで出力するか否か返す。
115      * @return クォータニオンで出力するならtrue
116      */
117     public boolean isQuaternionMode(){
118         return this.isQuaternionMode;
119     }
120
121     /**
122      * Generatorメタ情報を設定する。
123      * @param generatorArg Generatorメタ情報。nullならXML出力しない。
124      */
125     public void setGenerator(String generatorArg){
126         this.generator = generatorArg;
127         return;
128     }
129
130     /**
131      * Generatorメタ情報を取得する。
132      * @return Generatorメタ情報。XML出力しないならnullを返す。
133      */
134     public String getGenerator(){
135         return this.generator;
136     }
137
138     /**
139      * VMDモーションデータをXML形式で出力する。
140      * @param vmdMotion VMDモーションデータ
141      * @param xmlOut 出力先
142      * @throws IOException 出力エラー
143      * @throws IllegalVmdDataException 不正なモーションデータを検出
144      */
145     public void putVmdXml(VmdMotion vmdMotion, Appendable xmlOut)
146             throws IOException, IllegalVmdDataException{
147         setAppendable(xmlOut);
148
149         try{
150             putVmdXmlImpl(vmdMotion);
151         }finally{
152             flush();
153         }
154         return;
155     }
156
157     /**
158      * VMDモーションデータをXML形式で出力する。
159      * @param vmdMotion VMDモーションデータ
160      * @throws IOException 出力エラー
161      * @throws IllegalVmdDataException 不正なモーションデータを検出
162      */
163     private void putVmdXmlImpl(VmdMotion vmdMotion)
164             throws IOException, IllegalVmdDataException{
165         putVmdRootOpen();
166
167         putGenerator();
168
169         if(vmdMotion.isModelMotion()){
170             putModelName(vmdMotion);
171             putBoneMotionSequence(vmdMotion);
172             putMorphSequence(vmdMotion);
173             if(this.xmlType == XmlMotionFileType.XML_130609){
174                 this.flagXmlExporter.putFlagSequence(vmdMotion);
175             }
176         }else{
177             this.cameraXmlExporter.putCameraSequence(vmdMotion);
178             this.lightingExporter.putLuminousSequence(vmdMotion);
179             this.lightingExporter.putShadowSequence(vmdMotion);
180         }
181
182         ind().putETag(VmdTag.VMD_MOTION.tag()).ln(2);
183         ind().putLineComment("EOF").ln();
184
185         return;
186     }
187
188     /**
189      * ルート要素がオープンするまでの各種宣言を出力する。
190      * @throws IOException 出力エラー
191      */
192     private void putVmdRootOpen() throws IOException{
193         ind().putRawText(XML_DECL).ln(2);
194
195         ind().putBlockComment(XmlComment.TOP_COMMENT).ln(2);
196
197         String namespace;
198         String schemaUrl;
199         String schemaVer;
200
201         switch(this.xmlType){
202         case XML_110820:
203             namespace = Schema110820.NS_VMDXML;
204             schemaUrl = Schema110820.SCHEMA_VMDXML;
205             schemaVer = Schema110820.VER_VMDXML;
206             break;
207         case XML_130609:
208             namespace = Schema130609.NS_VMDXML;
209             schemaUrl = Schema130609.SCHEMA_VMDXML;
210             schemaVer = Schema130609.VER_VMDXML;
211             break;
212         default:
213             assert false;
214             throw new AssertionError();
215         }
216
217         ind().putOpenSTag(VmdTag.VMD_MOTION.tag()).ln();
218         pushNest();
219         ind().putAttr("xmlns", namespace).ln();
220         ind().putAttr("xmlns:" + XSINS, SchemaUtil.NS_XSD).ln();
221
222         ind().putRawText(XSINS).putRawText(":schemaLocation=")
223              .putRawCh('"');
224         putRawText(namespace).ln();
225         ind().sp(2).putRawText(schemaUrl)
226              .putRawCh('"')
227              .ln();
228
229         ind().putAttr(XmlAttr.ATTR_VERSION, schemaVer).ln();
230         popNest();
231         putCloseSTag().ln(2);
232
233         return;
234     }
235
236     /**
237      * ジェネレータ名を出力する。
238      * @throws IOException 出力エラー
239      */
240     private void putGenerator() throws IOException{
241         String genTxt = getGenerator();
242         if(genTxt == null) return;
243         if(genTxt.isEmpty()) return;
244
245         ind().putOpenSTag(VmdTag.META.tag()).sp();
246         putAttr(XmlAttr.ATTR_NAME, "generator").sp();
247         putAttr(XmlAttr.ATTR_CONTENT, genTxt).sp();
248         putCloseEmpty().ln(2);
249
250         return;
251     }
252
253     /**
254      * モデル名を出力する。
255      * @param vmdMotion モーションデータ
256      * @throws IOException 出力エラー
257      */
258     private void putModelName(VmdMotion vmdMotion)
259             throws IOException{
260         String modelName = vmdMotion.getModelName();
261
262         String modelComm;
263         if(modelName != null && modelName.length() > 0){
264             modelComm = modelName;
265         }else{
266             modelComm = "[NAMELESS]";
267         }
268
269         ind().putLineComment(modelComm).ln();
270         ind().putOpenSTag(VmdTag.MODEL_NAME.tag()).sp();
271         putAttr(XmlAttr.ATTR_NAME, modelName).sp();
272         putCloseEmpty().ln(2);
273
274         return;
275     }
276
277     /**
278      * ボーンモーションデータを出力する。
279      * @param vmdMotion モーションデータ
280      * @throws IOException 出力エラー
281      */
282     private void putBoneMotionSequence(VmdMotion vmdMotion)
283             throws IOException{
284         Map<String, List<BoneMotion>> boneMap = vmdMotion.getBonePartMap();
285
286         if( ! boneMap.isEmpty() ){
287             ind().putBlockComment(XmlComment.QUATERNION_COMMENT);
288             ind().putBlockComment(XmlComment.BEZIER_COMMENT);
289         }
290
291         ind().putSimpleSTag(VmdTag.BONE_M_SEQUENCE.tag()).ln();
292
293         pushNest();
294         if( ! boneMap.isEmpty() ) ln();
295         for(Map.Entry<String, List<BoneMotion>> entry : boneMap.entrySet()){
296             putBonePart(entry.getKey(), entry.getValue());
297         }
298         popNest();
299
300         ind().putETag(VmdTag.BONE_M_SEQUENCE.tag()).ln(2);
301
302         return;
303     }
304
305     /**
306      * ボーン別モーションデータを出力する。
307      * @param boneName ボーン名
308      * @param list ボーンモーションのリスト
309      * @throws IOException 出力エラー
310      */
311     private void putBonePart(String boneName, List<BoneMotion> list)
312             throws IOException{
313         ind().putLineComment(boneName);
314         String globalName = TypicalBone.primary2global(boneName);
315         if(globalName != null){
316             String gname =
317                     MessageFormat.format(MSG_MAYBE, globalName);
318             sp(2).putLineComment(gname);
319         }
320         ln();
321
322         ind().putOpenSTag(VmdTag.BONE_PART.tag()).sp();
323         putAttr(XmlAttr.ATTR_NAME, boneName).sp();
324         putCloseSTag().ln(2);
325
326         pushNest();
327         for(BoneMotion bone : list){
328             putBoneMotion(bone);
329         }
330         popNest();
331
332         ind().putETag(VmdTag.BONE_PART.tag()).ln(2);
333
334         return;
335     }
336
337     /**
338      * ボーンモーションを出力する。
339      * @param boneMotion ボーンモーション
340      * @throws IOException 出力エラー
341      */
342     private void putBoneMotion(BoneMotion boneMotion)
343             throws IOException{
344         ind().putOpenSTag(VmdTag.BONE_MOTION.tag()).sp();
345         int frameNo = boneMotion.getFrameNumber();
346         putIntAttr(XmlAttr.ATTR_FRAME, frameNo).sp();
347         putCloseSTag().ln();
348
349         pushNest();
350         putBonePosition(boneMotion);
351         if(isQuaternionMode()){
352             putBoneRotQuat(boneMotion);
353         }else{
354             putBoneRotEyxz(boneMotion);
355         }
356         popNest();
357
358         ind().putETag(VmdTag.BONE_MOTION.tag()).ln(2);
359
360         return;
361     }
362
363     /**
364      * ボーン位置を出力する。
365      * @param boneMotion ボーンモーション
366      * @throws IOException 出力エラー
367      */
368     private void putBonePosition(BoneMotion boneMotion)
369             throws IOException{
370         if(boneMotion.hasImplicitPosition()){
371             return;
372         }
373
374         ind().putOpenSTag(VmdTag.BONE_POSITION.tag()).sp();
375
376         MkPos3D position = boneMotion.getPosition();
377
378         float xPos = (float) position.getXpos();
379         float yPos = (float) position.getYpos();
380         float zPos = (float) position.getZpos();
381
382         putFloatAttr(XmlAttr.ATTR_X_POS, xPos).sp();
383         putFloatAttr(XmlAttr.ATTR_Y_POS, yPos).sp();
384         putFloatAttr(XmlAttr.ATTR_Z_POS, zPos).sp();
385
386         PosCurve posCurve = boneMotion.getPosCurve();
387         if(posCurve.isDefaultLinear()){
388             putCloseEmpty().ln();
389         }else{
390             putCloseSTag().ln();
391
392             pushNest();
393             this.extraExporter.putPositionCurve(posCurve);
394             popNest();
395
396             ind().putETag(VmdTag.BONE_POSITION.tag()).ln();
397         }
398
399         return;
400     }
401
402     /**
403      * ボーン回転を出力する。
404      * @param boneMotion ボーンモーション
405      * @throws IOException 出力エラー
406      */
407     private void putBoneRotQuat(BoneMotion boneMotion)
408             throws IOException{
409         MkQuat rotation = boneMotion.getRotation();
410         BezierParam rotCurve = boneMotion.getIntpltRotation();
411
412         ind().putOpenSTag(VmdTag.BONE_ROT_QUAT.tag()).ln();
413         pushNest();
414
415         float qx = (float) rotation.getQ1();
416         float qy = (float) rotation.getQ2();
417         float qz = (float) rotation.getQ3();
418         float qw = (float) rotation.getQW();
419
420         ind().putFloatAttr(XmlAttr.ATTR_QX, qx).ln();
421         ind().putFloatAttr(XmlAttr.ATTR_QY, qy).ln();
422         ind().putFloatAttr(XmlAttr.ATTR_QZ, qz).ln();
423         ind().putFloatAttr(XmlAttr.ATTR_QW, qw).ln();
424
425         popNest();
426         ind();
427
428         if(rotCurve.isDefaultLinear()){
429             putCloseEmpty().ln();
430         }else{
431             putCloseSTag().ln();
432             pushNest();
433             ind();
434             this.extraExporter.putBezierCurve(rotCurve);
435             ln();
436             popNest();
437             ind().putETag(VmdTag.BONE_ROT_QUAT.tag()).ln();
438         }
439
440         return;
441     }
442
443     /**
444      * ボーン回転を出力する。
445      * @param boneMotion ボーンモーション
446      * @throws IOException 出力エラー
447      */
448     private void putBoneRotEyxz(BoneMotion boneMotion)
449             throws IOException{
450         MkQuat rotation = boneMotion.getRotation();
451         BezierParam rotCurve = boneMotion.getIntpltRotation();
452
453         EulerYXZ euler = new EulerYXZ();
454         rotation.toEulerYXZ(euler);
455         float xDeg = (float)StrictMath.toDegrees(euler.getXRot());
456         float yDeg = (float)StrictMath.toDegrees(euler.getYRot());
457         float zDeg = (float)StrictMath.toDegrees(euler.getZRot());
458
459         ind().putOpenSTag(VmdTag.BONE_ROT_EYXZ.tag()).ln();
460         pushNest();
461         ind().putFloatAttr(XmlAttr.ATTR_X_DEG, xDeg).ln();
462         ind().putFloatAttr(XmlAttr.ATTR_Y_DEG, yDeg).ln();
463         ind().putFloatAttr(XmlAttr.ATTR_Z_DEG, zDeg).ln();
464         popNest();
465         ind();
466
467         if(rotCurve.isDefaultLinear()){
468             putCloseEmpty().ln();
469         }else{
470             putCloseSTag().ln();
471             pushNest();
472             ind();
473             this.extraExporter.putBezierCurve(rotCurve);
474             ln();
475             popNest();
476             ind().putETag(VmdTag.BONE_ROT_EYXZ.tag()).ln();
477         }
478
479         return;
480     }
481
482     /**
483      * モーフデータを出力する。
484      * @param vmdMotion モーションデータ
485      * @throws IOException 出力エラー
486      */
487     private void putMorphSequence(VmdMotion vmdMotion)
488             throws IOException{
489         ind().putSimpleSTag(VmdTag.MORPH_SEQUENCE.tag()).ln();
490
491         pushNest();
492         Map<String, List<MorphMotion>> listMap = vmdMotion.getMorphPartMap();
493         if( ! listMap.isEmpty() ) ln();
494         putMorphPartList(listMap);
495         popNest();
496
497         ind().putETag(VmdTag.MORPH_SEQUENCE.tag()).ln(2);
498
499         return;
500     }
501
502     /**
503      * 箇所別モーフデータを出力する。
504      * @param listMap モーフデータの名前付きリストマップ
505      * @throws IOException 出力エラー
506      */
507     private void putMorphPartList(Map<String, List<MorphMotion>> listMap)
508             throws IOException{
509         for(Map.Entry<String, List<MorphMotion>> entry : listMap.entrySet()){
510             String morphName       = entry.getKey();
511             List<MorphMotion> list = entry.getValue();
512
513             if(VmdUniq.isBaseMorphName(morphName)) continue;
514
515             ind().putLineComment(morphName);
516             String globalName = TypicalMorph.primary2global(morphName);
517             if(globalName != null){
518                 String gname =
519                         MessageFormat.format(MSG_MAYBE, globalName);
520                 sp(2).putLineComment(gname);
521             }
522             ln();
523
524             ind().putOpenSTag(VmdTag.MORPH_PART.tag()).sp();
525             putAttr(XmlAttr.ATTR_NAME, morphName).sp();
526             putCloseSTag().ln();
527
528             pushNest();
529             for(MorphMotion morph : list){
530                 putMorphMotion(morph);
531             }
532             popNest();
533
534             ind().putETag(VmdTag.MORPH_PART.tag()).ln(2);
535         }
536
537         return;
538     }
539
540     /**
541      * 個別のモーフモーションを出力する。
542      * @param morphMotion モーフモーション
543      * @throws IOException 出力エラー
544      */
545     private void putMorphMotion(MorphMotion morphMotion)
546             throws IOException{
547         ind().putOpenSTag(VmdTag.MORPH_MOTION.tag()).sp();
548
549         int frameNo = morphMotion.getFrameNumber();
550         float flex = morphMotion.getFlex();
551
552         putIntAttr(XmlAttr.ATTR_FRAME, frameNo).sp();
553         putFloatAttr(XmlAttr.ATTR_FLEX, flex).sp();
554
555         putCloseEmpty().ln();
556
557         return;
558     }
559
560 }