-/*\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;
+ }
+
+}