OSDN Git Service

1.105.3-SNAPSHOT版開発開始
[mikutoga/TogaGem.git] / src / main / java / jp / sourceforge / mikutoga / pmd / pmdexporter / PmdExporterBase.java
index d8866eb..3b1c342 100644 (file)
-/*\r
- * model exporter for pmd-file\r
- *\r
- * License : The MIT License\r
- * Copyright(c) 2010 MikuToga Partners\r
- */\r
-\r
-package jp.sourceforge.mikutoga.pmd.pmdexporter;\r
-\r
-import java.awt.Color;\r
-import java.io.IOException;\r
-import java.io.OutputStream;\r
-import java.util.Collections;\r
-import java.util.LinkedList;\r
-import java.util.List;\r
-import java.util.Map;\r
-import java.util.Set;\r
-import jp.sourceforge.mikutoga.corelib.SerialNumbered;\r
-import jp.sourceforge.mikutoga.parser.pmd.PmdLimits;\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.IKChain;\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.ShadeInfo;\r
-import jp.sourceforge.mikutoga.pmd.Surface;\r
-import jp.sourceforge.mikutoga.pmd.Vec3d;\r
-import jp.sourceforge.mikutoga.pmd.Vertex;\r
-\r
-/**\r
- * PMDファイルのエクスポーター(拡張無し基本フォーマット)。\r
- * <p>\r
- * 英名対応以降のPMDファイルフォーマットを\r
- * 使いたくない場合はこのエクスポーターを用いて出力せよ。\r
- */\r
-public class PmdExporterBase extends AbstractExporter{\r
-\r
-    /** 前(親)ボーンが無い場合の便宜的なボーンID。 */\r
-    public static final int NOPREVBONE_ID = 0xffff;\r
-    /** 次(子)ボーンが無い場合の便宜的なボーンID。 */\r
-    public static final int NONEXTBONE_ID = 0x0000;\r
-    /** 影響元IKボーンが無い場合の便宜的なボーンID。 */\r
-    public static final int NOIKBONE_ID = 0x0000;\r
-\r
-    private static final String MAGIC = "Pmd";\r
-\r
-    private static final byte[] NULLFILLER =\r
-        { (byte)0x00 };\r
-    private static final byte[] FDFILLER =\r
-        { (byte)0x00, (byte)0xfd };\r
-    private static final byte[] LFFILLER =\r
-        { (byte)0x0a, (byte)0x00, (byte)0xfd };\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
-    static{\r
-        assert NOPREVBONE_ID > PmdLimits.MAX_BONE - 1;\r
-    }\r
-\r
-    /**\r
-     * コンストラクタ。\r
-     * @param stream 出力ストリーム\r
-     * @throws NullPointerException 引数がnull\r
-     */\r
-    public PmdExporterBase(OutputStream stream)\r
-            throws NullPointerException{\r
-        super(stream);\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * 改行文字の正規化を行う。\r
-     * CR(0x0d)およびCRLF(0x0d0a)がLF(0x0a)へと正規化される。\r
-     * @param text 文字列\r
-     * @return 正規化の行われた文字列。\r
-     */\r
-    protected static String normalizeBreak(String text){\r
-        String result = text;\r
-\r
-        result = result.replace(CRLF, LF);\r
-        result = result.replace(CR, LF);\r
-\r
-        return result;\r
-    }\r
-\r
-    /**\r
-     * 文字列を指定されたバイト長で出力する。\r
-     * 文字列の改行記号はLF(0x0a)に正規化される。\r
-     * エンコード結果がバイト長に満たない場合は\r
-     * 1つの0x00及びそれに続く複数の0xfdがパディングされる。\r
-     * @param text 文字列\r
-     * @param maxByteLength バイト長指定\r
-     * @throws IOException 出力エラー\r
-     * @throws IllegalPmdTextException エンコード結果が\r
-     * 指定バイト長をはみ出した。\r
-     */\r
-    protected void dumpText(String text, int maxByteLength)\r
-            throws IOException, IllegalPmdTextException{\r
-        dumpText(text, maxByteLength, FDFILLER);\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * 文字列を指定されたバイト長で出力する。\r
-     * 文字列の改行記号はLF(0x0a)に正規化される。\r
-     * エンコード結果がバイト長に満たない場合は\r
-     * fillerがパディングされる。\r
-     * @param text 文字列\r
-     * @param maxByteLength バイト超指定\r
-     * @param filler 出力結果が足りない場合の詰め物。\r
-     * それでも足りない場合は最後のbyte要素が繰り返し出力される。\r
-     * @throws IOException 出力エラー\r
-     * @throws IllegalPmdTextException エンコード結果が\r
-     * 指定バイト長をはみ出した\r
-     */\r
-    protected void dumpText(String text, int maxByteLength, byte[] filler)\r
-            throws IOException, IllegalPmdTextException{\r
-        String normalized = normalizeBreak(text);\r
-        int blen = dumpCharSequence(normalized);\r
-        int remain = maxByteLength - blen;\r
-\r
-        if(remain < 0) throw new IllegalPmdTextException("too long text");\r
-\r
-        int fillerIdx = 0;\r
-        while(remain > 0){\r
-            if(fillerIdx >= filler.length){\r
-                fillerIdx = filler.length - 1;\r
-            }\r
-            dumpByte(filler[fillerIdx]);\r
-            fillerIdx++;\r
-            remain--;\r
-        }\r
-\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * モデルデータをPMDファイル形式で出力する。\r
-     * @param model モデルデータ\r
-     * @throws IOException 出力エラー\r
-     * @throws IllegalPmdException モデルデータに不備が発見された\r
-     */\r
-    public void dumpPmdModel(PmdModel model)\r
-            throws IOException, IllegalPmdException{\r
-        dumpBasic(model);\r
-        dumpVertexList(model);\r
-        dumpSurfaceList(model);\r
-        dumpMaterialList(model);\r
-        dumpBoneList(model);\r
-        dumpIKChainList(model);\r
-        dumpMorphList(model);\r
-        dumpMorphGroup(model);\r
-        dumpBoneGroupList(model);\r
-\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * モデル基本情報を出力する。\r
-     * @param model モデルデータ\r
-     * @throws IOException 出力エラー\r
-     * @throws IllegalPmdTextException モデル名もしくは説明が長すぎる\r
-     */\r
-    private void dumpBasic(PmdModel model)\r
-            throws IOException, IllegalPmdTextException{\r
-        dumpCharSequence(MAGIC);\r
-        float ver = model.getHeaderVersion();\r
-        dumpFloat(ver);\r
-\r
-        String modelName   = model.getModelName()  .getPrimaryText();\r
-        String description = model.getDescription().getPrimaryText();\r
-\r
-        dumpText(modelName, PmdLimits.MAXBYTES_MODELNAME);\r
-        dumpText(description, PmdLimits.MAXBYTES_MODELDESC);\r
-\r
-        flush();\r
-\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * 頂点リストを出力する。\r
-     * @param model モデルデータ\r
-     * @throws IOException 出力エラー\r
-     */\r
-    private void dumpVertexList(PmdModel model)\r
-            throws IOException{\r
-        List<Vertex> vList = model.getVertexList();\r
-\r
-        int vertexNum = vList.size();\r
-        dumpInt(vertexNum);\r
-\r
-        for(Vertex vertex : vList){\r
-            dumpVertex(vertex);\r
-        }\r
-\r
-        flush();\r
-\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * 個別の頂点データを出力する。\r
-     * @param vertex 頂点\r
-     * @throws IOException 出力エラー\r
-     */\r
-    private void dumpVertex(Vertex vertex)\r
-            throws IOException{\r
-        Pos3d position = vertex.getPosition();\r
-        dumpPos3d(position);\r
-\r
-        Vec3d normal = vertex.getNormal();\r
-        dumpVec3d(normal);\r
-\r
-        Pos2d uv = vertex.getUVPosition();\r
-        dumpPos2d(uv);\r
-\r
-        BoneInfo boneA = vertex.getBoneA();\r
-        BoneInfo boneB = vertex.getBoneB();\r
-        dumpSerialIdAsShort(boneA);\r
-        dumpSerialIdAsShort(boneB);\r
-\r
-        int weight = vertex.getWeightA();\r
-        dumpByte((byte)weight);\r
-\r
-        byte edgeFlag;\r
-        boolean hasEdge = vertex.getEdgeAppearance();\r
-        if(hasEdge) edgeFlag = 0x00;\r
-        else        edgeFlag = 0x01;\r
-        dumpByte(edgeFlag);\r
-\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * 面リストを出力する。\r
-     * @param model モデルデータ\r
-     * @throws IOException 出力エラー\r
-     */\r
-    private void dumpSurfaceList(PmdModel model)\r
-            throws IOException{\r
-        int surfaceNum = 0;\r
-        List<Material> materialList = model.getMaterialList();\r
-        for(Material material : materialList){\r
-            surfaceNum += material.getSurfaceList().size();\r
-        }\r
-        dumpInt(surfaceNum * 3);\r
-\r
-        Vertex[] triangle = new Vertex[3];\r
-        for(Material material : materialList){\r
-            for(Surface surface : material){\r
-                surface.getTriangle(triangle);\r
-                dumpShort(triangle[0].getSerialNumber());\r
-                dumpShort(triangle[1].getSerialNumber());\r
-                dumpShort(triangle[2].getSerialNumber());\r
-            }\r
-        }\r
-\r
-        flush();\r
-\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * マテリアル素材リストを出力する。\r
-     * @param model モデルデータ\r
-     * @throws IOException 出力エラー\r
-     * @throws IllegalPmdTextException シェーディングファイル情報が長すぎる\r
-     */\r
-    private void dumpMaterialList(PmdModel model)\r
-            throws IOException, IllegalPmdTextException{\r
-        List<Material> materialList = model.getMaterialList();\r
-\r
-        int materialNum = materialList.size();\r
-        dumpInt(materialNum);\r
-\r
-        float[] rgba = new float[4];\r
-\r
-        for(Material material : materialList){\r
-            Color diffuse = material.getDiffuseColor();\r
-            diffuse.getRGBComponents(rgba);\r
-            dumpFloat(rgba[0]);\r
-            dumpFloat(rgba[1]);\r
-            dumpFloat(rgba[2]);\r
-            dumpFloat(rgba[3]);\r
-\r
-            float shininess = material.getShininess();\r
-            dumpFloat(shininess);\r
-\r
-            Color specular = material.getSpecularColor();\r
-            specular.getRGBComponents(rgba);\r
-            dumpFloat(rgba[0]);\r
-            dumpFloat(rgba[1]);\r
-            dumpFloat(rgba[2]);\r
-\r
-            Color ambient = material.getAmbientColor();\r
-            ambient.getRGBComponents(rgba);\r
-            dumpFloat(rgba[0]);\r
-            dumpFloat(rgba[1]);\r
-            dumpFloat(rgba[2]);\r
-\r
-            ShadeInfo shade = material.getShadeInfo();\r
-            int toonIdx = shade.getToonIndex();\r
-            dumpByte(toonIdx);\r
-\r
-            byte edgeFlag;\r
-            boolean showEdge = material.getEdgeAppearance();\r
-            if(showEdge) edgeFlag = 0x01;\r
-            else         edgeFlag = 0x00;\r
-            dumpByte(edgeFlag);\r
-\r
-            int surfaceNum = material.getSurfaceList().size();\r
-            dumpInt(surfaceNum * 3);\r
-\r
-            dumpShadeFileInfo(shade);\r
-        }\r
-\r
-        flush();\r
-\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * シェーディングファイル情報を出力する。\r
-     * @param shade シェーディング情報\r
-     * @throws IOException 出力エラー\r
-     * @throws IllegalPmdTextException ファイル名が長すぎる\r
-     */\r
-    private void dumpShadeFileInfo(ShadeInfo shade)\r
-            throws IOException, IllegalPmdTextException{\r
-        String textureFile   = shade.getTextureFileName();\r
-        String spheremapFile = shade.getSpheremapFileName();\r
-\r
-        StringBuilder text = new StringBuilder();\r
-        if(textureFile != null) text.append(textureFile);\r
-        if(spheremapFile != null && spheremapFile.length() > 0){\r
-            text.append('*')\r
-                  .append(spheremapFile);\r
-        }\r
-\r
-        byte[] filler;\r
-        if(text.length() <= 0) filler = NULLFILLER;\r
-        else                   filler = FDFILLER;\r
-\r
-        dumpText(text.toString(),\r
-                 PmdLimits.MAXBYTES_TEXTUREFILENAME,\r
-                 filler );\r
-\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * ボーンリストを出力する。\r
-     * @param model モデルデータ\r
-     * @throws IOException 出力エラー\r
-     * @throws IllegalPmdTextException ボーン名が長すぎる\r
-     */\r
-    private void dumpBoneList(PmdModel model)\r
-            throws IOException, IllegalPmdTextException{\r
-        List<BoneInfo> boneList = model.getBoneList();\r
-\r
-        int boneNum = boneList.size();\r
-        dumpShort(boneNum);\r
-\r
-        for(BoneInfo bone : boneList){\r
-            dumpBone(bone);\r
-        }\r
-\r
-        flush();\r
-\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * 個別のボーン情報を出力する。\r
-     * @param bone ボーン情報\r
-     * @throws IOException 出力エラー\r
-     * @throws IllegalPmdTextException ボーン名が長すぎる\r
-     */\r
-    private void dumpBone(BoneInfo bone)\r
-            throws IOException, IllegalPmdTextException{\r
-        String boneName = bone.getBoneName().getPrimaryText();\r
-        dumpText(boneName, PmdLimits.MAXBYTES_BONENAME);\r
-\r
-        BoneInfo prev = bone.getPrevBone();\r
-        if(prev != null) dumpSerialIdAsShort(prev);\r
-        else             dumpShort(NOPREVBONE_ID);\r
-\r
-        BoneInfo next = bone.getNextBone();\r
-        if(next != null) dumpSerialIdAsShort(next);\r
-        else             dumpShort(NONEXTBONE_ID);\r
-\r
-        BoneType type = bone.getBoneType();\r
-        dumpByte(type.encode());\r
-\r
-        if(type == BoneType.LINKEDROT){\r
-            int ratio = bone.getRotationRatio();\r
-            dumpShort(ratio);\r
-        }else{\r
-            BoneInfo ik = bone.getIKBone();\r
-            if(ik != null) dumpSerialIdAsShort(ik);\r
-            else           dumpShort(NOIKBONE_ID);\r
-        }\r
-\r
-        Pos3d position = bone.getPosition();\r
-        dumpPos3d(position);\r
-\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * IKチェーンリストを出力する。\r
-     * @param model モデルデータ\r
-     * @throws IOException 出力エラー\r
-     */\r
-    private void dumpIKChainList(PmdModel model)\r
-            throws IOException{\r
-        List<IKChain> ikChainList = model.getIKChainList();\r
-\r
-        int ikNum = ikChainList.size();\r
-        dumpShort(ikNum);\r
-\r
-        for(IKChain chain : ikChainList){\r
-            dumpIKChain(chain);\r
-        }\r
-\r
-        flush();\r
-\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * IKチェーンを出力する。\r
-     * @param chain IKチェーン\r
-     * @throws IOException 出力エラー\r
-     */\r
-    // TODO ボーンリストから自動抽出できる情報ではないのか?\r
-    private void dumpIKChain(IKChain chain)\r
-            throws IOException{\r
-        BoneInfo ikBone = chain.getIkBone();\r
-        dumpSerialIdAsShort(ikBone);\r
-\r
-        List<BoneInfo> boneList = chain.getChainedBoneList();\r
-\r
-        BoneInfo bone1st = boneList.get(0);\r
-        dumpSerialIdAsShort(bone1st);\r
-\r
-        int boneNum = boneList.size();\r
-        dumpByte(boneNum - 1);\r
-\r
-        int depth = chain.getIKDepth();\r
-        float weight = chain.getIKWeight();\r
-\r
-        dumpShort(depth);\r
-        dumpFloat(weight);\r
-\r
-        for(int idx = 1; idx < boneNum; idx++){ // リストの2番目以降全て\r
-            BoneInfo bone = boneList.get(idx);\r
-            dumpSerialIdAsShort(bone);\r
-        }\r
-\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * モーフリストを出力する。\r
-     * @param model モデルデータ\r
-     * @throws IOException 出力エラー\r
-     * @throws IllegalPmdTextException モーフ名が長すぎる\r
-     */\r
-    private void dumpMorphList(PmdModel model)\r
-            throws IOException, IllegalPmdTextException{\r
-        Map<MorphType, List<MorphPart>> morphMap = model.getMorphMap();\r
-        Set<MorphType> typeSet = morphMap.keySet();\r
-        List<MorphVertex> mergedMorphVertexList = model.mergeMorphVertex();\r
-\r
-        int totalMorphPart = 0;\r
-        for(MorphType type : typeSet){\r
-            List<MorphPart> partList = morphMap.get(type);\r
-            if(partList == null) continue;\r
-            totalMorphPart += partList.size();\r
-        }\r
-\r
-        if(totalMorphPart <= 0){\r
-            dumpShort(0);\r
-            return;\r
-        }else{\r
-            totalMorphPart++;  // baseの分\r
-            dumpShort(totalMorphPart);\r
-        }\r
-\r
-        dumpText("base", PmdLimits.MAXBYTES_MORPHNAME);\r
-        int totalVertex = mergedMorphVertexList.size();\r
-        dumpInt(totalVertex);\r
-        dumpByte(MorphType.BASE.encode());\r
-        for(MorphVertex morphVertex : mergedMorphVertexList){\r
-            Vertex baseVertex = morphVertex.getBaseVertex();\r
-            dumpInt(baseVertex.getSerialNumber());\r
-            dumpPos3d(baseVertex.getPosition());\r
-        }\r
-\r
-        for(MorphType type : typeSet){\r
-            List<MorphPart> partList = morphMap.get(type);\r
-            if(partList == null) continue;\r
-            for(MorphPart part : partList){\r
-                dumpText(part.getMorphName().getPrimaryText(),\r
-                         PmdLimits.MAXBYTES_MORPHNAME );\r
-                List<MorphVertex> morphVertexList = part.getMorphVertexList();\r
-                dumpInt(morphVertexList.size());\r
-                dumpByte(part.getMorphType().encode());\r
-\r
-                for(MorphVertex morphVertex : morphVertexList){\r
-                    dumpInt(morphVertex.getSerialNumber());\r
-                    dumpPos3d(morphVertex.getOffset());\r
-                }\r
-            }\r
-        }\r
-\r
-        flush();\r
-\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * モーフグループを出力する。\r
-     * @param model モデルデータ\r
-     * @throws IOException 出力エラー\r
-     */\r
-    private void dumpMorphGroup(PmdModel model)\r
-            throws IOException{\r
-        Map<MorphType, List<MorphPart>> morphMap = model.getMorphMap();\r
-        Set<MorphType> typeSet = morphMap.keySet();\r
-\r
-        int totalMorph = 0;\r
-        for(MorphType type : typeSet){\r
-            List<MorphPart> partList = morphMap.get(type);\r
-            if(partList == null) continue;\r
-            totalMorph += partList.size();\r
-        }\r
-        dumpByte(totalMorph);\r
-\r
-        List<MorphType> typeList = new LinkedList<MorphType>();\r
-        for(MorphType type : typeSet){\r
-            assert ! type.isBase();\r
-            typeList.add(type);\r
-        }\r
-        Collections.reverse(typeList);  // 一応本家と互換性を\r
-\r
-        for(MorphType type : typeList){\r
-            List<MorphPart> partList = morphMap.get(type);\r
-            if(partList == null) continue;\r
-            for(MorphPart part : partList){\r
-                dumpSerialIdAsShort(part);\r
-            }\r
-        }\r
-\r
-        flush();\r
-\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * ボーングループリストを出力する。\r
-     * デフォルトボーングループ内訳は出力されない。\r
-     * @param model モデルデータ\r
-     * @throws IOException 出力エラー\r
-     * @throws IllegalPmdTextException ボーングループ名が長すぎる\r
-     */\r
-    private void dumpBoneGroupList(PmdModel model)\r
-            throws IOException, IllegalPmdTextException{\r
-        List<BoneGroup> groupList = model.getBoneGroupList();\r
-        int groupNum = groupList.size();\r
-        dumpByte(groupNum - 1);\r
-\r
-        int dispBoneNum = 0;\r
-        for(BoneGroup group : groupList){\r
-            if(group.isDefaultBoneGroup()) continue;\r
-            dumpText(group.getGroupName().getPrimaryText(),\r
-                     PmdLimits.MAXBYTES_BONEGROUPNAME, LFFILLER );\r
-            dispBoneNum += group.getBoneList().size();\r
-        }\r
-        dumpInt(dispBoneNum);\r
-\r
-        for(BoneGroup group : groupList){\r
-            if(group.isDefaultBoneGroup()) continue;\r
-            for(BoneInfo bone : group){\r
-                dumpSerialIdAsShort(bone);\r
-                int groupId = group.getSerialNumber();\r
-                dumpByte(groupId);\r
-            }\r
-        }\r
-\r
-        flush();\r
-\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * 各種通し番号をshort値で出力する。\r
-     * short値に収まらない上位ビットは捨てられる。\r
-     * @param obj 番号づけられたオブジェクト\r
-     * @throws IOException 出力エラー\r
-     */\r
-    protected void dumpSerialIdAsShort(SerialNumbered obj)\r
-            throws IOException{\r
-        int serialId = obj.getSerialNumber();\r
-        dumpShort(serialId);\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * 2次元位置情報を出力する。\r
-     * @param position 2次元位置情報\r
-     * @throws IOException 出力エラー\r
-     */\r
-    protected void dumpPos2d(Pos2d position) throws IOException{\r
-        float xPos = position.getXPos();\r
-        float yPos = position.getYPos();\r
-\r
-        dumpFloat(xPos);\r
-        dumpFloat(yPos);\r
-\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * 3次元位置情報を出力する。\r
-     * @param position 3次元位置情報\r
-     * @throws IOException 出力エラー\r
-     */\r
-    protected void dumpPos3d(Pos3d position) throws IOException{\r
-        float xPos = position.getXPos();\r
-        float yPos = position.getYPos();\r
-        float zPos = position.getZPos();\r
-\r
-        dumpFloat(xPos);\r
-        dumpFloat(yPos);\r
-        dumpFloat(zPos);\r
-\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * 3次元ベクトル情報を出力する。\r
-     * @param vector 3次元ベクトル\r
-     * @throws IOException 出力エラー\r
-     */\r
-    protected void dumpVec3d(Vec3d vector) throws IOException{\r
-        float xVal = vector.getXVal();\r
-        float yVal = vector.getYVal();\r
-        float zVal = vector.getZVal();\r
-\r
-        dumpFloat(xVal);\r
-        dumpFloat(yVal);\r
-        dumpFloat(zVal);\r
-\r
-        return;\r
-    }\r
-\r
-}\r
+/*
+ * model exporter for pmd-file
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.pmdexporter;
+
+import java.awt.Color;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import jp.sourceforge.mikutoga.corelib.SerialNumbered;
+import jp.sourceforge.mikutoga.parser.pmd.PmdLimits;
+import jp.sourceforge.mikutoga.pmd.BoneGroup;
+import jp.sourceforge.mikutoga.pmd.BoneInfo;
+import jp.sourceforge.mikutoga.pmd.BoneType;
+import jp.sourceforge.mikutoga.pmd.IKChain;
+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.ShadeInfo;
+import jp.sourceforge.mikutoga.pmd.Surface;
+import jp.sourceforge.mikutoga.pmd.Vec3d;
+import jp.sourceforge.mikutoga.pmd.Vertex;
+
+/**
+ * PMDファイルのエクスポーター(拡張無し基本フォーマット)。
+ * <p>
+ * 英名対応以降のPMDファイルフォーマットを
+ * 使いたくない場合はこのエクスポーターを用いて出力せよ。
+ */
+public class PmdExporterBase extends AbstractExporter{
+
+    /** 前(親)ボーンが無い場合の便宜的なボーンID。 */
+    public static final int NOPREVBONE_ID = 0xffff;
+    /** 次(子)ボーンが無い場合の便宜的なボーンID。 */
+    public static final int NONEXTBONE_ID = 0x0000;
+    /** 影響元IKボーンが無い場合の便宜的なボーンID。 */
+    public static final int NOIKBONE_ID = 0x0000;
+
+    private static final String MAGIC = "Pmd";
+
+    private static final byte[] NULLFILLER =
+        { (byte)0x00 };
+    private static final byte[] FDFILLER =
+        { (byte)0x00, (byte)0xfd };
+    private static final byte[] LFFILLER =
+        { (byte)0x0a, (byte)0x00, (byte)0xfd };
+
+    /** 改行文字列 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
+
+    static{
+        assert NOPREVBONE_ID > PmdLimits.MAX_BONE - 1;
+    }
+
+    /**
+     * コンストラクタ。
+     * @param stream 出力ストリーム
+     * @throws NullPointerException 引数がnull
+     */
+    public PmdExporterBase(OutputStream stream)
+            throws NullPointerException{
+        super(stream);
+        return;
+    }
+
+    /**
+     * 改行文字の正規化を行う。
+     * CR(0x0d)およびCRLF(0x0d0a)がLF(0x0a)へと正規化される。
+     * @param text 文字列
+     * @return 正規化の行われた文字列。
+     */
+    protected static String normalizeBreak(String text){
+        String result = text;
+
+        result = result.replace(CRLF, LF);
+        result = result.replace(CR, LF);
+
+        return result;
+    }
+
+    /**
+     * 文字列を指定されたバイト長で出力する。
+     * 文字列の改行記号はLF(0x0a)に正規化される。
+     * エンコード結果がバイト長に満たない場合は
+     * 1つの0x00及びそれに続く複数の0xfdがパディングされる。
+     * @param text 文字列
+     * @param maxByteLength バイト長指定
+     * @throws IOException 出力エラー
+     * @throws IllegalPmdTextException エンコード結果が
+     * 指定バイト長をはみ出した。
+     */
+    protected void dumpText(String text, int maxByteLength)
+            throws IOException, IllegalPmdTextException{
+        dumpText(text, maxByteLength, FDFILLER);
+        return;
+    }
+
+    /**
+     * 文字列を指定されたバイト長で出力する。
+     * 文字列の改行記号はLF(0x0a)に正規化される。
+     * エンコード結果がバイト長に満たない場合は
+     * fillerがパディングされる。
+     * @param text 文字列
+     * @param maxByteLength バイト超指定
+     * @param filler 出力結果が足りない場合の詰め物。
+     * それでも足りない場合は最後のbyte要素が繰り返し出力される。
+     * @throws IOException 出力エラー
+     * @throws IllegalPmdTextException エンコード結果が
+     * 指定バイト長をはみ出した
+     */
+    protected void dumpText(String text, int maxByteLength, byte[] filler)
+            throws IOException, IllegalPmdTextException{
+        String normalized = normalizeBreak(text);
+        int blen = dumpCharSequence(normalized);
+        int remain = maxByteLength - blen;
+
+        if(remain < 0) throw new IllegalPmdTextException("too long text");
+
+        int fillerIdx = 0;
+        while(remain > 0){
+            if(fillerIdx >= filler.length){
+                fillerIdx = filler.length - 1;
+            }
+            dumpByte(filler[fillerIdx]);
+            fillerIdx++;
+            remain--;
+        }
+
+        return;
+    }
+
+    /**
+     * モデルデータをPMDファイル形式で出力する。
+     * @param model モデルデータ
+     * @throws IOException 出力エラー
+     * @throws IllegalPmdException モデルデータに不備が発見された
+     */
+    public void dumpPmdModel(PmdModel model)
+            throws IOException, IllegalPmdException{
+        dumpBasic(model);
+        dumpVertexList(model);
+        dumpSurfaceList(model);
+        dumpMaterialList(model);
+        dumpBoneList(model);
+        dumpIKChainList(model);
+        dumpMorphList(model);
+        dumpMorphGroup(model);
+        dumpBoneGroupList(model);
+
+        return;
+    }
+
+    /**
+     * モデル基本情報を出力する。
+     * @param model モデルデータ
+     * @throws IOException 出力エラー
+     * @throws IllegalPmdTextException モデル名もしくは説明が長すぎる
+     */
+    private void dumpBasic(PmdModel model)
+            throws IOException, IllegalPmdTextException{
+        dumpCharSequence(MAGIC);
+        float ver = model.getHeaderVersion();
+        dumpFloat(ver);
+
+        String modelName   = model.getModelName()  .getPrimaryText();
+        String description = model.getDescription().getPrimaryText();
+
+        dumpText(modelName, PmdLimits.MAXBYTES_MODELNAME);
+        dumpText(description, PmdLimits.MAXBYTES_MODELDESC);
+
+        flush();
+
+        return;
+    }
+
+    /**
+     * 頂点リストを出力する。
+     * @param model モデルデータ
+     * @throws IOException 出力エラー
+     */
+    private void dumpVertexList(PmdModel model)
+            throws IOException{
+        List<Vertex> vList = model.getVertexList();
+
+        int vertexNum = vList.size();
+        dumpInt(vertexNum);
+
+        for(Vertex vertex : vList){
+            dumpVertex(vertex);
+        }
+
+        flush();
+
+        return;
+    }
+
+    /**
+     * 個別の頂点データを出力する。
+     * @param vertex 頂点
+     * @throws IOException 出力エラー
+     */
+    private void dumpVertex(Vertex vertex)
+            throws IOException{
+        Pos3d position = vertex.getPosition();
+        dumpPos3d(position);
+
+        Vec3d normal = vertex.getNormal();
+        dumpVec3d(normal);
+
+        Pos2d uv = vertex.getUVPosition();
+        dumpPos2d(uv);
+
+        BoneInfo boneA = vertex.getBoneA();
+        BoneInfo boneB = vertex.getBoneB();
+        dumpSerialIdAsShort(boneA);
+        dumpSerialIdAsShort(boneB);
+
+        int weight = vertex.getWeightA();
+        dumpByte((byte)weight);
+
+        byte edgeFlag;
+        boolean hasEdge = vertex.getEdgeAppearance();
+        if(hasEdge) edgeFlag = 0x00;
+        else        edgeFlag = 0x01;
+        dumpByte(edgeFlag);
+
+        return;
+    }
+
+    /**
+     * 面リストを出力する。
+     * @param model モデルデータ
+     * @throws IOException 出力エラー
+     */
+    private void dumpSurfaceList(PmdModel model)
+            throws IOException{
+        int surfaceNum = 0;
+        List<Material> materialList = model.getMaterialList();
+        for(Material material : materialList){
+            surfaceNum += material.getSurfaceList().size();
+        }
+        dumpInt(surfaceNum * 3);
+
+        Vertex[] triangle = new Vertex[3];
+        for(Material material : materialList){
+            for(Surface surface : material){
+                surface.getTriangle(triangle);
+                dumpShort(triangle[0].getSerialNumber());
+                dumpShort(triangle[1].getSerialNumber());
+                dumpShort(triangle[2].getSerialNumber());
+            }
+        }
+
+        flush();
+
+        return;
+    }
+
+    /**
+     * マテリアル素材リストを出力する。
+     * @param model モデルデータ
+     * @throws IOException 出力エラー
+     * @throws IllegalPmdTextException シェーディングファイル情報が長すぎる
+     */
+    private void dumpMaterialList(PmdModel model)
+            throws IOException, IllegalPmdTextException{
+        List<Material> materialList = model.getMaterialList();
+
+        int materialNum = materialList.size();
+        dumpInt(materialNum);
+
+        float[] rgba = new float[4];
+
+        for(Material material : materialList){
+            Color diffuse = material.getDiffuseColor();
+            diffuse.getRGBComponents(rgba);
+            dumpFloat(rgba[0]);
+            dumpFloat(rgba[1]);
+            dumpFloat(rgba[2]);
+            dumpFloat(rgba[3]);
+
+            float shininess = material.getShininess();
+            dumpFloat(shininess);
+
+            Color specular = material.getSpecularColor();
+            specular.getRGBComponents(rgba);
+            dumpFloat(rgba[0]);
+            dumpFloat(rgba[1]);
+            dumpFloat(rgba[2]);
+
+            Color ambient = material.getAmbientColor();
+            ambient.getRGBComponents(rgba);
+            dumpFloat(rgba[0]);
+            dumpFloat(rgba[1]);
+            dumpFloat(rgba[2]);
+
+            ShadeInfo shade = material.getShadeInfo();
+            int toonIdx = shade.getToonIndex();
+            dumpByte(toonIdx);
+
+            byte edgeFlag;
+            boolean showEdge = material.getEdgeAppearance();
+            if(showEdge) edgeFlag = 0x01;
+            else         edgeFlag = 0x00;
+            dumpByte(edgeFlag);
+
+            int surfaceNum = material.getSurfaceList().size();
+            dumpInt(surfaceNum * 3);
+
+            dumpShadeFileInfo(shade);
+        }
+
+        flush();
+
+        return;
+    }
+
+    /**
+     * シェーディングファイル情報を出力する。
+     * @param shade シェーディング情報
+     * @throws IOException 出力エラー
+     * @throws IllegalPmdTextException ファイル名が長すぎる
+     */
+    private void dumpShadeFileInfo(ShadeInfo shade)
+            throws IOException, IllegalPmdTextException{
+        String textureFile   = shade.getTextureFileName();
+        String spheremapFile = shade.getSpheremapFileName();
+
+        StringBuilder text = new StringBuilder();
+        if(textureFile != null) text.append(textureFile);
+        if(spheremapFile != null && spheremapFile.length() > 0){
+            text.append('*')
+                  .append(spheremapFile);
+        }
+
+        byte[] filler;
+        if(text.length() <= 0) filler = NULLFILLER;
+        else                   filler = FDFILLER;
+
+        dumpText(text.toString(),
+                 PmdLimits.MAXBYTES_TEXTUREFILENAME,
+                 filler );
+
+        return;
+    }
+
+    /**
+     * ボーンリストを出力する。
+     * @param model モデルデータ
+     * @throws IOException 出力エラー
+     * @throws IllegalPmdTextException ボーン名が長すぎる
+     */
+    private void dumpBoneList(PmdModel model)
+            throws IOException, IllegalPmdTextException{
+        List<BoneInfo> boneList = model.getBoneList();
+
+        int boneNum = boneList.size();
+        dumpShort(boneNum);
+
+        for(BoneInfo bone : boneList){
+            dumpBone(bone);
+        }
+
+        flush();
+
+        return;
+    }
+
+    /**
+     * 個別のボーン情報を出力する。
+     * @param bone ボーン情報
+     * @throws IOException 出力エラー
+     * @throws IllegalPmdTextException ボーン名が長すぎる
+     */
+    private void dumpBone(BoneInfo bone)
+            throws IOException, IllegalPmdTextException{
+        String boneName = bone.getBoneName().getPrimaryText();
+        dumpText(boneName, PmdLimits.MAXBYTES_BONENAME);
+
+        BoneInfo prev = bone.getPrevBone();
+        if(prev != null) dumpSerialIdAsShort(prev);
+        else             dumpShort(NOPREVBONE_ID);
+
+        BoneInfo next = bone.getNextBone();
+        if(next != null) dumpSerialIdAsShort(next);
+        else             dumpShort(NONEXTBONE_ID);
+
+        BoneType type = bone.getBoneType();
+        dumpByte(type.encode());
+
+        if(type == BoneType.LINKEDROT){
+            int ratio = bone.getRotationRatio();
+            dumpShort(ratio);
+        }else{
+            BoneInfo ik = bone.getIKBone();
+            if(ik != null) dumpSerialIdAsShort(ik);
+            else           dumpShort(NOIKBONE_ID);
+        }
+
+        Pos3d position = bone.getPosition();
+        dumpPos3d(position);
+
+        return;
+    }
+
+    /**
+     * IKチェーンリストを出力する。
+     * @param model モデルデータ
+     * @throws IOException 出力エラー
+     */
+    private void dumpIKChainList(PmdModel model)
+            throws IOException{
+        List<IKChain> ikChainList = model.getIKChainList();
+
+        int ikNum = ikChainList.size();
+        dumpShort(ikNum);
+
+        for(IKChain chain : ikChainList){
+            dumpIKChain(chain);
+        }
+
+        flush();
+
+        return;
+    }
+
+    /**
+     * IKチェーンを出力する。
+     * @param chain IKチェーン
+     * @throws IOException 出力エラー
+     */
+    // TODO ボーンリストから自動抽出できる情報ではないのか?
+    private void dumpIKChain(IKChain chain)
+            throws IOException{
+        BoneInfo ikBone = chain.getIkBone();
+        dumpSerialIdAsShort(ikBone);
+
+        List<BoneInfo> boneList = chain.getChainedBoneList();
+
+        BoneInfo bone1st = boneList.get(0);
+        dumpSerialIdAsShort(bone1st);
+
+        int boneNum = boneList.size();
+        dumpByte(boneNum - 1);
+
+        int depth = chain.getIKDepth();
+        float weight = chain.getIKWeight();
+
+        dumpShort(depth);
+        dumpFloat(weight);
+
+        for(int idx = 1; idx < boneNum; idx++){ // リストの2番目以降全て
+            BoneInfo bone = boneList.get(idx);
+            dumpSerialIdAsShort(bone);
+        }
+
+        return;
+    }
+
+    /**
+     * モーフリストを出力する。
+     * @param model モデルデータ
+     * @throws IOException 出力エラー
+     * @throws IllegalPmdTextException モーフ名が長すぎる
+     */
+    private void dumpMorphList(PmdModel model)
+            throws IOException, IllegalPmdTextException{
+        Map<MorphType, List<MorphPart>> morphMap = model.getMorphMap();
+        Set<MorphType> typeSet = morphMap.keySet();
+        List<MorphVertex> mergedMorphVertexList = model.mergeMorphVertex();
+
+        int totalMorphPart = 0;
+        for(MorphType type : typeSet){
+            List<MorphPart> partList = morphMap.get(type);
+            if(partList == null) continue;
+            totalMorphPart += partList.size();
+        }
+
+        if(totalMorphPart <= 0){
+            dumpShort(0);
+            return;
+        }else{
+            totalMorphPart++;  // baseの分
+            dumpShort(totalMorphPart);
+        }
+
+        dumpText("base", PmdLimits.MAXBYTES_MORPHNAME);
+        int totalVertex = mergedMorphVertexList.size();
+        dumpInt(totalVertex);
+        dumpByte(MorphType.BASE.encode());
+        for(MorphVertex morphVertex : mergedMorphVertexList){
+            Vertex baseVertex = morphVertex.getBaseVertex();
+            dumpInt(baseVertex.getSerialNumber());
+            dumpPos3d(baseVertex.getPosition());
+        }
+
+        for(MorphType type : typeSet){
+            List<MorphPart> partList = morphMap.get(type);
+            if(partList == null) continue;
+            for(MorphPart part : partList){
+                dumpText(part.getMorphName().getPrimaryText(),
+                         PmdLimits.MAXBYTES_MORPHNAME );
+                List<MorphVertex> morphVertexList = part.getMorphVertexList();
+                dumpInt(morphVertexList.size());
+                dumpByte(part.getMorphType().encode());
+
+                for(MorphVertex morphVertex : morphVertexList){
+                    dumpInt(morphVertex.getSerialNumber());
+                    dumpPos3d(morphVertex.getOffset());
+                }
+            }
+        }
+
+        flush();
+
+        return;
+    }
+
+    /**
+     * モーフグループを出力する。
+     * @param model モデルデータ
+     * @throws IOException 出力エラー
+     */
+    private void dumpMorphGroup(PmdModel model)
+            throws IOException{
+        Map<MorphType, List<MorphPart>> morphMap = model.getMorphMap();
+        Set<MorphType> typeSet = morphMap.keySet();
+
+        int totalMorph = 0;
+        for(MorphType type : typeSet){
+            List<MorphPart> partList = morphMap.get(type);
+            if(partList == null) continue;
+            totalMorph += partList.size();
+        }
+        dumpByte(totalMorph);
+
+        List<MorphType> typeList = new LinkedList<MorphType>();
+        for(MorphType type : typeSet){
+            assert ! type.isBase();
+            typeList.add(type);
+        }
+        Collections.reverse(typeList);  // 一応本家と互換性を
+
+        for(MorphType type : typeList){
+            List<MorphPart> partList = morphMap.get(type);
+            if(partList == null) continue;
+            for(MorphPart part : partList){
+                dumpSerialIdAsShort(part);
+            }
+        }
+
+        flush();
+
+        return;
+    }
+
+    /**
+     * ボーングループリストを出力する。
+     * デフォルトボーングループ内訳は出力されない。
+     * @param model モデルデータ
+     * @throws IOException 出力エラー
+     * @throws IllegalPmdTextException ボーングループ名が長すぎる
+     */
+    private void dumpBoneGroupList(PmdModel model)
+            throws IOException, IllegalPmdTextException{
+        List<BoneGroup> groupList = model.getBoneGroupList();
+        int groupNum = groupList.size();
+        dumpByte(groupNum - 1);
+
+        int dispBoneNum = 0;
+        for(BoneGroup group : groupList){
+            if(group.isDefaultBoneGroup()) continue;
+            dumpText(group.getGroupName().getPrimaryText(),
+                     PmdLimits.MAXBYTES_BONEGROUPNAME, LFFILLER );
+            dispBoneNum += group.getBoneList().size();
+        }
+        dumpInt(dispBoneNum);
+
+        for(BoneGroup group : groupList){
+            if(group.isDefaultBoneGroup()) continue;
+            for(BoneInfo bone : group){
+                dumpSerialIdAsShort(bone);
+                int groupId = group.getSerialNumber();
+                dumpByte(groupId);
+            }
+        }
+
+        flush();
+
+        return;
+    }
+
+    /**
+     * 各種通し番号をshort値で出力する。
+     * short値に収まらない上位ビットは捨てられる。
+     * @param obj 番号づけられたオブジェクト
+     * @throws IOException 出力エラー
+     */
+    protected void dumpSerialIdAsShort(SerialNumbered obj)
+            throws IOException{
+        int serialId = obj.getSerialNumber();
+        dumpShort(serialId);
+        return;
+    }
+
+    /**
+     * 2次元位置情報を出力する。
+     * @param position 2次元位置情報
+     * @throws IOException 出力エラー
+     */
+    protected void dumpPos2d(Pos2d position) throws IOException{
+        float xPos = position.getXPos();
+        float yPos = position.getYPos();
+
+        dumpFloat(xPos);
+        dumpFloat(yPos);
+
+        return;
+    }
+
+    /**
+     * 3次元位置情報を出力する。
+     * @param position 3次元位置情報
+     * @throws IOException 出力エラー
+     */
+    protected void dumpPos3d(Pos3d position) throws IOException{
+        float xPos = position.getXPos();
+        float yPos = position.getYPos();
+        float zPos = position.getZPos();
+
+        dumpFloat(xPos);
+        dumpFloat(yPos);
+        dumpFloat(zPos);
+
+        return;
+    }
+
+    /**
+     * 3次元ベクトル情報を出力する。
+     * @param vector 3次元ベクトル
+     * @throws IOException 出力エラー
+     */
+    protected void dumpVec3d(Vec3d vector) throws IOException{
+        float xVal = vector.getXVal();
+        float yVal = vector.getYVal();
+        float zVal = vector.getZVal();
+
+        dumpFloat(xVal);
+        dumpFloat(yVal);
+        dumpFloat(zVal);
+
+        return;
+    }
+
+}