Model loading framwork

Hello all, I’ve been fiddling around some with model loading, and I found it to be very tedious work, where you tend to do the same thing over and over again. So I started work on a model loading framework.



First version is availlable for download at http://www.ractoc.com/flux/Model.rar



There’s a ModelTest class in there that should be fairly easy to understand. The idea is you create a Properties object and feed that to the loadModel method of the framework. This then takes care of the loading / converting of the model. It even caches the converted / created Spatial to a predifined location on disk.



Mark

Sorta like the ModelLoader class that's in jME? :wink:

Yes and no, this one will do a bit more. And it allows you to plugin your own derived model classes. In my case, I have a Ship and

Cool, sounds interesting at least.  If you have time to just make a bulleted list of thoughts on what is hard/repetitive about model loading and any thoughts you have on correcting or improving things, that would be most helpful.

Most of the things are handled by the curent modelloader class that's in jme, But I find that I don't want to thave to think about the fact that, before I can put a model into jme, I need to convert it to the correct format to have the much needed performance boost from not converting every time the model is loaded. Aside from that, there are a lot of similarities between loading a 3d model and creating one in JME. Like setting a bounding box etc. I thought I could set things up in such a way that you only have to specify if your object is a loaded model or a created model, and then just have thestuff that's the same for both be handled in a central location.



Besides this, I found I can get an even bigger performance increase by storing the loaded models as a Spatial, once they have been loaded / created. This means JMe doesnt have to do any calculations next time around. And seeing how I'm caching straight after loading, the cached object is very clean with resoect to rotation etc. Those state reliant issues are not cached, although I will implement a refresh option to enable you to recache later on if you really want to. (for example when closing the game and enableing a quick continue next time.

You might find this code useful, cant remember where it came from - but its not mine (just in case any of you recognise it  :wink: )



import com.jme.scene.Node;
import com.jme.util.LoggingSystem;
import com.jme.util.export.Savable;
import com.jme.util.export.binary.BinaryExporter;
import com.jme.util.export.binary.BinaryImporter;
import com.jmex.model.XMLparser.Converters.AseToJme;
import com.jmex.model.XMLparser.Converters.FormatConverter;
import com.jmex.model.XMLparser.Converters.MaxToJme;
import com.jmex.model.XMLparser.Converters.Md2ToJme;
import com.jmex.model.XMLparser.Converters.Md3ToJme;
import com.jmex.model.XMLparser.Converters.MilkToJme;
import com.jmex.model.XMLparser.Converters.ObjToJme;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.logging.Level;

public static Node loadModel(String file) {

   Node model = null;
   FormatConverter formatConverter;
   ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
   String format = file.substring(file.lastIndexOf(".") + 1, file.length());
   String binary = file.substring(0, file.lastIndexOf(".") + 1) + "jbin";
   URL url = getModel(binary);

   // verify the presence of the jbin model
   if(url == null) {

      url = getModel(file);

      // evaluate all compatable model formats
      if(format.equalsIgnoreCase("3ds")) {
         formatConverter = new MaxToJme();

      } else if(format.equalsIgnoreCase("md2")) {
         formatConverter = new Md2ToJme();

      } else if(format.equalsIgnoreCase("md3")) {
         formatConverter = new Md3ToJme();

      } else if(format.equalsIgnoreCase("ms3d")) {
         formatConverter = new MilkToJme();

      } else if(format.equalsIgnoreCase("ase")) {
         formatConverter = new AseToJme();

      } else if(format.equalsIgnoreCase("obj")) {
         formatConverter = new ObjToJme();

      } else {
         LoggingSystem.getLogger().log(Level.WARNING, "Problem loading model: Unrecognised file type");

         return model;
      }

      try {
         // load in model
         formatConverter.setProperty("mtllib", url);
         formatConverter.convert(url.openStream(), byteArrayOutputStream);

         model = (Node) BinaryImporter.getInstance().load(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));

         // output model in jbin format
         BinaryExporter.getInstance().save((Savable) model, new File(MODELS + System.getProperty("file.separator") + binary));

      } catch(IOException e) {
         LoggingSystem.getLogger().log(Level.WARNING, "Problem loading model: " + e.getMessage());
      }

   } else {
      try {
         // load model in jbin format
         model = (Node) BinaryImporter.getInstance().load(url.openStream());

      } catch(IOException e) {
         LoggingSystem.getLogger().log(Level.WARNING, "Problem loading model: " + e.getMessage());
      }
   }

   return model;
}

