2 * model exporter for pmd-file
4 * License : The MIT License
5 * Copyright(c) 2010 MikuToga Partners
8 package jp.sourceforge.mikutoga.pmd.pmdexporter;
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.parser.pmd.PmdLimits;
20 import jp.sourceforge.mikutoga.pmd.BoneGroup;
21 import jp.sourceforge.mikutoga.pmd.BoneInfo;
22 import jp.sourceforge.mikutoga.pmd.BoneType;
23 import jp.sourceforge.mikutoga.pmd.IKChain;
24 import jp.sourceforge.mikutoga.pmd.Material;
25 import jp.sourceforge.mikutoga.pmd.MorphPart;
26 import jp.sourceforge.mikutoga.pmd.MorphType;
27 import jp.sourceforge.mikutoga.pmd.MorphVertex;
28 import jp.sourceforge.mikutoga.pmd.PmdModel;
29 import jp.sourceforge.mikutoga.pmd.Pos2d;
30 import jp.sourceforge.mikutoga.pmd.Pos3d;
31 import jp.sourceforge.mikutoga.pmd.ShadeInfo;
32 import jp.sourceforge.mikutoga.pmd.Surface;
33 import jp.sourceforge.mikutoga.pmd.Vec3d;
34 import jp.sourceforge.mikutoga.pmd.Vertex;
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 String MAGIC = "Pmd";
53 private static final byte[] NULLFILLER =
55 private static final byte[] FDFILLER =
56 { (byte)0x00, (byte)0xfd };
57 private static final byte[] LFFILLER =
58 { (byte)0x0a, (byte)0x00, (byte)0xfd };
61 private static final String CR = "\r"; // 0x0d
63 private static final String LF = "\n"; // 0x0a
65 private static final String CRLF = CR + LF; // 0x0d, 0x0a
68 assert NOPREVBONE_ID > PmdLimits.MAX_BONE - 1;
73 * @param stream 出力ストリーム
74 * @throws NullPointerException 引数がnull
76 public PmdExporterBase(OutputStream stream)
77 throws NullPointerException{
84 * CR(0x0d)およびCRLF(0x0d0a)がLF(0x0a)へと正規化される。
86 * @return 正規化の行われた文字列。
88 protected static String normalizeBreak(String text){
91 result = result.replace(CRLF, LF);
92 result = result.replace(CR, LF);
99 * 文字列の改行記号はLF(0x0a)に正規化される。
100 * エンコード結果がバイト長に満たない場合は
101 * 1つの0x00及びそれに続く複数の0xfdがパディングされる。
103 * @param maxByteLength バイト長指定
104 * @throws IOException 出力エラー
105 * @throws IllegalPmdTextException エンコード結果が
108 protected void dumpText(String text, int maxByteLength)
109 throws IOException, IllegalPmdTextException{
110 dumpText(text, maxByteLength, FDFILLER);
115 * 文字列を指定されたバイト長で出力する。
116 * 文字列の改行記号はLF(0x0a)に正規化される。
117 * エンコード結果がバイト長に満たない場合は
120 * @param maxByteLength バイト超指定
121 * @param filler 出力結果が足りない場合の詰め物。
122 * それでも足りない場合は最後のbyte要素が繰り返し出力される。
123 * @throws IOException 出力エラー
124 * @throws IllegalPmdTextException エンコード結果が
127 protected void dumpText(String text, int maxByteLength, byte[] filler)
128 throws IOException, IllegalPmdTextException{
129 String normalized = normalizeBreak(text);
130 int blen = dumpCharSequence(normalized);
131 int remain = maxByteLength - blen;
133 if(remain < 0) throw new IllegalPmdTextException("too long text");
137 if(fillerIdx >= filler.length){
138 fillerIdx = filler.length - 1;
140 dumpByte(filler[fillerIdx]);
149 * モデルデータをPMDファイル形式で出力する。
150 * @param model モデルデータ
151 * @throws IOException 出力エラー
152 * @throws IllegalPmdException モデルデータに不備が発見された
154 public void dumpPmdModel(PmdModel model)
155 throws IOException, IllegalPmdException{
157 dumpVertexList(model);
158 dumpSurfaceList(model);
159 dumpMaterialList(model);
161 dumpIKChainList(model);
162 dumpMorphList(model);
163 dumpMorphGroup(model);
164 dumpBoneGroupList(model);
171 * @param model モデルデータ
172 * @throws IOException 出力エラー
173 * @throws IllegalPmdTextException モデル名もしくは説明が長すぎる
175 private void dumpBasic(PmdModel model)
176 throws IOException, IllegalPmdTextException{
177 dumpCharSequence(MAGIC);
178 float ver = model.getHeaderVersion();
181 String modelName = model.getModelName() .getPrimaryText();
182 String description = model.getDescription().getPrimaryText();
184 dumpText(modelName, PmdLimits.MAXBYTES_MODELNAME);
185 dumpText(description, PmdLimits.MAXBYTES_MODELDESC);
194 * @param model モデルデータ
195 * @throws IOException 出力エラー
197 private void dumpVertexList(PmdModel model)
199 List<Vertex> vList = model.getVertexList();
201 int vertexNum = vList.size();
204 for(Vertex vertex : vList){
216 * @throws IOException 出力エラー
218 private void dumpVertex(Vertex vertex)
220 Pos3d position = vertex.getPosition();
223 Vec3d normal = vertex.getNormal();
226 Pos2d uv = vertex.getUVPosition();
229 BoneInfo boneA = vertex.getBoneA();
230 BoneInfo boneB = vertex.getBoneB();
231 dumpSerialIdAsShort(boneA);
232 dumpSerialIdAsShort(boneB);
234 int weight = vertex.getWeightA();
235 dumpByte((byte)weight);
238 boolean hasEdge = vertex.getEdgeAppearance();
239 if(hasEdge) edgeFlag = 0x00;
240 else edgeFlag = 0x01;
248 * @param model モデルデータ
249 * @throws IOException 出力エラー
251 private void dumpSurfaceList(PmdModel model)
254 List<Material> materialList = model.getMaterialList();
255 for(Material material : materialList){
256 surfaceNum += material.getSurfaceList().size();
258 dumpInt(surfaceNum * 3);
260 Vertex[] triangle = new Vertex[3];
261 for(Material material : materialList){
262 for(Surface surface : material){
263 surface.getTriangle(triangle);
264 dumpShort(triangle[0].getSerialNumber());
265 dumpShort(triangle[1].getSerialNumber());
266 dumpShort(triangle[2].getSerialNumber());
277 * @param model モデルデータ
278 * @throws IOException 出力エラー
279 * @throws IllegalPmdTextException シェーディングファイル情報が長すぎる
281 private void dumpMaterialList(PmdModel model)
282 throws IOException, IllegalPmdTextException{
283 List<Material> materialList = model.getMaterialList();
285 int materialNum = materialList.size();
286 dumpInt(materialNum);
288 float[] rgba = new float[4];
290 for(Material material : materialList){
291 Color diffuse = material.getDiffuseColor();
292 diffuse.getRGBComponents(rgba);
298 float shininess = material.getShininess();
299 dumpFloat(shininess);
301 Color specular = material.getSpecularColor();
302 specular.getRGBComponents(rgba);
307 Color ambient = material.getAmbientColor();
308 ambient.getRGBComponents(rgba);
313 ShadeInfo shade = material.getShadeInfo();
314 int toonIdx = shade.getToonIndex();
318 boolean showEdge = material.getEdgeAppearance();
319 if(showEdge) edgeFlag = 0x01;
320 else edgeFlag = 0x00;
323 int surfaceNum = material.getSurfaceList().size();
324 dumpInt(surfaceNum * 3);
326 dumpShadeFileInfo(shade);
335 * シェーディングファイル情報を出力する。
336 * @param shade シェーディング情報
337 * @throws IOException 出力エラー
338 * @throws IllegalPmdTextException ファイル名が長すぎる
340 private void dumpShadeFileInfo(ShadeInfo shade)
341 throws IOException, IllegalPmdTextException{
342 String textureFile = shade.getTextureFileName();
343 String spheremapFile = shade.getSpheremapFileName();
345 StringBuilder text = new StringBuilder();
346 if(textureFile != null) text.append(textureFile);
347 if(spheremapFile != null && spheremapFile.length() > 0){
349 .append(spheremapFile);
353 if(text.length() <= 0) filler = NULLFILLER;
354 else filler = FDFILLER;
356 dumpText(text.toString(),
357 PmdLimits.MAXBYTES_TEXTUREFILENAME,
365 * @param model モデルデータ
366 * @throws IOException 出力エラー
367 * @throws IllegalPmdTextException ボーン名が長すぎる
369 private void dumpBoneList(PmdModel model)
370 throws IOException, IllegalPmdTextException{
371 List<BoneInfo> boneList = model.getBoneList();
373 int boneNum = boneList.size();
376 for(BoneInfo bone : boneList){
388 * @throws IOException 出力エラー
389 * @throws IllegalPmdTextException ボーン名が長すぎる
391 private void dumpBone(BoneInfo bone)
392 throws IOException, IllegalPmdTextException{
393 String boneName = bone.getBoneName().getPrimaryText();
394 dumpText(boneName, PmdLimits.MAXBYTES_BONENAME);
396 BoneInfo prev = bone.getPrevBone();
397 if(prev != null) dumpSerialIdAsShort(prev);
398 else dumpShort(NOPREVBONE_ID);
400 BoneInfo next = bone.getNextBone();
401 if(next != null) dumpSerialIdAsShort(next);
402 else dumpShort(NONEXTBONE_ID);
404 BoneType type = bone.getBoneType();
405 dumpByte(type.encode());
407 if(type == BoneType.LINKEDROT){
408 int ratio = bone.getRotationRatio();
411 BoneInfo ik = bone.getIKBone();
412 if(ik != null) dumpSerialIdAsShort(ik);
413 else dumpShort(NOIKBONE_ID);
416 Pos3d position = bone.getPosition();
424 * @param model モデルデータ
425 * @throws IOException 出力エラー
427 private void dumpIKChainList(PmdModel model)
429 List<IKChain> ikChainList = model.getIKChainList();
431 int ikNum = ikChainList.size();
434 for(IKChain chain : ikChainList){
445 * @param chain IKチェーン
446 * @throws IOException 出力エラー
448 // TODO ボーンリストから自動抽出できる情報ではないのか?
449 private void dumpIKChain(IKChain chain)
451 BoneInfo ikBone = chain.getIkBone();
452 dumpSerialIdAsShort(ikBone);
454 List<BoneInfo> boneList = chain.getChainedBoneList();
456 BoneInfo bone1st = boneList.get(0);
457 dumpSerialIdAsShort(bone1st);
459 int boneNum = boneList.size();
460 dumpByte(boneNum - 1);
462 int depth = chain.getIKDepth();
463 float weight = chain.getIKWeight();
468 for(int idx = 1; idx < boneNum; idx++){ // リストの2番目以降全て
469 BoneInfo bone = boneList.get(idx);
470 dumpSerialIdAsShort(bone);
478 * @param model モデルデータ
479 * @throws IOException 出力エラー
480 * @throws IllegalPmdTextException モーフ名が長すぎる
482 private void dumpMorphList(PmdModel model)
483 throws IOException, IllegalPmdTextException{
484 Map<MorphType, List<MorphPart>> morphMap = model.getMorphMap();
485 Set<MorphType> typeSet = morphMap.keySet();
486 List<MorphVertex> mergedMorphVertexList = model.mergeMorphVertex();
488 int totalMorphPart = 0;
489 for(MorphType type : typeSet){
490 List<MorphPart> partList = morphMap.get(type);
491 if(partList == null) continue;
492 totalMorphPart += partList.size();
495 if(totalMorphPart <= 0){
499 totalMorphPart++; // baseの分
500 dumpShort(totalMorphPart);
503 dumpText("base", PmdLimits.MAXBYTES_MORPHNAME);
504 int totalVertex = mergedMorphVertexList.size();
505 dumpInt(totalVertex);
506 dumpByte(MorphType.BASE.encode());
507 for(MorphVertex morphVertex : mergedMorphVertexList){
508 Vertex baseVertex = morphVertex.getBaseVertex();
509 dumpInt(baseVertex.getSerialNumber());
510 dumpPos3d(baseVertex.getPosition());
513 for(MorphType type : typeSet){
514 List<MorphPart> partList = morphMap.get(type);
515 if(partList == null) continue;
516 for(MorphPart part : partList){
517 dumpText(part.getMorphName().getPrimaryText(),
518 PmdLimits.MAXBYTES_MORPHNAME );
519 List<MorphVertex> morphVertexList = part.getMorphVertexList();
520 dumpInt(morphVertexList.size());
521 dumpByte(part.getMorphType().encode());
523 for(MorphVertex morphVertex : morphVertexList){
524 dumpInt(morphVertex.getSerialNumber());
525 dumpPos3d(morphVertex.getOffset());
537 * @param model モデルデータ
538 * @throws IOException 出力エラー
540 private void dumpMorphGroup(PmdModel model)
542 Map<MorphType, List<MorphPart>> morphMap = model.getMorphMap();
543 Set<MorphType> typeSet = morphMap.keySet();
546 for(MorphType type : typeSet){
547 List<MorphPart> partList = morphMap.get(type);
548 if(partList == null) continue;
549 totalMorph += partList.size();
551 dumpByte(totalMorph);
553 List<MorphType> typeList = new LinkedList<MorphType>();
554 for(MorphType type : typeSet){
555 assert ! type.isBase();
558 Collections.reverse(typeList); // 一応本家と互換性を
560 for(MorphType type : typeList){
561 List<MorphPart> partList = morphMap.get(type);
562 if(partList == null) continue;
563 for(MorphPart part : partList){
564 dumpSerialIdAsShort(part);
575 * デフォルトボーングループ内訳は出力されない。
576 * @param model モデルデータ
577 * @throws IOException 出力エラー
578 * @throws IllegalPmdTextException ボーングループ名が長すぎる
580 private void dumpBoneGroupList(PmdModel model)
581 throws IOException, IllegalPmdTextException{
582 List<BoneGroup> groupList = model.getBoneGroupList();
583 int groupNum = groupList.size();
584 dumpByte(groupNum - 1);
587 for(BoneGroup group : groupList){
588 if(group.isDefaultBoneGroup()) continue;
589 dumpText(group.getGroupName().getPrimaryText(),
590 PmdLimits.MAXBYTES_BONEGROUPNAME, LFFILLER );
591 dispBoneNum += group.getBoneList().size();
593 dumpInt(dispBoneNum);
595 for(BoneGroup group : groupList){
596 if(group.isDefaultBoneGroup()) continue;
597 for(BoneInfo bone : group){
598 dumpSerialIdAsShort(bone);
599 int groupId = group.getSerialNumber();
610 * 各種通し番号をshort値で出力する。
611 * short値に収まらない上位ビットは捨てられる。
612 * @param obj 番号づけられたオブジェクト
613 * @throws IOException 出力エラー
615 protected void dumpSerialIdAsShort(SerialNumbered obj)
617 int serialId = obj.getSerialNumber();
624 * @param position 2次元位置情報
625 * @throws IOException 出力エラー
627 protected void dumpPos2d(Pos2d position) throws IOException{
628 float xPos = position.getXPos();
629 float yPos = position.getYPos();
639 * @param position 3次元位置情報
640 * @throws IOException 出力エラー
642 protected void dumpPos3d(Pos3d position) throws IOException{
643 float xPos = position.getXPos();
644 float yPos = position.getYPos();
645 float zPos = position.getZPos();
656 * @param vector 3次元ベクトル
657 * @throws IOException 出力エラー
659 protected void dumpVec3d(Vec3d vector) throws IOException{
660 float xVal = vector.getXVal();
661 float yVal = vector.getYVal();
662 float zVal = vector.getZVal();