OSDN Git Service

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