OSDN Git Service

Set optimal mime types and executable settings.
[mikumikustudio/MikuMikuStudio.git] / src / com / jmex / model / converters / MilkToJme.java
1 /*
2  * Copyright (c) 2003-2009 jMonkeyEngine
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * * Redistributions of source code must retain the above copyright
10  *   notice, this list of conditions and the following disclaimer.
11  *
12  * * Redistributions in binary form must reproduce the above copyright
13  *   notice, this list of conditions and the following disclaimer in the
14  *   documentation and/or other materials provided with the distribution.
15  *
16  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors 
17  *   may be used to endorse or promote products derived from this software 
18  *   without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32
33 package com.jmex.model.converters;
34
35 import java.io.DataInput;
36 import java.io.IOException;
37 import java.io.InputStream;
38 import java.io.OutputStream;
39 import java.net.URL;
40
41 import com.jme.image.Image;
42 import com.jme.image.Texture;
43 import com.jme.image.Texture2D;
44 import com.jme.math.Vector2f;
45 import com.jme.math.Vector3f;
46 import com.jme.renderer.ColorRGBA;
47 import com.jme.scene.Controller;
48 import com.jme.scene.Node;
49 import com.jme.scene.TexCoords;
50 import com.jme.scene.TriMesh;
51 import com.jme.scene.state.CullState;
52 import com.jme.scene.state.MaterialState;
53 import com.jme.scene.state.TextureState;
54 import com.jme.system.DisplaySystem;
55 import com.jme.system.JmeException;
56 import com.jme.system.dummy.DummyDisplaySystem;
57 import com.jme.system.dummy.DummySystemProvider;
58 import com.jme.util.LittleEndien;
59 import com.jme.util.TextureKey;
60 import com.jme.util.TextureManager;
61 import com.jme.util.export.binary.BinaryExporter;
62 import com.jme.util.geom.BufferUtils;
63 import com.jme.util.resource.ResourceLocatorTool;
64 import com.jmex.model.JointMesh;
65 import com.jmex.model.animation.JointController;
66
67 /**
68  * Started Date: Jun 8, 2004
69  * <p>
70  * This class converts a .ms3d file to jME's binary format. The way it converts
71  * is by first building the .ms3d scenegraph object, then saving that object to
72  * binary format via {@link com.jmex.model.XMLparser.JmeBinaryWriter}. This
73  * requires a {@link com.jme.system.DisplaySystem} to function correctly
74  * (as all loaders do).
75  * <p>
76  * This will normally be provided within a game environment (such as
77  * {@link com.jme.app.SimpleGame}). However if you wish to use this in a
78  * stand-alone environment, such as part of a tool conversion utility, you
79  * should create a {@link DummyDisplaySystem} before using this class.
80  * @author Jack Lindamood
81  */
82 public class MilkToJme extends FormatConverter{
83     
84     private DataInput inFile;
85     private byte[] tempChar=new byte[128];
86     private int nNumVertices;
87     private int nNumTriangles;
88     private MilkTriangle[] myTris;
89     private MilkVertex[] myVerts;
90     private int[] materialIndexes;
91
92     /**
93      * Converts a MS3D file to jME format.  The syntax is: "MilkToJme runner.ms3d out.jme".
94      * @param args The array of parameters
95      */
96     public static void main(String[] args){
97         DisplaySystem.getDisplaySystem(DummySystemProvider.DUMMY_SYSTEM_IDENTIFIER);
98         new MilkToJme().attemptFileConvert(args);
99     }
100
101
102     /**
103      * The node that represents the .ms3d file.  It's chidren are MS meshes
104      */
105     private Node finalNode;
106
107     /**
108      * This class's only public function.  It creates a node from a .ms3d stream and then writes that node to the given
109      * OutputStream in binary format
110      * @param MSFile An inputStream that is the .ms3d file
111      * @param o The Stream to write it's jME binary equivalent to
112      * @throws IOException If anything funky goes wrong with reading information
113      */
114     public void convert(InputStream MSFile,OutputStream o) throws IOException {
115         inFile=new LittleEndien(MSFile);
116         finalNode=new Node("ms3d file");
117         CullState CS=DisplaySystem.getDisplaySystem().getRenderer().createCullState();
118         CS.setCullFace(CullState.Face.Back);
119         CS.setEnabled(true);
120         finalNode.setRenderState(CS);
121         checkHeader();
122         readVerts();
123         readTriangles();
124         readGroups();
125         readMats();
126         readJoints();
127         
128         
129         BinaryExporter.getInstance().save(finalNode,o);
130         nullAll();
131     }
132     
133     private void addJointMeshes(Node parentNode, JointController jc) {
134         for (int i=0;i<parentNode.getQuantity();i++){
135             if (parentNode.getChild(i) instanceof JointMesh)
136                 jc.addJointMesh((JointMesh) parentNode.getChild(i));
137         }
138     }
139
140     private void nullAll() {
141         myTris=null;
142         myVerts=null;
143         finalNode=null;
144     }
145
146     private boolean readJoints() throws IOException {
147         float fAnimationFPS=inFile.readFloat();
148         float curTime=inFile.readFloat();     // Ignore currentTime
149         int iTotalFrames=inFile.readInt();      // Ignore total Frames
150         int nNumJoints=inFile.readUnsignedShort();
151         if (nNumJoints==0) return false;
152         String[] jointNames=new String[nNumJoints];
153         String[] parentNames=new String[nNumJoints];
154         JointController jc=new JointController(nNumJoints);
155         jc.FPS=fAnimationFPS;
156
157         for (int i=0;i<nNumJoints;i++){
158             inFile.readByte();  // Ignore flags
159             inFile.readFully(tempChar,0,32);
160             jointNames[i]=cutAtNull(tempChar);
161             inFile.readFully(tempChar,0,32);
162             parentNames[i]=cutAtNull(tempChar);
163             jc.localRefMatrix[i].setEulerRot(inFile.readFloat(),inFile.readFloat(),inFile.readFloat());
164             jc.localRefMatrix[i].setTranslation(inFile.readFloat(),inFile.readFloat(),inFile.readFloat());
165             int numKeyFramesRot=inFile.readUnsignedShort();
166             int numKeyFramesTrans=inFile.readUnsignedShort();
167             for (int j=0;j<numKeyFramesRot;j++)
168                 jc.setRotation(i,inFile.readFloat(),inFile.readFloat(),inFile.readFloat(),inFile.readFloat());
169             for (int j=0;j<numKeyFramesTrans;j++)
170                 jc.setTranslation(i,inFile.readFloat(),inFile.readFloat(),inFile.readFloat(),inFile.readFloat());
171
172         }
173         for (int i=0;i<nNumJoints;i++){
174             jc.parentIndex[i]=-1;
175             for (int j=0;j<nNumJoints;j++){
176                 if (parentNames[i].equals(jointNames[j])) jc.parentIndex[i]=j;
177             }
178         }
179         jc.setRepeatType(Controller.RT_WRAP);
180         finalNode.addController(jc);
181         addJointMeshes(finalNode, jc);
182         return true;
183     }
184
185     private void readMats() throws IOException {
186         int nNumMaterials=inFile.readUnsignedShort();
187         for (int i=0;i<nNumMaterials;i++){
188             inFile.skipBytes(32);   // Skip the name, unused
189             MaterialState matState=DisplaySystem.getDisplaySystem().getRenderer().createMaterialState();
190             matState.setAmbient(getNextColor());
191             matState.setDiffuse(getNextColor());
192             matState.setSpecular(getNextColor());
193             matState.setEmissive(getNextColor());
194             matState.setShininess(inFile.readFloat());
195             float alpha = inFile.readFloat();
196             matState.getDiffuse().a = alpha;
197             matState.getEmissive().a = alpha;
198             matState.getAmbient().a = alpha;
199             
200             inFile.readByte();      // Mode is ignored
201
202             inFile.readFully(tempChar,0,128);
203             TextureState texState=null;
204             String texFile=cutAtNull(tempChar);
205             if (texFile.length()!=0){
206                 URL texURL = ResourceLocatorTool.locateResource(
207                         ResourceLocatorTool.TYPE_TEXTURE, texFile);
208                 if (texURL != null) {
209                     texState = DisplaySystem.getDisplaySystem().getRenderer()
210                     .createTextureState();
211                     Texture tempTex = new Texture2D();
212                     tempTex.setTextureKey(new TextureKey(texURL, true,
213                             TextureManager.COMPRESS_BY_DEFAULT ? Image.Format.Guess
214                                     : Image.Format.GuessNoCompression));
215                     tempTex.setAnisotropicFilterPercent(0.0f);
216                     tempTex.setMinificationFilter(Texture.MinificationFilter.BilinearNearestMipMap);
217                     tempTex.setMagnificationFilter(Texture.MagnificationFilter.Bilinear);
218                     tempTex.setWrap(Texture.WrapMode.Repeat);
219                     texState.setTexture(tempTex);
220                 }
221             }
222             inFile.readFully(tempChar,0,128);   // Alpha map, but it is ignored
223             //TODO: Implement Alpha Maps
224
225             applyStates(matState,texState,i);
226         }
227     }
228
229     /**
230      * Every child of finalNode (IE the .ms3d file) whos materialIndex is the given index, gets the two RenderStates applied
231      * @param matState A Material state to apply
232      * @param texState A TextureState to apply
233      * @param index The index that a TriMesh should have from <code>materialIndex</code> to get the given
234      * states
235      */
236     private void applyStates(MaterialState matState, TextureState texState,int index) {
237         for (int i=0;i<finalNode.getQuantity();i++){
238             if (materialIndexes[i]==index){
239                 if (matState!=null) ((TriMesh)finalNode.getChild(i)).setRenderState(matState);
240                 if (texState!=null) ((TriMesh)finalNode.getChild(i)).setRenderState(texState);
241             }
242         }
243     }
244
245     private ColorRGBA getNextColor() throws IOException {
246         return new ColorRGBA(
247                 inFile.readFloat(),
248                 inFile.readFloat(),
249                 inFile.readFloat(),
250                 inFile.readFloat()
251         );
252     }
253
254     private void readGroups() throws IOException {
255         int nNumGroups=inFile.readUnsignedShort();
256         materialIndexes=new int[nNumGroups];
257         for (int i=0;i<nNumGroups;i++){
258             inFile.readByte();      // Ignore flags
259             inFile.readFully(tempChar,0,32);    // Name
260             int numTriLocal=inFile.readUnsignedShort();
261             Vector3f[] meshVerts=new Vector3f[numTriLocal*3],meshNormals=new Vector3f[numTriLocal*3];
262             Vector3f[] origVerts=new Vector3f[numTriLocal*3],origNormals=new Vector3f[numTriLocal*3];
263             Vector2f[] meshTexCoords=new Vector2f[numTriLocal*3];
264             int[] meshIndex=new int[numTriLocal*3];
265             int[] jointIndex=new int[numTriLocal*3];
266             JointMesh theMesh=new JointMesh(cutAtNull(tempChar));
267
268             for (int j=0;j<numTriLocal;j++){
269                 int triIndex=inFile.readUnsignedShort();
270                 for (int k=0;k<3;k++){
271                     meshVerts       [j*3+k]=myVerts[myTris[triIndex].vertIndicies[k]].vertex;
272                     meshNormals     [j*3+k]=myTris[triIndex].vertNormals[k];
273                     meshTexCoords   [j*3+k]=myTris[triIndex].vertTexCoords[k];
274                     meshIndex       [j*3+k]=j*3+k;
275                     origVerts       [j*3+k]=meshVerts[j*3+k];
276                     origNormals     [j*3+k]=meshNormals[j*3+k];
277                     jointIndex      [j*3+k]=myVerts[myTris[triIndex].vertIndicies[k]].boneID;
278                 }
279             }
280             theMesh.reconstruct(BufferUtils.createFloatBuffer(meshVerts),
281                     BufferUtils.createFloatBuffer(meshNormals), null,
282                     TexCoords.makeNew(meshTexCoords), 
283                     BufferUtils.createIntBuffer(meshIndex));
284             theMesh.originalNormal=origNormals;
285             theMesh.originalVertex=origVerts;
286             theMesh.jointIndex=jointIndex;
287             finalNode.attachChild(theMesh);
288             materialIndexes[i]=inFile.readByte();
289         }
290     }
291
292     private void readTriangles() throws IOException {
293         nNumTriangles=inFile.readUnsignedShort();
294         myTris=new MilkTriangle[nNumTriangles];
295         for (int i=0;i<nNumTriangles;i++){
296             int j;
297             myTris[i]=new MilkTriangle();
298             inFile.readUnsignedShort(); // Ignore flags
299             for (j=0;j<3;j++)
300                 myTris[i].vertIndicies[j]=inFile.readUnsignedShort();
301             for (j=0;j<3;j++){
302                 myTris[i].vertNormals[j]=new Vector3f(
303                         inFile.readFloat(),
304                         inFile.readFloat(),
305                         inFile.readFloat()
306                 );
307             }
308             for (j=0;j<3;j++){
309                 myTris[i].vertTexCoords[j]=new Vector2f();
310                 myTris[i].vertTexCoords[j].x=inFile.readFloat();
311             }
312             for (j=0;j<3;j++)
313                 myTris[i].vertTexCoords[j].y=1-inFile.readFloat();
314             inFile.readByte();      // Ignore smoothingGroup
315             inFile.readByte();      // Ignore groupIndex
316         }
317     }
318
319     private void readVerts() throws IOException {
320         nNumVertices=inFile.readUnsignedShort();
321         myVerts=new MilkVertex[nNumVertices];
322         for (int i=0;i<nNumVertices;i++){
323             myVerts[i]=new MilkVertex();
324             inFile.readByte(); // Ignore flags
325             myVerts[i].vertex=new Vector3f(
326                     inFile.readFloat(),
327                     inFile.readFloat(),
328                     inFile.readFloat()
329             );
330             myVerts[i].boneID=inFile.readByte();
331             inFile.readByte();  // Ignore referenceCount
332         }
333     }
334
335     private void checkHeader() throws IOException {
336         inFile.readFully(tempChar,0,10);
337         if (!"MS3D000000".equals(new String(tempChar,0,10))) throw new JmeException("Wrong File type: not Milkshape file??");
338         if (inFile.readInt()!=4) throw new JmeException("Wrong file version: Not 4");   // version
339     }
340
341     private static class MilkVertex{
342         Vector3f vertex;
343         byte boneID;
344     }
345     private static class MilkTriangle{
346         int[] vertIndicies=new int[3];              // 3 ints
347         Vector3f[] vertNormals=new Vector3f[3];     // 3 Vector3fs
348         Vector2f[] vertTexCoords=new Vector2f[3];   // 3 Texture Coords
349     }
350     private static String cutAtNull(byte[] inString) {
351         for (int i=0;i<inString.length;i++)
352             if (inString[i]==0) return new String(inString,0,i);
353         return new String(inString);
354     }
355
356     /**
357      * This function returns the controller of a loaded Milkshape3D model.  Will return
358      * null if a correct JointController could not be found, or if one does not exist.
359      * @param model The model that was loaded.
360      * @return The controller for that milkshape model.
361      */
362     public static JointController findController(Node model){
363         if (model.getQuantity()==0 ||
364                 model.getChild(0).getControllers().size()==0 ||
365                 !(model.getChild(0).getController(0) instanceof JointController))
366             return null;
367         return (JointController) (model.getChild(0)).getController(0);
368     }
369 }