OSDN Git Service

1.105.3-SNAPSHOT版開発開始
[mikutoga/TogaGem.git] / src / main / java / jp / sourceforge / mikutoga / pmd / xml / PmdXmlExporter.java
index 65100b4..4e682c5 100644 (file)
-/*\r
- * pmd-xml exporter\r
- *\r
- * License : The MIT License\r
- * Copyright(c) 2010 MikuToga Partners\r
- */\r
-\r
-package jp.sourceforge.mikutoga.pmd.xml;\r
-\r
-import java.awt.Color;\r
-import java.io.IOException;\r
-import java.io.OutputStream;\r
-import java.util.List;\r
-import java.util.Map;\r
-import jp.sourceforge.mikutoga.corelib.I18nText;\r
-import jp.sourceforge.mikutoga.corelib.SerialNumbered;\r
-import jp.sourceforge.mikutoga.pmd.BoneGroup;\r
-import jp.sourceforge.mikutoga.pmd.BoneInfo;\r
-import jp.sourceforge.mikutoga.pmd.BoneType;\r
-import jp.sourceforge.mikutoga.pmd.Deg3d;\r
-import jp.sourceforge.mikutoga.pmd.DynamicsInfo;\r
-import jp.sourceforge.mikutoga.pmd.IKChain;\r
-import jp.sourceforge.mikutoga.pmd.JointInfo;\r
-import jp.sourceforge.mikutoga.pmd.Material;\r
-import jp.sourceforge.mikutoga.pmd.MorphPart;\r
-import jp.sourceforge.mikutoga.pmd.MorphType;\r
-import jp.sourceforge.mikutoga.pmd.MorphVertex;\r
-import jp.sourceforge.mikutoga.pmd.PmdModel;\r
-import jp.sourceforge.mikutoga.pmd.Pos2d;\r
-import jp.sourceforge.mikutoga.pmd.Pos3d;\r
-import jp.sourceforge.mikutoga.pmd.Rad3d;\r
-import jp.sourceforge.mikutoga.pmd.RigidGroup;\r
-import jp.sourceforge.mikutoga.pmd.RigidInfo;\r
-import jp.sourceforge.mikutoga.pmd.RigidShape;\r
-import jp.sourceforge.mikutoga.pmd.RigidShapeType;\r
-import jp.sourceforge.mikutoga.pmd.ShadeInfo;\r
-import jp.sourceforge.mikutoga.pmd.Surface;\r
-import jp.sourceforge.mikutoga.pmd.ToonMap;\r
-import jp.sourceforge.mikutoga.pmd.TripletRange;\r
-import jp.sourceforge.mikutoga.pmd.Vec3d;\r
-import jp.sourceforge.mikutoga.pmd.Vertex;\r
-import jp.sourceforge.mikutoga.xml.BasicXmlExporter;\r
-import jp.sourceforge.mikutoga.xml.XmlResourceResolver;\r
-\r
-/**\r
- * XML形式でPMDモデルデータを出力する。\r
- */\r
-public class PmdXmlExporter extends BasicXmlExporter{\r
-\r
-    private static final String TOP_COMMENT =\r
-            "  MikuMikuDance\n    model-data(*.pmd) on XML";\r
-    private static final String SCHEMA_LOCATION =\r
-            PmdXmlResources.NS_PMDXML + " " + PmdXmlResources.SCHEMA_PMDXML;\r
-\r
-    /** 改行文字列 CR。 */\r
-    private static final String CR = "\r";       // 0x0d\r
-    /** 改行文字列 LF。 */\r
-    private static final String LF = "\n";       // 0x0a\r
-    /** 改行文字列 CRLF。 */\r
-    private static final String CRLF = CR + LF;  // 0x0d, 0x0a\r
-\r
-    private static final String PFX_SURFACEGROUP = "sg";\r
-    private static final String PFX_TOONFILE = "tf";\r
-    private static final String PFX_VERTEX = "vtx";\r
-    private static final String PFX_BONE = "bn";\r
-    private static final String PFX_RIGID = "rd";\r
-    private static final String PFX_RIGIDGROUP = "rg";\r
-\r
-    private static final String BONETYPE_COMMENT =\r
-          "Bone types:\n"\r
-        + "[0 : ROTATE      : Rotate       : 回転           :]\n"\r
-        + "[1 : ROTMOV      : Rotate/Move  : 回転/移動      :]\n"\r
-        + "[2 : IK          : IK           : IK             :]\n"\r
-        + "[3 : UNKNOWN     : Unknown      : 不明           :]\n"\r
-        + "[4 : UNDERIK     : Under IK     : IK影響下(回転) :]\n"\r
-        + "[5 : UNDERROT    : Under rotate : 回転影響下     :]\n"\r
-        + "[6 : IKCONNECTED : IK connected : IK接続先       :]\n"\r
-        + "[7 : HIDDEN      : Hidden       : 非表示         :]\n"\r
-        + "[8 : TWIST       : Twist        : 捩り           :]\n"\r
-        + "[9 : LINKEDROT   : Linked Rotate: 回転連動       :]\n";\r
-\r
-    private static final String MORPHTYPE_COMMENT =\r
-          "Morph types:\n"\r
-        + "[1 : EYEBROW : まゆ   ]\n"\r
-        + "[2 : EYE     : 目     ]\n"\r
-        + "[3 : LIP     : リップ ]\n"\r
-        + "[4 : EXTRA   : その他 ]\n";\r
-\r
-    private static final String RIGIDBEHAVIOR_COMMENT =\r
-          "Rigid behavior types:\n"\r
-        + "[0 : FOLLOWBONE    : ボーン追従       ]\n"\r
-        + "[1 : ONLYDYNAMICS  : 物理演算         ]\n"\r
-        + "[2 : BONEDDYNAMICS : ボーン位置合わせ ]\n";\r
-\r
-    private String generator = "";\r
-\r
-    /**\r
-     * コンストラクタ。\r
-     * 文字エンコーディングはUTF-8が用いられる。\r
-     * @param stream 出力ストリーム\r
-     */\r
-    public PmdXmlExporter(OutputStream stream){\r
-        super(stream);\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * Generatorメタ情報を設定する。\r
-     * @param generatorArg Generatorメタ情報\r
-     * @throws NullPointerException 引数がnull\r
-     */\r
-    public void setGenerator(String generatorArg)\r
-            throws NullPointerException{\r
-        if(generatorArg == null) throw new NullPointerException();\r
-        this.generator = generatorArg;\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * 任意の文字列がBasicLatin文字のみから構成されるか判定する。\r
-     * @param seq 文字列\r
-     * @return null、長さ0もしくはBasicLatin文字のみから構成されるならtrue\r
-     */\r
-    public static boolean hasOnlyBasicLatin(CharSequence seq){\r
-        if(seq == null) return true;\r
-        int length = seq.length();\r
-        for(int pos = 0; pos < length; pos++){\r
-            char ch = seq.charAt(pos);\r
-            if(ch > 0x007f) return false;\r
-        }\r
-        return true;\r
-    }\r
-\r
-    /**\r
-     * {@inheritDoc}\r
-     * @return {@inheritDoc}\r
-     * @throws IOException {@inheritDoc}\r
-     */\r
-    @Override\r
-    public PmdXmlExporter ind() throws IOException{\r
-        super.ind();\r
-        return this;\r
-    }\r
-\r
-    /**\r
-     * 文字参照によるエスケープを補佐するためのコメントを出力する。\r
-     * @param seq 文字列\r
-     * @return this本体\r
-     * @throws IOException 出力エラー\r
-     */\r
-    protected PmdXmlExporter putUnescapedComment(CharSequence seq)\r
-            throws IOException{\r
-        if( ! isBasicLatinOnlyOut() ) return this;\r
-        if(hasOnlyBasicLatin(seq)) return this;\r
-        put(' ').putLineComment(seq);\r
-        return this;\r
-    }\r
-\r
-    /**\r
-     * 多言語化された各種識別名を出力する。\r
-     * プライマリ名は出力対象外。\r
-     * @param text 多言語文字列\r
-     * @return this本体\r
-     * @throws IOException 出力エラー\r
-     */\r
-    protected PmdXmlExporter putI18nName(I18nText text) throws IOException{\r
-        for(String lang639 : text.lang639CodeList()){\r
-            if(lang639.equals(I18nText.CODE639_PRIMARY)) continue;\r
-            String name = text.getText(lang639);\r
-            ind().put("<i18nName ");\r
-            putAttr("lang", lang639).put(' ');\r
-            putAttr("name", name);\r
-            put(" />");\r
-            putUnescapedComment(name);\r
-            ln();\r
-        }\r
-        return this;\r
-    }\r
-\r
-    /**\r
-     * 番号付けされたID(IDREF)属性を出力する。\r
-     * @param attrName 属性名\r
-     * @param prefix IDプレフィクス\r
-     * @param num 番号\r
-     * @return this本体\r
-     * @throws IOException 出力エラー\r
-     */\r
-    protected PmdXmlExporter putNumberedIdAttr(CharSequence attrName,\r
-                                                 CharSequence prefix,\r
-                                                 int num )\r
-            throws IOException{\r
-        put(attrName).put("=\"");\r
-        put(prefix).put(num);\r
-        put('"');\r
-        return this;\r
-    }\r
-\r
-    /**\r
-     * 番号付けされたID(IDREF)属性を出力する。\r
-     * @param attrName 属性名\r
-     * @param prefix IDプレフィクス\r
-     * @param numbered 番号付けされたオブジェクト\r
-     * @return this本体\r
-     * @throws IOException 出力エラー\r
-     */\r
-    protected PmdXmlExporter putNumberedIdAttr(CharSequence attrName,\r
-                                                 CharSequence prefix,\r
-                                                 SerialNumbered numbered )\r
-            throws IOException{\r
-        putNumberedIdAttr(attrName, prefix, numbered.getSerialNumber());\r
-        return this;\r
-    }\r
-\r
-    /**\r
-     * 位置情報を出力する。\r
-     * @param position 位置情報\r
-     * @return this本体\r
-     * @throws IOException 出力エラー\r
-     */\r
-    protected PmdXmlExporter putPosition(Pos3d position) throws IOException{\r
-        put("<position ");\r
-        putFloatAttr("x", position.getXPos()).put(' ');\r
-        putFloatAttr("y", position.getYPos()).put(' ');\r
-        putFloatAttr("z", position.getZPos()).put(' ');\r
-        put("/>");\r
-        return this;\r
-    }\r
-\r
-    /**\r
-     * 姿勢情報(ラジアン)を出力する。\r
-     * @param rotation 姿勢情報\r
-     * @return this本体\r
-     * @throws IOException 出力エラー\r
-     */\r
-    protected PmdXmlExporter putRadRotation(Rad3d rotation)\r
-            throws IOException{\r
-        put("<radRotation ");\r
-        putFloatAttr("xRad", rotation.getXRad()).put(' ');\r
-        putFloatAttr("yRad", rotation.getYRad()).put(' ');\r
-        putFloatAttr("zRad", rotation.getZRad()).put(' ');\r
-        put("/>");\r
-        return this;\r
-    }\r
-\r
-    /**\r
-     * 多言語識別名属性のローカルな名前をコメント出力する。\r
-     * @param name 多言語識別名\r
-     * @return this本体\r
-     * @throws IOException 出力エラー\r
-     */\r
-    protected PmdXmlExporter putLocalNameComment(I18nText name)\r
-            throws IOException{\r
-        String localName = name.getText();\r
-        ind().putLineComment(localName);\r
-        return this;\r
-    }\r
-\r
-    /**\r
-     * 多言語識別名属性のプライマリな名前を出力する。\r
-     * @param attrName 属性名\r
-     * @param name 多言語識別名\r
-     * @return this本体\r
-     * @throws IOException 出力エラー\r
-     */\r
-    protected PmdXmlExporter putPrimaryNameAttr(CharSequence attrName,\r
-                                                   I18nText name)\r
-            throws IOException{\r
-        String primaryName = name.getPrimaryText();\r
-        putAttr(attrName, primaryName);\r
-        return this;\r
-    }\r
-\r
-    /**\r
-     * PMDモデルデータをXML形式で出力する。\r
-     * @param model PMDモデルデータ\r
-     * @throws IOException 出力エラー\r
-     */\r
-    public void putPmdModel(PmdModel model) throws IOException{\r
-        ind().put("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>").ln(2);\r
-\r
-        ind().putBlockComment(TOP_COMMENT).ln(2);\r
-\r
-        /*\r
-        ind().put("<!DOCTYPE pmdModel").ln();\r
-        ind().put(" SYSTEM \"")\r
-             .put(PmdXmlResources.DTD_PMDXML)\r
-             .put("\" >")\r
-             .ln(3);\r
-         */\r
-\r
-        I18nText modelName = model.getModelName();\r
-        ind().putLocalNameComment(modelName).ln();\r
-        ind().put("<pmdModel").ln();\r
-        pushNest();\r
-        ind().putAttr("xmlns", PmdXmlResources.NS_PMDXML).ln();\r
-        ind().putAttr("xmlns:xsi", XmlResourceResolver.NS_XSD).ln();\r
-        ind().putAttr("xsi:schemaLocation", SCHEMA_LOCATION).ln();\r
-        ind().putAttr("schemaVersion", PmdXmlResources.VER_PMDXML).ln(2);\r
-        ind().putPrimaryNameAttr("name", modelName).ln();\r
-        popNest();\r
-        put(">").ln(2);\r
-\r
-        putModelInfo(model).flush();\r
-        putMetaInfo(model).flush();\r
-        putMaterialList(model).flush();\r
-        putToonMap(model).flush();\r
-        putBoneList(model).flush();\r
-        putBoneGroupList(model).flush();\r
-        putIKChainList(model).flush();\r
-        putMorphList(model).flush();\r
-        putRigidList(model).flush();\r
-        putRigidGroupList(model).flush();\r
-        putJointList(model).flush();\r
-        putSurfaceGroupList(model).flush();\r
-        putVertexList(model).flush();\r
-\r
-        ind().put("</pmdModel>").ln(2);\r
-        ind().put("<!-- EOF -->").ln();\r
-\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * モデル基本情報を出力する。\r
-     * @param model モデル情報\r
-     * @return this本体\r
-     * @throws IOException 出力エラー\r
-     */\r
-    private PmdXmlExporter putModelInfo(PmdModel model)\r
-            throws IOException{\r
-        I18nText modelName = model.getModelName();\r
-        putI18nName(modelName);\r
-        ln();\r
-\r
-        I18nText description = model.getDescription();\r
-        for(String lang639 : description.lang639CodeList()){\r
-            String descText = description.getText(lang639);\r
-            putDescription(lang639, descText);\r
-            ln();\r
-        }\r
-\r
-        return this;\r
-    }\r
-\r
-    /**\r
-     * モデル詳細テキストを出力する。\r
-     * @param lang639 言語コード\r
-     * @param content 詳細内容\r
-     * @return this本体\r
-     * @throws IOException 出力エラー\r
-     */\r
-    private PmdXmlExporter putDescription(CharSequence lang639,\r
-                                              CharSequence content)\r
-            throws IOException{\r
-        String text = content.toString();\r
-        text = text.replace(CRLF, LF);\r
-        text = text.replace(CR,   LF);\r
-\r
-        ind().put("<description");\r
-        if( ! I18nText.CODE639_PRIMARY.equals(lang639) ){\r
-            put(" ");\r
-            putAttr("lang", lang639);\r
-        }\r
-        put(">").ln();\r
-\r
-        putBRedContent(text);\r
-\r
-        ln();\r
-        ind().put("</description>").ln();\r
-\r
-        if( ! hasOnlyBasicLatin(text) && isBasicLatinOnlyOut() ){\r
-            putBlockComment(text);\r
-        }\r
-\r
-        return this;\r
-    }\r
-\r
-    /**\r
-     * break要素を含む要素内容を出力する。\r
-     * 必要に応じてXML定義済み実体文字が割り振られた文字、\r
-     * コントロールコード、および非BasicLatin文字がエスケープされる。\r
-     * \nはbrタグに変換される。\r
-     * @param content 内容\r
-     * @return this本体\r
-     * @throws IOException 出力エラー\r
-     */\r
-    protected BasicXmlExporter putBRedContent(CharSequence content)\r
-            throws IOException{\r
-        int length = content.length();\r
-\r
-        for(int pos = 0; pos < length; pos++){\r
-            char ch = content.charAt(pos);\r
-            if(ch == '\n'){\r
-                put("<br/>").ln();\r
-            }else if(Character.isISOControl(ch)){\r
-                putCharRef2Hex(ch);\r
-            }else if( ! isBasicLatin(ch) && isBasicLatinOnlyOut()){\r
-                putCharRef4Hex(ch);\r
-            }else{\r
-                switch(ch){\r
-                case '&':  put("&amp;");  break;\r
-                case '<':  put("&lt;");   break;\r
-                case '>':  put("&gt;");   break;\r
-                case '"':  put("&quot;"); break;\r
-                case '\'': put("&apos;"); break;\r
-                default:   put(ch);       break;\r
-                }\r
-            }\r
-        }\r
-\r
-        return this;\r
-    }\r
-\r
-    /**\r
-     * 各種メタ情報を出力する。\r
-     * @param model モデルデータ\r
-     * @return this本体\r
-     * @throws IOException 出力エラー\r
-     */\r
-    private PmdXmlExporter putMetaInfo(PmdModel model) throws IOException{\r
-        ind().put("<license>").ln();\r
-        ind().put("</license>").ln(2);\r
-\r
-        ind().put("<credits>").ln();\r
-        ind().put("</credits>").ln(2);\r
-\r
-        ind().put("<meta ");\r
-        putAttr("name", "generator").put(' ')\r
-                                    .putAttr("content", this.generator);\r
-        put(" />").ln();\r
-        ind().put("<meta ");\r
-        putAttr("name", "siteURL").put(' ').putAttr("content", "");\r
-        put(" />").ln();\r
-        ind().put("<meta ");\r
-        putAttr("name", "imageURL").put(' ').putAttr("content", "");\r
-        put(" />").ln(2);\r
-\r
-        return this;\r
-    }\r
-\r
-    /**\r
-     * マテリアル素材一覧を出力する。\r
-     * @param model モデルデータ\r
-     * @return this本体\r
-     * @throws IOException 出力エラー\r
-     */\r
-    private PmdXmlExporter putMaterialList(PmdModel model)\r
-            throws IOException{\r
-        ind().put("<materialList>").ln(2);\r
-        pushNest();\r
-\r
-        int ct = 0;\r
-        for(Material material : model.getMaterialList()){\r
-            putMaterial(material, ct++);\r
-        }\r
-\r
-        popNest();\r
-        ind().put("</materialList>").ln(2);\r
-\r
-        return this;\r
-    }\r
-\r
-    /**\r
-     * マテリアル素材情報を出力する。\r
-     * @param material マテリアル素材\r
-     * @param no マテリアル通し番号\r
-     * @return this本体\r
-     * @throws IOException 出力エラー\r
-     */\r
-    private PmdXmlExporter putMaterial(Material material, int no)\r
-            throws IOException{\r
-        String bool;\r
-        if(material.getEdgeAppearance()) bool = "true";\r
-        else                             bool = "false";\r
-        I18nText name = material.getMaterialName();\r
-        String primary = name.getPrimaryText();\r
-        String local = name.getText();\r
-\r
-        if(local != null && local.length() > 0){\r
-            ind().putLineComment(local).ln();\r
-        }\r
-        ind().put("<material ");\r
-        if(primary != null && primary.length() > 0){\r
-            putAttr("name", primary).put(' ');\r
-        }\r
-\r
-        putAttr("showEdge", bool);\r
-        put(" ");\r
-        putNumberedIdAttr("surfaceGroupIdRef", PFX_SURFACEGROUP, no);\r
-        put('>').ln();\r
-        pushNest();\r
-\r
-        putI18nName(name);\r
-\r
-        float[] rgba = new float[4];\r
-\r
-        Color diffuse = material.getDiffuseColor();\r
-        diffuse.getRGBComponents(rgba);\r
-        ind().put("<diffuse ");\r
-        putFloatAttr("r", rgba[0]).put(' ');\r
-        putFloatAttr("g", rgba[1]).put(' ');\r
-        putFloatAttr("b", rgba[2]).put(' ');\r
-        putFloatAttr("alpha", rgba[3]).put(' ');\r
-        put("/>").ln();\r
-\r
-        Color specular = material.getSpecularColor();\r
-        specular.getRGBComponents(rgba);\r
-        float shininess = material.getShininess();\r
-        ind().put("<specular ");\r
-        putFloatAttr("r", rgba[0]).put(' ');\r
-        putFloatAttr("g", rgba[1]).put(' ');\r
-        putFloatAttr("b", rgba[2]).put(' ');\r
-        putFloatAttr("shininess", shininess).put(' ');\r
-        put("/>").ln();\r
-\r
-        Color ambient = material.getAmbientColor();\r
-        ambient.getRGBComponents(rgba);\r
-        ind().put("<ambient ");\r
-        putFloatAttr("r", rgba[0]).put(' ');\r
-        putFloatAttr("g", rgba[1]).put(' ');\r
-        putFloatAttr("b", rgba[2]).put(' ');\r
-        put("/>").ln();\r
-\r
-        ShadeInfo shade = material.getShadeInfo();\r
-        String textureFileName = shade.getTextureFileName();\r
-        String spheremapFileName = shade.getSpheremapFileName();\r
-\r
-        if(shade.isValidToonIndex()){\r
-            ind().put("<toon ");\r
-            int toonIdx = shade.getToonIndex();\r
-            putNumberedIdAttr("toonFileIdRef", PFX_TOONFILE, toonIdx);\r
-            put(" />");\r
-            String toonFileName = shade.getToonFileName();\r
-            if(toonFileName != null && toonFileName.length() > 0){\r
-                put(' ').putLineComment(toonFileName);\r
-            }\r
-            ln();\r
-        }\r
-\r
-        if(textureFileName != null && textureFileName.length() > 0){\r
-            ind().put("<textureFile ");\r
-            putAttr("winFileName", textureFileName);\r
-            put(" />").ln();\r
-        }\r
-\r
-        if(spheremapFileName != null && spheremapFileName.length() > 0){\r
-            ind().put("<spheremapFile ");\r
-            putAttr("winFileName", spheremapFileName);\r
-            put(" />").ln();\r
-        }\r
-\r
-        popNest();\r
-        ind().put("</material>").ln(2);\r
-\r
-        return this;\r
-    }\r
-\r
-    /**\r
-     * トゥーンファイルマッピング情報を出力する。\r
-     * @param model モデルデータ\r
-     * @return this本体\r
-     * @throws IOException 出力エラー\r
-     */\r
-    private PmdXmlExporter putToonMap(PmdModel model)\r
-            throws IOException{\r
-        ind().put("<toonMap>").ln();\r
-        pushNest();\r
-\r
-        ToonMap map = model.getToonMap();\r
-        for(int index = 0; index <= 9; index++){\r
-            ind().putToon(map, index).ln();\r
-        }\r
-\r
-        popNest();\r
-        ind().put("</toonMap>").ln(2);\r
-        return this;\r
-    }\r
-\r
-    /**\r
-     * 個別のトゥーンファイル情報を出力する。\r
-     * @param map トゥーンマップ\r
-     * @param index インデックス値\r
-     * @return this本体\r
-     * @throws IOException 出力エラー\r
-     */\r
-    private PmdXmlExporter putToon(ToonMap map, int index)\r
-            throws IOException{\r
-        put("<toonDef ");\r
-        putNumberedIdAttr("toonFileId", PFX_TOONFILE, index).put(' ');\r
-        putIntAttr("index", index).put(' ');\r
-        String toonFile = map.getIndexedToon(index);\r
-        putAttr("winFileName", toonFile);\r
-        put(" />");\r
-        putUnescapedComment(toonFile);\r
-        return this;\r
-    }\r
-\r
-    /**\r
-     * サーフェイスグループリストを出力する。\r
-     * @param model モデルデータ\r
-     * @return this本体\r
-     * @throws IOException 出力エラー\r
-     */\r
-    private PmdXmlExporter putSurfaceGroupList(PmdModel model)\r
-            throws IOException{\r
-        ind().put("<surfaceGroupList>").ln(2);\r
-        pushNest();\r
-\r
-        int ct = 0;\r
-        for(Material material : model.getMaterialList()){\r
-            List<Surface> surfaceList = material.getSurfaceList();\r
-            putSurfaceList(surfaceList, ct++);\r
-        }\r
-\r
-        popNest();\r
-        ind().put("</surfaceGroupList>").ln(2);\r
-\r
-        return this;\r
-    }\r
-\r
-    /**\r
-     * 個別のサーフェイスグループを出力する。\r
-     * @param surfaceList サーフェイスのリスト\r
-     * @param index グループインデックス\r
-     * @return this本体\r
-     * @throws IOException 出力エラー\r
-     */\r
-    private PmdXmlExporter putSurfaceList(List<Surface> surfaceList,\r
-                                              int index)\r
-            throws IOException{\r
-        ind().put("<surfaceGroup ");\r
-        putNumberedIdAttr("surfaceGroupId", PFX_SURFACEGROUP, index);\r
-        put(">").ln();\r
-        pushNest();\r
-\r
-        for(Surface surface : surfaceList){\r
-            putSurface(surface);\r
-        }\r
-\r
-        popNest();\r
-        ind().put("</surfaceGroup>").ln(2);\r
-\r
-        return this;\r
-    }\r
-\r
-    /**\r
-     * 個別のサーフェイスを出力する。\r
-     * @param surface サーフェイス\r
-     * @return this本体\r
-     * @throws IOException 出力エラー\r
-     */\r
-    private PmdXmlExporter putSurface(Surface surface)\r
-            throws IOException{\r
-        ind().put("<surface ");\r
-\r
-        Vertex vertex1 = surface.getVertex1();\r
-        Vertex vertex2 = surface.getVertex2();\r
-        Vertex vertex3 = surface.getVertex3();\r
-\r
-        putNumberedIdAttr("vtxIdRef1", PFX_VERTEX, vertex1).put(' ');\r
-        putNumberedIdAttr("vtxIdRef2", PFX_VERTEX, vertex2).put(' ');\r
-        putNumberedIdAttr("vtxIdRef3", PFX_VERTEX, vertex3).put(' ');\r
-\r
-        put("/>").ln();\r
-        return this;\r
-    }\r
-\r
-    /**\r
-     * 頂点リストを出力する。\r
-     * @param model モデルデータ\r
-     * @return this本体\r
-     * @throws IOException 出力エラー\r
-     */\r
-    private PmdXmlExporter putVertexList(PmdModel model)\r
-            throws IOException{\r
-        ind().put("<vertexList>").ln(2);\r
-        pushNest();\r
-\r
-        for(Vertex vertex : model.getVertexList()){\r
-            putVertex(vertex);\r
-        }\r
-\r
-        popNest();\r
-        ind().put("</vertexList>").ln(2);\r
-\r
-        return this;\r
-    }\r
-\r
-    /**\r
-     * 個別の頂点情報を出力する。\r
-     * @param vertex 頂点\r
-     * @return this本体\r
-     * @throws IOException 出力エラー\r
-     */\r
-    private PmdXmlExporter putVertex(Vertex vertex)\r
-            throws IOException{\r
-        String bool;\r
-        if(vertex.getEdgeAppearance()) bool = "true";\r
-        else                           bool = "false";\r
-\r
-        ind().put("<vertex ");\r
-        putNumberedIdAttr("vtxId", PFX_VERTEX, vertex).put(' ');\r
-        putAttr("showEdge", bool);\r
-        put(">").ln();\r
-        pushNest();\r
-\r
-        Pos3d position = vertex.getPosition();\r
-        ind().putPosition(position).ln();\r
-\r
-        Vec3d normal = vertex.getNormal();\r
-        ind().put("<normal ");\r
-        putFloatAttr("x", normal.getXVal()).put(' ');\r
-        putFloatAttr("y", normal.getYVal()).put(' ');\r
-        putFloatAttr("z", normal.getZVal()).put(' ');\r
-        put("/>").ln();\r
-\r
-        Pos2d uvPos = vertex.getUVPosition();\r
-        ind().put("<uvMap ");\r
-        putFloatAttr("u", uvPos.getXPos()).put(' ');\r
-        putFloatAttr("v", uvPos.getYPos()).put(' ');\r
-        put("/>").ln();\r
-\r
-        BoneInfo boneA = vertex.getBoneA();\r
-        BoneInfo boneB = vertex.getBoneB();\r
-        int weight = vertex.getWeightA();\r
-        ind().put("<skinning ");\r
-        putNumberedIdAttr("boneIdRef1", PFX_BONE, boneA).put(' ');\r
-        putNumberedIdAttr("boneIdRef2", PFX_BONE, boneB).put(' ');\r
-        putIntAttr("weightBalance", weight).put(' ');\r
-        put("/>").ln();\r
-\r
-        popNest();\r
-        ind().put("</vertex>").ln(2);\r
-\r
-        return this;\r
-    }\r
-\r
-    /**\r
-     * ボーンリストを出力する。\r
-     * @param model モデルデータ\r
-     * @return this本体\r
-     * @throws IOException 出力エラー\r
-     */\r
-    private PmdXmlExporter putBoneList(PmdModel model)\r
-            throws IOException{\r
-        ind().put("<boneList>").ln(2);\r
-        pushNest();\r
-\r
-        putBlockComment(BONETYPE_COMMENT).ln();\r
-\r
-        for(BoneInfo bone : model.getBoneList()){\r
-            putBone(bone);\r
-        }\r
-\r
-        popNest();\r
-        ind().put("</boneList>").ln(2);\r
-\r
-        return this;\r
-    }\r
-\r
-    /**\r
-     * 個別のボーン情報を出力する。\r
-     * @param bone ボーン情報\r
-     * @return this本体\r
-     * @throws IOException 出力エラー\r
-     */\r
-    private PmdXmlExporter putBone(BoneInfo bone)\r
-            throws IOException{\r
-        I18nText i18nName = bone.getBoneName();\r
-        BoneType type = bone.getBoneType();\r
-\r
-        putLocalNameComment(i18nName).putLineComment(type.getGuiName()).ln();\r
-        ind().put("<bone ");\r
-        putPrimaryNameAttr("name", i18nName).put(' ');\r
-        putNumberedIdAttr("boneId", PFX_BONE, bone).put(' ');\r
-        putAttr("type", type.name());\r
-        put(">").ln();\r
-        pushNest();\r
-\r
-        putI18nName(i18nName);\r
-\r
-        Pos3d position = bone.getPosition();\r
-        ind().putPosition(position).ln();\r
-\r
-        BoneInfo ikBone = bone.getIKBone();\r
-        if(bone.getBoneType() == BoneType.LINKEDROT){\r
-            ind().put("<rotationRatio ");\r
-            putIntAttr("ratio", bone.getRotationRatio());\r
-            put(" />").ln();\r
-        }else if(ikBone != null){\r
-            ind().put("<ikBone ");\r
-            putNumberedIdAttr("boneIdRef", PFX_BONE, ikBone);\r
-            put(" /> ");\r
-            String ikBoneName = "Ref:" + ikBone.getBoneName().getText();\r
-            putLineComment(ikBoneName);\r
-            ln();\r
-        }\r
-\r
-        StringBuilder chainComment = new StringBuilder();\r
-        ind().put("<boneChain");\r
-        BoneInfo prev = bone.getPrevBone();\r
-        BoneInfo next = bone.getNextBone();\r
-        if(prev != null){\r
-            put(' ');\r
-            putNumberedIdAttr("prevBoneIdRef", PFX_BONE, prev);\r
-            chainComment.append('[')\r
-                        .append(prev.getBoneName().getPrimaryText())\r
-                        .append(']')\r
-                        .append("=> #");\r
-        }\r
-        if(next != null){\r
-            put(' ');\r
-            putNumberedIdAttr("nextBoneIdRef", PFX_BONE, next);\r
-            if(chainComment.length() <= 0) chainComment.append("#");\r
-            chainComment.append(" =>")\r
-                        .append('[')\r
-                        .append(next.getBoneName().getPrimaryText())\r
-                        .append(']');\r
-        }\r
-        put(" />").ln();\r
-        ind().putLineComment(chainComment).ln();\r
-\r
-        popNest();\r
-        ind().put("</bone>").ln(2);\r
-\r
-        return this;\r
-    }\r
-\r
-    /**\r
-     * ボーングループリストを出力する。\r
-     * @param model モデルデータ\r
-     * @return this本体\r
-     * @throws IOException 出力エラー\r
-     */\r
-    private PmdXmlExporter putBoneGroupList(PmdModel model)\r
-            throws IOException{\r
-        ind().put("<boneGroupList>").ln(2);\r
-        pushNest();\r
-\r
-        for(BoneGroup group : model.getBoneGroupList()){\r
-            if(group.isDefaultBoneGroup()) continue;\r
-            putBoneGroup(group);\r
-        }\r
-\r
-        popNest();\r
-        ind().put("</boneGroupList>").ln(2);\r
-\r
-        return this;\r
-    }\r
-\r
-    /**\r
-     * 個別のボーングループ情報を出力する。\r
-     * @param group ボーングループ情報\r
-     * @return this本体\r
-     * @throws IOException 出力エラー\r
-     */\r
-    private PmdXmlExporter putBoneGroup(BoneGroup group)\r
-            throws IOException{\r
-        I18nText i18nName = group.getGroupName();\r
-\r
-        putLocalNameComment(i18nName).ln();\r
-        ind().put("<boneGroup ");\r
-        putPrimaryNameAttr("name", i18nName);\r
-        put(">").ln();\r
-        pushNest();\r
-\r
-        putI18nName(i18nName);\r
-\r
-        for(BoneInfo bone : group){\r
-            ind().put("<boneGroupMember ");\r
-            putNumberedIdAttr("boneIdRef", PFX_BONE, bone);\r
-            put(" /> ");\r
-            String boneName = "Ref:" + bone.getBoneName().getText();\r
-            putLineComment(boneName).ln();\r
-        }\r
-\r
-        popNest();\r
-        ind().put("</boneGroup>").ln(2);\r
-\r
-        return this;\r
-    }\r
-\r
-    /**\r
-     * IKチェーンリストを出力する。\r
-     * @param model モデルデータ\r
-     * @return this本体\r
-     * @throws IOException 出力エラー\r
-     */\r
-    private PmdXmlExporter putIKChainList(PmdModel model)\r
-            throws IOException{\r
-        ind().put("<ikChainList>").ln(2);\r
-        pushNest();\r
-\r
-        for(IKChain chain : model.getIKChainList()){\r
-            putIKChain(chain);\r
-        }\r
-\r
-        popNest();\r
-        ind().put("</ikChainList>").ln(2);\r
-\r
-        return this;\r
-    }\r
-\r
-    /**\r
-     * 個別のIKチェーン情報を出力する。\r
-     * @param chain チェーン情報\r
-     * @return this本体\r
-     * @throws IOException 出力エラー\r
-     */\r
-    private PmdXmlExporter putIKChain(IKChain chain)\r
-            throws IOException{\r
-        int depth = chain.getIKDepth();\r
-        float weight = chain.getIKWeight();\r
-        BoneInfo ikBone = chain.getIkBone();\r
-\r
-        ind().putLineComment("Ref:" + ikBone.getBoneName().getText()).ln();\r
-        ind().put("<ikChain ");\r
-        putNumberedIdAttr("ikBoneIdRef", PFX_BONE, ikBone).put(' ');\r
-        putIntAttr("recursiveDepth", depth).put(' ');\r
-        putFloatAttr("weight", weight);\r
-        put("> ").ln();\r
-        pushNest();\r
-\r
-        for(BoneInfo bone : chain){\r
-            ind().put("<chainOrder ");\r
-            putNumberedIdAttr("boneIdRef", PFX_BONE, bone);\r
-            put(" /> ");\r
-            putLineComment("Ref:" + bone.getBoneName().getText());\r
-            ln();\r
-        }\r
-\r
-        popNest();\r
-        ind().put("</ikChain>").ln(2);\r
-\r
-        return this;\r
-    }\r
-\r
-    /**\r
-     * モーフリストを出力する。\r
-     * @param model モデルデータ\r
-     * @return this本体\r
-     * @throws IOException 出力エラー\r
-     */\r
-    private PmdXmlExporter putMorphList(PmdModel model)\r
-            throws IOException{\r
-        ind().put("<morphList>").ln(2);\r
-        pushNest();\r
-\r
-        putBlockComment(MORPHTYPE_COMMENT).ln();\r
-\r
-        Map<MorphType, List<MorphPart>> morphMap = model.getMorphMap();\r
-        for(MorphType type : MorphType.values()){\r
-            if(type == MorphType.BASE) continue;\r
-            List<MorphPart> partList = morphMap.get(type);\r
-            if(partList == null) continue;\r
-            for(MorphPart part : partList){\r
-                putMorphPart(part);\r
-            }\r
-        }\r
-\r
-        popNest();\r
-        ind().put("</morphList>").ln(2);\r
-\r
-        return this;\r
-    }\r
-\r
-    /**\r
-     * 個別のモーフ情報を出力する。\r
-     * @param part モーフ情報\r
-     * @return this本体\r
-     * @throws IOException 出力エラー\r
-     */\r
-    private PmdXmlExporter putMorphPart(MorphPart part)\r
-            throws IOException{\r
-        I18nText i18nName = part.getMorphName();\r
-        String primary = i18nName.getPrimaryText();\r
-\r
-        ind().put("<morph ");\r
-        putAttr("name", primary).put(' ');\r
-        putAttr("type", part.getMorphType().name());\r
-        put(">");\r
-        putUnescapedComment(primary);\r
-        ln();\r
-        pushNest();\r
-\r
-        putI18nName(i18nName);\r
-\r
-        for(MorphVertex mvertex : part){\r
-            Pos3d offset = mvertex.getOffset();\r
-            Vertex base = mvertex.getBaseVertex();\r
-\r
-            ind().put("<morphVertex ");\r
-            putNumberedIdAttr("vtxIdRef", PFX_VERTEX, base).put(' ');\r
-            putFloatAttr("xOff", offset.getXPos()).put(' ');\r
-            putFloatAttr("yOff", offset.getYPos()).put(' ');\r
-            putFloatAttr("zOff", offset.getZPos()).put(' ');\r
-            put("/>");\r
-            ln();\r
-        }\r
-\r
-        popNest();\r
-        ind().put("</morph>").ln(2);\r
-\r
-        return this;\r
-    }\r
-\r
-    /**\r
-     * 剛体リストを出力する。\r
-     * @param model モデルデータ\r
-     * @return this本体\r
-     * @throws IOException 出力エラー\r
-     */\r
-    private PmdXmlExporter putRigidList(PmdModel model)\r
-            throws IOException{\r
-        ind().put("<rigidList>").ln(2);\r
-        pushNest();\r
-\r
-        putBlockComment(RIGIDBEHAVIOR_COMMENT).ln();\r
-\r
-        for(RigidInfo rigid : model.getRigidList()){\r
-            putRigid(rigid);\r
-        }\r
-\r
-        popNest();\r
-        ind().put("</rigidList>").ln(2);\r
-\r
-        return this;\r
-    }\r
-\r
-    /**\r
-     * 個別の剛体情報を出力する。\r
-     * @param rigid 剛体情報\r
-     * @return this本体\r
-     * @throws IOException 出力エラー\r
-     */\r
-    private PmdXmlExporter putRigid(RigidInfo rigid)\r
-            throws IOException{\r
-        BoneInfo linkedBone = rigid.getLinkedBone();\r
-        I18nText i18nName = rigid.getRigidName();\r
-        String primary = i18nName.getPrimaryText();\r
-\r
-        putLocalNameComment(i18nName).ln();\r
-        ind().put("<rigid ");\r
-        putAttr("name", primary).put(' ');\r
-        putNumberedIdAttr("rigidId", PFX_RIGID, rigid).put(' ');\r
-        putAttr("behavior", rigid.getBehaviorType().name());\r
-        put(">").ln();\r
-        pushNest();\r
-\r
-        putI18nName(i18nName);\r
-\r
-        if(linkedBone != null){\r
-            ind().put("<linkedBone ");\r
-            putNumberedIdAttr("boneIdRef", PFX_BONE, linkedBone);\r
-            put(" /> ");\r
-            putLineComment("Ref:" + linkedBone.getBoneName().getText());\r
-            ln(2);\r
-        }\r
-\r
-        RigidShape shape = rigid.getRigidShape();\r
-        putRigidShape(shape);\r
-\r
-        Pos3d position = rigid.getPosition();\r
-        ind().putPosition(position).ln();\r
-\r
-        Rad3d rotation = rigid.getRotation();\r
-        ind().putRadRotation(rotation).ln();\r
-\r
-        DynamicsInfo dynamics = rigid.getDynamicsInfo();\r
-        putDynamics(dynamics).ln();\r
-\r
-        for(RigidGroup group : rigid.getThroughGroupColl()){\r
-            ind().put("<throughRigidGroup ");\r
-            putNumberedIdAttr("rigidGroupIdRef",\r
-                              PFX_RIGIDGROUP,\r
-                              group.getSerialNumber() + 1).put(' ');\r
-            put(" />").ln();\r
-        }\r
-\r
-        popNest();\r
-        ind().put("</rigid>").ln(2);\r
-\r
-        return this;\r
-    }\r
-\r
-    /**\r
-     * 剛体形状を出力する。\r
-     * @param shape 剛体形状\r
-     * @return this本体\r
-     * @throws IOException 出力エラー\r
-     */\r
-    private PmdXmlExporter putRigidShape(RigidShape shape)\r
-            throws IOException{\r
-        RigidShapeType type = shape.getShapeType();\r
-\r
-        switch(type){\r
-        case BOX:\r
-            ind().put("<rigidShapeBox ");\r
-            putFloatAttr("width", shape.getWidth()).put(' ');\r
-            putFloatAttr("height", shape.getHeight()).put(' ');\r
-            putFloatAttr("depth", shape.getDepth()).put(' ');\r
-            break;\r
-        case SPHERE:\r
-            ind().put("<rigidShapeSphere ");\r
-            putFloatAttr("radius", shape.getRadius()).put(' ');\r
-            break;\r
-        case CAPSULE:\r
-            ind().put("<rigidShapeCapsule ");\r
-            putFloatAttr("height", shape.getHeight()).put(' ');\r
-            putFloatAttr("radius", shape.getRadius()).put(' ');\r
-            break;\r
-        default:\r
-            assert false;\r
-            throw new AssertionError();\r
-        }\r
-\r
-        put("/>").ln();\r
-\r
-        return this;\r
-    }\r
-\r
-    /**\r
-     * 力学設定を出力する。\r
-     * @param dynamics 力学設定\r
-     * @return this本体\r
-     * @throws IOException 出力エラー\r
-     */\r
-    private PmdXmlExporter putDynamics(DynamicsInfo dynamics)\r
-            throws IOException{\r
-        ind().put("<dynamics").ln();\r
-        pushNest();\r
-        ind().putFloatAttr("mass", dynamics.getMass()).ln();\r
-        ind().putFloatAttr("dampingPosition",\r
-                dynamics.getDampingPosition()).ln();\r
-        ind().putFloatAttr("dampingRotation",\r
-                dynamics.getDampingRotation()).ln();\r
-        ind().putFloatAttr("restitution", dynamics.getRestitution()).ln();\r
-        ind().putFloatAttr("friction", dynamics.getFriction()).ln();\r
-        popNest();\r
-        ind().put("/>").ln();\r
-\r
-        return this;\r
-    }\r
-\r
-    /**\r
-     * 剛体グループリストを出力する。\r
-     * @param model モデルデータ\r
-     * @return this本体\r
-     * @throws IOException 出力エラー\r
-     */\r
-    private PmdXmlExporter putRigidGroupList(PmdModel model)\r
-            throws IOException{\r
-        ind().put("<rigidGroupList>").ln(2);\r
-        pushNest();\r
-\r
-        for(RigidGroup group : model.getRigidGroupList()){\r
-            ind().put("<rigidGroup ");\r
-            putNumberedIdAttr("rigidGroupId",\r
-                              PFX_RIGIDGROUP,\r
-                              group.getSerialNumber() + 1);\r
-            List<RigidInfo> rigidList = group.getRigidList();\r
-            if(rigidList.size() <= 0){\r
-                put(" />").ln(2);\r
-                continue;\r
-            }\r
-            put(">").ln();\r
-            pushNest();\r
-\r
-            for(RigidInfo rigid : rigidList){\r
-                ind().put("<rigidGroupMember ");\r
-                putNumberedIdAttr("rigidIdRef", PFX_RIGID, rigid).put(' ');\r
-                put("/>");\r
-                put(' ');\r
-                putLineComment("Ref:" + rigid.getRigidName().getText());\r
-                ln();\r
-            }\r
-\r
-            popNest();\r
-            ind().put("</rigidGroup>").ln(2);\r
-        }\r
-\r
-        popNest();\r
-        ind().put("</rigidGroupList>").ln(2);\r
-\r
-        return this;\r
-    }\r
-\r
-    /**\r
-     * ジョイントリストを出力する。\r
-     * @param model モデルデータ\r
-     * @return this本体\r
-     * @throws IOException 出力エラー\r
-     */\r
-    private PmdXmlExporter putJointList(PmdModel model)\r
-            throws IOException{\r
-        ind().put("<jointList>").ln(2);\r
-        pushNest();\r
-\r
-        for(JointInfo joint : model.getJointList()){\r
-            putJoint(joint);\r
-        }\r
-\r
-        popNest();\r
-        ind().put("</jointList>").ln(2);\r
-\r
-        return this;\r
-    }\r
-\r
-    /**\r
-     * 個別のジョイント情報を出力する。\r
-     * @param joint ジョイント情報\r
-     * @return this本体\r
-     * @throws IOException 出力エラー\r
-     */\r
-    private PmdXmlExporter putJoint(JointInfo joint)\r
-            throws IOException{\r
-        I18nText i18nName = joint.getJointName();\r
-\r
-        putLocalNameComment(i18nName).ln();\r
-        ind().put("<joint ");\r
-        putPrimaryNameAttr("name", i18nName);\r
-        put(">").ln();\r
-        pushNest();\r
-\r
-        putI18nName(i18nName);\r
-\r
-        RigidInfo rigidA = joint.getRigidA();\r
-        RigidInfo rigidB = joint.getRigidB();\r
-        ind().put("<jointedRigidPair ");\r
-        putNumberedIdAttr("rigidIdRef1", PFX_RIGID, rigidA).put(' ');\r
-        putNumberedIdAttr("rigidIdRef2", PFX_RIGID, rigidB).put(' ');\r
-        put("/>").ln();\r
-        ind();\r
-        putLineComment("[" + rigidA.getRigidName().getText() + "]"\r
-                + " <=> [" + rigidB.getRigidName().getText() + "]");\r
-        ln(2);\r
-\r
-        Pos3d position = joint.getPosition();\r
-        ind().putPosition(position).ln();\r
-\r
-        TripletRange posRange = joint.getPositionRange();\r
-        ind().put("<limitPosition").ln();\r
-        pushNest();\r
-        ind();\r
-        putFloatAttr("xFrom", posRange.getXFrom()).put(' ');\r
-        putFloatAttr("xTo",   posRange.getXTo()).ln();\r
-        ind();\r
-        putFloatAttr("yFrom", posRange.getYFrom()).put(' ');\r
-        putFloatAttr("yTo",   posRange.getYTo()).ln();\r
-        ind();\r
-        putFloatAttr("zFrom", posRange.getZFrom()).put(' ');\r
-        putFloatAttr("zTo",   posRange.getZTo()).ln();\r
-        popNest();\r
-        ind().put("/>").ln(2);\r
-\r
-        Rad3d rotation = joint.getRotation();\r
-        ind().putRadRotation(rotation).ln();\r
-        TripletRange rotRange = joint.getRotationRange();\r
-        ind().put("<limitRotation").ln();\r
-        pushNest();\r
-        ind();\r
-        putFloatAttr("xFrom", rotRange.getXFrom()).put(' ');\r
-        putFloatAttr("xTo",   rotRange.getXTo()).ln();\r
-        ind();\r
-        putFloatAttr("yFrom", rotRange.getYFrom()).put(' ');\r
-        putFloatAttr("yTo",   rotRange.getYTo()).ln();\r
-        ind();\r
-        putFloatAttr("zFrom", rotRange.getZFrom()).put(' ');\r
-        putFloatAttr("zTo",   rotRange.getZTo()).ln();\r
-        popNest();\r
-        ind().put("/>").ln(2);\r
-\r
-        Pos3d elaPosition = joint.getElasticPosition();\r
-        ind().put("<elasticPosition ");\r
-        putFloatAttr("x", elaPosition.getXPos()).put(' ');\r
-        putFloatAttr("y", elaPosition.getYPos()).put(' ');\r
-        putFloatAttr("z", elaPosition.getZPos()).put(' ');\r
-        put("/>").ln();\r
-\r
-        Deg3d elaRotation = joint.getElasticRotation();\r
-        ind().put("<elasticRotation ");\r
-        putFloatAttr("xDeg", elaRotation.getXDeg()).put(' ');\r
-        putFloatAttr("yDeg", elaRotation.getYDeg()).put(' ');\r
-        putFloatAttr("zDeg", elaRotation.getZDeg()).put(' ');\r
-        put("/>").ln(2);\r
-\r
-        popNest();\r
-        ind().put("</joint>").ln(2);\r
-\r
-        return this;\r
-    }\r
-\r
-}\r
+/*
+ * pmd-xml exporter
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.xml;
+
+import java.awt.Color;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.List;
+import java.util.Map;
+import jp.sourceforge.mikutoga.corelib.I18nText;
+import jp.sourceforge.mikutoga.corelib.SerialNumbered;
+import jp.sourceforge.mikutoga.pmd.BoneGroup;
+import jp.sourceforge.mikutoga.pmd.BoneInfo;
+import jp.sourceforge.mikutoga.pmd.BoneType;
+import jp.sourceforge.mikutoga.pmd.Deg3d;
+import jp.sourceforge.mikutoga.pmd.DynamicsInfo;
+import jp.sourceforge.mikutoga.pmd.IKChain;
+import jp.sourceforge.mikutoga.pmd.JointInfo;
+import jp.sourceforge.mikutoga.pmd.Material;
+import jp.sourceforge.mikutoga.pmd.MorphPart;
+import jp.sourceforge.mikutoga.pmd.MorphType;
+import jp.sourceforge.mikutoga.pmd.MorphVertex;
+import jp.sourceforge.mikutoga.pmd.PmdModel;
+import jp.sourceforge.mikutoga.pmd.Pos2d;
+import jp.sourceforge.mikutoga.pmd.Pos3d;
+import jp.sourceforge.mikutoga.pmd.Rad3d;
+import jp.sourceforge.mikutoga.pmd.RigidGroup;
+import jp.sourceforge.mikutoga.pmd.RigidInfo;
+import jp.sourceforge.mikutoga.pmd.RigidShape;
+import jp.sourceforge.mikutoga.pmd.RigidShapeType;
+import jp.sourceforge.mikutoga.pmd.ShadeInfo;
+import jp.sourceforge.mikutoga.pmd.Surface;
+import jp.sourceforge.mikutoga.pmd.ToonMap;
+import jp.sourceforge.mikutoga.pmd.TripletRange;
+import jp.sourceforge.mikutoga.pmd.Vec3d;
+import jp.sourceforge.mikutoga.pmd.Vertex;
+import jp.sourceforge.mikutoga.xml.BasicXmlExporter;
+import jp.sourceforge.mikutoga.xml.XmlResourceResolver;
+
+/**
+ * XML形式でPMDモデルデータを出力する。
+ */
+public class PmdXmlExporter extends BasicXmlExporter{
+
+    private static final String TOP_COMMENT =
+            "  MikuMikuDance\n    model-data(*.pmd) on XML";
+    private static final String SCHEMA_LOCATION =
+            PmdXmlResources.NS_PMDXML + " " + PmdXmlResources.SCHEMA_PMDXML;
+
+    /** 改行文字列 CR。 */
+    private static final String CR = "\r";       // 0x0d
+    /** 改行文字列 LF。 */
+    private static final String LF = "\n";       // 0x0a
+    /** 改行文字列 CRLF。 */
+    private static final String CRLF = CR + LF;  // 0x0d, 0x0a
+
+    private static final String PFX_SURFACEGROUP = "sg";
+    private static final String PFX_TOONFILE = "tf";
+    private static final String PFX_VERTEX = "vtx";
+    private static final String PFX_BONE = "bn";
+    private static final String PFX_RIGID = "rd";
+    private static final String PFX_RIGIDGROUP = "rg";
+
+    private static final String BONETYPE_COMMENT =
+          "Bone types:\n"
+        + "[0 : ROTATE      : Rotate       : 回転           :]\n"
+        + "[1 : ROTMOV      : Rotate/Move  : 回転/移動      :]\n"
+        + "[2 : IK          : IK           : IK             :]\n"
+        + "[3 : UNKNOWN     : Unknown      : 不明           :]\n"
+        + "[4 : UNDERIK     : Under IK     : IK影響下(回転) :]\n"
+        + "[5 : UNDERROT    : Under rotate : 回転影響下     :]\n"
+        + "[6 : IKCONNECTED : IK connected : IK接続先       :]\n"
+        + "[7 : HIDDEN      : Hidden       : 非表示         :]\n"
+        + "[8 : TWIST       : Twist        : 捩り           :]\n"
+        + "[9 : LINKEDROT   : Linked Rotate: 回転連動       :]\n";
+
+    private static final String MORPHTYPE_COMMENT =
+          "Morph types:\n"
+        + "[1 : EYEBROW : まゆ   ]\n"
+        + "[2 : EYE     : 目     ]\n"
+        + "[3 : LIP     : リップ ]\n"
+        + "[4 : EXTRA   : その他 ]\n";
+
+    private static final String RIGIDBEHAVIOR_COMMENT =
+          "Rigid behavior types:\n"
+        + "[0 : FOLLOWBONE    : ボーン追従       ]\n"
+        + "[1 : ONLYDYNAMICS  : 物理演算         ]\n"
+        + "[2 : BONEDDYNAMICS : ボーン位置合わせ ]\n";
+
+    private String generator = "";
+
+    /**
+     * コンストラクタ。
+     * 文字エンコーディングはUTF-8が用いられる。
+     * @param stream 出力ストリーム
+     */
+    public PmdXmlExporter(OutputStream stream){
+        super(stream);
+        return;
+    }
+
+    /**
+     * Generatorメタ情報を設定する。
+     * @param generatorArg Generatorメタ情報
+     * @throws NullPointerException 引数がnull
+     */
+    public void setGenerator(String generatorArg)
+            throws NullPointerException{
+        if(generatorArg == null) throw new NullPointerException();
+        this.generator = generatorArg;
+        return;
+    }
+
+    /**
+     * 任意の文字列がBasicLatin文字のみから構成されるか判定する。
+     * @param seq 文字列
+     * @return null、長さ0もしくはBasicLatin文字のみから構成されるならtrue
+     */
+    public static boolean hasOnlyBasicLatin(CharSequence seq){
+        if(seq == null) return true;
+        int length = seq.length();
+        for(int pos = 0; pos < length; pos++){
+            char ch = seq.charAt(pos);
+            if(ch > 0x007f) return false;
+        }
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     * @throws IOException {@inheritDoc}
+     */
+    @Override
+    public PmdXmlExporter ind() throws IOException{
+        super.ind();
+        return this;
+    }
+
+    /**
+     * 文字参照によるエスケープを補佐するためのコメントを出力する。
+     * @param seq 文字列
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    protected PmdXmlExporter putUnescapedComment(CharSequence seq)
+            throws IOException{
+        if( ! isBasicLatinOnlyOut() ) return this;
+        if(hasOnlyBasicLatin(seq)) return this;
+        put(' ').putLineComment(seq);
+        return this;
+    }
+
+    /**
+     * 多言語化された各種識別名を出力する。
+     * プライマリ名は出力対象外。
+     * @param text 多言語文字列
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    protected PmdXmlExporter putI18nName(I18nText text) throws IOException{
+        for(String lang639 : text.lang639CodeList()){
+            if(lang639.equals(I18nText.CODE639_PRIMARY)) continue;
+            String name = text.getText(lang639);
+            ind().put("<i18nName ");
+            putAttr("lang", lang639).put(' ');
+            putAttr("name", name);
+            put(" />");
+            putUnescapedComment(name);
+            ln();
+        }
+        return this;
+    }
+
+    /**
+     * 番号付けされたID(IDREF)属性を出力する。
+     * @param attrName 属性名
+     * @param prefix IDプレフィクス
+     * @param num 番号
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    protected PmdXmlExporter putNumberedIdAttr(CharSequence attrName,
+                                                 CharSequence prefix,
+                                                 int num )
+            throws IOException{
+        put(attrName).put("=\"");
+        put(prefix).put(num);
+        put('"');
+        return this;
+    }
+
+    /**
+     * 番号付けされたID(IDREF)属性を出力する。
+     * @param attrName 属性名
+     * @param prefix IDプレフィクス
+     * @param numbered 番号付けされたオブジェクト
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    protected PmdXmlExporter putNumberedIdAttr(CharSequence attrName,
+                                                 CharSequence prefix,
+                                                 SerialNumbered numbered )
+            throws IOException{
+        putNumberedIdAttr(attrName, prefix, numbered.getSerialNumber());
+        return this;
+    }
+
+    /**
+     * 位置情報を出力する。
+     * @param position 位置情報
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    protected PmdXmlExporter putPosition(Pos3d position) throws IOException{
+        put("<position ");
+        putFloatAttr("x", position.getXPos()).put(' ');
+        putFloatAttr("y", position.getYPos()).put(' ');
+        putFloatAttr("z", position.getZPos()).put(' ');
+        put("/>");
+        return this;
+    }
+
+    /**
+     * 姿勢情報(ラジアン)を出力する。
+     * @param rotation 姿勢情報
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    protected PmdXmlExporter putRadRotation(Rad3d rotation)
+            throws IOException{
+        put("<radRotation ");
+        putFloatAttr("xRad", rotation.getXRad()).put(' ');
+        putFloatAttr("yRad", rotation.getYRad()).put(' ');
+        putFloatAttr("zRad", rotation.getZRad()).put(' ');
+        put("/>");
+        return this;
+    }
+
+    /**
+     * 多言語識別名属性のローカルな名前をコメント出力する。
+     * @param name 多言語識別名
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    protected PmdXmlExporter putLocalNameComment(I18nText name)
+            throws IOException{
+        String localName = name.getText();
+        ind().putLineComment(localName);
+        return this;
+    }
+
+    /**
+     * 多言語識別名属性のプライマリな名前を出力する。
+     * @param attrName 属性名
+     * @param name 多言語識別名
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    protected PmdXmlExporter putPrimaryNameAttr(CharSequence attrName,
+                                                   I18nText name)
+            throws IOException{
+        String primaryName = name.getPrimaryText();
+        putAttr(attrName, primaryName);
+        return this;
+    }
+
+    /**
+     * PMDモデルデータをXML形式で出力する。
+     * @param model PMDモデルデータ
+     * @throws IOException 出力エラー
+     */
+    public void putPmdModel(PmdModel model) throws IOException{
+        ind().put("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>").ln(2);
+
+        ind().putBlockComment(TOP_COMMENT).ln(2);
+
+        /*
+        ind().put("<!DOCTYPE pmdModel").ln();
+        ind().put(" SYSTEM \"")
+             .put(PmdXmlResources.DTD_PMDXML)
+             .put("\" >")
+             .ln(3);
+         */
+
+        I18nText modelName = model.getModelName();
+        ind().putLocalNameComment(modelName).ln();
+        ind().put("<pmdModel").ln();
+        pushNest();
+        ind().putAttr("xmlns", PmdXmlResources.NS_PMDXML).ln();
+        ind().putAttr("xmlns:xsi", XmlResourceResolver.NS_XSD).ln();
+        ind().putAttr("xsi:schemaLocation", SCHEMA_LOCATION).ln();
+        ind().putAttr("schemaVersion", PmdXmlResources.VER_PMDXML).ln(2);
+        ind().putPrimaryNameAttr("name", modelName).ln();
+        popNest();
+        put(">").ln(2);
+
+        putModelInfo(model).flush();
+        putMetaInfo(model).flush();
+        putMaterialList(model).flush();
+        putToonMap(model).flush();
+        putBoneList(model).flush();
+        putBoneGroupList(model).flush();
+        putIKChainList(model).flush();
+        putMorphList(model).flush();
+        putRigidList(model).flush();
+        putRigidGroupList(model).flush();
+        putJointList(model).flush();
+        putSurfaceGroupList(model).flush();
+        putVertexList(model).flush();
+
+        ind().put("</pmdModel>").ln(2);
+        ind().put("<!-- EOF -->").ln();
+
+        return;
+    }
+
+    /**
+     * モデル基本情報を出力する。
+     * @param model モデル情報
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putModelInfo(PmdModel model)
+            throws IOException{
+        I18nText modelName = model.getModelName();
+        putI18nName(modelName);
+        ln();
+
+        I18nText description = model.getDescription();
+        for(String lang639 : description.lang639CodeList()){
+            String descText = description.getText(lang639);
+            putDescription(lang639, descText);
+            ln();
+        }
+
+        return this;
+    }
+
+    /**
+     * モデル詳細テキストを出力する。
+     * @param lang639 言語コード
+     * @param content 詳細内容
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putDescription(CharSequence lang639,
+                                              CharSequence content)
+            throws IOException{
+        String text = content.toString();
+        text = text.replace(CRLF, LF);
+        text = text.replace(CR,   LF);
+
+        ind().put("<description");
+        if( ! I18nText.CODE639_PRIMARY.equals(lang639) ){
+            put(" ");
+            putAttr("lang", lang639);
+        }
+        put(">").ln();
+
+        putBRedContent(text);
+
+        ln();
+        ind().put("</description>").ln();
+
+        if( ! hasOnlyBasicLatin(text) && isBasicLatinOnlyOut() ){
+            putBlockComment(text);
+        }
+
+        return this;
+    }
+
+    /**
+     * break要素を含む要素内容を出力する。
+     * 必要に応じてXML定義済み実体文字が割り振られた文字、
+     * コントロールコード、および非BasicLatin文字がエスケープされる。
+     * \nはbrタグに変換される。
+     * @param content 内容
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    protected BasicXmlExporter putBRedContent(CharSequence content)
+            throws IOException{
+        int length = content.length();
+
+        for(int pos = 0; pos < length; pos++){
+            char ch = content.charAt(pos);
+            if(ch == '\n'){
+                put("<br/>").ln();
+            }else if(Character.isISOControl(ch)){
+                putCharRef2Hex(ch);
+            }else if( ! isBasicLatin(ch) && isBasicLatinOnlyOut()){
+                putCharRef4Hex(ch);
+            }else{
+                switch(ch){
+                case '&':  put("&amp;");  break;
+                case '<':  put("&lt;");   break;
+                case '>':  put("&gt;");   break;
+                case '"':  put("&quot;"); break;
+                case '\'': put("&apos;"); break;
+                default:   put(ch);       break;
+                }
+            }
+        }
+
+        return this;
+    }
+
+    /**
+     * 各種メタ情報を出力する。
+     * @param model モデルデータ
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putMetaInfo(PmdModel model) throws IOException{
+        ind().put("<license>").ln();
+        ind().put("</license>").ln(2);
+
+        ind().put("<credits>").ln();
+        ind().put("</credits>").ln(2);
+
+        ind().put("<meta ");
+        putAttr("name", "generator").put(' ')
+                                    .putAttr("content", this.generator);
+        put(" />").ln();
+        ind().put("<meta ");
+        putAttr("name", "siteURL").put(' ').putAttr("content", "");
+        put(" />").ln();
+        ind().put("<meta ");
+        putAttr("name", "imageURL").put(' ').putAttr("content", "");
+        put(" />").ln(2);
+
+        return this;
+    }
+
+    /**
+     * マテリアル素材一覧を出力する。
+     * @param model モデルデータ
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putMaterialList(PmdModel model)
+            throws IOException{
+        ind().put("<materialList>").ln(2);
+        pushNest();
+
+        int ct = 0;
+        for(Material material : model.getMaterialList()){
+            putMaterial(material, ct++);
+        }
+
+        popNest();
+        ind().put("</materialList>").ln(2);
+
+        return this;
+    }
+
+    /**
+     * マテリアル素材情報を出力する。
+     * @param material マテリアル素材
+     * @param no マテリアル通し番号
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putMaterial(Material material, int no)
+            throws IOException{
+        String bool;
+        if(material.getEdgeAppearance()) bool = "true";
+        else                             bool = "false";
+        I18nText name = material.getMaterialName();
+        String primary = name.getPrimaryText();
+        String local = name.getText();
+
+        if(local != null && local.length() > 0){
+            ind().putLineComment(local).ln();
+        }
+        ind().put("<material ");
+        if(primary != null && primary.length() > 0){
+            putAttr("name", primary).put(' ');
+        }
+
+        putAttr("showEdge", bool);
+        put(" ");
+        putNumberedIdAttr("surfaceGroupIdRef", PFX_SURFACEGROUP, no);
+        put('>').ln();
+        pushNest();
+
+        putI18nName(name);
+
+        float[] rgba = new float[4];
+
+        Color diffuse = material.getDiffuseColor();
+        diffuse.getRGBComponents(rgba);
+        ind().put("<diffuse ");
+        putFloatAttr("r", rgba[0]).put(' ');
+        putFloatAttr("g", rgba[1]).put(' ');
+        putFloatAttr("b", rgba[2]).put(' ');
+        putFloatAttr("alpha", rgba[3]).put(' ');
+        put("/>").ln();
+
+        Color specular = material.getSpecularColor();
+        specular.getRGBComponents(rgba);
+        float shininess = material.getShininess();
+        ind().put("<specular ");
+        putFloatAttr("r", rgba[0]).put(' ');
+        putFloatAttr("g", rgba[1]).put(' ');
+        putFloatAttr("b", rgba[2]).put(' ');
+        putFloatAttr("shininess", shininess).put(' ');
+        put("/>").ln();
+
+        Color ambient = material.getAmbientColor();
+        ambient.getRGBComponents(rgba);
+        ind().put("<ambient ");
+        putFloatAttr("r", rgba[0]).put(' ');
+        putFloatAttr("g", rgba[1]).put(' ');
+        putFloatAttr("b", rgba[2]).put(' ');
+        put("/>").ln();
+
+        ShadeInfo shade = material.getShadeInfo();
+        String textureFileName = shade.getTextureFileName();
+        String spheremapFileName = shade.getSpheremapFileName();
+
+        if(shade.isValidToonIndex()){
+            ind().put("<toon ");
+            int toonIdx = shade.getToonIndex();
+            putNumberedIdAttr("toonFileIdRef", PFX_TOONFILE, toonIdx);
+            put(" />");
+            String toonFileName = shade.getToonFileName();
+            if(toonFileName != null && toonFileName.length() > 0){
+                put(' ').putLineComment(toonFileName);
+            }
+            ln();
+        }
+
+        if(textureFileName != null && textureFileName.length() > 0){
+            ind().put("<textureFile ");
+            putAttr("winFileName", textureFileName);
+            put(" />").ln();
+        }
+
+        if(spheremapFileName != null && spheremapFileName.length() > 0){
+            ind().put("<spheremapFile ");
+            putAttr("winFileName", spheremapFileName);
+            put(" />").ln();
+        }
+
+        popNest();
+        ind().put("</material>").ln(2);
+
+        return this;
+    }
+
+    /**
+     * トゥーンファイルマッピング情報を出力する。
+     * @param model モデルデータ
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putToonMap(PmdModel model)
+            throws IOException{
+        ind().put("<toonMap>").ln();
+        pushNest();
+
+        ToonMap map = model.getToonMap();
+        for(int index = 0; index <= 9; index++){
+            ind().putToon(map, index).ln();
+        }
+
+        popNest();
+        ind().put("</toonMap>").ln(2);
+        return this;
+    }
+
+    /**
+     * 個別のトゥーンファイル情報を出力する。
+     * @param map トゥーンマップ
+     * @param index インデックス値
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putToon(ToonMap map, int index)
+            throws IOException{
+        put("<toonDef ");
+        putNumberedIdAttr("toonFileId", PFX_TOONFILE, index).put(' ');
+        putIntAttr("index", index).put(' ');
+        String toonFile = map.getIndexedToon(index);
+        putAttr("winFileName", toonFile);
+        put(" />");
+        putUnescapedComment(toonFile);
+        return this;
+    }
+
+    /**
+     * サーフェイスグループリストを出力する。
+     * @param model モデルデータ
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putSurfaceGroupList(PmdModel model)
+            throws IOException{
+        ind().put("<surfaceGroupList>").ln(2);
+        pushNest();
+
+        int ct = 0;
+        for(Material material : model.getMaterialList()){
+            List<Surface> surfaceList = material.getSurfaceList();
+            putSurfaceList(surfaceList, ct++);
+        }
+
+        popNest();
+        ind().put("</surfaceGroupList>").ln(2);
+
+        return this;
+    }
+
+    /**
+     * 個別のサーフェイスグループを出力する。
+     * @param surfaceList サーフェイスのリスト
+     * @param index グループインデックス
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putSurfaceList(List<Surface> surfaceList,
+                                              int index)
+            throws IOException{
+        ind().put("<surfaceGroup ");
+        putNumberedIdAttr("surfaceGroupId", PFX_SURFACEGROUP, index);
+        put(">").ln();
+        pushNest();
+
+        for(Surface surface : surfaceList){
+            putSurface(surface);
+        }
+
+        popNest();
+        ind().put("</surfaceGroup>").ln(2);
+
+        return this;
+    }
+
+    /**
+     * 個別のサーフェイスを出力する。
+     * @param surface サーフェイス
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putSurface(Surface surface)
+            throws IOException{
+        ind().put("<surface ");
+
+        Vertex vertex1 = surface.getVertex1();
+        Vertex vertex2 = surface.getVertex2();
+        Vertex vertex3 = surface.getVertex3();
+
+        putNumberedIdAttr("vtxIdRef1", PFX_VERTEX, vertex1).put(' ');
+        putNumberedIdAttr("vtxIdRef2", PFX_VERTEX, vertex2).put(' ');
+        putNumberedIdAttr("vtxIdRef3", PFX_VERTEX, vertex3).put(' ');
+
+        put("/>").ln();
+        return this;
+    }
+
+    /**
+     * 頂点リストを出力する。
+     * @param model モデルデータ
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putVertexList(PmdModel model)
+            throws IOException{
+        ind().put("<vertexList>").ln(2);
+        pushNest();
+
+        for(Vertex vertex : model.getVertexList()){
+            putVertex(vertex);
+        }
+
+        popNest();
+        ind().put("</vertexList>").ln(2);
+
+        return this;
+    }
+
+    /**
+     * 個別の頂点情報を出力する。
+     * @param vertex 頂点
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putVertex(Vertex vertex)
+            throws IOException{
+        String bool;
+        if(vertex.getEdgeAppearance()) bool = "true";
+        else                           bool = "false";
+
+        ind().put("<vertex ");
+        putNumberedIdAttr("vtxId", PFX_VERTEX, vertex).put(' ');
+        putAttr("showEdge", bool);
+        put(">").ln();
+        pushNest();
+
+        Pos3d position = vertex.getPosition();
+        ind().putPosition(position).ln();
+
+        Vec3d normal = vertex.getNormal();
+        ind().put("<normal ");
+        putFloatAttr("x", normal.getXVal()).put(' ');
+        putFloatAttr("y", normal.getYVal()).put(' ');
+        putFloatAttr("z", normal.getZVal()).put(' ');
+        put("/>").ln();
+
+        Pos2d uvPos = vertex.getUVPosition();
+        ind().put("<uvMap ");
+        putFloatAttr("u", uvPos.getXPos()).put(' ');
+        putFloatAttr("v", uvPos.getYPos()).put(' ');
+        put("/>").ln();
+
+        BoneInfo boneA = vertex.getBoneA();
+        BoneInfo boneB = vertex.getBoneB();
+        int weight = vertex.getWeightA();
+        ind().put("<skinning ");
+        putNumberedIdAttr("boneIdRef1", PFX_BONE, boneA).put(' ');
+        putNumberedIdAttr("boneIdRef2", PFX_BONE, boneB).put(' ');
+        putIntAttr("weightBalance", weight).put(' ');
+        put("/>").ln();
+
+        popNest();
+        ind().put("</vertex>").ln(2);
+
+        return this;
+    }
+
+    /**
+     * ボーンリストを出力する。
+     * @param model モデルデータ
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putBoneList(PmdModel model)
+            throws IOException{
+        ind().put("<boneList>").ln(2);
+        pushNest();
+
+        putBlockComment(BONETYPE_COMMENT).ln();
+
+        for(BoneInfo bone : model.getBoneList()){
+            putBone(bone);
+        }
+
+        popNest();
+        ind().put("</boneList>").ln(2);
+
+        return this;
+    }
+
+    /**
+     * 個別のボーン情報を出力する。
+     * @param bone ボーン情報
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putBone(BoneInfo bone)
+            throws IOException{
+        I18nText i18nName = bone.getBoneName();
+        BoneType type = bone.getBoneType();
+
+        putLocalNameComment(i18nName).putLineComment(type.getGuiName()).ln();
+        ind().put("<bone ");
+        putPrimaryNameAttr("name", i18nName).put(' ');
+        putNumberedIdAttr("boneId", PFX_BONE, bone).put(' ');
+        putAttr("type", type.name());
+        put(">").ln();
+        pushNest();
+
+        putI18nName(i18nName);
+
+        Pos3d position = bone.getPosition();
+        ind().putPosition(position).ln();
+
+        BoneInfo ikBone = bone.getIKBone();
+        if(bone.getBoneType() == BoneType.LINKEDROT){
+            ind().put("<rotationRatio ");
+            putIntAttr("ratio", bone.getRotationRatio());
+            put(" />").ln();
+        }else if(ikBone != null){
+            ind().put("<ikBone ");
+            putNumberedIdAttr("boneIdRef", PFX_BONE, ikBone);
+            put(" /> ");
+            String ikBoneName = "Ref:" + ikBone.getBoneName().getText();
+            putLineComment(ikBoneName);
+            ln();
+        }
+
+        StringBuilder chainComment = new StringBuilder();
+        ind().put("<boneChain");
+        BoneInfo prev = bone.getPrevBone();
+        BoneInfo next = bone.getNextBone();
+        if(prev != null){
+            put(' ');
+            putNumberedIdAttr("prevBoneIdRef", PFX_BONE, prev);
+            chainComment.append('[')
+                        .append(prev.getBoneName().getPrimaryText())
+                        .append(']')
+                        .append("=> #");
+        }
+        if(next != null){
+            put(' ');
+            putNumberedIdAttr("nextBoneIdRef", PFX_BONE, next);
+            if(chainComment.length() <= 0) chainComment.append("#");
+            chainComment.append(" =>")
+                        .append('[')
+                        .append(next.getBoneName().getPrimaryText())
+                        .append(']');
+        }
+        put(" />").ln();
+        ind().putLineComment(chainComment).ln();
+
+        popNest();
+        ind().put("</bone>").ln(2);
+
+        return this;
+    }
+
+    /**
+     * ボーングループリストを出力する。
+     * @param model モデルデータ
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putBoneGroupList(PmdModel model)
+            throws IOException{
+        ind().put("<boneGroupList>").ln(2);
+        pushNest();
+
+        for(BoneGroup group : model.getBoneGroupList()){
+            if(group.isDefaultBoneGroup()) continue;
+            putBoneGroup(group);
+        }
+
+        popNest();
+        ind().put("</boneGroupList>").ln(2);
+
+        return this;
+    }
+
+    /**
+     * 個別のボーングループ情報を出力する。
+     * @param group ボーングループ情報
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putBoneGroup(BoneGroup group)
+            throws IOException{
+        I18nText i18nName = group.getGroupName();
+
+        putLocalNameComment(i18nName).ln();
+        ind().put("<boneGroup ");
+        putPrimaryNameAttr("name", i18nName);
+        put(">").ln();
+        pushNest();
+
+        putI18nName(i18nName);
+
+        for(BoneInfo bone : group){
+            ind().put("<boneGroupMember ");
+            putNumberedIdAttr("boneIdRef", PFX_BONE, bone);
+            put(" /> ");
+            String boneName = "Ref:" + bone.getBoneName().getText();
+            putLineComment(boneName).ln();
+        }
+
+        popNest();
+        ind().put("</boneGroup>").ln(2);
+
+        return this;
+    }
+
+    /**
+     * IKチェーンリストを出力する。
+     * @param model モデルデータ
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putIKChainList(PmdModel model)
+            throws IOException{
+        ind().put("<ikChainList>").ln(2);
+        pushNest();
+
+        for(IKChain chain : model.getIKChainList()){
+            putIKChain(chain);
+        }
+
+        popNest();
+        ind().put("</ikChainList>").ln(2);
+
+        return this;
+    }
+
+    /**
+     * 個別のIKチェーン情報を出力する。
+     * @param chain チェーン情報
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putIKChain(IKChain chain)
+            throws IOException{
+        int depth = chain.getIKDepth();
+        float weight = chain.getIKWeight();
+        BoneInfo ikBone = chain.getIkBone();
+
+        ind().putLineComment("Ref:" + ikBone.getBoneName().getText()).ln();
+        ind().put("<ikChain ");
+        putNumberedIdAttr("ikBoneIdRef", PFX_BONE, ikBone).put(' ');
+        putIntAttr("recursiveDepth", depth).put(' ');
+        putFloatAttr("weight", weight);
+        put("> ").ln();
+        pushNest();
+
+        for(BoneInfo bone : chain){
+            ind().put("<chainOrder ");
+            putNumberedIdAttr("boneIdRef", PFX_BONE, bone);
+            put(" /> ");
+            putLineComment("Ref:" + bone.getBoneName().getText());
+            ln();
+        }
+
+        popNest();
+        ind().put("</ikChain>").ln(2);
+
+        return this;
+    }
+
+    /**
+     * モーフリストを出力する。
+     * @param model モデルデータ
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putMorphList(PmdModel model)
+            throws IOException{
+        ind().put("<morphList>").ln(2);
+        pushNest();
+
+        putBlockComment(MORPHTYPE_COMMENT).ln();
+
+        Map<MorphType, List<MorphPart>> morphMap = model.getMorphMap();
+        for(MorphType type : MorphType.values()){
+            if(type == MorphType.BASE) continue;
+            List<MorphPart> partList = morphMap.get(type);
+            if(partList == null) continue;
+            for(MorphPart part : partList){
+                putMorphPart(part);
+            }
+        }
+
+        popNest();
+        ind().put("</morphList>").ln(2);
+
+        return this;
+    }
+
+    /**
+     * 個別のモーフ情報を出力する。
+     * @param part モーフ情報
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putMorphPart(MorphPart part)
+            throws IOException{
+        I18nText i18nName = part.getMorphName();
+        String primary = i18nName.getPrimaryText();
+
+        ind().put("<morph ");
+        putAttr("name", primary).put(' ');
+        putAttr("type", part.getMorphType().name());
+        put(">");
+        putUnescapedComment(primary);
+        ln();
+        pushNest();
+
+        putI18nName(i18nName);
+
+        for(MorphVertex mvertex : part){
+            Pos3d offset = mvertex.getOffset();
+            Vertex base = mvertex.getBaseVertex();
+
+            ind().put("<morphVertex ");
+            putNumberedIdAttr("vtxIdRef", PFX_VERTEX, base).put(' ');
+            putFloatAttr("xOff", offset.getXPos()).put(' ');
+            putFloatAttr("yOff", offset.getYPos()).put(' ');
+            putFloatAttr("zOff", offset.getZPos()).put(' ');
+            put("/>");
+            ln();
+        }
+
+        popNest();
+        ind().put("</morph>").ln(2);
+
+        return this;
+    }
+
+    /**
+     * 剛体リストを出力する。
+     * @param model モデルデータ
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putRigidList(PmdModel model)
+            throws IOException{
+        ind().put("<rigidList>").ln(2);
+        pushNest();
+
+        putBlockComment(RIGIDBEHAVIOR_COMMENT).ln();
+
+        for(RigidInfo rigid : model.getRigidList()){
+            putRigid(rigid);
+        }
+
+        popNest();
+        ind().put("</rigidList>").ln(2);
+
+        return this;
+    }
+
+    /**
+     * 個別の剛体情報を出力する。
+     * @param rigid 剛体情報
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putRigid(RigidInfo rigid)
+            throws IOException{
+        BoneInfo linkedBone = rigid.getLinkedBone();
+        I18nText i18nName = rigid.getRigidName();
+        String primary = i18nName.getPrimaryText();
+
+        putLocalNameComment(i18nName).ln();
+        ind().put("<rigid ");
+        putAttr("name", primary).put(' ');
+        putNumberedIdAttr("rigidId", PFX_RIGID, rigid).put(' ');
+        putAttr("behavior", rigid.getBehaviorType().name());
+        put(">").ln();
+        pushNest();
+
+        putI18nName(i18nName);
+
+        if(linkedBone != null){
+            ind().put("<linkedBone ");
+            putNumberedIdAttr("boneIdRef", PFX_BONE, linkedBone);
+            put(" /> ");
+            putLineComment("Ref:" + linkedBone.getBoneName().getText());
+            ln(2);
+        }
+
+        RigidShape shape = rigid.getRigidShape();
+        putRigidShape(shape);
+
+        Pos3d position = rigid.getPosition();
+        ind().putPosition(position).ln();
+
+        Rad3d rotation = rigid.getRotation();
+        ind().putRadRotation(rotation).ln();
+
+        DynamicsInfo dynamics = rigid.getDynamicsInfo();
+        putDynamics(dynamics).ln();
+
+        for(RigidGroup group : rigid.getThroughGroupColl()){
+            ind().put("<throughRigidGroup ");
+            putNumberedIdAttr("rigidGroupIdRef",
+                              PFX_RIGIDGROUP,
+                              group.getSerialNumber() + 1).put(' ');
+            put(" />").ln();
+        }
+
+        popNest();
+        ind().put("</rigid>").ln(2);
+
+        return this;
+    }
+
+    /**
+     * 剛体形状を出力する。
+     * @param shape 剛体形状
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putRigidShape(RigidShape shape)
+            throws IOException{
+        RigidShapeType type = shape.getShapeType();
+
+        switch(type){
+        case BOX:
+            ind().put("<rigidShapeBox ");
+            putFloatAttr("width", shape.getWidth()).put(' ');
+            putFloatAttr("height", shape.getHeight()).put(' ');
+            putFloatAttr("depth", shape.getDepth()).put(' ');
+            break;
+        case SPHERE:
+            ind().put("<rigidShapeSphere ");
+            putFloatAttr("radius", shape.getRadius()).put(' ');
+            break;
+        case CAPSULE:
+            ind().put("<rigidShapeCapsule ");
+            putFloatAttr("height", shape.getHeight()).put(' ');
+            putFloatAttr("radius", shape.getRadius()).put(' ');
+            break;
+        default:
+            assert false;
+            throw new AssertionError();
+        }
+
+        put("/>").ln();
+
+        return this;
+    }
+
+    /**
+     * 力学設定を出力する。
+     * @param dynamics 力学設定
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putDynamics(DynamicsInfo dynamics)
+            throws IOException{
+        ind().put("<dynamics").ln();
+        pushNest();
+        ind().putFloatAttr("mass", dynamics.getMass()).ln();
+        ind().putFloatAttr("dampingPosition",
+                dynamics.getDampingPosition()).ln();
+        ind().putFloatAttr("dampingRotation",
+                dynamics.getDampingRotation()).ln();
+        ind().putFloatAttr("restitution", dynamics.getRestitution()).ln();
+        ind().putFloatAttr("friction", dynamics.getFriction()).ln();
+        popNest();
+        ind().put("/>").ln();
+
+        return this;
+    }
+
+    /**
+     * 剛体グループリストを出力する。
+     * @param model モデルデータ
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putRigidGroupList(PmdModel model)
+            throws IOException{
+        ind().put("<rigidGroupList>").ln(2);
+        pushNest();
+
+        for(RigidGroup group : model.getRigidGroupList()){
+            ind().put("<rigidGroup ");
+            putNumberedIdAttr("rigidGroupId",
+                              PFX_RIGIDGROUP,
+                              group.getSerialNumber() + 1);
+            List<RigidInfo> rigidList = group.getRigidList();
+            if(rigidList.size() <= 0){
+                put(" />").ln(2);
+                continue;
+            }
+            put(">").ln();
+            pushNest();
+
+            for(RigidInfo rigid : rigidList){
+                ind().put("<rigidGroupMember ");
+                putNumberedIdAttr("rigidIdRef", PFX_RIGID, rigid).put(' ');
+                put("/>");
+                put(' ');
+                putLineComment("Ref:" + rigid.getRigidName().getText());
+                ln();
+            }
+
+            popNest();
+            ind().put("</rigidGroup>").ln(2);
+        }
+
+        popNest();
+        ind().put("</rigidGroupList>").ln(2);
+
+        return this;
+    }
+
+    /**
+     * ジョイントリストを出力する。
+     * @param model モデルデータ
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putJointList(PmdModel model)
+            throws IOException{
+        ind().put("<jointList>").ln(2);
+        pushNest();
+
+        for(JointInfo joint : model.getJointList()){
+            putJoint(joint);
+        }
+
+        popNest();
+        ind().put("</jointList>").ln(2);
+
+        return this;
+    }
+
+    /**
+     * 個別のジョイント情報を出力する。
+     * @param joint ジョイント情報
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putJoint(JointInfo joint)
+            throws IOException{
+        I18nText i18nName = joint.getJointName();
+
+        putLocalNameComment(i18nName).ln();
+        ind().put("<joint ");
+        putPrimaryNameAttr("name", i18nName);
+        put(">").ln();
+        pushNest();
+
+        putI18nName(i18nName);
+
+        RigidInfo rigidA = joint.getRigidA();
+        RigidInfo rigidB = joint.getRigidB();
+        ind().put("<jointedRigidPair ");
+        putNumberedIdAttr("rigidIdRef1", PFX_RIGID, rigidA).put(' ');
+        putNumberedIdAttr("rigidIdRef2", PFX_RIGID, rigidB).put(' ');
+        put("/>").ln();
+        ind();
+        putLineComment("[" + rigidA.getRigidName().getText() + "]"
+                + " <=> [" + rigidB.getRigidName().getText() + "]");
+        ln(2);
+
+        Pos3d position = joint.getPosition();
+        ind().putPosition(position).ln();
+
+        TripletRange posRange = joint.getPositionRange();
+        ind().put("<limitPosition").ln();
+        pushNest();
+        ind();
+        putFloatAttr("xFrom", posRange.getXFrom()).put(' ');
+        putFloatAttr("xTo",   posRange.getXTo()).ln();
+        ind();
+        putFloatAttr("yFrom", posRange.getYFrom()).put(' ');
+        putFloatAttr("yTo",   posRange.getYTo()).ln();
+        ind();
+        putFloatAttr("zFrom", posRange.getZFrom()).put(' ');
+        putFloatAttr("zTo",   posRange.getZTo()).ln();
+        popNest();
+        ind().put("/>").ln(2);
+
+        Rad3d rotation = joint.getRotation();
+        ind().putRadRotation(rotation).ln();
+        TripletRange rotRange = joint.getRotationRange();
+        ind().put("<limitRotation").ln();
+        pushNest();
+        ind();
+        putFloatAttr("xFrom", rotRange.getXFrom()).put(' ');
+        putFloatAttr("xTo",   rotRange.getXTo()).ln();
+        ind();
+        putFloatAttr("yFrom", rotRange.getYFrom()).put(' ');
+        putFloatAttr("yTo",   rotRange.getYTo()).ln();
+        ind();
+        putFloatAttr("zFrom", rotRange.getZFrom()).put(' ');
+        putFloatAttr("zTo",   rotRange.getZTo()).ln();
+        popNest();
+        ind().put("/>").ln(2);
+
+        Pos3d elaPosition = joint.getElasticPosition();
+        ind().put("<elasticPosition ");
+        putFloatAttr("x", elaPosition.getXPos()).put(' ');
+        putFloatAttr("y", elaPosition.getYPos()).put(' ');
+        putFloatAttr("z", elaPosition.getZPos()).put(' ');
+        put("/>").ln();
+
+        Deg3d elaRotation = joint.getElasticRotation();
+        ind().put("<elasticRotation ");
+        putFloatAttr("xDeg", elaRotation.getXDeg()).put(' ');
+        putFloatAttr("yDeg", elaRotation.getYDeg()).put(' ');
+        putFloatAttr("zDeg", elaRotation.getZDeg()).put(' ');
+        put("/>").ln(2);
+
+        popNest();
+        ind().put("</joint>").ln(2);
+
+        return this;
+    }
+
+}