OSDN Git Service

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