OSDN Git Service

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