OSDN Git Service

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