OSDN Git Service

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