OSDN Git Service

8b8f8ff8dd98699f86dccc8b529b277fa701ecdd
[mikutoga/Pmd2XML.git] / src / main / java / jp / sfjp / mikutoga / pmd / binio / PmdExporterBase.java
1 /*
2  * model exporter for pmd-file
3  *
4  * License : The MIT License
5  * Copyright(c) 2010 MikuToga Partners
6  */
7
8 package jp.sfjp.mikutoga.pmd.binio;
9
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;
16 import java.util.Map;
17 import java.util.Set;
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.PmdLimits;
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;
38
39 /**
40  * PMDファイルのエクスポーター(拡張無し基本フォーマット)。
41  * <p>
42  * 英名対応以降のPMDファイルフォーマットを
43  * 使いたくない場合はこのエクスポーターを用いて出力せよ。
44  */
45 public class PmdExporterBase extends BinaryExporter{
46
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;
53
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
57     };
58
59     private static final byte[] NULLFILLER =
60         { (byte)0x00 };
61     private static final byte[] FDFILLER =
62         { (byte)0x00, (byte)0xfd };
63     private static final byte[] LFFILLER =
64         { (byte)0x0a, (byte)0x00, (byte)0xfd };
65
66     /** 改行文字列 CR。 */
67     private static final String CR = "\r";       // 0x0d
68     /** 改行文字列 LF。 */
69     private static final String LF = "\n";       // 0x0a
70     /** 改行文字列 CRLF。 */
71     private static final String CRLF = CR + LF;  // 0x0d, 0x0a
72
73     static{
74         assert NOPREVBONE_ID > PmdLimits.MAX_BONE - 1;
75     }
76
77     /**
78      * コンストラクタ。
79      * @param stream 出力ストリーム
80      * @throws NullPointerException 引数がnull
81      */
82     public PmdExporterBase(OutputStream stream)
83             throws NullPointerException{
84         super(stream);
85         return;
86     }
87
88     /**
89      * 改行文字の正規化を行う。
90      * CR(0x0d)およびCRLF(0x0d0a)がLF(0x0a)へと正規化される。
91      * @param text 文字列
92      * @return 正規化の行われた文字列。
93      */
94     protected static String normalizeBreak(String text){
95         String result = text;
96
97         result = result.replace(CRLF, LF);
98         result = result.replace(CR, LF);
99
100         return result;
101     }
102
103     /**
104      * 文字列を指定されたバイト長で出力する。
105      * 文字列の改行記号はLF(0x0a)に正規化される。
106      * エンコード結果がバイト長に満たない場合は
107      * 1つの0x00及びそれに続く複数の0xfdがパディングされる。
108      * @param text 文字列
109      * @param maxByteLength バイト長指定
110      * @throws IOException 出力エラー
111      * @throws IllegalTextExportException エンコード結果が
112      * 指定バイト長をはみ出した。
113      */
114     protected void dumpText(String text, int maxByteLength)
115             throws IOException, IllegalTextExportException{
116         dumpFixedW31j(text, maxByteLength, FDFILLER);
117         return;
118     }
119
120     /**
121      * モデルデータをPMDファイル形式で出力する。
122      * @param model モデルデータ
123      * @throws IOException 出力エラー
124      * @throws IllegalPmdDataException モデルデータに不備が発見された
125      */
126     public void dumpPmdModel(PmdModel model)
127             throws IOException, IllegalPmdDataException{
128         try{
129             dumpBasic(model);
130             dumpVertexList(model);
131             dumpSurfaceList(model);
132             dumpMaterialList(model);
133             dumpBoneList(model);
134             dumpIKChainList(model);
135             dumpMorphList(model);
136             dumpMorphGroup(model);
137             dumpBoneGroupList(model);
138         }catch(IllegalTextExportException e){
139             throw new IllegalPmdDataException(e);
140         }
141
142         return;
143     }
144
145     /**
146      * モデル基本情報を出力する。
147      * @param model モデルデータ
148      * @throws IOException 出力エラー
149      * @throws IllegalTextExportException モデル名もしくは説明が長すぎる
150      */
151     private void dumpBasic(PmdModel model)
152             throws IOException, IllegalTextExportException{
153         for(int idx=0; idx < MAGIC_BYTES.length; idx++){
154             dumpByte(MAGIC_BYTES[idx]);
155         }
156
157         String modelName   = model.getModelName()  .getPrimaryText();
158         String description = model.getDescription().getPrimaryText();
159
160         dumpText(modelName, PmdLimits.MAXBYTES_MODELNAME);
161         dumpText(description, PmdLimits.MAXBYTES_MODELDESC);
162
163         flush();
164
165         return;
166     }
167
168     /**
169      * 頂点リストを出力する。
170      * @param model モデルデータ
171      * @throws IOException 出力エラー
172      */
173     private void dumpVertexList(PmdModel model)
174             throws IOException{
175         List<Vertex> vList = model.getVertexList();
176
177         int vertexNum = vList.size();
178         dumpLeInt(vertexNum);
179
180         for(Vertex vertex : vList){
181             dumpVertex(vertex);
182         }
183
184         flush();
185
186         return;
187     }
188
189     /**
190      * 個別の頂点データを出力する。
191      * @param vertex 頂点
192      * @throws IOException 出力エラー
193      */
194     private void dumpVertex(Vertex vertex)
195             throws IOException{
196         MkPos3D position = vertex.getPosition();
197         dumpPos3D(position);
198
199         MkVec3D normal = vertex.getNormal();
200         dumpVec3D(normal);
201
202         MkPos2D uv = vertex.getUVPosition();
203         dumpPos2d(uv);
204
205         BoneInfo boneA = vertex.getBoneA();
206         BoneInfo boneB = vertex.getBoneB();
207         dumpSerialIdAsShort(boneA);
208         dumpSerialIdAsShort(boneB);
209
210         int weight = vertex.getWeightA();
211         dumpByte((byte)weight);
212
213         byte edgeFlag;
214         boolean hasEdge = vertex.getEdgeAppearance();
215         if(hasEdge) edgeFlag = 0x00;
216         else        edgeFlag = 0x01;
217         dumpByte(edgeFlag);
218
219         return;
220     }
221
222     /**
223      * 面リストを出力する。
224      * @param model モデルデータ
225      * @throws IOException 出力エラー
226      */
227     private void dumpSurfaceList(PmdModel model)
228             throws IOException{
229         int surfaceNum = 0;
230         List<Material> materialList = model.getMaterialList();
231         for(Material material : materialList){
232             surfaceNum += material.getSurfaceList().size();
233         }
234         dumpLeInt(surfaceNum * 3);
235
236         Vertex[] triangle = new Vertex[3];
237         for(Material material : materialList){
238             for(Surface surface : material){
239                 surface.getTriangle(triangle);
240                 dumpLeShort(triangle[0].getSerialNumber());
241                 dumpLeShort(triangle[1].getSerialNumber());
242                 dumpLeShort(triangle[2].getSerialNumber());
243             }
244         }
245
246         flush();
247
248         return;
249     }
250
251     /**
252      * マテリアル素材リストを出力する。
253      * @param model モデルデータ
254      * @throws IOException 出力エラー
255      * @throws IllegalTextExportException シェーディングファイル情報が長すぎる
256      */
257     private void dumpMaterialList(PmdModel model)
258             throws IOException, IllegalTextExportException{
259         List<Material> materialList = model.getMaterialList();
260
261         int materialNum = materialList.size();
262         dumpLeInt(materialNum);
263
264         float[] rgba = new float[4];
265
266         for(Material material : materialList){
267             Color diffuse = material.getDiffuseColor();
268             diffuse.getRGBComponents(rgba);
269             dumpLeFloat(rgba[0]);
270             dumpLeFloat(rgba[1]);
271             dumpLeFloat(rgba[2]);
272             dumpLeFloat(rgba[3]);
273
274             float shininess = material.getShininess();
275             dumpLeFloat(shininess);
276
277             Color specular = material.getSpecularColor();
278             specular.getRGBComponents(rgba);
279             dumpLeFloat(rgba[0]);
280             dumpLeFloat(rgba[1]);
281             dumpLeFloat(rgba[2]);
282
283             Color ambient = material.getAmbientColor();
284             ambient.getRGBComponents(rgba);
285             dumpLeFloat(rgba[0]);
286             dumpLeFloat(rgba[1]);
287             dumpLeFloat(rgba[2]);
288
289             ShadeInfo shade = material.getShadeInfo();
290             int toonIdx = shade.getToonIndex();
291             dumpByte(toonIdx);
292
293             byte edgeFlag;
294             boolean showEdge = material.getEdgeAppearance();
295             if(showEdge) edgeFlag = 0x01;
296             else         edgeFlag = 0x00;
297             dumpByte(edgeFlag);
298
299             int surfaceNum = material.getSurfaceList().size();
300             dumpLeInt(surfaceNum * 3);
301
302             dumpShadeFileInfo(shade);
303         }
304
305         flush();
306
307         return;
308     }
309
310     /**
311      * シェーディングファイル情報を出力する。
312      * @param shade シェーディング情報
313      * @throws IOException 出力エラー
314      * @throws IllegalTextExportException ファイル名が長すぎる
315      */
316     private void dumpShadeFileInfo(ShadeInfo shade)
317             throws IOException, IllegalTextExportException{
318         String textureFile   = shade.getTextureFileName();
319         String spheremapFile = shade.getSpheremapFileName();
320
321         StringBuilder text = new StringBuilder();
322         if(textureFile != null) text.append(textureFile);
323         if(spheremapFile != null && spheremapFile.length() > 0){
324             text.append('*')
325                   .append(spheremapFile);
326         }
327
328         byte[] filler;
329         if(text.length() <= 0) filler = NULLFILLER;
330         else                   filler = FDFILLER;
331
332         dumpFixedW31j(text.toString(),
333                       PmdLimits.MAXBYTES_TEXTUREFILENAME,
334                       filler );
335
336         return;
337     }
338
339     /**
340      * ボーンリストを出力する。
341      * @param model モデルデータ
342      * @throws IOException 出力エラー
343      * @throws IllegalTextExportException ボーン名が長すぎる
344      */
345     private void dumpBoneList(PmdModel model)
346             throws IOException, IllegalTextExportException{
347         List<BoneInfo> boneList = model.getBoneList();
348
349         int boneNum = boneList.size();
350         dumpLeShort(boneNum);
351
352         for(BoneInfo bone : boneList){
353             dumpBone(bone);
354         }
355
356         flush();
357
358         return;
359     }
360
361     /**
362      * 個別のボーン情報を出力する。
363      * @param bone ボーン情報
364      * @throws IOException 出力エラー
365      * @throws IllegalTextExportException ボーン名が長すぎる
366      */
367     private void dumpBone(BoneInfo bone)
368             throws IOException, IllegalTextExportException{
369         String boneName = bone.getBoneName().getPrimaryText();
370         dumpText(boneName, PmdLimits.MAXBYTES_BONENAME);
371
372         BoneInfo prev = bone.getPrevBone();
373         if(prev != null) dumpSerialIdAsShort(prev);
374         else             dumpLeShort(NOPREVBONE_ID);
375
376         BoneInfo next = bone.getNextBone();
377         if(next != null) dumpSerialIdAsShort(next);
378         else             dumpLeShort(NONEXTBONE_ID);
379
380         BoneType type = bone.getBoneType();
381         dumpByte(type.encode());
382
383         if(type == BoneType.LINKEDROT){
384             int ratio = bone.getRotationRatio();
385             dumpLeShort(ratio);
386         }else{
387             BoneInfo srcBone = bone.getSrcBone();
388             if(srcBone != null) dumpSerialIdAsShort(srcBone);
389             else                dumpLeShort(NOIKBONE_ID);
390         }
391
392         MkPos3D position = bone.getPosition();
393         dumpPos3D(position);
394
395         return;
396     }
397
398     /**
399      * IKチェーンリストを出力する。
400      * @param model モデルデータ
401      * @throws IOException 出力エラー
402      */
403     private void dumpIKChainList(PmdModel model)
404             throws IOException{
405         List<IKChain> ikChainList = model.getIKChainList();
406
407         int ikNum = ikChainList.size();
408         dumpLeShort(ikNum);
409
410         for(IKChain chain : ikChainList){
411             dumpIKChain(chain);
412         }
413
414         flush();
415
416         return;
417     }
418
419     /**
420      * IKチェーンを出力する。
421      * @param chain IKチェーン
422      * @throws IOException 出力エラー
423      */
424     // TODO ボーンリストから自動抽出できる情報ではないのか?
425     private void dumpIKChain(IKChain chain)
426             throws IOException{
427         BoneInfo ikBone = chain.getIkBone();
428         dumpSerialIdAsShort(ikBone);
429
430         List<BoneInfo> boneList = chain.getChainedBoneList();
431
432         BoneInfo bone1st = boneList.get(0);
433         dumpSerialIdAsShort(bone1st);
434
435         int boneNum = boneList.size();
436         dumpByte(boneNum - 1);
437
438         int depth = chain.getIKDepth();
439         float weight = chain.getIKWeight();
440
441         dumpLeShort(depth);
442         dumpLeFloat(weight);
443
444         for(int idx = 1; idx < boneNum; idx++){ // リストの2番目以降全て
445             BoneInfo bone = boneList.get(idx);
446             dumpSerialIdAsShort(bone);
447         }
448
449         return;
450     }
451
452     /**
453      * モーフリストを出力する。
454      * @param model モデルデータ
455      * @throws IOException 出力エラー
456      * @throws IllegalTextExportException モーフ名が長すぎる
457      */
458     private void dumpMorphList(PmdModel model)
459             throws IOException, IllegalTextExportException{
460         Map<MorphType, List<MorphPart>> morphMap = model.getMorphMap();
461         Set<MorphType> typeSet = morphMap.keySet();
462         List<MorphVertex> mergedMorphVertexList = model.mergeMorphVertex();
463
464         int totalMorphPart = 0;
465         for(MorphType type : typeSet){
466             List<MorphPart> partList = morphMap.get(type);
467             if(partList == null) continue;
468             totalMorphPart += partList.size();
469         }
470
471         if(totalMorphPart <= 0){
472             dumpLeShort(0);
473             return;
474         }else{
475             totalMorphPart++;  // baseの分
476             dumpLeShort(totalMorphPart);
477         }
478
479         dumpText("base", PmdLimits.MAXBYTES_MORPHNAME);
480         int totalVertex = mergedMorphVertexList.size();
481         dumpLeInt(totalVertex);
482         dumpByte(MorphType.BASE.encode());
483         for(MorphVertex morphVertex : mergedMorphVertexList){
484             Vertex baseVertex = morphVertex.getBaseVertex();
485             dumpLeInt(baseVertex.getSerialNumber());
486             dumpPos3D(baseVertex.getPosition());
487         }
488
489         for(MorphType type : typeSet){
490             List<MorphPart> partList = morphMap.get(type);
491             if(partList == null) continue;
492             for(MorphPart part : partList){
493                 dumpText(part.getMorphName().getPrimaryText(),
494                          PmdLimits.MAXBYTES_MORPHNAME );
495                 List<MorphVertex> morphVertexList = part.getMorphVertexList();
496                 dumpLeInt(morphVertexList.size());
497                 dumpByte(part.getMorphType().encode());
498
499                 for(MorphVertex morphVertex : morphVertexList){
500                     dumpLeInt(morphVertex.getSerialNumber());
501                     dumpPos3D(morphVertex.getOffset());
502                 }
503             }
504         }
505
506         flush();
507
508         return;
509     }
510
511     /**
512      * モーフグループを出力する。
513      * @param model モデルデータ
514      * @throws IOException 出力エラー
515      */
516     private void dumpMorphGroup(PmdModel model)
517             throws IOException{
518         Map<MorphType, List<MorphPart>> morphMap = model.getMorphMap();
519         Set<MorphType> typeSet = morphMap.keySet();
520
521         int totalMorph = 0;
522         for(MorphType type : typeSet){
523             List<MorphPart> partList = morphMap.get(type);
524             if(partList == null) continue;
525             totalMorph += partList.size();
526         }
527         dumpByte(totalMorph);
528
529         List<MorphType> typeList = new LinkedList<MorphType>();
530         for(MorphType type : typeSet){
531             assert ! type.isBase();
532             typeList.add(type);
533         }
534         Collections.reverse(typeList);  // 一応本家と互換性を
535
536         for(MorphType type : typeList){
537             List<MorphPart> partList = morphMap.get(type);
538             if(partList == null) continue;
539             for(MorphPart part : partList){
540                 dumpSerialIdAsShort(part);
541             }
542         }
543
544         flush();
545
546         return;
547     }
548
549     /**
550      * ボーングループリストを出力する。
551      * デフォルトボーングループ内訳は出力されない。
552      * @param model モデルデータ
553      * @throws IOException 出力エラー
554      * @throws IllegalTextExportException ボーングループ名が長すぎる
555      */
556     private void dumpBoneGroupList(PmdModel model)
557             throws IOException, IllegalTextExportException{
558         List<BoneGroup> groupList = model.getBoneGroupList();
559         int groupNum = groupList.size();
560         dumpByte(groupNum - 1);
561
562         int dispBoneNum = 0;
563         for(BoneGroup group : groupList){
564             if(group.isDefaultBoneGroup()) continue;
565             dumpFixedW31j(group.getGroupName().getPrimaryText(),
566                           PmdLimits.MAXBYTES_BONEGROUPNAME, LFFILLER );
567             dispBoneNum += group.getBoneList().size();
568         }
569         dumpLeInt(dispBoneNum);
570
571         for(BoneGroup group : groupList){
572             if(group.isDefaultBoneGroup()) continue;
573             for(BoneInfo bone : group){
574                 dumpSerialIdAsShort(bone);
575                 int groupId = group.getSerialNumber();
576                 dumpByte(groupId);
577             }
578         }
579
580         flush();
581
582         return;
583     }
584
585     /**
586      * 各種通し番号をshort値で出力する。
587      * short値に収まらない上位ビットは捨てられる。
588      * @param obj 番号づけられたオブジェクト
589      * @throws IOException 出力エラー
590      */
591     protected void dumpSerialIdAsShort(SerialNumbered obj)
592             throws IOException{
593         int serialId = obj.getSerialNumber();
594         dumpLeShort(serialId);
595         return;
596     }
597
598     /**
599      * 2次元位置情報を出力する。
600      * @param position 2次元位置情報
601      * @throws IOException 出力エラー
602      */
603     protected void dumpPos2d(MkPos2D position) throws IOException{
604         float xPos = (float) position.getXpos();
605         float yPos = (float) position.getYpos();
606
607         dumpLeFloat(xPos);
608         dumpLeFloat(yPos);
609
610         return;
611     }
612
613     /**
614      * 3次元位置情報を出力する。
615      * @param position 3次元位置情報
616      * @throws IOException 出力エラー
617      */
618     protected void dumpPos3D(MkPos3D position) throws IOException{
619         float xPos = (float) position.getXpos();
620         float yPos = (float) position.getYpos();
621         float zPos = (float) position.getZpos();
622
623         dumpLeFloat(xPos);
624         dumpLeFloat(yPos);
625         dumpLeFloat(zPos);
626
627         return;
628     }
629
630     /**
631      * 3次元ベクトル情報を出力する。
632      * @param vector 3次元ベクトル
633      * @throws IOException 出力エラー
634      */
635     protected void dumpVec3D(MkVec3D vector) throws IOException{
636         float xVal = (float) vector.getXVal();
637         float yVal = (float) vector.getYVal();
638         float zVal = (float) vector.getZVal();
639
640         dumpLeFloat(xVal);
641         dumpLeFloat(yVal);
642         dumpLeFloat(zVal);
643
644         return;
645     }
646
647 }