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.binio.BinaryExporter;
19 import jp.sourceforge.mikutoga.binio.IllegalTextExportException;
20 import jp.sourceforge.mikutoga.math.MkPos2D;
21 import jp.sourceforge.mikutoga.math.MkPos3D;
22 import jp.sourceforge.mikutoga.math.MkVec3D;
23 import jp.sourceforge.mikutoga.pmd.BoneType;
24 import jp.sourceforge.mikutoga.pmd.MorphType;
25 import jp.sourceforge.mikutoga.pmd.model.BoneGroup;
26 import jp.sourceforge.mikutoga.pmd.model.BoneInfo;
27 import jp.sourceforge.mikutoga.pmd.model.IKChain;
28 import jp.sourceforge.mikutoga.pmd.model.Material;
29 import jp.sourceforge.mikutoga.pmd.model.MorphPart;
30 import jp.sourceforge.mikutoga.pmd.model.MorphVertex;
31 import jp.sourceforge.mikutoga.pmd.model.PmdModel;
32 import jp.sourceforge.mikutoga.pmd.model.SerialNumbered;
33 import jp.sourceforge.mikutoga.pmd.model.ShadeInfo;
34 import jp.sourceforge.mikutoga.pmd.model.Surface;
35 import jp.sourceforge.mikutoga.pmd.model.Vertex;
36 import jp.sourceforge.mikutoga.pmd.parser.PmdLimits;
39 * PMDファイルのエクスポーター(拡張無し基本フォーマット)。
41 * 英名対応以降のPMDファイルフォーマットを
42 * 使いたくない場合はこのエクスポーターを用いて出力せよ。
44 public class PmdExporterBase extends BinaryExporter{
46 /** 前(親)ボーンが無い場合の便宜的なボーンID。 */
47 public static final int NOPREVBONE_ID = 0xffff;
48 /** 次(子)ボーンが無い場合の便宜的なボーンID。 */
49 public static final int NONEXTBONE_ID = 0x0000;
50 /** 影響元IKボーンが無い場合の便宜的なボーンID。 */
51 public static final int NOIKBONE_ID = 0x0000;
53 private static final byte[] MAGIC_BYTES = {
54 (byte)0x50, (byte)0x6d, (byte)0x64, // "Pmd"
55 (byte)0x00, (byte)0x00, (byte)0x80, (byte)0x3f, // 1.0f
58 private static final byte[] NULLFILLER =
60 private static final byte[] FDFILLER =
61 { (byte)0x00, (byte)0xfd };
62 private static final byte[] LFFILLER =
63 { (byte)0x0a, (byte)0x00, (byte)0xfd };
66 private static final String CR = "\r"; // 0x0d
68 private static final String LF = "\n"; // 0x0a
70 private static final String CRLF = CR + LF; // 0x0d, 0x0a
73 assert NOPREVBONE_ID > PmdLimits.MAX_BONE - 1;
78 * @param stream 出力ストリーム
79 * @throws NullPointerException 引数がnull
81 public PmdExporterBase(OutputStream stream)
82 throws NullPointerException{
89 * CR(0x0d)およびCRLF(0x0d0a)がLF(0x0a)へと正規化される。
91 * @return 正規化の行われた文字列。
93 protected static String normalizeBreak(String text){
96 result = result.replace(CRLF, LF);
97 result = result.replace(CR, LF);
103 * 文字列を指定されたバイト長で出力する。
104 * 文字列の改行記号はLF(0x0a)に正規化される。
105 * エンコード結果がバイト長に満たない場合は
106 * 1つの0x00及びそれに続く複数の0xfdがパディングされる。
108 * @param maxByteLength バイト長指定
109 * @throws IOException 出力エラー
110 * @throws IllegalPmdTextException エンコード結果が
113 protected void dumpText(String text, int maxByteLength)
114 throws IOException, IllegalTextExportException{
115 dumpFixedW31j(text, maxByteLength, FDFILLER);
120 * モデルデータをPMDファイル形式で出力する。
121 * @param model モデルデータ
122 * @throws IOException 出力エラー
123 * @throws IllegalPmdException モデルデータに不備が発見された
125 public void dumpPmdModel(PmdModel model)
126 throws IOException, IllegalPmdException{
129 dumpVertexList(model);
130 dumpSurfaceList(model);
131 dumpMaterialList(model);
133 dumpIKChainList(model);
134 dumpMorphList(model);
135 dumpMorphGroup(model);
136 dumpBoneGroupList(model);
137 }catch(IllegalTextExportException e){
138 throw new IllegalPmdException(e);
146 * @param model モデルデータ
147 * @throws IOException 出力エラー
148 * @throws IllegalPmdTextException モデル名もしくは説明が長すぎる
150 private void dumpBasic(PmdModel model)
151 throws IOException, IllegalTextExportException{
152 for(int idx=0; idx < MAGIC_BYTES.length; idx++){
153 dumpByte(MAGIC_BYTES[idx]);
156 String modelName = model.getModelName() .getPrimaryText();
157 String description = model.getDescription().getPrimaryText();
159 dumpText(modelName, PmdLimits.MAXBYTES_MODELNAME);
160 dumpText(description, PmdLimits.MAXBYTES_MODELDESC);
169 * @param model モデルデータ
170 * @throws IOException 出力エラー
172 private void dumpVertexList(PmdModel model)
174 List<Vertex> vList = model.getVertexList();
176 int vertexNum = vList.size();
179 for(Vertex vertex : vList){
191 * @throws IOException 出力エラー
193 private void dumpVertex(Vertex vertex)
195 MkPos3D position = vertex.getPosition();
198 MkVec3D normal = vertex.getNormal();
201 MkPos2D uv = vertex.getUVPosition();
204 BoneInfo boneA = vertex.getBoneA();
205 BoneInfo boneB = vertex.getBoneB();
206 dumpSerialIdAsShort(boneA);
207 dumpSerialIdAsShort(boneB);
209 int weight = vertex.getWeightA();
210 dumpByte((byte)weight);
213 boolean hasEdge = vertex.getEdgeAppearance();
214 if(hasEdge) edgeFlag = 0x00;
215 else edgeFlag = 0x01;
223 * @param model モデルデータ
224 * @throws IOException 出力エラー
226 private void dumpSurfaceList(PmdModel model)
229 List<Material> materialList = model.getMaterialList();
230 for(Material material : materialList){
231 surfaceNum += material.getSurfaceList().size();
233 dumpInt(surfaceNum * 3);
235 Vertex[] triangle = new Vertex[3];
236 for(Material material : materialList){
237 for(Surface surface : material){
238 surface.getTriangle(triangle);
239 dumpShort(triangle[0].getSerialNumber());
240 dumpShort(triangle[1].getSerialNumber());
241 dumpShort(triangle[2].getSerialNumber());
252 * @param model モデルデータ
253 * @throws IOException 出力エラー
254 * @throws IllegalPmdTextException シェーディングファイル情報が長すぎる
256 private void dumpMaterialList(PmdModel model)
257 throws IOException, IllegalTextExportException{
258 List<Material> materialList = model.getMaterialList();
260 int materialNum = materialList.size();
261 dumpInt(materialNum);
263 float[] rgba = new float[4];
265 for(Material material : materialList){
266 Color diffuse = material.getDiffuseColor();
267 diffuse.getRGBComponents(rgba);
273 float shininess = material.getShininess();
274 dumpFloat(shininess);
276 Color specular = material.getSpecularColor();
277 specular.getRGBComponents(rgba);
282 Color ambient = material.getAmbientColor();
283 ambient.getRGBComponents(rgba);
288 ShadeInfo shade = material.getShadeInfo();
289 int toonIdx = shade.getToonIndex();
293 boolean showEdge = material.getEdgeAppearance();
294 if(showEdge) edgeFlag = 0x01;
295 else edgeFlag = 0x00;
298 int surfaceNum = material.getSurfaceList().size();
299 dumpInt(surfaceNum * 3);
301 dumpShadeFileInfo(shade);
310 * シェーディングファイル情報を出力する。
311 * @param shade シェーディング情報
312 * @throws IOException 出力エラー
313 * @throws IllegalTextExportException ファイル名が長すぎる
315 private void dumpShadeFileInfo(ShadeInfo shade)
316 throws IOException, IllegalTextExportException{
317 String textureFile = shade.getTextureFileName();
318 String spheremapFile = shade.getSpheremapFileName();
320 StringBuilder text = new StringBuilder();
321 if(textureFile != null) text.append(textureFile);
322 if(spheremapFile != null && spheremapFile.length() > 0){
324 .append(spheremapFile);
328 if(text.length() <= 0) filler = NULLFILLER;
329 else filler = FDFILLER;
331 dumpFixedW31j(text.toString(),
332 PmdLimits.MAXBYTES_TEXTUREFILENAME,
340 * @param model モデルデータ
341 * @throws IOException 出力エラー
342 * @throws IllegalPmdTextException ボーン名が長すぎる
344 private void dumpBoneList(PmdModel model)
345 throws IOException, IllegalTextExportException{
346 List<BoneInfo> boneList = model.getBoneList();
348 int boneNum = boneList.size();
351 for(BoneInfo bone : boneList){
363 * @throws IOException 出力エラー
364 * @throws IllegalPmdTextException ボーン名が長すぎる
366 private void dumpBone(BoneInfo bone)
367 throws IOException, IllegalTextExportException{
368 String boneName = bone.getBoneName().getPrimaryText();
369 dumpText(boneName, PmdLimits.MAXBYTES_BONENAME);
371 BoneInfo prev = bone.getPrevBone();
372 if(prev != null) dumpSerialIdAsShort(prev);
373 else dumpShort(NOPREVBONE_ID);
375 BoneInfo next = bone.getNextBone();
376 if(next != null) dumpSerialIdAsShort(next);
377 else dumpShort(NONEXTBONE_ID);
379 BoneType type = bone.getBoneType();
380 dumpByte(type.encode());
382 if(type == BoneType.LINKEDROT){
383 int ratio = bone.getRotationRatio();
386 BoneInfo ik = bone.getIKBone();
387 if(ik != null) dumpSerialIdAsShort(ik);
388 else dumpShort(NOIKBONE_ID);
391 MkPos3D position = bone.getPosition();
399 * @param model モデルデータ
400 * @throws IOException 出力エラー
402 private void dumpIKChainList(PmdModel model)
404 List<IKChain> ikChainList = model.getIKChainList();
406 int ikNum = ikChainList.size();
409 for(IKChain chain : ikChainList){
420 * @param chain IKチェーン
421 * @throws IOException 出力エラー
423 // TODO ボーンリストから自動抽出できる情報ではないのか?
424 private void dumpIKChain(IKChain chain)
426 BoneInfo ikBone = chain.getIkBone();
427 dumpSerialIdAsShort(ikBone);
429 List<BoneInfo> boneList = chain.getChainedBoneList();
431 BoneInfo bone1st = boneList.get(0);
432 dumpSerialIdAsShort(bone1st);
434 int boneNum = boneList.size();
435 dumpByte(boneNum - 1);
437 int depth = chain.getIKDepth();
438 float weight = chain.getIKWeight();
443 for(int idx = 1; idx < boneNum; idx++){ // リストの2番目以降全て
444 BoneInfo bone = boneList.get(idx);
445 dumpSerialIdAsShort(bone);
453 * @param model モデルデータ
454 * @throws IOException 出力エラー
455 * @throws IllegalPmdTextException モーフ名が長すぎる
457 private void dumpMorphList(PmdModel model)
458 throws IOException, IllegalTextExportException{
459 Map<MorphType, List<MorphPart>> morphMap = model.getMorphMap();
460 Set<MorphType> typeSet = morphMap.keySet();
461 List<MorphVertex> mergedMorphVertexList = model.mergeMorphVertex();
463 int totalMorphPart = 0;
464 for(MorphType type : typeSet){
465 List<MorphPart> partList = morphMap.get(type);
466 if(partList == null) continue;
467 totalMorphPart += partList.size();
470 if(totalMorphPart <= 0){
474 totalMorphPart++; // baseの分
475 dumpShort(totalMorphPart);
478 dumpText("base", PmdLimits.MAXBYTES_MORPHNAME);
479 int totalVertex = mergedMorphVertexList.size();
480 dumpInt(totalVertex);
481 dumpByte(MorphType.BASE.encode());
482 for(MorphVertex morphVertex : mergedMorphVertexList){
483 Vertex baseVertex = morphVertex.getBaseVertex();
484 dumpInt(baseVertex.getSerialNumber());
485 dumpPos3D(baseVertex.getPosition());
488 for(MorphType type : typeSet){
489 List<MorphPart> partList = morphMap.get(type);
490 if(partList == null) continue;
491 for(MorphPart part : partList){
492 dumpText(part.getMorphName().getPrimaryText(),
493 PmdLimits.MAXBYTES_MORPHNAME );
494 List<MorphVertex> morphVertexList = part.getMorphVertexList();
495 dumpInt(morphVertexList.size());
496 dumpByte(part.getMorphType().encode());
498 for(MorphVertex morphVertex : morphVertexList){
499 dumpInt(morphVertex.getSerialNumber());
500 dumpPos3D(morphVertex.getOffset());
512 * @param model モデルデータ
513 * @throws IOException 出力エラー
515 private void dumpMorphGroup(PmdModel model)
517 Map<MorphType, List<MorphPart>> morphMap = model.getMorphMap();
518 Set<MorphType> typeSet = morphMap.keySet();
521 for(MorphType type : typeSet){
522 List<MorphPart> partList = morphMap.get(type);
523 if(partList == null) continue;
524 totalMorph += partList.size();
526 dumpByte(totalMorph);
528 List<MorphType> typeList = new LinkedList<MorphType>();
529 for(MorphType type : typeSet){
530 assert ! type.isBase();
533 Collections.reverse(typeList); // 一応本家と互換性を
535 for(MorphType type : typeList){
536 List<MorphPart> partList = morphMap.get(type);
537 if(partList == null) continue;
538 for(MorphPart part : partList){
539 dumpSerialIdAsShort(part);
550 * デフォルトボーングループ内訳は出力されない。
551 * @param model モデルデータ
552 * @throws IOException 出力エラー
553 * @throws IllegalTextExportException ボーングループ名が長すぎる
555 private void dumpBoneGroupList(PmdModel model)
556 throws IOException, IllegalTextExportException{
557 List<BoneGroup> groupList = model.getBoneGroupList();
558 int groupNum = groupList.size();
559 dumpByte(groupNum - 1);
562 for(BoneGroup group : groupList){
563 if(group.isDefaultBoneGroup()) continue;
564 dumpFixedW31j(group.getGroupName().getPrimaryText(),
565 PmdLimits.MAXBYTES_BONEGROUPNAME, LFFILLER );
566 dispBoneNum += group.getBoneList().size();
568 dumpInt(dispBoneNum);
570 for(BoneGroup group : groupList){
571 if(group.isDefaultBoneGroup()) continue;
572 for(BoneInfo bone : group){
573 dumpSerialIdAsShort(bone);
574 int groupId = group.getSerialNumber();
585 * 各種通し番号をshort値で出力する。
586 * short値に収まらない上位ビットは捨てられる。
587 * @param obj 番号づけられたオブジェクト
588 * @throws IOException 出力エラー
590 protected void dumpSerialIdAsShort(SerialNumbered obj)
592 int serialId = obj.getSerialNumber();
599 * @param position 2次元位置情報
600 * @throws IOException 出力エラー
602 protected void dumpPos2d(MkPos2D position) throws IOException{
603 float xPos = (float) position.getXpos();
604 float yPos = (float) position.getYpos();
614 * @param position 3次元位置情報
615 * @throws IOException 出力エラー
617 protected void dumpPos3D(MkPos3D position) throws IOException{
618 float xPos = (float) position.getXpos();
619 float yPos = (float) position.getYpos();
620 float zPos = (float) position.getZpos();
631 * @param vector 3次元ベクトル
632 * @throws IOException 出力エラー
634 protected void dumpVec3D(MkVec3D vector) throws IOException{
635 float xVal = (float) vector.getXVal();
636 float yVal = (float) vector.getYVal();
637 float zVal = (float) vector.getZVal();