OSDN Git Service

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