OSDN Git Service

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