-/*\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("&"); break;\r
- case '<': put("<"); break;\r
- case '>': put(">"); break;\r
- case '"': put("""); break;\r
- case '\'': put("'"); 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("&"); break;
+ case '<': put("<"); break;
+ case '>': put(">"); break;
+ case '"': put("""); break;
+ case '\'': put("'"); 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;
+ }
+
+}