OSDN Git Service

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