2 * Copyright (c) 2003-2009 jMonkeyEngine
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
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.
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.
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.
33 package com.jmex.model.converters;
35 import java.io.DataInput;
36 import java.io.IOException;
37 import java.io.InputStream;
38 import java.io.OutputStream;
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;
68 * Started Date: Jun 8, 2004
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).
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
82 public class MilkToJme extends FormatConverter{
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;
93 * Converts a MS3D file to jME format. The syntax is: "MilkToJme runner.ms3d out.jme".
94 * @param args The array of parameters
96 public static void main(String[] args){
97 DisplaySystem.getDisplaySystem(DummySystemProvider.DUMMY_SYSTEM_IDENTIFIER);
98 new MilkToJme().attemptFileConvert(args);
103 * The node that represents the .ms3d file. It's chidren are MS meshes
105 private Node finalNode;
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
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);
120 finalNode.setRenderState(CS);
129 BinaryExporter.getInstance().save(finalNode,o);
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));
140 private void nullAll() {
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;
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());
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;
179 jc.setRepeatType(Controller.RT_WRAP);
180 finalNode.addController(jc);
181 addJointMeshes(finalNode, jc);
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;
200 inFile.readByte(); // Mode is ignored
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);
222 inFile.readFully(tempChar,0,128); // Alpha map, but it is ignored
223 //TODO: Implement Alpha Maps
225 applyStates(matState,texState,i);
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
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);
245 private ColorRGBA getNextColor() throws IOException {
246 return new ColorRGBA(
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));
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;
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();
292 private void readTriangles() throws IOException {
293 nNumTriangles=inFile.readUnsignedShort();
294 myTris=new MilkTriangle[nNumTriangles];
295 for (int i=0;i<nNumTriangles;i++){
297 myTris[i]=new MilkTriangle();
298 inFile.readUnsignedShort(); // Ignore flags
300 myTris[i].vertIndicies[j]=inFile.readUnsignedShort();
302 myTris[i].vertNormals[j]=new Vector3f(
309 myTris[i].vertTexCoords[j]=new Vector2f();
310 myTris[i].vertTexCoords[j].x=inFile.readFloat();
313 myTris[i].vertTexCoords[j].y=1-inFile.readFloat();
314 inFile.readByte(); // Ignore smoothingGroup
315 inFile.readByte(); // Ignore groupIndex
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(
330 myVerts[i].boneID=inFile.readByte();
331 inFile.readByte(); // Ignore referenceCount
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
341 private static class MilkVertex{
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
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);
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.
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))
367 return (JointController) (model.getChild(0)).getController(0);