OSDN Git Service

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