OSDN Git Service

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