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 * <p>英名対応以降の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;
92 * @param stream 出力ストリーム
93 * @throws NullPointerException 引数がnull
95 public PmdExporterBase(OutputStream stream)
96 throws NullPointerException{
103 * CR(0x0d)およびCRLF(0x0d0a)がLF(0x0a)へと正規化される。
106 * @return 正規化の行われた文字列。
108 protected static String normalizeBreak(String text){
109 String result = text;
111 result = result.replace(CRLF, LF);
112 result = result.replace(CR, LF);
118 * 文字列を指定されたバイト長で出力する。
119 * 文字列の改行記号はLF(0x0a)に正規化される。
120 * エンコード結果がバイト長に満たない場合は
121 * 1つの0x00及びそれに続く複数の0xfdがパディングされる。
124 * @param maxByteLength バイト長指定
125 * @throws IOException 出力エラー
126 * @throws IllegalTextExportException エンコード結果が
129 protected void dumpText(String text, int maxByteLength)
130 throws IOException, IllegalTextExportException{
131 dumpFixedW31j(text, maxByteLength, FDFILLER);
136 * モデルデータをPMDファイル形式で出力する。
138 * @param model モデルデータ
139 * @throws IOException 出力エラー
140 * @throws IllegalPmdDataException モデルデータに不備が発見された
142 public void dumpPmdModel(PmdModel model)
143 throws IOException, IllegalPmdDataException{
146 dumpVertexList(model);
147 dumpSurfaceList(model);
148 dumpMaterialList(model);
150 dumpIKChainList(model);
151 dumpMorphList(model);
152 dumpMorphGroup(model);
153 dumpBoneGroupList(model);
154 }catch(IllegalTextExportException e){
155 throw new IllegalPmdDataException(e);
164 * @param model モデルデータ
165 * @throws IOException 出力エラー
166 * @throws IllegalTextExportException モデル名もしくは説明が長すぎる
168 private void dumpBasic(PmdModel model)
169 throws IOException, IllegalTextExportException{
170 for(int idx = 0; idx < MAGIC_BYTES.length; idx++){
171 dumpByte(MAGIC_BYTES[idx]);
174 String modelName = model.getModelName() .getPrimaryText();
175 String description = model.getDescription().getPrimaryText();
177 dumpText(modelName, PmdConst.MAXBYTES_MODELNAME);
178 dumpText(description, PmdConst.MAXBYTES_MODELDESC);
188 * @param model モデルデータ
189 * @throws IOException 出力エラー
191 private void dumpVertexList(PmdModel model)
193 List<Vertex> vList = model.getVertexList();
195 int vertexNum = vList.size();
196 dumpLeInt(vertexNum);
198 for(Vertex vertex : vList){
211 * @throws IOException 出力エラー
213 private void dumpVertex(Vertex vertex)
215 MkPos3D position = vertex.getPosition();
218 MkVec3D normal = vertex.getNormal();
221 MkPos2D uv = vertex.getUVPosition();
224 BoneInfo boneA = vertex.getBoneA();
225 BoneInfo boneB = vertex.getBoneB();
226 dumpSerialIdAsShort(boneA);
227 dumpSerialIdAsShort(boneB);
229 int weight = vertex.getWeightA();
230 dumpByte((byte)weight);
233 boolean hasEdge = vertex.getEdgeAppearance();
234 if(hasEdge) edgeFlag = 0x00;
235 else edgeFlag = 0x01;
244 * @param model モデルデータ
245 * @throws IOException 出力エラー
247 private void dumpSurfaceList(PmdModel model)
250 List<Material> materialList = model.getMaterialList();
251 for(Material material : materialList){
252 surfaceNum += material.getSurfaceList().size();
254 dumpLeInt(surfaceNum * TRIANGLE);
256 Vertex[] triangle = new Vertex[TRIANGLE];
257 for(Material material : materialList){
258 for(Surface surface : material){
259 surface.getTriangle(triangle);
260 dumpLeShort(triangle[0].getSerialNumber());
261 dumpLeShort(triangle[1].getSerialNumber());
262 dumpLeShort(triangle[2].getSerialNumber());
274 * @param model モデルデータ
275 * @throws IOException 出力エラー
276 * @throws IllegalTextExportException シェーディングファイル情報が長すぎる
278 private void dumpMaterialList(PmdModel model)
279 throws IOException, IllegalTextExportException{
280 List<Material> materialList = model.getMaterialList();
282 int materialNum = materialList.size();
283 dumpLeInt(materialNum);
285 for(Material material : materialList){
286 dumpColorInfo(material);
287 dumpShadeInfo(material);
296 * フォンシェーディングの色情報を出力する。
298 * @param material マテリアル情報
299 * @throws IOException 出力エラー
301 private void dumpColorInfo(Material material)
303 Color diffuse = material.getDiffuseColor();
304 this.rgbaBuf = diffuse.getRGBComponents(this.rgbaBuf);
305 dumpLeFloat(this.rgbaBuf[IDX_RED]);
306 dumpLeFloat(this.rgbaBuf[IDX_GREEN]);
307 dumpLeFloat(this.rgbaBuf[IDX_BLUE]);
308 dumpLeFloat(this.rgbaBuf[IDX_ALPHA]);
310 float shininess = material.getShininess();
311 dumpLeFloat(shininess);
313 Color specular = material.getSpecularColor();
314 this.rgbaBuf = specular.getRGBComponents(this.rgbaBuf);
315 dumpLeFloat(this.rgbaBuf[IDX_RED]);
316 dumpLeFloat(this.rgbaBuf[IDX_GREEN]);
317 dumpLeFloat(this.rgbaBuf[IDX_BLUE]);
319 Color ambient = material.getAmbientColor();
320 this.rgbaBuf = ambient.getRGBComponents(this.rgbaBuf);
321 dumpLeFloat(this.rgbaBuf[IDX_RED]);
322 dumpLeFloat(this.rgbaBuf[IDX_GREEN]);
323 dumpLeFloat(this.rgbaBuf[IDX_BLUE]);
331 * @param material マテリアル情報
332 * @throws IOException 出力エラー
333 * @throws IllegalTextExportException ファイル名が長すぎる
335 private void dumpShadeInfo(Material material)
336 throws IOException, IllegalTextExportException{
337 ShadeInfo shade = material.getShadeInfo();
338 int toonIdx = shade.getToonIndex();
341 boolean showEdge = material.getEdgeAppearance();
343 if(showEdge) edgeFlag = 0x01;
344 else edgeFlag = 0x00;
347 int surfaceNum = material.getSurfaceList().size();
348 dumpLeInt(surfaceNum * TRIANGLE);
350 dumpShadeFileInfo(shade);
356 * シェーディングファイル情報を出力する。
358 * @param shade シェーディング情報
359 * @throws IOException 出力エラー
360 * @throws IllegalTextExportException ファイル名が長すぎる
362 private void dumpShadeFileInfo(ShadeInfo shade)
363 throws IOException, IllegalTextExportException{
364 String textureFile = shade.getTextureFileName();
365 String spheremapFile = shade.getSpheremapFileName();
367 StringBuilder text = new StringBuilder();
368 if(textureFile != null) text.append(textureFile);
369 if(spheremapFile != null && spheremapFile.length() > 0){
371 .append(spheremapFile);
375 if(text.length() <= 0) filler = NULLFILLER;
376 else filler = FDFILLER;
378 dumpFixedW31j(text.toString(),
379 PmdConst.MAXBYTES_TEXTUREFILENAME,
388 * @param model モデルデータ
389 * @throws IOException 出力エラー
390 * @throws IllegalTextExportException ボーン名が長すぎる
392 private void dumpBoneList(PmdModel model)
393 throws IOException, IllegalTextExportException{
394 List<BoneInfo> boneList = model.getBoneList();
396 int boneNum = boneList.size();
397 dumpLeShort(boneNum);
399 for(BoneInfo bone : boneList){
412 * @throws IOException 出力エラー
413 * @throws IllegalTextExportException ボーン名が長すぎる
415 private void dumpBone(BoneInfo bone)
416 throws IOException, IllegalTextExportException{
417 String boneName = bone.getBoneName().getPrimaryText();
418 dumpText(boneName, PmdConst.MAXBYTES_BONENAME);
420 BoneInfo prev = bone.getPrevBone();
421 if(prev != null) dumpSerialIdAsShort(prev);
422 else dumpLeShort(NOPREVBONE_ID);
424 BoneInfo next = bone.getNextBone();
425 if(next != null) dumpSerialIdAsShort(next);
426 else dumpLeShort(NONEXTBONE_ID);
428 BoneType type = bone.getBoneType();
429 dumpByte(type.encode());
431 if(type == BoneType.LINKEDROT){
432 int ratio = bone.getRotationRatio();
435 BoneInfo srcBone = bone.getSrcBone();
436 if(srcBone != null) dumpSerialIdAsShort(srcBone);
437 else dumpLeShort(NOIKBONE_ID);
440 MkPos3D position = bone.getPosition();
449 * @param model モデルデータ
450 * @throws IOException 出力エラー
452 private void dumpIKChainList(PmdModel model)
454 List<IKChain> ikChainList = model.getIKChainList();
456 int ikNum = ikChainList.size();
459 for(IKChain chain : ikChainList){
471 * @param chain IKチェーン
472 * @throws IOException 出力エラー
474 // TODO ボーンリストから自動抽出できる情報ではないのか?
475 private void dumpIKChain(IKChain chain)
477 BoneInfo ikBone = chain.getIkBone();
478 dumpSerialIdAsShort(ikBone);
480 List<BoneInfo> boneList = chain.getChainedBoneList();
482 BoneInfo bone1st = boneList.get(0);
483 dumpSerialIdAsShort(bone1st);
485 int boneNum = boneList.size();
486 dumpByte(boneNum - 1);
488 int depth = chain.getIKDepth();
489 float weight = chain.getIKWeight();
494 for(int idx = 1; idx < boneNum; idx++){ // リストの2番目以降全て
495 BoneInfo bone = boneList.get(idx);
496 dumpSerialIdAsShort(bone);
505 * @param model モデルデータ
506 * @throws IOException 出力エラー
507 * @throws IllegalTextExportException モーフ名が長すぎる
509 private void dumpMorphList(PmdModel model)
510 throws IOException, IllegalTextExportException{
511 Map<MorphType, List<MorphPart>> morphMap = model.getMorphMap();
512 Set<MorphType> typeSet = morphMap.keySet();
513 List<MorphPart> morphPartList = new LinkedList<>();
515 for(MorphType type : typeSet){
516 List<MorphPart> partList = morphMap.get(type);
517 if(partList == null) continue;
518 morphPartList.addAll(partList);
521 int totalMorphPart = morphPartList.size();
522 if(totalMorphPart <= 0){
526 totalMorphPart++; // baseの分
527 dumpLeShort(totalMorphPart);
530 dumpBaseMorph(model);
532 for(MorphPart part : morphPartList){
533 dumpText(part.getMorphName().getPrimaryText(),
534 PmdConst.MAXBYTES_MORPHNAME );
536 List<MorphVertex> morphVertexList = part.getMorphVertexList();
537 dumpLeInt(morphVertexList.size());
539 dumpByte(part.getMorphType().encode());
540 for(MorphVertex morphVertex : morphVertexList){
541 dumpLeInt(morphVertex.getSerialNumber());
542 dumpPos3D(morphVertex.getOffset());
554 * @param model モデルデータ
555 * @throws IOException 出力エラー
556 * @throws IllegalTextExportException モーフ名が長すぎる
558 private void dumpBaseMorph(PmdModel model)
559 throws IOException, IllegalTextExportException{
560 dumpText("base", PmdConst.MAXBYTES_MORPHNAME);
562 List<MorphVertex> mergedMorphVertexList = model.mergeMorphVertex();
563 int totalVertex = mergedMorphVertexList.size();
564 dumpLeInt(totalVertex);
566 dumpByte(MorphType.BASE.encode());
567 for(MorphVertex morphVertex : mergedMorphVertexList){
568 Vertex baseVertex = morphVertex.getBaseVertex();
569 dumpLeInt(baseVertex.getSerialNumber());
570 dumpPos3D(baseVertex.getPosition());
579 * @param model モデルデータ
580 * @throws IOException 出力エラー
582 private void dumpMorphGroup(PmdModel model)
584 Map<MorphType, List<MorphPart>> morphMap = model.getMorphMap();
585 Set<MorphType> typeSet = morphMap.keySet();
588 for(MorphType type : typeSet){
589 List<MorphPart> partList = morphMap.get(type);
590 if(partList == null) continue;
591 totalMorph += partList.size();
593 dumpByte(totalMorph);
595 List<MorphType> typeList = new LinkedList<>();
596 for(MorphType type : typeSet){
597 assert ! type.isBase();
600 Collections.reverse(typeList); // 一応本家と互換性を
602 for(MorphType type : typeList){
603 List<MorphPart> partList = morphMap.get(type);
604 if(partList == null) continue;
605 for(MorphPart part : partList){
606 dumpSerialIdAsShort(part);
617 * デフォルトボーングループ内訳は出力されない。
619 * @param model モデルデータ
620 * @throws IOException 出力エラー
621 * @throws IllegalTextExportException ボーングループ名が長すぎる
623 private void dumpBoneGroupList(PmdModel model)
624 throws IOException, IllegalTextExportException{
625 List<BoneGroup> groupList = model.getBoneGroupList();
626 int groupNum = groupList.size();
627 dumpByte(groupNum - 1);
630 for(BoneGroup group : groupList){
631 if(group.isDefaultBoneGroup()) continue;
632 dumpFixedW31j(group.getGroupName().getPrimaryText(),
633 PmdConst.MAXBYTES_BONEGROUPNAME, LFFILLER );
634 dispBoneNum += group.getBoneList().size();
636 dumpLeInt(dispBoneNum);
638 for(BoneGroup group : groupList){
639 if(group.isDefaultBoneGroup()) continue;
640 for(BoneInfo bone : group){
641 dumpSerialIdAsShort(bone);
642 int groupId = group.getSerialNumber();
653 * 各種通し番号をshort値で出力する。
654 * short値に収まらない上位ビットは捨てられる。
656 * @param obj 番号づけられたオブジェクト
657 * @throws IOException 出力エラー
659 protected void dumpSerialIdAsShort(SerialNumbered obj)
661 int serialId = obj.getSerialNumber();
662 dumpLeShort(serialId);
669 * @param position 2次元位置情報
670 * @throws IOException 出力エラー
672 protected void dumpPos2d(MkPos2D position) throws IOException{
673 float xPos = (float) position.getXpos();
674 float yPos = (float) position.getYpos();
685 * @param position 3次元位置情報
686 * @throws IOException 出力エラー
688 protected void dumpPos3D(MkPos3D position) throws IOException{
689 float xPos = (float) position.getXpos();
690 float yPos = (float) position.getYpos();
691 float zPos = (float) position.getZpos();
703 * @param vector 3次元ベクトル
704 * @throws IOException 出力エラー
706 protected void dumpVec3D(MkVec3D vector) throws IOException{
707 float xVal = (float) vector.getXVal();
708 float yVal = (float) vector.getYVal();
709 float zVal = (float) vector.getZVal();