2 * model exporter for pmd-file
4 * License : The MIT License
5 * Copyright(c) 2010 MikuToga Partners
8 package jp.sfjp.mikutoga.pmd.model.binio;
10 import java.awt.Color;
11 import java.io.IOException;
12 import java.io.OutputStream;
13 import java.util.Collections;
14 import java.util.LinkedList;
15 import java.util.List;
18 import jp.sfjp.mikutoga.bin.export.BinaryExporter;
19 import jp.sfjp.mikutoga.bin.export.IllegalTextExportException;
20 import jp.sfjp.mikutoga.math.MkPos2D;
21 import jp.sfjp.mikutoga.math.MkPos3D;
22 import jp.sfjp.mikutoga.math.MkVec3D;
23 import jp.sfjp.mikutoga.pmd.BoneType;
24 import jp.sfjp.mikutoga.pmd.IllegalPmdDataException;
25 import jp.sfjp.mikutoga.pmd.MorphType;
26 import jp.sfjp.mikutoga.pmd.PmdConst;
27 import jp.sfjp.mikutoga.pmd.model.BoneGroup;
28 import jp.sfjp.mikutoga.pmd.model.BoneInfo;
29 import jp.sfjp.mikutoga.pmd.model.IKChain;
30 import jp.sfjp.mikutoga.pmd.model.Material;
31 import jp.sfjp.mikutoga.pmd.model.MorphPart;
32 import jp.sfjp.mikutoga.pmd.model.MorphVertex;
33 import jp.sfjp.mikutoga.pmd.model.PmdModel;
34 import jp.sfjp.mikutoga.pmd.model.SerialNumbered;
35 import jp.sfjp.mikutoga.pmd.model.ShadeInfo;
36 import jp.sfjp.mikutoga.pmd.model.Surface;
37 import jp.sfjp.mikutoga.pmd.model.Vertex;
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 private static final int IDX_RED = 0;
75 private static final int IDX_GREEN = 1;
76 private static final int IDX_BLUE = 2;
77 private static final int IDX_ALPHA = 3;
79 private static final int TRIANGLE = 3;
82 assert NOPREVBONE_ID > PmdConst.MAX_BONE - 1;
86 private float[] rgbaBuf = null;
91 * @param stream 出力ストリーム
92 * @throws NullPointerException 引数がnull
94 public PmdExporterBase(OutputStream stream)
95 throws NullPointerException{
102 * CR(0x0d)およびCRLF(0x0d0a)がLF(0x0a)へと正規化される。
104 * @return 正規化の行われた文字列。
106 protected static String normalizeBreak(String text){
107 String result = text;
109 result = result.replace(CRLF, LF);
110 result = result.replace(CR, LF);
116 * 文字列を指定されたバイト長で出力する。
117 * 文字列の改行記号はLF(0x0a)に正規化される。
118 * エンコード結果がバイト長に満たない場合は
119 * 1つの0x00及びそれに続く複数の0xfdがパディングされる。
121 * @param maxByteLength バイト長指定
122 * @throws IOException 出力エラー
123 * @throws IllegalTextExportException エンコード結果が
126 protected void dumpText(String text, int maxByteLength)
127 throws IOException, IllegalTextExportException{
128 dumpFixedW31j(text, maxByteLength, FDFILLER);
133 * モデルデータをPMDファイル形式で出力する。
134 * @param model モデルデータ
135 * @throws IOException 出力エラー
136 * @throws IllegalPmdDataException モデルデータに不備が発見された
138 public void dumpPmdModel(PmdModel model)
139 throws IOException, IllegalPmdDataException{
142 dumpVertexList(model);
143 dumpSurfaceList(model);
144 dumpMaterialList(model);
146 dumpIKChainList(model);
147 dumpMorphList(model);
148 dumpMorphGroup(model);
149 dumpBoneGroupList(model);
150 }catch(IllegalTextExportException e){
151 throw new IllegalPmdDataException(e);
159 * @param model モデルデータ
160 * @throws IOException 出力エラー
161 * @throws IllegalTextExportException モデル名もしくは説明が長すぎる
163 private void dumpBasic(PmdModel model)
164 throws IOException, IllegalTextExportException{
165 for(int idx = 0; idx < MAGIC_BYTES.length; idx++){
166 dumpByte(MAGIC_BYTES[idx]);
169 String modelName = model.getModelName() .getPrimaryText();
170 String description = model.getDescription().getPrimaryText();
172 dumpText(modelName, PmdConst.MAXBYTES_MODELNAME);
173 dumpText(description, PmdConst.MAXBYTES_MODELDESC);
182 * @param model モデルデータ
183 * @throws IOException 出力エラー
185 private void dumpVertexList(PmdModel model)
187 List<Vertex> vList = model.getVertexList();
189 int vertexNum = vList.size();
190 dumpLeInt(vertexNum);
192 for(Vertex vertex : vList){
204 * @throws IOException 出力エラー
206 private void dumpVertex(Vertex vertex)
208 MkPos3D position = vertex.getPosition();
211 MkVec3D normal = vertex.getNormal();
214 MkPos2D uv = vertex.getUVPosition();
217 BoneInfo boneA = vertex.getBoneA();
218 BoneInfo boneB = vertex.getBoneB();
219 dumpSerialIdAsShort(boneA);
220 dumpSerialIdAsShort(boneB);
222 int weight = vertex.getWeightA();
223 dumpByte((byte)weight);
226 boolean hasEdge = vertex.getEdgeAppearance();
227 if(hasEdge) edgeFlag = 0x00;
228 else edgeFlag = 0x01;
236 * @param model モデルデータ
237 * @throws IOException 出力エラー
239 private void dumpSurfaceList(PmdModel model)
242 List<Material> materialList = model.getMaterialList();
243 for(Material material : materialList){
244 surfaceNum += material.getSurfaceList().size();
246 dumpLeInt(surfaceNum * TRIANGLE);
248 Vertex[] triangle = new Vertex[TRIANGLE];
249 for(Material material : materialList){
250 for(Surface surface : material){
251 surface.getTriangle(triangle);
252 dumpLeShort(triangle[0].getSerialNumber());
253 dumpLeShort(triangle[1].getSerialNumber());
254 dumpLeShort(triangle[2].getSerialNumber());
265 * @param model モデルデータ
266 * @throws IOException 出力エラー
267 * @throws IllegalTextExportException シェーディングファイル情報が長すぎる
269 private void dumpMaterialList(PmdModel model)
270 throws IOException, IllegalTextExportException{
271 List<Material> materialList = model.getMaterialList();
273 int materialNum = materialList.size();
274 dumpLeInt(materialNum);
276 for(Material material : materialList){
277 dumpColorInfo(material);
278 dumpShadeInfo(material);
287 * フォンシェーディングの色情報を出力する。
288 * @param material マテリアル情報
289 * @throws IOException 出力エラー
291 private void dumpColorInfo(Material material)
293 Color diffuse = material.getDiffuseColor();
294 this.rgbaBuf = diffuse.getRGBComponents(this.rgbaBuf);
295 dumpLeFloat(this.rgbaBuf[IDX_RED]);
296 dumpLeFloat(this.rgbaBuf[IDX_GREEN]);
297 dumpLeFloat(this.rgbaBuf[IDX_BLUE]);
298 dumpLeFloat(this.rgbaBuf[IDX_ALPHA]);
300 float shininess = material.getShininess();
301 dumpLeFloat(shininess);
303 Color specular = material.getSpecularColor();
304 this.rgbaBuf = specular.getRGBComponents(this.rgbaBuf);
305 dumpLeFloat(this.rgbaBuf[IDX_RED]);
306 dumpLeFloat(this.rgbaBuf[IDX_GREEN]);
307 dumpLeFloat(this.rgbaBuf[IDX_BLUE]);
309 Color ambient = material.getAmbientColor();
310 this.rgbaBuf = ambient.getRGBComponents(this.rgbaBuf);
311 dumpLeFloat(this.rgbaBuf[IDX_RED]);
312 dumpLeFloat(this.rgbaBuf[IDX_GREEN]);
313 dumpLeFloat(this.rgbaBuf[IDX_BLUE]);
320 * @param material マテリアル情報
321 * @throws IOException 出力エラー
322 * @throws IllegalTextExportException ファイル名が長すぎる
324 private void dumpShadeInfo(Material material)
325 throws IOException, IllegalTextExportException{
326 ShadeInfo shade = material.getShadeInfo();
327 int toonIdx = shade.getToonIndex();
330 boolean showEdge = material.getEdgeAppearance();
332 if(showEdge) edgeFlag = 0x01;
333 else edgeFlag = 0x00;
336 int surfaceNum = material.getSurfaceList().size();
337 dumpLeInt(surfaceNum * TRIANGLE);
339 dumpShadeFileInfo(shade);
345 * シェーディングファイル情報を出力する。
346 * @param shade シェーディング情報
347 * @throws IOException 出力エラー
348 * @throws IllegalTextExportException ファイル名が長すぎる
350 private void dumpShadeFileInfo(ShadeInfo shade)
351 throws IOException, IllegalTextExportException{
352 String textureFile = shade.getTextureFileName();
353 String spheremapFile = shade.getSpheremapFileName();
355 StringBuilder text = new StringBuilder();
356 if(textureFile != null) text.append(textureFile);
357 if(spheremapFile != null && spheremapFile.length() > 0){
359 .append(spheremapFile);
363 if(text.length() <= 0) filler = NULLFILLER;
364 else filler = FDFILLER;
366 dumpFixedW31j(text.toString(),
367 PmdConst.MAXBYTES_TEXTUREFILENAME,
375 * @param model モデルデータ
376 * @throws IOException 出力エラー
377 * @throws IllegalTextExportException ボーン名が長すぎる
379 private void dumpBoneList(PmdModel model)
380 throws IOException, IllegalTextExportException{
381 List<BoneInfo> boneList = model.getBoneList();
383 int boneNum = boneList.size();
384 dumpLeShort(boneNum);
386 for(BoneInfo bone : boneList){
398 * @throws IOException 出力エラー
399 * @throws IllegalTextExportException ボーン名が長すぎる
401 private void dumpBone(BoneInfo bone)
402 throws IOException, IllegalTextExportException{
403 String boneName = bone.getBoneName().getPrimaryText();
404 dumpText(boneName, PmdConst.MAXBYTES_BONENAME);
406 BoneInfo prev = bone.getPrevBone();
407 if(prev != null) dumpSerialIdAsShort(prev);
408 else dumpLeShort(NOPREVBONE_ID);
410 BoneInfo next = bone.getNextBone();
411 if(next != null) dumpSerialIdAsShort(next);
412 else dumpLeShort(NONEXTBONE_ID);
414 BoneType type = bone.getBoneType();
415 dumpByte(type.encode());
417 if(type == BoneType.LINKEDROT){
418 int ratio = bone.getRotationRatio();
421 BoneInfo srcBone = bone.getSrcBone();
422 if(srcBone != null) dumpSerialIdAsShort(srcBone);
423 else dumpLeShort(NOIKBONE_ID);
426 MkPos3D position = bone.getPosition();
434 * @param model モデルデータ
435 * @throws IOException 出力エラー
437 private void dumpIKChainList(PmdModel model)
439 List<IKChain> ikChainList = model.getIKChainList();
441 int ikNum = ikChainList.size();
444 for(IKChain chain : ikChainList){
455 * @param chain IKチェーン
456 * @throws IOException 出力エラー
458 // TODO ボーンリストから自動抽出できる情報ではないのか?
459 private void dumpIKChain(IKChain chain)
461 BoneInfo ikBone = chain.getIkBone();
462 dumpSerialIdAsShort(ikBone);
464 List<BoneInfo> boneList = chain.getChainedBoneList();
466 BoneInfo bone1st = boneList.get(0);
467 dumpSerialIdAsShort(bone1st);
469 int boneNum = boneList.size();
470 dumpByte(boneNum - 1);
472 int depth = chain.getIKDepth();
473 float weight = chain.getIKWeight();
478 for(int idx = 1; idx < boneNum; idx++){ // リストの2番目以降全て
479 BoneInfo bone = boneList.get(idx);
480 dumpSerialIdAsShort(bone);
488 * @param model モデルデータ
489 * @throws IOException 出力エラー
490 * @throws IllegalTextExportException モーフ名が長すぎる
492 private void dumpMorphList(PmdModel model)
493 throws IOException, IllegalTextExportException{
494 Map<MorphType, List<MorphPart>> morphMap = model.getMorphMap();
495 Set<MorphType> typeSet = morphMap.keySet();
496 List<MorphPart> morphPartList = new LinkedList<MorphPart>();
498 for(MorphType type : typeSet){
499 List<MorphPart> partList = morphMap.get(type);
500 if(partList == null) continue;
501 morphPartList.addAll(partList);
504 int totalMorphPart = morphPartList.size();
505 if(totalMorphPart <= 0){
509 totalMorphPart++; // baseの分
510 dumpLeShort(totalMorphPart);
513 dumpBaseMorph(model);
515 for(MorphPart part : morphPartList){
516 dumpText(part.getMorphName().getPrimaryText(),
517 PmdConst.MAXBYTES_MORPHNAME );
519 List<MorphVertex> morphVertexList = part.getMorphVertexList();
520 dumpLeInt(morphVertexList.size());
522 dumpByte(part.getMorphType().encode());
523 for(MorphVertex morphVertex : morphVertexList){
524 dumpLeInt(morphVertex.getSerialNumber());
525 dumpPos3D(morphVertex.getOffset());
536 * @param model モデルデータ
537 * @throws IOException 出力エラー
538 * @throws IllegalTextExportException モーフ名が長すぎる
540 private void dumpBaseMorph(PmdModel model)
541 throws IOException, IllegalTextExportException{
542 dumpText("base", PmdConst.MAXBYTES_MORPHNAME);
544 List<MorphVertex> mergedMorphVertexList = model.mergeMorphVertex();
545 int totalVertex = mergedMorphVertexList.size();
546 dumpLeInt(totalVertex);
548 dumpByte(MorphType.BASE.encode());
549 for(MorphVertex morphVertex : mergedMorphVertexList){
550 Vertex baseVertex = morphVertex.getBaseVertex();
551 dumpLeInt(baseVertex.getSerialNumber());
552 dumpPos3D(baseVertex.getPosition());
560 * @param model モデルデータ
561 * @throws IOException 出力エラー
563 private void dumpMorphGroup(PmdModel model)
565 Map<MorphType, List<MorphPart>> morphMap = model.getMorphMap();
566 Set<MorphType> typeSet = morphMap.keySet();
569 for(MorphType type : typeSet){
570 List<MorphPart> partList = morphMap.get(type);
571 if(partList == null) continue;
572 totalMorph += partList.size();
574 dumpByte(totalMorph);
576 List<MorphType> typeList = new LinkedList<MorphType>();
577 for(MorphType type : typeSet){
578 assert ! type.isBase();
581 Collections.reverse(typeList); // 一応本家と互換性を
583 for(MorphType type : typeList){
584 List<MorphPart> partList = morphMap.get(type);
585 if(partList == null) continue;
586 for(MorphPart part : partList){
587 dumpSerialIdAsShort(part);
598 * デフォルトボーングループ内訳は出力されない。
599 * @param model モデルデータ
600 * @throws IOException 出力エラー
601 * @throws IllegalTextExportException ボーングループ名が長すぎる
603 private void dumpBoneGroupList(PmdModel model)
604 throws IOException, IllegalTextExportException{
605 List<BoneGroup> groupList = model.getBoneGroupList();
606 int groupNum = groupList.size();
607 dumpByte(groupNum - 1);
610 for(BoneGroup group : groupList){
611 if(group.isDefaultBoneGroup()) continue;
612 dumpFixedW31j(group.getGroupName().getPrimaryText(),
613 PmdConst.MAXBYTES_BONEGROUPNAME, LFFILLER );
614 dispBoneNum += group.getBoneList().size();
616 dumpLeInt(dispBoneNum);
618 for(BoneGroup group : groupList){
619 if(group.isDefaultBoneGroup()) continue;
620 for(BoneInfo bone : group){
621 dumpSerialIdAsShort(bone);
622 int groupId = group.getSerialNumber();
633 * 各種通し番号をshort値で出力する。
634 * short値に収まらない上位ビットは捨てられる。
635 * @param obj 番号づけられたオブジェクト
636 * @throws IOException 出力エラー
638 protected void dumpSerialIdAsShort(SerialNumbered obj)
640 int serialId = obj.getSerialNumber();
641 dumpLeShort(serialId);
647 * @param position 2次元位置情報
648 * @throws IOException 出力エラー
650 protected void dumpPos2d(MkPos2D position) throws IOException{
651 float xPos = (float) position.getXpos();
652 float yPos = (float) position.getYpos();
662 * @param position 3次元位置情報
663 * @throws IOException 出力エラー
665 protected void dumpPos3D(MkPos3D position) throws IOException{
666 float xPos = (float) position.getXpos();
667 float yPos = (float) position.getYpos();
668 float zPos = (float) position.getZpos();
679 * @param vector 3次元ベクトル
680 * @throws IOException 出力エラー
682 protected void dumpVec3D(MkVec3D vector) throws IOException{
683 float xVal = (float) vector.getXVal();
684 float yVal = (float) vector.getYVal();
685 float zVal = (float) vector.getZVal();