OSDN Git Service

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