2 * model exporter for pmd-file
4 * License : The MIT License
5 * Copyright(c) 2010 MikuToga Partners
8 package jp.sourceforge.mikutoga.pmd.model.binio;
10 import jp.sourceforge.mikutoga.pmd.IllegalPmdDataException;
11 import java.awt.Color;
12 import java.io.IOException;
13 import java.io.OutputStream;
14 import java.util.Collections;
15 import java.util.LinkedList;
16 import java.util.List;
19 import jp.sourceforge.mikutoga.binio.BinaryExporter;
20 import jp.sourceforge.mikutoga.binio.IllegalTextExportException;
21 import jp.sourceforge.mikutoga.math.MkPos2D;
22 import jp.sourceforge.mikutoga.math.MkPos3D;
23 import jp.sourceforge.mikutoga.math.MkVec3D;
24 import jp.sourceforge.mikutoga.pmd.BoneType;
25 import jp.sourceforge.mikutoga.pmd.MorphType;
26 import jp.sourceforge.mikutoga.pmd.model.BoneGroup;
27 import jp.sourceforge.mikutoga.pmd.model.BoneInfo;
28 import jp.sourceforge.mikutoga.pmd.model.IKChain;
29 import jp.sourceforge.mikutoga.pmd.model.Material;
30 import jp.sourceforge.mikutoga.pmd.model.MorphPart;
31 import jp.sourceforge.mikutoga.pmd.model.MorphVertex;
32 import jp.sourceforge.mikutoga.pmd.model.PmdModel;
33 import jp.sourceforge.mikutoga.pmd.model.SerialNumbered;
34 import jp.sourceforge.mikutoga.pmd.model.ShadeInfo;
35 import jp.sourceforge.mikutoga.pmd.model.Surface;
36 import jp.sourceforge.mikutoga.pmd.model.Vertex;
37 import jp.sourceforge.mikutoga.pmd.parser.PmdLimits;
40 * PMDファイルのエクスポーター(拡張無し基本フォーマット)。
42 * 英名対応以降のPMDファイルフォーマットを
43 * 使いたくない場合はこのエクスポーターを用いて出力せよ。
45 public class PmdExporterBase extends BinaryExporter{
47 /** 前(親)ボーンが無い場合の便宜的なボーンID。 */
48 public static final int NOPREVBONE_ID = 0xffff;
49 /** 次(子)ボーンが無い場合の便宜的なボーンID。 */
50 public static final int NONEXTBONE_ID = 0x0000;
51 /** 影響元IKボーンが無い場合の便宜的なボーンID。 */
52 public static final int NOIKBONE_ID = 0x0000;
54 private static final byte[] MAGIC_BYTES = {
55 (byte)0x50, (byte)0x6d, (byte)0x64, // "Pmd"
56 (byte)0x00, (byte)0x00, (byte)0x80, (byte)0x3f, // 1.0f
59 private static final byte[] NULLFILLER =
61 private static final byte[] FDFILLER =
62 { (byte)0x00, (byte)0xfd };
63 private static final byte[] LFFILLER =
64 { (byte)0x0a, (byte)0x00, (byte)0xfd };
67 private static final String CR = "\r"; // 0x0d
69 private static final String LF = "\n"; // 0x0a
71 private static final String CRLF = CR + LF; // 0x0d, 0x0a
74 assert NOPREVBONE_ID > PmdLimits.MAX_BONE - 1;
79 * @param stream 出力ストリーム
80 * @throws NullPointerException 引数がnull
82 public PmdExporterBase(OutputStream stream)
83 throws NullPointerException{
90 * CR(0x0d)およびCRLF(0x0d0a)がLF(0x0a)へと正規化される。
92 * @return 正規化の行われた文字列。
94 protected static String normalizeBreak(String text){
97 result = result.replace(CRLF, LF);
98 result = result.replace(CR, LF);
104 * 文字列を指定されたバイト長で出力する。
105 * 文字列の改行記号はLF(0x0a)に正規化される。
106 * エンコード結果がバイト長に満たない場合は
107 * 1つの0x00及びそれに続く複数の0xfdがパディングされる。
109 * @param maxByteLength バイト長指定
110 * @throws IOException 出力エラー
111 * @throws IllegalPmdTextException エンコード結果が
114 protected void dumpText(String text, int maxByteLength)
115 throws IOException, IllegalTextExportException{
116 dumpFixedW31j(text, maxByteLength, FDFILLER);
121 * モデルデータをPMDファイル形式で出力する。
122 * @param model モデルデータ
123 * @throws IOException 出力エラー
124 * @throws IllegalPmdDataException モデルデータに不備が発見された
126 public void dumpPmdModel(PmdModel model)
127 throws IOException, IllegalPmdDataException{
130 dumpVertexList(model);
131 dumpSurfaceList(model);
132 dumpMaterialList(model);
134 dumpIKChainList(model);
135 dumpMorphList(model);
136 dumpMorphGroup(model);
137 dumpBoneGroupList(model);
138 }catch(IllegalTextExportException e){
139 throw new IllegalPmdDataException(e);
147 * @param model モデルデータ
148 * @throws IOException 出力エラー
149 * @throws IllegalPmdTextException モデル名もしくは説明が長すぎる
151 private void dumpBasic(PmdModel model)
152 throws IOException, IllegalTextExportException{
153 for(int idx=0; idx < MAGIC_BYTES.length; idx++){
154 dumpByte(MAGIC_BYTES[idx]);
157 String modelName = model.getModelName() .getPrimaryText();
158 String description = model.getDescription().getPrimaryText();
160 dumpText(modelName, PmdLimits.MAXBYTES_MODELNAME);
161 dumpText(description, PmdLimits.MAXBYTES_MODELDESC);
170 * @param model モデルデータ
171 * @throws IOException 出力エラー
173 private void dumpVertexList(PmdModel model)
175 List<Vertex> vList = model.getVertexList();
177 int vertexNum = vList.size();
180 for(Vertex vertex : vList){
192 * @throws IOException 出力エラー
194 private void dumpVertex(Vertex vertex)
196 MkPos3D position = vertex.getPosition();
199 MkVec3D normal = vertex.getNormal();
202 MkPos2D uv = vertex.getUVPosition();
205 BoneInfo boneA = vertex.getBoneA();
206 BoneInfo boneB = vertex.getBoneB();
207 dumpSerialIdAsShort(boneA);
208 dumpSerialIdAsShort(boneB);
210 int weight = vertex.getWeightA();
211 dumpByte((byte)weight);
214 boolean hasEdge = vertex.getEdgeAppearance();
215 if(hasEdge) edgeFlag = 0x00;
216 else edgeFlag = 0x01;
224 * @param model モデルデータ
225 * @throws IOException 出力エラー
227 private void dumpSurfaceList(PmdModel model)
230 List<Material> materialList = model.getMaterialList();
231 for(Material material : materialList){
232 surfaceNum += material.getSurfaceList().size();
234 dumpInt(surfaceNum * 3);
236 Vertex[] triangle = new Vertex[3];
237 for(Material material : materialList){
238 for(Surface surface : material){
239 surface.getTriangle(triangle);
240 dumpShort(triangle[0].getSerialNumber());
241 dumpShort(triangle[1].getSerialNumber());
242 dumpShort(triangle[2].getSerialNumber());
253 * @param model モデルデータ
254 * @throws IOException 出力エラー
255 * @throws IllegalPmdTextException シェーディングファイル情報が長すぎる
257 private void dumpMaterialList(PmdModel model)
258 throws IOException, IllegalTextExportException{
259 List<Material> materialList = model.getMaterialList();
261 int materialNum = materialList.size();
262 dumpInt(materialNum);
264 float[] rgba = new float[4];
266 for(Material material : materialList){
267 Color diffuse = material.getDiffuseColor();
268 diffuse.getRGBComponents(rgba);
274 float shininess = material.getShininess();
275 dumpFloat(shininess);
277 Color specular = material.getSpecularColor();
278 specular.getRGBComponents(rgba);
283 Color ambient = material.getAmbientColor();
284 ambient.getRGBComponents(rgba);
289 ShadeInfo shade = material.getShadeInfo();
290 int toonIdx = shade.getToonIndex();
294 boolean showEdge = material.getEdgeAppearance();
295 if(showEdge) edgeFlag = 0x01;
296 else edgeFlag = 0x00;
299 int surfaceNum = material.getSurfaceList().size();
300 dumpInt(surfaceNum * 3);
302 dumpShadeFileInfo(shade);
311 * シェーディングファイル情報を出力する。
312 * @param shade シェーディング情報
313 * @throws IOException 出力エラー
314 * @throws IllegalTextExportException ファイル名が長すぎる
316 private void dumpShadeFileInfo(ShadeInfo shade)
317 throws IOException, IllegalTextExportException{
318 String textureFile = shade.getTextureFileName();
319 String spheremapFile = shade.getSpheremapFileName();
321 StringBuilder text = new StringBuilder();
322 if(textureFile != null) text.append(textureFile);
323 if(spheremapFile != null && spheremapFile.length() > 0){
325 .append(spheremapFile);
329 if(text.length() <= 0) filler = NULLFILLER;
330 else filler = FDFILLER;
332 dumpFixedW31j(text.toString(),
333 PmdLimits.MAXBYTES_TEXTUREFILENAME,
341 * @param model モデルデータ
342 * @throws IOException 出力エラー
343 * @throws IllegalPmdTextException ボーン名が長すぎる
345 private void dumpBoneList(PmdModel model)
346 throws IOException, IllegalTextExportException{
347 List<BoneInfo> boneList = model.getBoneList();
349 int boneNum = boneList.size();
352 for(BoneInfo bone : boneList){
364 * @throws IOException 出力エラー
365 * @throws IllegalPmdTextException ボーン名が長すぎる
367 private void dumpBone(BoneInfo bone)
368 throws IOException, IllegalTextExportException{
369 String boneName = bone.getBoneName().getPrimaryText();
370 dumpText(boneName, PmdLimits.MAXBYTES_BONENAME);
372 BoneInfo prev = bone.getPrevBone();
373 if(prev != null) dumpSerialIdAsShort(prev);
374 else dumpShort(NOPREVBONE_ID);
376 BoneInfo next = bone.getNextBone();
377 if(next != null) dumpSerialIdAsShort(next);
378 else dumpShort(NONEXTBONE_ID);
380 BoneType type = bone.getBoneType();
381 dumpByte(type.encode());
383 if(type == BoneType.LINKEDROT){
384 int ratio = bone.getRotationRatio();
387 BoneInfo ik = bone.getIKBone();
388 if(ik != null) dumpSerialIdAsShort(ik);
389 else dumpShort(NOIKBONE_ID);
392 MkPos3D position = bone.getPosition();
400 * @param model モデルデータ
401 * @throws IOException 出力エラー
403 private void dumpIKChainList(PmdModel model)
405 List<IKChain> ikChainList = model.getIKChainList();
407 int ikNum = ikChainList.size();
410 for(IKChain chain : ikChainList){
421 * @param chain IKチェーン
422 * @throws IOException 出力エラー
424 // TODO ボーンリストから自動抽出できる情報ではないのか?
425 private void dumpIKChain(IKChain chain)
427 BoneInfo ikBone = chain.getIkBone();
428 dumpSerialIdAsShort(ikBone);
430 List<BoneInfo> boneList = chain.getChainedBoneList();
432 BoneInfo bone1st = boneList.get(0);
433 dumpSerialIdAsShort(bone1st);
435 int boneNum = boneList.size();
436 dumpByte(boneNum - 1);
438 int depth = chain.getIKDepth();
439 float weight = chain.getIKWeight();
444 for(int idx = 1; idx < boneNum; idx++){ // リストの2番目以降全て
445 BoneInfo bone = boneList.get(idx);
446 dumpSerialIdAsShort(bone);
454 * @param model モデルデータ
455 * @throws IOException 出力エラー
456 * @throws IllegalPmdTextException モーフ名が長すぎる
458 private void dumpMorphList(PmdModel model)
459 throws IOException, IllegalTextExportException{
460 Map<MorphType, List<MorphPart>> morphMap = model.getMorphMap();
461 Set<MorphType> typeSet = morphMap.keySet();
462 List<MorphVertex> mergedMorphVertexList = model.mergeMorphVertex();
464 int totalMorphPart = 0;
465 for(MorphType type : typeSet){
466 List<MorphPart> partList = morphMap.get(type);
467 if(partList == null) continue;
468 totalMorphPart += partList.size();
471 if(totalMorphPart <= 0){
475 totalMorphPart++; // baseの分
476 dumpShort(totalMorphPart);
479 dumpText("base", PmdLimits.MAXBYTES_MORPHNAME);
480 int totalVertex = mergedMorphVertexList.size();
481 dumpInt(totalVertex);
482 dumpByte(MorphType.BASE.encode());
483 for(MorphVertex morphVertex : mergedMorphVertexList){
484 Vertex baseVertex = morphVertex.getBaseVertex();
485 dumpInt(baseVertex.getSerialNumber());
486 dumpPos3D(baseVertex.getPosition());
489 for(MorphType type : typeSet){
490 List<MorphPart> partList = morphMap.get(type);
491 if(partList == null) continue;
492 for(MorphPart part : partList){
493 dumpText(part.getMorphName().getPrimaryText(),
494 PmdLimits.MAXBYTES_MORPHNAME );
495 List<MorphVertex> morphVertexList = part.getMorphVertexList();
496 dumpInt(morphVertexList.size());
497 dumpByte(part.getMorphType().encode());
499 for(MorphVertex morphVertex : morphVertexList){
500 dumpInt(morphVertex.getSerialNumber());
501 dumpPos3D(morphVertex.getOffset());
513 * @param model モデルデータ
514 * @throws IOException 出力エラー
516 private void dumpMorphGroup(PmdModel model)
518 Map<MorphType, List<MorphPart>> morphMap = model.getMorphMap();
519 Set<MorphType> typeSet = morphMap.keySet();
522 for(MorphType type : typeSet){
523 List<MorphPart> partList = morphMap.get(type);
524 if(partList == null) continue;
525 totalMorph += partList.size();
527 dumpByte(totalMorph);
529 List<MorphType> typeList = new LinkedList<MorphType>();
530 for(MorphType type : typeSet){
531 assert ! type.isBase();
534 Collections.reverse(typeList); // 一応本家と互換性を
536 for(MorphType type : typeList){
537 List<MorphPart> partList = morphMap.get(type);
538 if(partList == null) continue;
539 for(MorphPart part : partList){
540 dumpSerialIdAsShort(part);
551 * デフォルトボーングループ内訳は出力されない。
552 * @param model モデルデータ
553 * @throws IOException 出力エラー
554 * @throws IllegalTextExportException ボーングループ名が長すぎる
556 private void dumpBoneGroupList(PmdModel model)
557 throws IOException, IllegalTextExportException{
558 List<BoneGroup> groupList = model.getBoneGroupList();
559 int groupNum = groupList.size();
560 dumpByte(groupNum - 1);
563 for(BoneGroup group : groupList){
564 if(group.isDefaultBoneGroup()) continue;
565 dumpFixedW31j(group.getGroupName().getPrimaryText(),
566 PmdLimits.MAXBYTES_BONEGROUPNAME, LFFILLER );
567 dispBoneNum += group.getBoneList().size();
569 dumpInt(dispBoneNum);
571 for(BoneGroup group : groupList){
572 if(group.isDefaultBoneGroup()) continue;
573 for(BoneInfo bone : group){
574 dumpSerialIdAsShort(bone);
575 int groupId = group.getSerialNumber();
586 * 各種通し番号をshort値で出力する。
587 * short値に収まらない上位ビットは捨てられる。
588 * @param obj 番号づけられたオブジェクト
589 * @throws IOException 出力エラー
591 protected void dumpSerialIdAsShort(SerialNumbered obj)
593 int serialId = obj.getSerialNumber();
600 * @param position 2次元位置情報
601 * @throws IOException 出力エラー
603 protected void dumpPos2d(MkPos2D position) throws IOException{
604 float xPos = (float) position.getXpos();
605 float yPos = (float) position.getYpos();
615 * @param position 3次元位置情報
616 * @throws IOException 出力エラー
618 protected void dumpPos3D(MkPos3D position) throws IOException{
619 float xPos = (float) position.getXpos();
620 float yPos = (float) position.getYpos();
621 float zPos = (float) position.getZpos();
632 * @param vector 3次元ベクトル
633 * @throws IOException 出力エラー
635 protected void dumpVec3D(MkVec3D vector) throws IOException{
636 float xVal = (float) vector.getXVal();
637 float yVal = (float) vector.getYVal();
638 float zVal = (float) vector.getZVal();