OSDN Git Service

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