OSDN Git Service

8265f54c2307eb9ef8e6605792c2cac3aaf8d395
[mikutoga/Pmd2XML.git] / src / main / java / jp / sfjp / mikutoga / pmd / xml / XmlExporter.java
1 /*
2  * pmd-xml exporter
3  *
4  * License : The MIT License
5  * Copyright(c) 2010 MikuToga Partners
6  */
7
8 package jp.sfjp.mikutoga.pmd.xml;
9
10 import java.awt.Color;
11 import java.io.IOException;
12 import java.io.OutputStream;
13 import java.util.List;
14 import java.util.Locale;
15 import java.util.Map;
16 import jp.sfjp.mikutoga.corelib.I18nText;
17 import jp.sfjp.mikutoga.math.MkPos2D;
18 import jp.sfjp.mikutoga.math.MkPos3D;
19 import jp.sfjp.mikutoga.math.MkVec3D;
20 import jp.sfjp.mikutoga.pmd.BoneType;
21 import jp.sfjp.mikutoga.pmd.Deg3d;
22 import jp.sfjp.mikutoga.pmd.MorphType;
23 import jp.sfjp.mikutoga.pmd.Rad3d;
24 import jp.sfjp.mikutoga.pmd.RigidShapeType;
25 import jp.sfjp.mikutoga.pmd.TripletRange;
26 import jp.sfjp.mikutoga.pmd.model.BoneGroup;
27 import jp.sfjp.mikutoga.pmd.model.BoneInfo;
28 import jp.sfjp.mikutoga.pmd.model.DynamicsInfo;
29 import jp.sfjp.mikutoga.pmd.model.IKChain;
30 import jp.sfjp.mikutoga.pmd.model.JointInfo;
31 import jp.sfjp.mikutoga.pmd.model.Material;
32 import jp.sfjp.mikutoga.pmd.model.MorphPart;
33 import jp.sfjp.mikutoga.pmd.model.MorphVertex;
34 import jp.sfjp.mikutoga.pmd.model.PmdModel;
35 import jp.sfjp.mikutoga.pmd.model.RigidGroup;
36 import jp.sfjp.mikutoga.pmd.model.RigidInfo;
37 import jp.sfjp.mikutoga.pmd.model.RigidShape;
38 import jp.sfjp.mikutoga.pmd.model.SerialNumbered;
39 import jp.sfjp.mikutoga.pmd.model.ShadeInfo;
40 import jp.sfjp.mikutoga.pmd.model.Surface;
41 import jp.sfjp.mikutoga.pmd.model.ToonMap;
42 import jp.sfjp.mikutoga.pmd.model.Vertex;
43 import jp.sourceforge.mikutoga.xml.BasicXmlExporter;
44 import jp.sourceforge.mikutoga.xml.XmlResourceResolver;
45
46 /**
47  * 101009形式XMLでPMDモデルデータを出力する。
48  */
49 public class XmlExporter extends BasicXmlExporter{
50
51     private static final String TOP_COMMENT =
52               "  MikuMikuDance\n"
53             + "    model-data(*.pmd) on XML";
54
55     /** 改行文字列 CR。 */
56     private static final String CR = "\r";       // 0x0d
57     /** 改行文字列 LF。 */
58     private static final String LF = "\n";       // 0x0a
59     /** 改行文字列 CRLF。 */
60     private static final String CRLF = CR + LF;  // 0x0d, 0x0a
61
62     private static final String PFX_SURFACEGROUP = "sg";
63     private static final String PFX_TOONFILE = "tf";
64     private static final String PFX_VERTEX = "vtx";
65     private static final String PFX_BONE = "bn";
66     private static final String PFX_RIGID = "rd";
67     private static final String PFX_RIGIDGROUP = "rg";
68
69     private static final String BONETYPE_COMMENT =
70           "Bone types:\n"
71         + "[0 : ROTATE      : Rotate       : 回転           :]\n"
72         + "[1 : ROTMOV      : Rotate/Move  : 回転/移動      :]\n"
73         + "[2 : IK          : IK           : IK             :]\n"
74         + "[3 : UNKNOWN     : Unknown      : 不明           :]\n"
75         + "[4 : UNDERIK     : Under IK     : IK影響下(回転) :]\n"
76         + "[5 : UNDERROT    : Under rotate : 回転影響下     :]\n"
77         + "[6 : IKCONNECTED : IK connected : IK接続先       :]\n"
78         + "[7 : HIDDEN      : Hidden       : 非表示         :]\n"
79         + "[8 : TWIST       : Twist        : 捩り           :]\n"
80         + "[9 : LINKEDROT   : Linked Rotate: 回転連動       :]\n";
81
82     private static final String MORPHTYPE_COMMENT =
83           "Morph types:\n"
84         + "[1 : EYEBROW : まゆ   ]\n"
85         + "[2 : EYE     : 目     ]\n"
86         + "[3 : LIP     : リップ ]\n"
87         + "[4 : EXTRA   : その他 ]\n";
88
89     private static final String RIGIDBEHAVIOR_COMMENT =
90           "Rigid behavior types:\n"
91         + "[0 : FOLLOWBONE    : ボーン追従       ]\n"
92         + "[1 : ONLYDYNAMICS  : 物理演算         ]\n"
93         + "[2 : BONEDDYNAMICS : ボーン位置合わせ ]\n";
94
95     private static final Locale DEF_LOCALE = Locale.JAPANESE;
96
97
98     private String generator = null;
99
100     private XmlModelFileType xmlType = XmlModelFileType.XML_101009;
101
102
103     /**
104      * コンストラクタ。
105      * 文字エンコーディングはUTF-8が用いられる。
106      * @param stream 出力ストリーム
107      */
108     public XmlExporter(OutputStream stream){
109         super(stream);
110         return;
111     }
112
113     /**
114      * 出力XMLファイル種別を返す。
115      * @return ファイル種別
116      */
117     public XmlModelFileType getXmlFileType(){
118         return this.xmlType;
119     }
120
121     /**
122      * 出力XMLファイル種別を設定する。
123      * @param type ファイル種別
124      */
125     public void setXmlFileType(XmlModelFileType type){
126         switch(type){
127         case XML_101009:
128         case XML_130128:
129             this.xmlType = type;
130             break;
131         case XML_AUTO:
132             this.xmlType = XmlModelFileType.XML_130128;
133             break;
134         default:
135             throw new IllegalArgumentException();
136         }
137
138         assert this.xmlType == XmlModelFileType.XML_101009
139             || this.xmlType == XmlModelFileType.XML_130128;
140
141         return;
142     }
143
144     /**
145      * Generatorメタ情報を設定する。
146      * @param generatorArg Generatorメタ情報。表示したくないときはnull
147      */
148     public void setGenerator(String generatorArg){
149         this.generator = generatorArg;
150         return;
151     }
152
153     /**
154      * 任意の文字列がBasicLatin文字のみから構成されるか判定する。
155      * @param seq 文字列
156      * @return null、長さ0もしくはBasicLatin文字のみから構成されるならtrue
157      */
158     public static boolean hasOnlyBasicLatin(CharSequence seq){
159         if(seq == null) return true;
160         int length = seq.length();
161         for(int pos = 0; pos < length; pos++){
162             char ch = seq.charAt(pos);
163             if(ch > 0x007f) return false;
164         }
165         return true;
166     }
167
168     /**
169      * {@inheritDoc}
170      * @return {@inheritDoc}
171      * @throws IOException {@inheritDoc}
172      */
173     @Override
174     public XmlExporter ind() throws IOException{
175         super.ind();
176         return this;
177     }
178
179     /**
180      * 文字参照によるエスケープを補佐するためのコメントを出力する。
181      * @param seq 文字列
182      * @return this本体
183      * @throws IOException 出力エラー
184      */
185     protected XmlExporter putUnescapedComment(CharSequence seq)
186             throws IOException{
187         if( ! isBasicLatinOnlyOut() ) return this;
188         if(hasOnlyBasicLatin(seq)) return this;
189         sp().putLineComment(seq);
190         return this;
191     }
192
193     /**
194      * 多言語化された各種識別名を出力する。
195      * プライマリ名は出力対象外。
196      * @param text 多言語文字列
197      * @return this本体
198      * @throws IOException 出力エラー
199      */
200     protected XmlExporter putI18nName(I18nText text) throws IOException{
201         for(String lang639 : text.lang639CodeList()){
202             if(lang639.equals(I18nText.CODE639_PRIMARY)) continue;
203             String name = text.getI18nText(lang639);
204             ind().putRawText("<i18nName ");
205             putAttr("lang", lang639).sp();
206             putAttr("name", name);
207             putRawText(" />");
208             putUnescapedComment(name);
209             ln();
210         }
211         return this;
212     }
213
214     /**
215      * 番号付けされたID(IDREF)属性を出力する。
216      * @param attrName 属性名
217      * @param prefix IDプレフィクス
218      * @param num 番号
219      * @return this本体
220      * @throws IOException 出力エラー
221      */
222     protected XmlExporter putNumberedIdAttr(CharSequence attrName,
223                                                  CharSequence prefix,
224                                                  int num )
225             throws IOException{
226         putRawText(attrName).putRawText("=\"");
227         putRawText(prefix).putXsdInt(num);
228         putRawCh('"');
229         return this;
230     }
231
232     /**
233      * 番号付けされたID(IDREF)属性を出力する。
234      * @param attrName 属性名
235      * @param prefix IDプレフィクス
236      * @param numbered 番号付けされたオブジェクト
237      * @return this本体
238      * @throws IOException 出力エラー
239      */
240     protected XmlExporter putNumberedIdAttr(CharSequence attrName,
241                                                  CharSequence prefix,
242                                                  SerialNumbered numbered )
243             throws IOException{
244         putNumberedIdAttr(attrName, prefix, numbered.getSerialNumber());
245         return this;
246     }
247
248     /**
249      * 位置情報を出力する。
250      * @param position 位置情報
251      * @return this本体
252      * @throws IOException 出力エラー
253      */
254     protected XmlExporter putPosition(MkPos3D position)
255             throws IOException{
256         putRawText("<position ");
257         putFloatAttr("x", (float) position.getXpos()).sp();
258         putFloatAttr("y", (float) position.getYpos()).sp();
259         putFloatAttr("z", (float) position.getZpos()).sp();
260         putRawText("/>");
261         return this;
262     }
263
264     /**
265      * 姿勢情報(ラジアン)を出力する。
266      * @param rotation 姿勢情報
267      * @return this本体
268      * @throws IOException 出力エラー
269      */
270     protected XmlExporter putRadRotation(Rad3d rotation)
271             throws IOException{
272         putRawText("<radRotation ");
273         putFloatAttr("xRad", rotation.getXRad()).sp();
274         putFloatAttr("yRad", rotation.getYRad()).sp();
275         putFloatAttr("zRad", rotation.getZRad()).sp();
276         putRawText("/>");
277         return this;
278     }
279
280     /**
281      * 多言語識別名属性のローカルな名前をコメント出力する。
282      * @param name 多言語識別名
283      * @return this本体
284      * @throws IOException 出力エラー
285      */
286     protected XmlExporter putLocalNameComment(I18nText name)
287             throws IOException{
288         String localName = name.getText();
289         if(localName.isEmpty()){
290             localName = "[NAMELESS]";
291         }
292         ind().putLineComment(localName);
293         return this;
294     }
295
296     /**
297      * 多言語識別名属性のプライマリな名前を出力する。
298      * @param attrName 属性名
299      * @param name 多言語識別名
300      * @return this本体
301      * @throws IOException 出力エラー
302      */
303     protected XmlExporter putPrimaryNameAttr(CharSequence attrName,
304                                                    I18nText name)
305             throws IOException{
306         String primaryName = name.getPrimaryText();
307         putAttr(attrName, primaryName);
308         return this;
309     }
310
311     /**
312      * PMDモデルデータをXML形式で出力する。
313      * @param model PMDモデルデータ
314      * @throws IOException 出力エラー
315      */
316     public void putPmdModel(PmdModel model) throws IOException{
317         ind().putRawText("<?xml")
318                 .sp().putAttr("version","1.0")
319                 .sp().putAttr("encoding","UTF-8")
320                 .sp().putRawText("?>").ln(2);
321
322         ind().putBlockComment(TOP_COMMENT).ln(2);
323
324         I18nText modelName = model.getModelName();
325         ind().putLocalNameComment(modelName).ln();
326         ind().putRawText("<pmdModel").ln();
327         pushNest();
328
329         String defns;
330         String xsduri;
331         String version;
332         if(this.xmlType == XmlModelFileType.XML_101009){
333             defns   = Schema101009.NS_PMDXML;
334             xsduri  = Schema101009.SCHEMA_PMDXML;
335             version = Schema101009.VER_PMDXML;
336         }else if(this.xmlType == XmlModelFileType.XML_130128){
337             defns   = Schema130128.NS_PMDXML;
338             xsduri  = Schema130128.SCHEMA_PMDXML;
339             version = Schema130128.VER_PMDXML;
340         }else{
341             assert false;
342             throw new AssertionError();
343         }
344
345         ind().putAttr("xmlns", defns).ln();
346
347         ind().putAttr("xmlns:xsi", XmlResourceResolver.NS_XSD).ln();
348
349         ind().putRawText("xsi:schemaLocation").putRawText("=\"");
350         putRawText(defns).ln();
351         pushNest();
352         ind().putRawText(xsduri).putRawCh('"').ln();
353         popNest();
354
355         ind().putAttr("schemaVersion", version).ln(2);
356         ind().putPrimaryNameAttr("name", modelName).ln();
357
358         popNest();
359         putRawText(">").ln(2);
360
361         putModelInfo(model).flush();
362         putMetaInfo(model).flush();
363         putMaterialList(model).flush();
364         putToonMap(model).flush();
365         putBoneList(model).flush();
366         putBoneGroupList(model).flush();
367         putIKChainList(model).flush();
368         putMorphList(model).flush();
369         putRigidList(model).flush();
370         putRigidGroupList(model).flush();
371         putJointList(model).flush();
372         putSurfaceGroupList(model).flush();
373         putVertexList(model).flush();
374
375         ind().putRawText("</pmdModel>").ln(2);
376         ind().putRawText("<!-- EOF -->").ln();
377
378         return;
379     }
380
381     /**
382      * モデル基本情報を出力する。
383      * @param model モデル情報
384      * @return this本体
385      * @throws IOException 出力エラー
386      */
387     private XmlExporter putModelInfo(PmdModel model)
388             throws IOException{
389         I18nText modelName = model.getModelName();
390         putI18nName(modelName);
391         ln();
392
393         I18nText description = model.getDescription();
394         for(String lang639 : description.lang639CodeList()){
395             String descText = description.getI18nText(lang639);
396             putDescription(lang639, descText);
397             ln();
398         }
399
400         return this;
401     }
402
403     /**
404      * モデル詳細テキストを出力する。
405      * @param lang639 言語コード
406      * @param content 詳細内容
407      * @return this本体
408      * @throws IOException 出力エラー
409      */
410     private XmlExporter putDescription(CharSequence lang639,
411                                               CharSequence content)
412             throws IOException{
413         String text = content.toString();
414         text = text.replace(CRLF, LF);
415         text = text.replace(CR,   LF);
416
417         ind().putRawText("<description");
418         if( ! I18nText.CODE639_PRIMARY.equals(lang639) ){
419             sp().putAttr("lang", lang639).sp();
420         }
421         putRawText(">").ln();
422
423         putBRedContent(text);
424
425         ind().putRawText("</description>").ln();
426
427         if( ! hasOnlyBasicLatin(text) && isBasicLatinOnlyOut() ){
428             putBlockComment(text);
429         }
430
431         return this;
432     }
433
434     /**
435      * break要素を含む要素内容を出力する。
436      * 必要に応じてXML定義済み実体文字が割り振られた文字、
437      * コントロールコード、および非BasicLatin文字がエスケープされる。
438      * \nはbrタグに変換される。
439      * @param content 内容
440      * @return this本体
441      * @throws IOException 出力エラー
442      */
443     protected BasicXmlExporter putBRedContent(CharSequence content)
444             throws IOException{
445         int length = content.length();
446
447         int startPos = 0;
448
449         for(int idx = 0; idx < length; idx++){
450             char ch = content.charAt(idx);
451             if(ch == '\n'){
452                 CharSequence seq = content.subSequence(startPos, idx);
453                 putContent(seq).putRawText("<br/>").ln();
454                 startPos = idx + 1;
455             }
456         }
457
458         if(startPos < length){
459             CharSequence seq = content.subSequence(startPos, length);
460             putContent(seq).ln();
461         }
462
463         return this;
464     }
465
466     /**
467      * 各種メタ情報を出力する。
468      * @param model モデルデータ
469      * @return this本体
470      * @throws IOException 出力エラー
471      */
472     private XmlExporter putMetaInfo(PmdModel model) throws IOException{
473         ind().putRawText("<license>").ln();
474         ind().putRawText("</license>").ln(2);
475
476         ind().putRawText("<credits>").ln();
477         ind().putRawText("</credits>").ln(2);
478
479         if(this.generator != null){
480             ind().putRawText("<meta ");
481             putAttr("name", "generator").sp()
482                                         .putAttr("content", this.generator);
483             putRawText(" />").ln();
484         }
485
486         ind().putRawText("<meta ");
487         putAttr("name", "siteURL").sp().putAttr("content", "");
488         putRawText(" />").ln();
489         ind().putRawText("<meta ");
490         putAttr("name", "imageURL").sp().putAttr("content", "");
491         putRawText(" />").ln(2);
492
493         return this;
494     }
495
496     /**
497      * マテリアル素材一覧を出力する。
498      * @param model モデルデータ
499      * @return this本体
500      * @throws IOException 出力エラー
501      */
502     private XmlExporter putMaterialList(PmdModel model)
503             throws IOException{
504         ind().putRawText("<materialList>").ln();
505
506         pushNest();
507         int ct = 0;
508         boolean dumped = false;
509         List<Material> materialList = model.getMaterialList();
510         for(Material material : materialList){
511             if( ! dumped ) ln();
512             putMaterial(material, ct++);
513             dumped = true;
514         }
515         popNest();
516
517         ind().putRawText("</materialList>").ln(2);
518
519         return this;
520     }
521
522     /**
523      * マテリアル素材情報を出力する。
524      * @param material マテリアル素材
525      * @param no マテリアル通し番号
526      * @return this本体
527      * @throws IOException 出力エラー
528      */
529     private XmlExporter putMaterial(Material material, int no)
530             throws IOException{
531         String bool;
532         if(material.getEdgeAppearance()) bool = "true";
533         else                             bool = "false";
534         I18nText name = material.getMaterialName();
535         String primary = name.getPrimaryText();
536         String local = name.getText();
537
538         if(local != null && local.length() > 0){
539             ind().putLineComment(local).ln();
540         }
541         ind().putRawText("<material ");
542         if(primary != null && primary.length() > 0){
543             putAttr("name", primary).sp();
544         }
545
546         putAttr("showEdge", bool);
547         sp();
548         putNumberedIdAttr("surfaceGroupIdRef", PFX_SURFACEGROUP, no);
549         sp().putRawCh('>').ln();
550         pushNest();
551
552         putI18nName(name);
553
554         float[] rgba = new float[4];
555
556         Color diffuse = material.getDiffuseColor();
557         diffuse.getRGBComponents(rgba);
558         ind().putRawText("<diffuse ");
559         putFloatAttr("r", rgba[0]).sp();
560         putFloatAttr("g", rgba[1]).sp();
561         putFloatAttr("b", rgba[2]).sp();
562         putFloatAttr("alpha", rgba[3]).sp();
563         putRawText("/>").ln();
564
565         Color specular = material.getSpecularColor();
566         specular.getRGBComponents(rgba);
567         float shininess = material.getShininess();
568         ind().putRawText("<specular ");
569         putFloatAttr("r", rgba[0]).sp();
570         putFloatAttr("g", rgba[1]).sp();
571         putFloatAttr("b", rgba[2]).sp();
572         putFloatAttr("shininess", shininess).sp();
573         putRawText("/>").ln();
574
575         Color ambient = material.getAmbientColor();
576         ambient.getRGBComponents(rgba);
577         ind().putRawText("<ambient ");
578         putFloatAttr("r", rgba[0]).sp();
579         putFloatAttr("g", rgba[1]).sp();
580         putFloatAttr("b", rgba[2]).sp();
581         putRawText("/>").ln();
582
583         ShadeInfo shade = material.getShadeInfo();
584         String textureFileName = shade.getTextureFileName();
585         String spheremapFileName = shade.getSpheremapFileName();
586
587         if(shade.isValidToonIndex()){
588             ind().putRawText("<toon ");
589             int toonIdx = shade.getToonIndex();
590             putNumberedIdAttr("toonFileIdRef", PFX_TOONFILE, toonIdx);
591             putRawText(" />");
592             String toonFileName = shade.getToonFileName();
593             if(toonFileName != null && toonFileName.length() > 0){
594                 sp().putLineComment(toonFileName);
595             }
596             ln();
597         }
598
599         if(textureFileName != null && textureFileName.length() > 0){
600             ind().putRawText("<textureFile ");
601             putAttr("winFileName", textureFileName);
602             putRawText(" />").ln();
603         }
604
605         if(spheremapFileName != null && spheremapFileName.length() > 0){
606             ind().putRawText("<spheremapFile ");
607             putAttr("winFileName", spheremapFileName);
608             putRawText(" />").ln();
609         }
610
611         popNest();
612         ind().putRawText("</material>").ln(2);
613
614         return this;
615     }
616
617     /**
618      * トゥーンファイルマッピング情報を出力する。
619      * @param model モデルデータ
620      * @return this本体
621      * @throws IOException 出力エラー
622      */
623     private XmlExporter putToonMap(PmdModel model)
624             throws IOException{
625         ind().putRawText("<toonMap>").ln();
626         pushNest();
627
628         ToonMap map = model.getToonMap();
629         for(int index = 0; index <= 9; index++){
630             ind().putToon(map, index).ln();
631         }
632
633         popNest();
634         ind().putRawText("</toonMap>").ln(2);
635         return this;
636     }
637
638     /**
639      * 個別のトゥーンファイル情報を出力する。
640      * @param map トゥーンマップ
641      * @param index インデックス値
642      * @return this本体
643      * @throws IOException 出力エラー
644      */
645     private XmlExporter putToon(ToonMap map, int index)
646             throws IOException{
647         putRawText("<toonDef ");
648         putNumberedIdAttr("toonFileId", PFX_TOONFILE, index).sp();
649         putIntAttr("index", index).sp();
650         String toonFile = map.getIndexedToon(index);
651         putAttr("winFileName", toonFile);
652         putRawText(" />");
653         putUnescapedComment(toonFile);
654         return this;
655     }
656
657     /**
658      * サーフェイスグループリストを出力する。
659      * @param model モデルデータ
660      * @return this本体
661      * @throws IOException 出力エラー
662      */
663     private XmlExporter putSurfaceGroupList(PmdModel model)
664             throws IOException{
665         ind().putRawText("<surfaceGroupList>").ln();
666
667         pushNest();
668         int ct = 0;
669         boolean dumped = false;
670         List<Material> materialList = model.getMaterialList();
671         for(Material material : materialList){
672             List<Surface> surfaceList = material.getSurfaceList();
673             if( ! dumped ) ln();
674             putSurfaceList(surfaceList, ct++);
675             dumped = true;
676         }
677         popNest();
678
679         ind().putRawText("</surfaceGroupList>").ln(2);
680
681         return this;
682     }
683
684     /**
685      * 個別のサーフェイスグループを出力する。
686      * @param surfaceList サーフェイスのリスト
687      * @param index グループインデックス
688      * @return this本体
689      * @throws IOException 出力エラー
690      */
691     private XmlExporter putSurfaceList(List<Surface> surfaceList,
692                                               int index)
693             throws IOException{
694         ind().putRawText("<surfaceGroup ");
695         putNumberedIdAttr("surfaceGroupId", PFX_SURFACEGROUP, index);
696         sp().putRawText(">").ln();
697         pushNest();
698
699         for(Surface surface : surfaceList){
700             putSurface(surface);
701         }
702
703         popNest();
704         ind().putRawText("</surfaceGroup>").ln(2);
705
706         return this;
707     }
708
709     /**
710      * 個別のサーフェイスを出力する。
711      * @param surface サーフェイス
712      * @return this本体
713      * @throws IOException 出力エラー
714      */
715     private XmlExporter putSurface(Surface surface)
716             throws IOException{
717         ind().putRawText("<surface ");
718
719         Vertex vertex1 = surface.getVertex1();
720         Vertex vertex2 = surface.getVertex2();
721         Vertex vertex3 = surface.getVertex3();
722
723         putNumberedIdAttr("vtxIdRef1", PFX_VERTEX, vertex1).sp();
724         putNumberedIdAttr("vtxIdRef2", PFX_VERTEX, vertex2).sp();
725         putNumberedIdAttr("vtxIdRef3", PFX_VERTEX, vertex3).sp();
726
727         putRawText("/>").ln();
728         return this;
729     }
730
731     /**
732      * 頂点リストを出力する。
733      * @param model モデルデータ
734      * @return this本体
735      * @throws IOException 出力エラー
736      */
737     private XmlExporter putVertexList(PmdModel model)
738             throws IOException{
739         ind().putRawText("<vertexList>").ln();
740
741         pushNest();
742         boolean dumped = false;
743         List<Vertex> vertexList = model.getVertexList();
744         for(Vertex vertex : vertexList){
745             if( ! dumped ) ln();
746             putVertex(vertex);
747             dumped = true;
748         }
749         popNest();
750
751         ind().putRawText("</vertexList>").ln(2);
752
753         return this;
754     }
755
756     /**
757      * 個別の頂点情報を出力する。
758      * @param vertex 頂点
759      * @return this本体
760      * @throws IOException 出力エラー
761      */
762     private XmlExporter putVertex(Vertex vertex)
763             throws IOException{
764         String bool;
765         if(vertex.getEdgeAppearance()) bool = "true";
766         else                           bool = "false";
767
768         ind().putRawText("<vertex ");
769         putNumberedIdAttr("vtxId", PFX_VERTEX, vertex).sp();
770         putAttr("showEdge", bool);
771         sp().putRawText(">").ln();
772         pushNest();
773
774         MkPos3D position = vertex.getPosition();
775         ind().putPosition(position).ln();
776
777         MkVec3D normal = vertex.getNormal();
778         ind().putRawText("<normal ");
779         putFloatAttr("x", (float) normal.getXVal()).sp();
780         putFloatAttr("y", (float) normal.getYVal()).sp();
781         putFloatAttr("z", (float) normal.getZVal()).sp();
782         putRawText("/>").ln();
783
784         MkPos2D uvPos = vertex.getUVPosition();
785         ind().putRawText("<uvMap ");
786         putFloatAttr("u", (float) uvPos.getXpos()).sp();
787         putFloatAttr("v", (float) uvPos.getYpos()).sp();
788         putRawText("/>").ln();
789
790         BoneInfo boneA = vertex.getBoneA();
791         BoneInfo boneB = vertex.getBoneB();
792         int weight = vertex.getWeightA();
793         ind().putRawText("<skinning ");
794         putNumberedIdAttr("boneIdRef1", PFX_BONE, boneA).sp();
795         putNumberedIdAttr("boneIdRef2", PFX_BONE, boneB).sp();
796         putIntAttr("weightBalance", weight).sp();
797         putRawText("/>").ln();
798
799         popNest();
800         ind().putRawText("</vertex>").ln(2);
801
802         return this;
803     }
804
805     /**
806      * ボーンリストを出力する。
807      * @param model モデルデータ
808      * @return this本体
809      * @throws IOException 出力エラー
810      */
811     private XmlExporter putBoneList(PmdModel model)
812             throws IOException{
813         ind().putRawText("<boneList>").ln();
814         pushNest();
815
816         boolean dumped = false;
817         for(BoneInfo bone : model.getBoneList()){
818             if( ! dumped ){
819                 ln().putBlockComment(BONETYPE_COMMENT).ln();
820             }
821             putBone(bone);
822             dumped = true;
823         }
824
825         popNest();
826         ind().putRawText("</boneList>").ln(2);
827
828         return this;
829     }
830
831     /**
832      * 個別のボーン情報を出力する。
833      * @param bone ボーン情報
834      * @return this本体
835      * @throws IOException 出力エラー
836      */
837     private XmlExporter putBone(BoneInfo bone)
838             throws IOException{
839         I18nText i18nName = bone.getBoneName();
840         BoneType type = bone.getBoneType();
841
842         StringBuilder boneComment = new StringBuilder();
843         String boneName = i18nName.getText();
844         if(boneName.isEmpty()){
845             boneName = "[NAMELESS]";
846         }
847         boneComment.append(boneName);
848         String typeName = type.getGuiName(DEF_LOCALE);
849         boneComment.append(" [").append(typeName).append(']');
850         ind().putLineComment(boneComment.toString()).ln();
851
852         ind().putRawText("<bone ");
853         putPrimaryNameAttr("name", i18nName).sp();
854         putNumberedIdAttr("boneId", PFX_BONE, bone).sp();
855         putAttr("type", type.name());
856         sp().putRawText(">").ln();
857         pushNest();
858
859         putI18nName(i18nName);
860
861         MkPos3D position = bone.getPosition();
862         ind().putPosition(position).ln();
863
864         BoneInfo srcBone = bone.getSrcBone();
865         if(bone.getBoneType() == BoneType.LINKEDROT){
866             ind().putRawText("<rotationRatio ");
867             putIntAttr("ratio", bone.getRotationRatio());
868             putRawText(" />").ln();
869         }else if(srcBone != null){
870             String iktag;
871             switch(getXmlFileType()){
872             case XML_101009:
873                 iktag = "<ikBone ";
874                 break;
875             case XML_130128:
876                 iktag = "<sourceBone ";
877                 break;
878             default:
879                 assert false;
880                 throw new AssertionError();
881             }
882             ind().putRawText(iktag);
883             putNumberedIdAttr("boneIdRef", PFX_BONE, srcBone);
884             putRawText(" /> ");
885             String ikBoneName = "Ref:" + srcBone.getBoneName().getText();
886             putLineComment(ikBoneName);
887             ln();
888         }
889
890         BoneInfo prev = bone.getPrevBone();
891         BoneInfo next = bone.getNextBone();
892
893         StringBuilder chainComment = new StringBuilder();
894         if(prev != null){
895             chainComment.append('[')
896                         .append(prev.getBoneName().getPrimaryText())
897                         .append(']')
898                         .append(" >>#");
899         }
900         if(next != null){
901             if(chainComment.length() <= 0) chainComment.append("#");
902             chainComment.append(">> ")
903                         .append('[')
904                         .append(next.getBoneName().getPrimaryText())
905                         .append(']');
906         }
907         if(chainComment.length() > 0){
908             ln();
909             ind().putLineComment(chainComment).ln();
910         }
911
912         ind().putRawText("<boneChain");
913         if(prev != null){
914             sp();
915             putNumberedIdAttr("prevBoneIdRef", PFX_BONE, prev);
916         }
917         if(next != null){
918             sp();
919             putNumberedIdAttr("nextBoneIdRef", PFX_BONE, next);
920         }
921         putRawText(" />").ln();
922
923         popNest();
924         ind().putRawText("</bone>").ln(2);
925
926         return this;
927     }
928
929     /**
930      * ボーングループリストを出力する。
931      * @param model モデルデータ
932      * @return this本体
933      * @throws IOException 出力エラー
934      */
935     private XmlExporter putBoneGroupList(PmdModel model)
936             throws IOException{
937         ind().putRawText("<boneGroupList>").ln();
938
939         pushNest();
940         boolean dumped = false;
941         List<BoneGroup> groupList = model.getBoneGroupList();
942         for(BoneGroup group : groupList){
943             if(group.isDefaultBoneGroup()) continue;
944             if( ! dumped ) ln();
945             putBoneGroup(group);
946             dumped = true;
947         }
948         popNest();
949
950         ind().putRawText("</boneGroupList>").ln(2);
951
952         return this;
953     }
954
955     /**
956      * 個別のボーングループ情報を出力する。
957      * @param group ボーングループ情報
958      * @return this本体
959      * @throws IOException 出力エラー
960      */
961     private XmlExporter putBoneGroup(BoneGroup group)
962             throws IOException{
963         I18nText i18nName = group.getGroupName();
964
965         putLocalNameComment(i18nName).ln();
966         ind().putRawText("<boneGroup ");
967         putPrimaryNameAttr("name", i18nName);
968         sp().putRawText(">").ln();
969         pushNest();
970
971         putI18nName(i18nName);
972
973         for(BoneInfo bone : group){
974             ind().putRawText("<boneGroupMember ");
975             putNumberedIdAttr("boneIdRef", PFX_BONE, bone);
976             putRawText(" /> ");
977             String boneName = "Ref:" + bone.getBoneName().getText();
978             putLineComment(boneName).ln();
979         }
980
981         popNest();
982         ind().putRawText("</boneGroup>").ln(2);
983
984         return this;
985     }
986
987     /**
988      * IKチェーンリストを出力する。
989      * @param model モデルデータ
990      * @return this本体
991      * @throws IOException 出力エラー
992      */
993     private XmlExporter putIKChainList(PmdModel model)
994             throws IOException{
995         ind().putRawText("<ikChainList>").ln();
996
997         pushNest();
998         boolean dumped = false;
999         List<IKChain> chainList = model.getIKChainList();
1000         for(IKChain chain : chainList){
1001             if( ! dumped ) ln();
1002             putIKChain(chain);
1003             dumped = true;
1004         }
1005         popNest();
1006
1007         ind().putRawText("</ikChainList>").ln(2);
1008
1009         return this;
1010     }
1011
1012     /**
1013      * 個別のIKチェーン情報を出力する。
1014      * @param chain チェーン情報
1015      * @return this本体
1016      * @throws IOException 出力エラー
1017      */
1018     private XmlExporter putIKChain(IKChain chain)
1019             throws IOException{
1020         int depth = chain.getIKDepth();
1021         float weight = chain.getIKWeight();
1022         BoneInfo ikBone = chain.getIkBone();
1023
1024         ind().putLineComment("Ref:" + ikBone.getBoneName().getText()).ln();
1025         ind().putRawText("<ikChain ");
1026         putNumberedIdAttr("ikBoneIdRef", PFX_BONE, ikBone).sp();
1027         putIntAttr("recursiveDepth", depth).sp();
1028         putFloatAttr("weight", weight);
1029         sp().putRawText(">").ln();
1030         pushNest();
1031
1032         for(BoneInfo bone : chain){
1033             ind().putRawText("<chainOrder ");
1034             putNumberedIdAttr("boneIdRef", PFX_BONE, bone);
1035             putRawText(" /> ");
1036             putLineComment("Ref:" + bone.getBoneName().getText());
1037             ln();
1038         }
1039
1040         popNest();
1041         ind().putRawText("</ikChain>").ln(2);
1042
1043         return this;
1044     }
1045
1046     /**
1047      * モーフリストを出力する。
1048      * @param model モデルデータ
1049      * @return this本体
1050      * @throws IOException 出力エラー
1051      */
1052     private XmlExporter putMorphList(PmdModel model)
1053             throws IOException{
1054         ind().putRawText("<morphList>").ln();
1055         pushNest();
1056
1057         boolean dumped = false;
1058         Map<MorphType, List<MorphPart>> morphMap = model.getMorphMap();
1059         for(MorphType type : MorphType.values()){
1060             if(type == MorphType.BASE) continue;
1061             List<MorphPart> partList = morphMap.get(type);
1062             if(partList == null) continue;
1063             for(MorphPart part : partList){
1064                 if( ! dumped ){
1065                     ln().putBlockComment(MORPHTYPE_COMMENT).ln();
1066                 }
1067                 putMorphPart(part);
1068                 dumped = true;
1069             }
1070         }
1071
1072         popNest();
1073         ind().putRawText("</morphList>").ln(2);
1074
1075         return this;
1076     }
1077
1078     /**
1079      * 個別のモーフ情報を出力する。
1080      * @param part モーフ情報
1081      * @return this本体
1082      * @throws IOException 出力エラー
1083      */
1084     private XmlExporter putMorphPart(MorphPart part)
1085             throws IOException{
1086         I18nText i18nName = part.getMorphName();
1087         String primary = i18nName.getPrimaryText();
1088
1089         putLocalNameComment(i18nName).ln();
1090         ind().putRawText("<morph ");
1091         putAttr("name", primary).sp();
1092         putAttr("type", part.getMorphType().name());
1093         sp().putRawText(">");
1094         ln();
1095         pushNest();
1096
1097         putI18nName(i18nName);
1098
1099         for(MorphVertex mvertex : part){
1100             MkPos3D offset = mvertex.getOffset();
1101             Vertex base = mvertex.getBaseVertex();
1102
1103             ind().putRawText("<morphVertex ");
1104             putNumberedIdAttr("vtxIdRef", PFX_VERTEX, base).sp();
1105             putFloatAttr("xOff", (float) offset.getXpos()).sp();
1106             putFloatAttr("yOff", (float) offset.getYpos()).sp();
1107             putFloatAttr("zOff", (float) offset.getZpos()).sp();
1108             putRawText("/>");
1109             ln();
1110         }
1111
1112         popNest();
1113         ind().putRawText("</morph>").ln(2);
1114
1115         return this;
1116     }
1117
1118     /**
1119      * 剛体リストを出力する。
1120      * @param model モデルデータ
1121      * @return this本体
1122      * @throws IOException 出力エラー
1123      */
1124     private XmlExporter putRigidList(PmdModel model)
1125             throws IOException{
1126         ind().putRawText("<rigidList>").ln();
1127         pushNest();
1128
1129         boolean dumped = false;
1130         for(RigidInfo rigid : model.getRigidList()){
1131             if( ! dumped ){
1132                 ln().putBlockComment(RIGIDBEHAVIOR_COMMENT).ln();
1133             }
1134             putRigid(rigid);
1135             dumped = true;
1136         }
1137
1138         popNest();
1139         ind().putRawText("</rigidList>").ln(2);
1140
1141         return this;
1142     }
1143
1144     /**
1145      * 個別の剛体情報を出力する。
1146      * @param rigid 剛体情報
1147      * @return this本体
1148      * @throws IOException 出力エラー
1149      */
1150     private XmlExporter putRigid(RigidInfo rigid)
1151             throws IOException{
1152         BoneInfo linkedBone = rigid.getLinkedBone();
1153         I18nText i18nName = rigid.getRigidName();
1154         String primary = i18nName.getPrimaryText();
1155
1156         putLocalNameComment(i18nName).ln();
1157         ind().putRawText("<rigid ");
1158         putAttr("name", primary).sp();
1159         putNumberedIdAttr("rigidId", PFX_RIGID, rigid).sp();
1160         putAttr("behavior", rigid.getBehaviorType().name());
1161         sp().putRawText(">").ln();
1162         pushNest();
1163
1164         putI18nName(i18nName);
1165
1166         if(linkedBone != null){
1167             ind().putRawText("<linkedBone ");
1168             putNumberedIdAttr("boneIdRef", PFX_BONE, linkedBone);
1169             putRawText(" /> ");
1170             putLineComment("Ref:" + linkedBone.getBoneName().getText());
1171             ln(2);
1172         }
1173
1174         RigidShape shape = rigid.getRigidShape();
1175         putRigidShape(shape);
1176
1177         MkPos3D position = rigid.getPosition();
1178         ind().putPosition(position).ln();
1179
1180         Rad3d rotation = rigid.getRotation();
1181         ind().putRadRotation(rotation).ln();
1182
1183         DynamicsInfo dynamics = rigid.getDynamicsInfo();
1184         putDynamics(dynamics).ln();
1185
1186         for(RigidGroup group : rigid.getThroughGroupColl()){
1187             ind().putRawText("<throughRigidGroup ");
1188             putNumberedIdAttr("rigidGroupIdRef",
1189                               PFX_RIGIDGROUP,
1190                               group.getSerialNumber() + 1).sp();
1191             putRawText(" />").ln();
1192         }
1193
1194         popNest();
1195         ind().putRawText("</rigid>").ln(2);
1196
1197         return this;
1198     }
1199
1200     /**
1201      * 剛体形状を出力する。
1202      * @param shape 剛体形状
1203      * @return this本体
1204      * @throws IOException 出力エラー
1205      */
1206     private XmlExporter putRigidShape(RigidShape shape)
1207             throws IOException{
1208         RigidShapeType type = shape.getShapeType();
1209
1210         switch(type){
1211         case BOX:
1212             ind().putRawText("<rigidShapeBox ");
1213             putFloatAttr("width", shape.getWidth()).sp();
1214             putFloatAttr("height", shape.getHeight()).sp();
1215             putFloatAttr("depth", shape.getDepth()).sp();
1216             break;
1217         case SPHERE:
1218             ind().putRawText("<rigidShapeSphere ");
1219             putFloatAttr("radius", shape.getRadius()).sp();
1220             break;
1221         case CAPSULE:
1222             ind().putRawText("<rigidShapeCapsule ");
1223             putFloatAttr("height", shape.getHeight()).sp();
1224             putFloatAttr("radius", shape.getRadius()).sp();
1225             break;
1226         default:
1227             assert false;
1228             throw new AssertionError();
1229         }
1230
1231         putRawText("/>").ln();
1232
1233         return this;
1234     }
1235
1236     /**
1237      * 力学設定を出力する。
1238      * @param dynamics 力学設定
1239      * @return this本体
1240      * @throws IOException 出力エラー
1241      */
1242     private XmlExporter putDynamics(DynamicsInfo dynamics)
1243             throws IOException{
1244         ind().putRawText("<dynamics").ln();
1245         pushNest();
1246         ind().putFloatAttr("mass", dynamics.getMass()).ln();
1247         ind().putFloatAttr("dampingPosition",
1248                 dynamics.getDampingPosition()).ln();
1249         ind().putFloatAttr("dampingRotation",
1250                 dynamics.getDampingRotation()).ln();
1251         ind().putFloatAttr("restitution", dynamics.getRestitution()).ln();
1252         ind().putFloatAttr("friction", dynamics.getFriction()).ln();
1253         popNest();
1254         ind().putRawText("/>").ln();
1255
1256         return this;
1257     }
1258
1259     /**
1260      * 剛体グループリストを出力する。
1261      * @param model モデルデータ
1262      * @return this本体
1263      * @throws IOException 出力エラー
1264      */
1265     private XmlExporter putRigidGroupList(PmdModel model)
1266             throws IOException{
1267         ind().putRawText("<rigidGroupList>").ln(2);
1268         pushNest();
1269
1270         boolean singleLast = false;
1271         for(RigidGroup group : model.getRigidGroupList()){
1272             List<RigidInfo> rigidList = group.getRigidList();
1273             if(singleLast &&  ! rigidList.isEmpty()){
1274                 ln();
1275             }
1276             ind().putRawText("<rigidGroup ");
1277             putNumberedIdAttr("rigidGroupId",
1278                               PFX_RIGIDGROUP,
1279                               group.getSerialNumber() + 1);
1280             if(rigidList.isEmpty()){
1281                 putRawText(" />").ln();
1282                 singleLast = true;
1283                 continue;
1284             }
1285             putRawText(" >").ln();
1286             pushNest();
1287
1288             for(RigidInfo rigid : rigidList){
1289                 ind().putRawText("<rigidGroupMember ");
1290                 putNumberedIdAttr("rigidIdRef", PFX_RIGID, rigid).sp();
1291                 putRawText("/>");
1292                 sp();
1293                 putLineComment("Ref:" + rigid.getRigidName().getText());
1294                 ln();
1295             }
1296
1297             popNest();
1298             ind().putRawText("</rigidGroup>").ln(2);
1299             singleLast = false;
1300         }
1301
1302         if(singleLast){
1303             ln();
1304         }
1305
1306         popNest();
1307         ind().putRawText("</rigidGroupList>").ln(2);
1308
1309         return this;
1310     }
1311
1312     /**
1313      * ジョイントリストを出力する。
1314      * @param model モデルデータ
1315      * @return this本体
1316      * @throws IOException 出力エラー
1317      */
1318     private XmlExporter putJointList(PmdModel model)
1319             throws IOException{
1320         ind().putRawText("<jointList>").ln();
1321
1322         pushNest();
1323         boolean dumped = false;
1324         List<JointInfo> jointList = model.getJointList();
1325         for(JointInfo joint : jointList){
1326             if( ! dumped ) ln();
1327             putJoint(joint);
1328             dumped = true;
1329         }
1330         popNest();
1331
1332         ind().putRawText("</jointList>").ln(2);
1333
1334         return this;
1335     }
1336
1337     /**
1338      * 個別のジョイント情報を出力する。
1339      * @param joint ジョイント情報
1340      * @return this本体
1341      * @throws IOException 出力エラー
1342      */
1343     private XmlExporter putJoint(JointInfo joint)
1344             throws IOException{
1345         I18nText i18nName = joint.getJointName();
1346
1347         putLocalNameComment(i18nName).ln();
1348         ind().putRawText("<joint ");
1349         putPrimaryNameAttr("name", i18nName);
1350         sp().putRawText(">").ln();
1351         pushNest();
1352
1353         putI18nName(i18nName);
1354
1355         RigidInfo rigidA = joint.getRigidA();
1356         RigidInfo rigidB = joint.getRigidB();
1357
1358         ind();
1359         putLineComment("[" + rigidA.getRigidName().getText() + "]"
1360                 + " <=> [" + rigidB.getRigidName().getText() + "]");
1361         ln();
1362
1363         ind().putRawText("<jointedRigidPair ");
1364         putNumberedIdAttr("rigidIdRef1", PFX_RIGID, rigidA).sp();
1365         putNumberedIdAttr("rigidIdRef2", PFX_RIGID, rigidB).sp();
1366         putRawText("/>").ln(2);
1367
1368         MkPos3D position = joint.getPosition();
1369         ind().putPosition(position).ln();
1370
1371         TripletRange posRange = joint.getPositionRange();
1372         ind().putRawText("<limitPosition").ln();
1373         pushNest();
1374         ind();
1375         putFloatAttr("xFrom", posRange.getXFrom()).sp();
1376         putFloatAttr("xTo",   posRange.getXTo()).ln();
1377         ind();
1378         putFloatAttr("yFrom", posRange.getYFrom()).sp();
1379         putFloatAttr("yTo",   posRange.getYTo()).ln();
1380         ind();
1381         putFloatAttr("zFrom", posRange.getZFrom()).sp();
1382         putFloatAttr("zTo",   posRange.getZTo()).ln();
1383         popNest();
1384         ind().putRawText("/>").ln(2);
1385
1386         Rad3d rotation = joint.getRotation();
1387         ind().putRadRotation(rotation).ln();
1388         TripletRange rotRange = joint.getRotationRange();
1389         ind().putRawText("<limitRotation").ln();
1390         pushNest();
1391         ind();
1392         putFloatAttr("xFrom", rotRange.getXFrom()).sp();
1393         putFloatAttr("xTo",   rotRange.getXTo()).ln();
1394         ind();
1395         putFloatAttr("yFrom", rotRange.getYFrom()).sp();
1396         putFloatAttr("yTo",   rotRange.getYTo()).ln();
1397         ind();
1398         putFloatAttr("zFrom", rotRange.getZFrom()).sp();
1399         putFloatAttr("zTo",   rotRange.getZTo()).ln();
1400         popNest();
1401         ind().putRawText("/>").ln(2);
1402
1403         MkPos3D elaPosition = joint.getElasticPosition();
1404         ind().putRawText("<elasticPosition ");
1405         putFloatAttr("x", (float) elaPosition.getXpos()).sp();
1406         putFloatAttr("y", (float) elaPosition.getYpos()).sp();
1407         putFloatAttr("z", (float) elaPosition.getZpos()).sp();
1408         putRawText("/>").ln();
1409
1410         Deg3d elaRotation = joint.getElasticRotation();
1411         ind().putRawText("<elasticRotation ");
1412         putFloatAttr("xDeg", elaRotation.getXDeg()).sp();
1413         putFloatAttr("yDeg", elaRotation.getYDeg()).sp();
1414         putFloatAttr("zDeg", elaRotation.getZDeg()).sp();
1415         putRawText("/>").ln(2);
1416
1417         popNest();
1418         ind().putRawText("</joint>").ln(2);
1419
1420         return this;
1421     }
1422
1423 }