+/*\r
+ * pmd file parser\r
+ *\r
+ * License : The MIT License\r
+ * Copyright(c) 2010 MikuToga Partners\r
+ */\r
+\r
+package jp.sourceforge.mikutoga.parser.pmd;\r
+\r
+import java.io.IOException;\r
+import jp.sourceforge.mikutoga.parser.CommonParser;\r
+import jp.sourceforge.mikutoga.parser.MmdFormatException;\r
+import jp.sourceforge.mikutoga.parser.MmdSource;\r
+\r
+/**\r
+ * PMDモデルファイルのパーサ基本部。\r
+ */\r
+public class PmdParserBase extends CommonParser {\r
+\r
+ /** 改行文字列 CR。 */\r
+ protected static final String CR = "\r"; // 0x0d\r
+ /** 改行文字列 LF。 */\r
+ protected static final String LF = "\n"; // 0x0a\r
+ /** 改行文字列 CRLF。 */\r
+ protected static final String CRLF = CR + LF; // 0x0d, 0x0a\r
+\r
+ private static final String MAGIC = "Pmd";\r
+ private static final int MAGIC_SZ = MAGIC.getBytes(CS_WIN31J).length;\r
+\r
+ private static final int VERTEX_DATA_SZ = 38;\r
+ private static final int SURFACE_DATA_SZ = 6;\r
+ private static final int MATERIAL_DATA_SZ = 70;\r
+ private static final int BONE_DATA_SZ = 39;\r
+ private static final int MORPHVERTEX_DATA_SZ = 16;\r
+ private static final int MORPHORDER_DATA_SZ = 2;\r
+ private static final int BONEGROUP_DATA_SZ = 50;\r
+ private static final int GROUPEDBONE_DATA_SZ = 3;\r
+\r
+\r
+ private PmdBasicHandler basicHandler = null;\r
+ private PmdShapeHandler shapeHandler = null;\r
+ private PmdMaterialHandler materialHandler = null;\r
+ private PmdBoneHandler boneHandler = null;\r
+ private PmdMorphHandler morphHandler = null;\r
+\r
+ private int boneCount = -1;\r
+ private int morphCount = -1;\r
+ private int boneGroupCount = -1;\r
+\r
+\r
+ /**\r
+ * コンストラクタ。\r
+ * @param source 入力ソース\r
+ */\r
+ public PmdParserBase(MmdSource source){\r
+ super(source);\r
+ return;\r
+ }\r
+\r
+ /**\r
+ * 文字列の最後がLF(0x0a)の場合削除する。\r
+ * @param name 文字列\r
+ * @return 末尾LFが削除された文字列\r
+ */\r
+ public static String chopLastLF(String name){\r
+ String result;\r
+\r
+ if(name.endsWith(LF)){\r
+ result = name.substring(0, name.length() - 1);\r
+ }else{\r
+ result = name;\r
+ }\r
+\r
+ return result;\r
+ }\r
+\r
+ /**\r
+ * シェーディング用ファイル情報から\r
+ * テクスチャファイル名とスフィアマップファイル名を分離する。\r
+ * @param shadingFile シェーディング用ファイル情報\r
+ * @return [0]:テクスチャファイル名 [1]:スフィアマップファイル名。\r
+ * 該当ファイル名が無い場合は空文字列。\r
+ */\r
+ public static String[] splitShadingFileInfo(String shadingFile){\r
+ String[] result;\r
+\r
+ result = shadingFile.split('\\'+"*", 2);\r
+ assert result.length == 1 || result.length == 2;\r
+\r
+ if(result.length == 1){\r
+ String onlyFile = result[0];\r
+ result = new String[2];\r
+ result[0] = "";\r
+ result[1] = "";\r
+ if(onlyFile.endsWith(".sph") || onlyFile.endsWith(".spa")){\r
+ result[1] = onlyFile;\r
+ }else{\r
+ result[0] = onlyFile;\r
+ }\r
+ }\r
+\r
+ assert result.length == 2;\r
+\r
+ return result;\r
+ }\r
+\r
+ /**\r
+ * 基本情報通知ハンドラを登録する。\r
+ * @param handler ハンドラ\r
+ */\r
+ public void setBasicHandler(PmdBasicHandler handler){\r
+ this.basicHandler = handler;\r
+ return;\r
+ }\r
+\r
+ /**\r
+ * 形状情報通知ハンドラを登録する。\r
+ * @param handler ハンドラ\r
+ */\r
+ public void setShapeHandler(PmdShapeHandler handler){\r
+ this.shapeHandler = handler;\r
+ return;\r
+ }\r
+\r
+ /**\r
+ * 材質情報通知ハンドラを登録する。\r
+ * @param handler ハンドラ\r
+ */\r
+ public void setMaterialHandler(PmdMaterialHandler handler){\r
+ this.materialHandler = handler;\r
+ return;\r
+ }\r
+\r
+ /**\r
+ * ボーン情報通知ハンドラを登録する。\r
+ * @param handler ハンドラ\r
+ */\r
+ public void setBoneHandler(PmdBoneHandler handler){\r
+ this.boneHandler = handler;\r
+ return;\r
+ }\r
+\r
+ /**\r
+ * モーフ情報通知ハンドラを登録する。\r
+ * @param handler ハンドラ\r
+ */\r
+ public void setMorphHandler(PmdMorphHandler handler){\r
+ this.morphHandler = handler;\r
+ return;\r
+ }\r
+\r
+ /**\r
+ * パースによって得られたボーン数を返す。\r
+ * @return ボーン数\r
+ */\r
+ protected int getBoneCount(){\r
+ return this.boneCount;\r
+ }\r
+\r
+ /**\r
+ * パースによって得られたモーフ数を返す。\r
+ * @return モーフ数\r
+ */\r
+ protected int getMorphCount(){\r
+ return this.morphCount;\r
+ }\r
+\r
+ /**\r
+ * パースによって得られたボーングループ数を返す。\r
+ * @return ボーングループ数\r
+ */\r
+ protected int getBoneGroupCount(){\r
+ return this.boneGroupCount;\r
+ }\r
+\r
+ /**\r
+ * PMDファイルのパースを開始する。\r
+ * @throws IOException IOエラー\r
+ * @throws MmdFormatException フォーマットエラー\r
+ */\r
+ public void parsePmd()\r
+ throws IOException, MmdFormatException {\r
+ if(this.basicHandler != null){\r
+ this.basicHandler.pmdParseStart();\r
+ }\r
+\r
+ parseBody();\r
+\r
+ boolean hasMoreData = hasMore();\r
+ if(this.basicHandler != null){\r
+ this.basicHandler.pmdParseEnd(hasMoreData);\r
+ }\r
+\r
+ return;\r
+ }\r
+\r
+ /**\r
+ * PMDファイル本体のパースを開始する。\r
+ * パーサを拡張する場合はこのメソッドをオーバーライドする。\r
+ * @throws IOException IOエラー\r
+ * @throws MmdFormatException フォーマットエラー\r
+ */\r
+ protected void parseBody() throws IOException, MmdFormatException{\r
+ parsePmdHeader();\r
+\r
+ parseVertexList();\r
+ parseSurfaceList();\r
+ parseMaterialList();\r
+ parseBoneList();\r
+ parseIKList();\r
+ parseMorphList();\r
+ parseMorphOrderList();\r
+ parseBoneGroupList();\r
+ parseGroupedBoneList();\r
+\r
+ return;\r
+ }\r
+\r
+ /**\r
+ * PMDファイルヘッダ部のパースと通知。\r
+ * @throws IOException IOエラー\r
+ * @throws MmdFormatException フォーマットエラー\r
+ */\r
+ private void parsePmdHeader() throws IOException, MmdFormatException{\r
+ String magic = parseZeroTermString(MAGIC_SZ);\r
+ if( ! magic.equals(MAGIC) ){\r
+ throw new MmdFormatException("unrecognized magic data");\r
+ }\r
+\r
+ float ver = parseFloat();\r
+ String modelName =\r
+ parseZeroTermString(PmdLimits.MAXBYTES_MODELNAME);\r
+ String description =\r
+ parseZeroTermString(PmdLimits.MAXBYTES_MODELDESC);\r
+ description = description.replace(CRLF, LF);\r
+\r
+ if(this.basicHandler != null){\r
+ this.basicHandler.pmdHeaderInfo(ver);\r
+ this.basicHandler.pmdModelInfo(modelName, description);\r
+ }\r
+\r
+ return;\r
+ }\r
+\r
+ /**\r
+ * 頂点情報のパースと通知。\r
+ * @throws IOException IOエラー\r
+ * @throws MmdFormatException フォーマットエラー\r
+ */\r
+ private void parseVertexList() throws IOException, MmdFormatException{\r
+ int vertexNum = parseInteger();\r
+\r
+ if(this.shapeHandler == null){\r
+ skip(VERTEX_DATA_SZ * vertexNum);\r
+ return;\r
+ }\r
+\r
+ this.shapeHandler.loopStart(PmdShapeHandler.VERTEX_LIST, vertexNum);\r
+\r
+ for(int ct = 0; ct < vertexNum; ct++){\r
+ float xPos = parseFloat();\r
+ float yPos = parseFloat();\r
+ float zPos = parseFloat();\r
+ this.shapeHandler.pmdVertexPosition(xPos, yPos, zPos);\r
+\r
+ float xVec = parseFloat();\r
+ float yVec = parseFloat();\r
+ float zVec = parseFloat();\r
+ this.shapeHandler.pmdVertexNormal(xVec, yVec, zVec);\r
+\r
+ float uVal = parseFloat();\r
+ float vVal = parseFloat();\r
+ this.shapeHandler.pmdVertexUV(uVal, vVal);\r
+\r
+ int boneId1 = parseUShortAsInteger();\r
+ int boneId2 = parseUShortAsInteger();\r
+ int weightForB1 = parseUByteAsInteger();\r
+ this.shapeHandler.pmdVertexWeight(boneId1, boneId2, weightForB1);\r
+\r
+ boolean hideEdge = parseBoolean();\r
+ this.shapeHandler.pmdVertexEdge(hideEdge);\r
+\r
+ this.shapeHandler.loopNext(PmdShapeHandler.VERTEX_LIST);\r
+ }\r
+\r
+ this.shapeHandler.loopEnd(PmdShapeHandler.VERTEX_LIST);\r
+\r
+ return;\r
+ }\r
+\r
+ /**\r
+ * 面情報のパースと通知。\r
+ * @throws IOException IOエラー\r
+ * @throws MmdFormatException フォーマットエラー\r
+ */\r
+ private void parseSurfaceList() throws IOException, MmdFormatException{\r
+ int vertexNum = parseInteger();\r
+ if(vertexNum % 3 != 0) throw new MmdFormatException();\r
+ int surfaceNum = vertexNum / 3;\r
+\r
+ if(this.shapeHandler == null){\r
+ skip(SURFACE_DATA_SZ * surfaceNum);\r
+ return;\r
+ }\r
+\r
+ this.shapeHandler.loopStart(PmdShapeHandler.SURFACE_LIST, surfaceNum);\r
+\r
+ for(int ct = 0; ct < surfaceNum; ct++){\r
+ int vertexId1 = parseUShortAsInteger();\r
+ int vertexId2 = parseUShortAsInteger();\r
+ int vertexId3 = parseUShortAsInteger();\r
+ this.shapeHandler.pmdSurfaceTriangle(vertexId1,\r
+ vertexId2,\r
+ vertexId3 );\r
+ this.shapeHandler.loopNext(PmdShapeHandler.SURFACE_LIST);\r
+ }\r
+\r
+ this.shapeHandler.loopEnd(PmdShapeHandler.SURFACE_LIST);\r
+\r
+ return;\r
+ }\r
+\r
+ /**\r
+ * 材質情報のパースと通知。\r
+ * @throws IOException IOエラー\r
+ * @throws MmdFormatException フォーマットエラー\r
+ */\r
+ private void parseMaterialList() throws IOException, MmdFormatException{\r
+ int materialNum = parseInteger();\r
+\r
+ if(this.materialHandler == null){\r
+ skip(MATERIAL_DATA_SZ * materialNum);\r
+ return;\r
+ }\r
+\r
+ this.materialHandler.loopStart(PmdMaterialHandler.MATERIAL_LIST,\r
+ materialNum );\r
+\r
+ for(int ct = 0; ct < materialNum; ct++){\r
+ float red;\r
+ float green;\r
+ float blue;\r
+\r
+ red = parseFloat();\r
+ green = parseFloat();\r
+ blue = parseFloat();\r
+ float alpha = parseFloat();\r
+ this.materialHandler.pmdMaterialDiffuse(red, green, blue, alpha);\r
+\r
+ float shininess = parseFloat();\r
+ red = parseFloat();\r
+ green = parseFloat();\r
+ blue = parseFloat();\r
+ this.materialHandler.pmdMaterialSpecular(red, green, blue,\r
+ shininess);\r
+\r
+ red = parseFloat();\r
+ green = parseFloat();\r
+ blue = parseFloat();\r
+ this.materialHandler.pmdMaterialAmbient(red, green, blue);\r
+\r
+ int toonidx = parseUByteAsInteger();\r
+ boolean hasEdge = parseBoolean();\r
+ int surfaceCount = parseInteger();\r
+ String shadingFile =\r
+ parseZeroTermString(PmdLimits.MAXBYTES_TEXTUREFILENAME);\r
+ String[] splitted = splitShadingFileInfo(shadingFile);\r
+ String textureFile = splitted[0];\r
+ String sphereFile = splitted[1];\r
+\r
+ this.materialHandler.pmdMaterialShading(toonidx,\r
+ textureFile, sphereFile );\r
+ this.materialHandler.pmdMaterialInfo(hasEdge, surfaceCount);\r
+\r
+ this.materialHandler.loopNext(PmdMaterialHandler.MATERIAL_LIST);\r
+ }\r
+\r
+ this.materialHandler.loopEnd(PmdMaterialHandler.MATERIAL_LIST);\r
+\r
+ return;\r
+ }\r
+\r
+ /**\r
+ * ボーン情報のパースと通知。\r
+ * @throws IOException IOエラー\r
+ * @throws MmdFormatException フォーマットエラー\r
+ */\r
+ private void parseBoneList() throws IOException, MmdFormatException{\r
+ this.boneCount = parseUShortAsInteger();\r
+\r
+ if(this.boneHandler == null){\r
+ skip(BONE_DATA_SZ * this.boneCount);\r
+ return;\r
+ }\r
+\r
+ this.boneHandler.loopStart(PmdBoneHandler.BONE_LIST, this.boneCount);\r
+\r
+ for(int ct = 0; ct < this.boneCount; ct++){\r
+ String boneName =\r
+ parseZeroTermString(PmdLimits.MAXBYTES_BONENAME);\r
+ int parentId = parseUShortAsInteger();\r
+ int tailId = parseUShortAsInteger();\r
+ byte boneKind = parseByte();\r
+ int ikId = parseUShortAsInteger();\r
+\r
+ this.boneHandler.pmdBoneInfo(boneName, boneKind);\r
+ this.boneHandler.pmdBoneLink(parentId, tailId, ikId);\r
+\r
+ float xPos = parseFloat();\r
+ float yPos = parseFloat();\r
+ float zPos = parseFloat();\r
+\r
+ this.boneHandler.pmdBonePosition(xPos, yPos, zPos);\r
+\r
+ this.boneHandler.loopNext(PmdBoneHandler.BONE_LIST);\r
+ }\r
+\r
+ this.boneHandler.loopEnd(PmdBoneHandler.BONE_LIST);\r
+\r
+ return;\r
+ }\r
+\r
+ /**\r
+ * IKリスト情報のパースと通知。\r
+ * @throws IOException IOエラー\r
+ * @throws MmdFormatException フォーマットエラー\r
+ */\r
+ private void parseIKList() throws IOException, MmdFormatException{\r
+ int ikCount = parseUShortAsInteger();\r
+\r
+ if(this.boneHandler != null){\r
+ this.boneHandler.loopStart(PmdBoneHandler.IK_LIST, ikCount);\r
+ }\r
+\r
+ for(int ct = 0; ct < ikCount; ct++){\r
+ int boneId = parseUShortAsInteger();\r
+ int targetId = parseUShortAsInteger();\r
+ int chainLength = parseUByteAsInteger();\r
+ int depth = parseUShortAsInteger();\r
+ float weight = parseFloat();\r
+\r
+ parseIKChainList(chainLength);\r
+\r
+ if(this.boneHandler != null){\r
+ this.boneHandler.pmdIKInfo(boneId, targetId, depth, weight);\r
+ this.boneHandler.loopNext(PmdBoneHandler.IK_LIST);\r
+ }\r
+ }\r
+\r
+ if(this.boneHandler != null){\r
+ this.boneHandler.loopEnd(PmdBoneHandler.IK_LIST);\r
+ }\r
+\r
+ return;\r
+ }\r
+\r
+ /**\r
+ * IKチェーン情報のパースと通知。\r
+ * @param chainLength チェーン長\r
+ * @throws IOException IOエラー\r
+ * @throws MmdFormatException フォーマットエラー\r
+ */\r
+ private void parseIKChainList(int chainLength)\r
+ throws IOException, MmdFormatException{\r
+ if(this.boneHandler != null){\r
+ this.boneHandler.loopStart(PmdBoneHandler.IKCHAIN_LIST,\r
+ chainLength);\r
+ }\r
+\r
+ for(int ct = 0; ct < chainLength; ct++){\r
+ int childId = parseUShortAsInteger();\r
+ if(this.boneHandler != null){\r
+ this.boneHandler.pmdIKChainInfo(childId);\r
+ this.boneHandler.loopNext(PmdBoneHandler.IKCHAIN_LIST);\r
+ }\r
+ }\r
+\r
+ if(this.boneHandler != null){\r
+ this.boneHandler.loopEnd(PmdBoneHandler.IKCHAIN_LIST);\r
+ }\r
+\r
+ return;\r
+ }\r
+\r
+ /**\r
+ * モーフ情報のパースと通知。\r
+ * @throws IOException IOエラー\r
+ * @throws MmdFormatException フォーマットエラー\r
+ */\r
+ private void parseMorphList() throws IOException, MmdFormatException{\r
+ this.morphCount = parseUShortAsInteger();\r
+\r
+ if(this.morphHandler != null){\r
+ this.morphHandler.loopStart(PmdMorphHandler.MORPH_LIST,\r
+ this.morphCount );\r
+ }\r
+\r
+ for(int ct = 0; ct < this.morphCount; ct++){\r
+ String morphName =\r
+ parseZeroTermString(PmdLimits.MAXBYTES_MORPHNAME);\r
+ int vertexCount = parseInteger();\r
+ byte morphType = parseByte();\r
+\r
+ if(this.morphHandler != null){\r
+ this.morphHandler.pmdMorphInfo(morphName, morphType);\r
+ }\r
+\r
+ parseMorphVertexList(vertexCount);\r
+\r
+ if(this.morphHandler != null){\r
+ this.morphHandler.loopNext(PmdMorphHandler.MORPH_LIST);\r
+ }\r
+ }\r
+\r
+ if(this.morphHandler != null){\r
+ this.morphHandler.loopEnd(PmdMorphHandler.MORPH_LIST);\r
+ }\r
+\r
+ return;\r
+ }\r
+\r
+ /**\r
+ * モーフ形状のパースと通知。\r
+ * @param vertexCount 頂点数\r
+ * @throws IOException IOエラー\r
+ * @throws MmdFormatException フォーマットエラー\r
+ */\r
+ private void parseMorphVertexList(int vertexCount)\r
+ throws IOException, MmdFormatException{\r
+ if(this.morphHandler == null){\r
+ skip(MORPHVERTEX_DATA_SZ * vertexCount);\r
+ return;\r
+ }\r
+\r
+ this.morphHandler.loopStart(PmdMorphHandler.MORPHVERTEX_LIST,\r
+ vertexCount );\r
+\r
+ for(int ct = 0; ct < vertexCount; ct++){\r
+ int vertexId = parseInteger();\r
+ float xPos = parseFloat();\r
+ float yPos = parseFloat();\r
+ float zPos = parseFloat();\r
+ this.morphHandler.pmdMorphVertexInfo(vertexId, xPos, yPos, zPos);\r
+ this.morphHandler.loopNext(PmdMorphHandler.MORPHVERTEX_LIST);\r
+ }\r
+\r
+ this.morphHandler.loopEnd(PmdMorphHandler.MORPHVERTEX_LIST);\r
+\r
+ return;\r
+ }\r
+\r
+ /**\r
+ * モーフGUI表示順のパースと通知。\r
+ * @throws IOException IOエラー\r
+ * @throws MmdFormatException フォーマットエラー\r
+ */\r
+ private void parseMorphOrderList()\r
+ throws IOException, MmdFormatException{\r
+ int morphOrderCount = parseUByteAsInteger();\r
+\r
+ if(this.morphHandler == null){\r
+ skip(MORPHORDER_DATA_SZ * morphOrderCount);\r
+ return;\r
+ }\r
+\r
+ this.morphHandler.loopStart(PmdMorphHandler.MORPHORDER_LIST,\r
+ morphOrderCount );\r
+\r
+ for(int ct = 0; ct < morphOrderCount; ct++){\r
+ int morphId = parseUShortAsInteger();\r
+ this.morphHandler.pmdMorphOrderInfo(morphId);\r
+\r
+ this.morphHandler.loopNext(PmdMorphHandler.MORPHORDER_LIST);\r
+ }\r
+\r
+ this.morphHandler.loopEnd(PmdMorphHandler.MORPHORDER_LIST);\r
+\r
+ return;\r
+ }\r
+\r
+ /**\r
+ * ボーングループ名のパースと通知。\r
+ * @throws IOException IOエラー\r
+ * @throws MmdFormatException フォーマットエラー\r
+ */\r
+ private void parseBoneGroupList()\r
+ throws IOException, MmdFormatException{\r
+ this.boneGroupCount = parseUByteAsInteger();\r
+\r
+ if(this.boneHandler == null){\r
+ skip(BONEGROUP_DATA_SZ * this.boneGroupCount);\r
+ return;\r
+ }\r
+\r
+ this.boneHandler.loopStart(PmdBoneHandler.BONEGROUP_LIST,\r
+ this.boneGroupCount);\r
+\r
+ for(int ct = 0; ct < this.boneGroupCount; ct++){\r
+ String groupName =\r
+ parseZeroTermString(PmdLimits.MAXBYTES_BONEGROUPNAME);\r
+ groupName = chopLastLF(groupName);\r
+ this.boneHandler.pmdBoneGroupInfo(groupName);\r
+\r
+ this.boneHandler.loopNext(PmdBoneHandler.BONEGROUP_LIST);\r
+ }\r
+\r
+ this.boneHandler.loopEnd(PmdBoneHandler.BONEGROUP_LIST);\r
+\r
+ return;\r
+ }\r
+\r
+ /**\r
+ * ボーングループ内訳のパースと通知。\r
+ * @throws IOException IOエラー\r
+ * @throws MmdFormatException フォーマットエラー\r
+ */\r
+ private void parseGroupedBoneList()\r
+ throws IOException, MmdFormatException{\r
+ int groupedBoneCount = parseInteger();\r
+\r
+ if(this.boneHandler == null){\r
+ skip(GROUPEDBONE_DATA_SZ * groupedBoneCount);\r
+ return;\r
+ }\r
+\r
+ this.boneHandler.loopStart(PmdBoneHandler.GROUPEDBONE_LIST,\r
+ groupedBoneCount);\r
+\r
+ for(int ct = 0; ct < groupedBoneCount; ct++){\r
+ int boneId = parseUShortAsInteger();\r
+ int groupId = parseUByteAsInteger();\r
+ this.boneHandler.pmdGroupedBoneInfo(boneId, groupId);\r
+\r
+ this.boneHandler.loopNext(PmdBoneHandler.GROUPEDBONE_LIST);\r
+ }\r
+\r
+ this.boneHandler.loopEnd(PmdBoneHandler.GROUPEDBONE_LIST);\r
+\r
+ return;\r
+ }\r
+\r
+}\r