ok, it took a bit of tweaking, but I got that bit of code integrated in the framework. Works like a charm, thanks.

I do believe that was originally taken from (or at least looks very similar to) the ModelLoader class.  :stuck_out_tongue:


ractoc said:

...before I can put a model into jme, I need to convert it to the correct format to have the much needed performance boost from not converting every time the model is loaded...


ModelLoader also already converts it to a .jme binary file btw. ;)

I like some of the features you've mentioned though.  I'd be very interested in bringing some of them into ModelLoader and others hopefully getting something into jmex to be a better way to create things in a scene rather than having to programmatically build and modify models and placement.

I'd be very curious to see a Spatial clone that fully works. ;)  Also, I'd love to see a performance comparison between that and creating it from a byte[] each time....I would think the byte[] would be smaller though and pretty fast to load, right?

Ok, I just uploaded a new version of the framework, same URL as before. I now added the code provided above, so it should work with all availlable convertor types. Not sure yet why the textures don't work on my end though, could just be something with the model I'm using.



Besides that, I added a new method to de Model class, load ModelsFrom Xml. This enable you to provide the location of an XML file which contains the params of the various models you want to build. It uses SAX for XML parsing and it loads the models when all the params for that specific model are parsed. It uses the same method for model loading as you use directly, so anything you have availlable when loading directly, you have availlable when loading via XML config.



And now I'm off to bed, we're nearing 03.00 here so…

What would be great is to also provide an XML <-> Binary conversion since it's probably not a good idea (performance and people that would monkey around with the data) to use XML in the final game…hmmm, I guess what I'm suggesting is an extension to the .jme format actually.  :stuck_out_tongue:

I'm currently just using the XML as a way to tell the framework where to find the models and a bunch of othe configuration stuff. Reason for doing it through a conig file is that I want to be able to add new models later on without having to rewrite my code. So I'll have a config file containing the config data for the various models used in the game. This way, when I want to change a model, or add a new one later on, I can do this more easily.



What I could do however is add functionality to have the preloading of the actual 3d model as optional. That way, you would have the entire config loaded at startup, but the loading of the 3d model would be postponed untill you actually need it.that should conserve at least some memory, but it would put the loading of models during gameplay instead of at the loading of the game.



Ah well, if I make it configurable per model, I guess that shouldn't be to big a problem seeing how you can then decide on a per model basis.



As to the XML <-> binary convertor, wouldn't that mean just a new FormatConverter class?



Where would the source XML come from? I only have the possibility of creating MILK and 3D-MAX object so…

What I was thinking you had was sort of a instruction set of laying out maps, objects, etc. and if you just had an object representation of that XML file before you did the work you could serialize that back to a binary file and utilize that in the production game rather than the XML file.  I'm opposed to having XML files in my production game for obvious reasons. :wink:

I'm sorry to barge in but I have two questions:



@ractoc, you said you have Model which is subclassed by Creator and Loader: one for creating scene elements and the other one for loading them. Pardon me, but either you choosed poor superclass name or you're violating design 'is-a' principle. Neither of those two behave as Models, so maybe they shouldn't be one. Just a thought.



@darkfrog, if you're referring to XML files for models only, ignore this comment.

