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 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.sourceforge.mikutoga.corelib.SerialNumbered;
19 import jp.sourceforge.mikutoga.pmd.BoneType;
20 import jp.sourceforge.mikutoga.pmd.MorphType;
21 import jp.sourceforge.mikutoga.pmd.Pos2d;
22 import jp.sourceforge.mikutoga.pmd.Pos3d;
23 import jp.sourceforge.mikutoga.pmd.Vec3d;
24 import jp.sourceforge.mikutoga.pmd.model.BoneGroup;
25 import jp.sourceforge.mikutoga.pmd.model.BoneInfo;
26 import jp.sourceforge.mikutoga.pmd.model.IKChain;
27 import jp.sourceforge.mikutoga.pmd.model.Material;
28 import jp.sourceforge.mikutoga.pmd.model.MorphPart;
29 import jp.sourceforge.mikutoga.pmd.model.MorphVertex;
30 import jp.sourceforge.mikutoga.pmd.model.PmdModel;
31 import jp.sourceforge.mikutoga.pmd.model.ShadeInfo;
32 import jp.sourceforge.mikutoga.pmd.model.Surface;
33 import jp.sourceforge.mikutoga.pmd.model.Vertex;
34 import jp.sourceforge.mikutoga.pmd.parser.PmdLimits;
37 * PMDファイルのエクスポーター(拡張無し基本フォーマット)。
39 * 英名対応以降のPMDファイルフォーマットを
40 * 使いたくない場合はこのエクスポーターを用いて出力せよ。
42 public class PmdExporterBase extends AbstractExporter{
44 /** 前(親)ボーンが無い場合の便宜的なボーンID。 */
45 public static final int NOPREVBONE_ID = 0xffff;
46 /** 次(子)ボーンが無い場合の便宜的なボーンID。 */
47 public static final int NONEXTBONE_ID = 0x0000;
48 /** 影響元IKボーンが無い場合の便宜的なボーンID。 */
49 public static final int NOIKBONE_ID = 0x0000;
51 private static final byte[] MAGIC_BYTES = {
52 (byte)0x50, (byte)0x6d, (byte)0x64, // "Pmd"
53 (byte)0x00, (byte)0x00, (byte)0x80, (byte)0x3f, // 1.0f
56 private static final byte[] NULLFILLER =
58 private static final byte[] FDFILLER =
59 { (byte)0x00, (byte)0xfd };
60 private static final byte[] LFFILLER =
61 { (byte)0x0a, (byte)0x00, (byte)0xfd };
64 private static final String CR = "\r"; // 0x0d
66 private static final String LF = "\n"; // 0x0a
68 private static final String CRLF = CR + LF; // 0x0d, 0x0a
71 assert NOPREVBONE_ID > PmdLimits.MAX_BONE - 1;
76 * @param stream 出力ストリーム
77 * @throws NullPointerException 引数がnull
79 public PmdExporterBase(OutputStream stream)
80 throws NullPointerException{
87 * CR(0x0d)およびCRLF(0x0d0a)がLF(0x0a)へと正規化される。
89 * @return 正規化の行われた文字列。
91 protected static String normalizeBreak(String text){
94 result = result.replace(CRLF, LF);
95 result = result.replace(CR, LF);
101 * 文字列を指定されたバイト長で出力する。
102 * 文字列の改行記号はLF(0x0a)に正規化される。
103 * エンコード結果がバイト長に満たない場合は
104 * 1つの0x00及びそれに続く複数の0xfdがパディングされる。
106 * @param maxByteLength バイト長指定
107 * @throws IOException 出力エラー
108 * @throws IllegalPmdTextException エンコード結果が
111 protected void dumpText(String text, int maxByteLength)
112 throws IOException, IllegalPmdTextException{
113 dumpText(text, maxByteLength, FDFILLER);
118 * 文字列を指定されたバイト長で出力する。
119 * 文字列の改行記号はLF(0x0a)に正規化される。
120 * エンコード結果がバイト長に満たない場合は
123 * @param maxByteLength バイト超指定
124 * @param filler 出力結果が足りない場合の詰め物。
125 * それでも足りない場合は最後のbyte要素が繰り返し出力される。
126 * @throws IOException 出力エラー
127 * @throws IllegalPmdTextException エンコード結果が
130 protected void dumpText(String text, int maxByteLength, byte[] filler)
131 throws IOException, IllegalPmdTextException{
132 String normalized = normalizeBreak(text);
133 int blen = dumpCharSequence(normalized);
134 int remain = maxByteLength - blen;
136 if(remain < 0) throw new IllegalPmdTextException("too long text");
140 if(fillerIdx >= filler.length){
141 fillerIdx = filler.length - 1;
143 dumpByte(filler[fillerIdx]);
152 * モデルデータをPMDファイル形式で出力する。
153 * @param model モデルデータ
154 * @throws IOException 出力エラー
155 * @throws IllegalPmdException モデルデータに不備が発見された
157 public void dumpPmdModel(PmdModel model)
158 throws IOException, IllegalPmdException{
160 dumpVertexList(model);
161 dumpSurfaceList(model);
162 dumpMaterialList(model);
164 dumpIKChainList(model);
165 dumpMorphList(model);
166 dumpMorphGroup(model);
167 dumpBoneGroupList(model);
174 * @param model モデルデータ
175 * @throws IOException 出力エラー
176 * @throws IllegalPmdTextException モデル名もしくは説明が長すぎる
178 private void dumpBasic(PmdModel model)
179 throws IOException, IllegalPmdTextException{
180 for(int idx=0; idx < MAGIC_BYTES.length; idx++){
181 dumpByte(MAGIC_BYTES[idx]);
184 String modelName = model.getModelName() .getPrimaryText();
185 String description = model.getDescription().getPrimaryText();
187 dumpText(modelName, PmdLimits.MAXBYTES_MODELNAME);
188 dumpText(description, PmdLimits.MAXBYTES_MODELDESC);
197 * @param model モデルデータ
198 * @throws IOException 出力エラー
200 private void dumpVertexList(PmdModel model)
202 List<Vertex> vList = model.getVertexList();
204 int vertexNum = vList.size();
207 for(Vertex vertex : vList){
219 * @throws IOException 出力エラー
221 private void dumpVertex(Vertex vertex)
223 Pos3d position = vertex.getPosition();
226 Vec3d normal = vertex.getNormal();
229 Pos2d uv = vertex.getUVPosition();
232 BoneInfo boneA = vertex.getBoneA();
233 BoneInfo boneB = vertex.getBoneB();
234 dumpSerialIdAsShort(boneA);
235 dumpSerialIdAsShort(boneB);
237 int weight = vertex.getWeightA();
238 dumpByte((byte)weight);
241 boolean hasEdge = vertex.getEdgeAppearance();
242 if(hasEdge) edgeFlag = 0x00;
243 else edgeFlag = 0x01;
251 * @param model モデルデータ
252 * @throws IOException 出力エラー
254 private void dumpSurfaceList(PmdModel model)
257 List<Material> materialList = model.getMaterialList();
258 for(Material material : materialList){
259 surfaceNum += material.getSurfaceList().size();
261 dumpInt(surfaceNum * 3);
263 Vertex[] triangle = new Vertex[3];
264 for(Material material : materialList){
265 for(Surface surface : material){
266 surface.getTriangle(triangle);
267 dumpShort(triangle[0].getSerialNumber());
268 dumpShort(triangle[1].getSerialNumber());
269 dumpShort(triangle[2].getSerialNumber());
280 * @param model モデルデータ
281 * @throws IOException 出力エラー
282 * @throws IllegalPmdTextException シェーディングファイル情報が長すぎる
284 private void dumpMaterialList(PmdModel model)
285 throws IOException, IllegalPmdTextException{
286 List<Material> materialList = model.getMaterialList();
288 int materialNum = materialList.size();
289 dumpInt(materialNum);
291 float[] rgba = new float[4];
293 for(Material material : materialList){
294 Color diffuse = material.getDiffuseColor();
295 diffuse.getRGBComponents(rgba);
301 float shininess = material.getShininess();
302 dumpFloat(shininess);
304 Color specular = material.getSpecularColor();
305 specular.getRGBComponents(rgba);
310 Color ambient = material.getAmbientColor();
311 ambient.getRGBComponents(rgba);
316 ShadeInfo shade = material.getShadeInfo();
317 int toonIdx = shade.getToonIndex();
321 boolean showEdge = material.getEdgeAppearance();
322 if(showEdge) edgeFlag = 0x01;
323 else edgeFlag = 0x00;
326 int surfaceNum = material.getSurfaceList().size();
327 dumpInt(surfaceNum * 3);
329 dumpShadeFileInfo(shade);
338 * シェーディングファイル情報を出力する。
339 * @param shade シェーディング情報
340 * @throws IOException 出力エラー
341 * @throws IllegalPmdTextException ファイル名が長すぎる
343 private void dumpShadeFileInfo(ShadeInfo shade)
344 throws IOException, IllegalPmdTextException{
345 String textureFile = shade.getTextureFileName();
346 String spheremapFile = shade.getSpheremapFileName();
348 StringBuilder text = new StringBuilder();
349 if(textureFile != null) text.append(textureFile);
350 if(spheremapFile != null && spheremapFile.length() > 0){
352 .append(spheremapFile);
356 if(text.length() <= 0) filler = NULLFILLER;
357 else filler = FDFILLER;
359 dumpText(text.toString(),
360 PmdLimits.MAXBYTES_TEXTUREFILENAME,
368 * @param model モデルデータ
369 * @throws IOException 出力エラー
370 * @throws IllegalPmdTextException ボーン名が長すぎる
372 private void dumpBoneList(PmdModel model)
373 throws IOException, IllegalPmdTextException{
374 List<BoneInfo> boneList = model.getBoneList();
376 int boneNum = boneList.size();
379 for(BoneInfo bone : boneList){
391 * @throws IOException 出力エラー
392 * @throws IllegalPmdTextException ボーン名が長すぎる
394 private void dumpBone(BoneInfo bone)
395 throws IOException, IllegalPmdTextException{
396 String boneName = bone.getBoneName().getPrimaryText();
397 dumpText(boneName, PmdLimits.MAXBYTES_BONENAME);
399 BoneInfo prev = bone.getPrevBone();
400 if(prev != null) dumpSerialIdAsShort(prev);
401 else dumpShort(NOPREVBONE_ID);
403 BoneInfo next = bone.getNextBone();
404 if(next != null) dumpSerialIdAsShort(next);
405 else dumpShort(NONEXTBONE_ID);
407 BoneType type = bone.getBoneType();
408 dumpByte(type.encode());
410 if(type == BoneType.LINKEDROT){
411 int ratio = bone.getRotationRatio();
414 BoneInfo ik = bone.getIKBone();
415 if(ik != null) dumpSerialIdAsShort(ik);
416 else dumpShort(NOIKBONE_ID);
419 Pos3d position = bone.getPosition();
427 * @param model モデルデータ
428 * @throws IOException 出力エラー
430 private void dumpIKChainList(PmdModel model)
432 List<IKChain> ikChainList = model.getIKChainList();
434 int ikNum = ikChainList.size();
437 for(IKChain chain : ikChainList){
448 * @param chain IKチェーン
449 * @throws IOException 出力エラー
451 // TODO ボーンリストから自動抽出できる情報ではないのか?
452 private void dumpIKChain(IKChain chain)
454 BoneInfo ikBone = chain.getIkBone();
455 dumpSerialIdAsShort(ikBone);
457 List<BoneInfo> boneList = chain.getChainedBoneList();
459 BoneInfo bone1st = boneList.get(0);
460 dumpSerialIdAsShort(bone1st);
462 int boneNum = boneList.size();
463 dumpByte(boneNum - 1);
465 int depth = chain.getIKDepth();
466 float weight = chain.getIKWeight();
471 for(int idx = 1; idx < boneNum; idx++){ // リストの2番目以降全て
472 BoneInfo bone = boneList.get(idx);
473 dumpSerialIdAsShort(bone);
481 * @param model モデルデータ
482 * @throws IOException 出力エラー
483 * @throws IllegalPmdTextException モーフ名が長すぎる
485 private void dumpMorphList(PmdModel model)
486 throws IOException, IllegalPmdTextException{
487 Map<MorphType, List<MorphPart>> morphMap = model.getMorphMap();
488 Set<MorphType> typeSet = morphMap.keySet();
489 List<MorphVertex> mergedMorphVertexList = model.mergeMorphVertex();
491 int totalMorphPart = 0;
492 for(MorphType type : typeSet){
493 List<MorphPart> partList = morphMap.get(type);
494 if(partList == null) continue;
495 totalMorphPart += partList.size();
498 if(totalMorphPart <= 0){
502 totalMorphPart++; // baseの分
503 dumpShort(totalMorphPart);
506 dumpText("base", PmdLimits.MAXBYTES_MORPHNAME);
507 int totalVertex = mergedMorphVertexList.size();
508 dumpInt(totalVertex);
509 dumpByte(MorphType.BASE.encode());
510 for(MorphVertex morphVertex : mergedMorphVertexList){
511 Vertex baseVertex = morphVertex.getBaseVertex();
512 dumpInt(baseVertex.getSerialNumber());
513 dumpPos3d(baseVertex.getPosition());
516 for(MorphType type : typeSet){
517 List<MorphPart> partList = morphMap.get(type);
518 if(partList == null) continue;
519 for(MorphPart part : partList){
520 dumpText(part.getMorphName().getPrimaryText(),
521 PmdLimits.MAXBYTES_MORPHNAME );
522 List<MorphVertex> morphVertexList = part.getMorphVertexList();
523 dumpInt(morphVertexList.size());
524 dumpByte(part.getMorphType().encode());
526 for(MorphVertex morphVertex : morphVertexList){
527 dumpInt(morphVertex.getSerialNumber());
528 dumpPos3d(morphVertex.getOffset());
540 * @param model モデルデータ
541 * @throws IOException 出力エラー
543 private void dumpMorphGroup(PmdModel model)
545 Map<MorphType, List<MorphPart>> morphMap = model.getMorphMap();
546 Set<MorphType> typeSet = morphMap.keySet();
549 for(MorphType type : typeSet){
550 List<MorphPart> partList = morphMap.get(type);
551 if(partList == null) continue;
552 totalMorph += partList.size();
554 dumpByte(totalMorph);
556 List<MorphType> typeList = new LinkedList<MorphType>();
557 for(MorphType type : typeSet){
558 assert ! type.isBase();
561 Collections.reverse(typeList); // 一応本家と互換性を
563 for(MorphType type : typeList){
564 List<MorphPart> partList = morphMap.get(type);
565 if(partList == null) continue;
566 for(MorphPart part : partList){
567 dumpSerialIdAsShort(part);
578 * デフォルトボーングループ内訳は出力されない。
579 * @param model モデルデータ
580 * @throws IOException 出力エラー
581 * @throws IllegalPmdTextException ボーングループ名が長すぎる
583 private void dumpBoneGroupList(PmdModel model)
584 throws IOException, IllegalPmdTextException{
585 List<BoneGroup> groupList = model.getBoneGroupList();
586 int groupNum = groupList.size();
587 dumpByte(groupNum - 1);
590 for(BoneGroup group : groupList){
591 if(group.isDefaultBoneGroup()) continue;
592 dumpText(group.getGroupName().getPrimaryText(),
593 PmdLimits.MAXBYTES_BONEGROUPNAME, LFFILLER );
594 dispBoneNum += group.getBoneList().size();
596 dumpInt(dispBoneNum);
598 for(BoneGroup group : groupList){
599 if(group.isDefaultBoneGroup()) continue;
600 for(BoneInfo bone : group){
601 dumpSerialIdAsShort(bone);
602 int groupId = group.getSerialNumber();
613 * 各種通し番号をshort値で出力する。
614 * short値に収まらない上位ビットは捨てられる。
615 * @param obj 番号づけられたオブジェクト
616 * @throws IOException 出力エラー
618 protected void dumpSerialIdAsShort(SerialNumbered obj)
620 int serialId = obj.getSerialNumber();
627 * @param position 2次元位置情報
628 * @throws IOException 出力エラー
630 protected void dumpPos2d(Pos2d position) throws IOException{
631 float xPos = position.getXPos();
632 float yPos = position.getYPos();
642 * @param position 3次元位置情報
643 * @throws IOException 出力エラー
645 protected void dumpPos3d(Pos3d position) throws IOException{
646 float xPos = position.getXPos();
647 float yPos = position.getYPos();
648 float zPos = position.getZPos();
659 * @param vector 3次元ベクトル
660 * @throws IOException 出力エラー
662 protected void dumpVec3d(Vec3d vector) throws IOException{
663 float xVal = vector.getXVal();
664 float yVal = vector.getYVal();
665 float zVal = vector.getZVal();