Otherwise, what's wrong with people tampering around game data? They'll destroy their own game experience, not mine… or perhaps, enhance it and post it back to me. I keep everything in XML's in my project, from computer hardware to software to users to items… of course you won't build objects directly from XML files (I'm referring to performance) but factories which produce one object type, defined by ID… I think its actually pretty flexible way of storing/creating game data…



If I have a class named Item which has properties width, height, texture, and set of actions, I cannot find better way of creating different Item objects than building ItemFactory which is configured by XML.

Something similar to SpringFramework…

@deus_ex, do you not remember Microsofts Freelancer? by simply changing a few lines within a text file you could increase the speed of your vessels thrusters, and various other things.



It would be nice if the XML (belonging solely to the developer) could be converted into a serialised collection of models which is distributed with the rest of the game package - much like dark frog pointed out.

I've build quite a few apps for use on mobile phones and with space being very important there, we used something called binary XML. What this means is that all tags are replaced with byte codes. This does 2 things. First, it makes your files a hell of a lot smaller and secondly it makes them much harder to edit, seeing how you now have binary files storing the data.



But I guess converting the whole XML document to a single binary file is also a way to go.I'm currently just caching the 3d model, but I could add a few lines of code that would make it possible to save theentire object to disk.



It kind of defeats the whole idea for using the XML though, if I do that. Because I opted for XML because of the ease with which I can add new things and change existing ones. I might need to do some masking though to protect the data inside my XML file. Hell, I might even pull out my code for file encryption. (very simple 8 bit file encryption by ddoing  bit shifting). It's not completely bulletproof, but for a thing like this, it hardly needs to be. And being a very simple encryption, it's hardly slower then just plain file access.



Going this way would enable to still work with XML for config, but it would also give me the security needed.



And with respect to having an XML file for laying out objects on the map, that would be the mapper I have planned for the next stage of the build.

ractoc, why cant the Xml convert to binary if it exists, meaning packages without the XML would fallback to the saved binary file - preventing people from adjusting its contents.

Someone just needs to write jME Punkbuster.  :D  Then people can edit the files if they want to cheat but if it's a multiplayer game the game server will reject anyone who has modified any of the game files.  You get the flexibility you want, but also protect the gaming integrity.

For use with multiplayer online games I agree with you on that one. I planned to have the calculation of damage, speed etc. be done server side, so you can alter your settings to have you move faster, but in the end it's the server that determines your actual location.



This said, it wouldn't be too hard to have at least some sort of protection on those files on the client side as well, no use in making it that easy on them.



Oh, and one thing on the XML file I'm using to load the models, this is just because I plan to do some bulk loading for all the standard static models in a solar system. (planets, station, etc.) The other, more modile models I will still load when needed. Which is also still possible. The only difference is that when you do the manual load, you will have to build the Properties object containing the params yourself, instead of getting it from a XML file.



So in the framework, both is possible, if you want to have the config in a binary file, you can do that, just ad a layer for it in front of the curent framework which reads the binary config file and from that creates the Properties objects to feed to the framework's loadModel method.



I've also been giving the comments on the class names some thought, and I have to agree choosing Model as a name for my base class is a bit confusing. While it is a Model that is loaded, it's not a 3D model, but more a complete Model as it is used in the game, with state and everything. So I plan to do a bit of refactoring on that end to have a better naming scheme. In that light, I'm also considering renaming a few methods to move away from the whole Model thing even more and just have Model be the 3DModel, the rest will have to be something else. Not sure yet about the name for the object which will contain both the model and the model state though, so any suggestions are welcome.



Mark

I know its a bit off topic, but relying on the server to do all those calculations for the client (rather than just validate that them not to be extreme) could be quite costly when presented with many clients and an active scene. (Although improves on security)

True, which is wh I'm currently leaning towards a slightly different approoach. I have the framework setup now in such a way that you can feed it a standard InputStream as well. That way, I can keep the model config files on the server and just stream them directly into the framework at load time.



That way, I keep the easy configuration capabilities and it's as secure as I can get it.



And if someone want to do it differently, he can either use the method which loads a single model, or he can create his own inputstream from wherever he likes.