Beginner Question

Hello! I'm having some trouble getting my brain around how to place objects properly after I create them. Let me explain and show a bit of code.



Picture a hexagon. I want to, say, put a couple cylinders as 'spokes' in that hex, and 'package' that as a Node to be placed in a larger scene. My thought was to create one spoke (at the origin) then slide it over so one end is at the origin, then create a second, slide it as well, then rotate it. I imagined this would end up with both cylinders having one end at the origin. What I get is an off-center X, and I'm not sure how to make it do what I want.



Here's some code:


tile = new Node("Type " + type + " Rot " + rot);
               
Cylinder c1 = new Cylinder("one", 5, 5, 5, hexWidth / 2);
c1.setLocalTranslation(new Vector3f(0, 0, hexWidth / 4));

Cylinder c2 = new Cylinder("two", 5, 5, 5, hexWidth / 2);
c2.setLocalTranslation(new Vector3f(0, 0, hexWidth / 4));

Quaternion q2 = new Quaternion();
q2.fromAngleAxis(60 * FastMath.PI / 180, new Vector3f(0,1,0));

c2.setLocalRotation(q2);

c1.setModelBound(new BoundingBox());
c1.updateModelBound();
c2.setModelBound(new BoundingBox());
c2.updateModelBound();
               
tile.attachChild(c1);
tile.attachChild(c2);

Quaternion tileRotate = new Quaternion();
tileRotate.fromAngleAxis((30 + (rot * 60)) * FastMath.PI / 180, new Vector3f(0,1,0));

tile.setLocalRotation(tileRotate);



I can see why I'm getting the off-center X of cylinders; it's obviously rotating the second one around the local origin, and not the... original origin? I'm guessing my fix is simply to adjust my translation vector, but I don't seem to be able to get it right. Is there a way to translate an object and then rotate it about the original origin?

Thanks for any tips!

I'm struggling to visualise exactly what you intend it to look like.

In general terms, the easy way to translate then rotate around the original origin is to add a node to represent the origin. eg. make a new node, attach your cylinder to it. Translate the cylinder but rotate the node.

Maybe a picture would help.







In a nutshell, as my first step, I want to turn the blue lines into 3D cylinders. They’re arranged as hex tiles in a meta-hex, and I have all the information about which tile connects to which others. I’m trying to walk that map and produce the jME objects on the fly to reproduce the blue line paths.



I see what you’re saying about adding the shape to a node, translating the shape, then rotating the node. I could do that for each ‘spoke’ in each tile, then add the nodes to the tile’s overall node. Question: is there a downside to having nodes just to rotate the one shape inside it? Or is that exactly what jME does, cull all the scene information down to an efficient display version, and it’s okay for me to add as many nodes as I need?



In short, I automatically try to minimize when I code. Should I stop that when coding something like this, and let jME do it for me? :slight_smile:

The heirarchy is like this:


        Spatial
           /
         /   
       /       
    Node      TriMesh


A spatial can be attached to a node, so just think of a node as a logical 'holder' for the scenegraph; each node can have only one parent. 
A TriMesh is the actual geometry which can have no children; this is what gets rendered (a node is never rendered).


One advantage of this is that you don't have to deal with rotations and translations the same way you do in basic 'openGL'.  No clearing rotation, translate, re-rotate ;);  just leverage the scenegraph the way Alric said.

...and yes there is definitely some 'culling' stuff under the hood ;)

Okay, this is excellent, thank you. I'm already seeing much saner results.



Now for my next question. :slight_smile: I'm having issues figuring out the coordinate system. For example, when you create a cylinder, it says "Creates a new Cylinder. By default its center is the origin." in the JavaDocs of the constructor. That's great, but how can I tell the orientation of the object? Just trial and error? Or is there a rule of thumb that would make it easier for me to generalize to new objects?



Related question: It seems from the camera output ('c' in SimpleGame), and the Flag Rush tutorial, that the plane that's usually considered the 'ground' is the X,Z axis, with the Y axis being up and down. For example, when I created my own Terrain, that seemed to be the case. Am I just confused and have all my coordinate planes mixed up, or is that how it is? I always thought the Z axis was up and down…



Thanks for the quick responses!

World coordinates can either be Y=vertical or Z=vertical, it just depends on what you want (not sure if there is any reason for choosing one over the other).  jME generally uses Y=vertical, so that should help with the general concept of the 'internal' objects.


Really should have put an 's' on the title of this, since I seem to always have more. :)

Today's topic: Lighting. Or perhaps, models. I can't tell where my problem is. So, I went through a ton of Blender tutorials, and created myself a nice die, with only one face. It's red, with white pips, all smooth and happy.



Then I create a simple BaseGame class intended to do little but show this model. I ripped most of the code from FlagRush2, with the lighting and chase cam code from FlagRush9 (so I can move the camera around). Since these forums show a nice little scroll box when there's a lot of code, I'll just dump the whole thing right here. You can see it's all ripped from FlagRush and the wiki on how to load models.


public class ModelTest extends BaseGame {
   
   private Node scene;
   protected Timer timer;
   private int width, height, depth, freq;
   private boolean fullscreen;
   private Camera cam;
    private ChaseCamera chaser;
   
   public static void main(String[] args) {
      ModelTest app = new ModelTest();
       app.setConfigShowMode(ConfigShowMode.ShowIfNoConfig);
       app.start();
   }

   protected void initSystem() {
      width = settings.getWidth();
      height = settings.getHeight();
      depth = settings.getDepth();
      freq = settings.getFrequency();
      fullscreen = settings.isFullscreen();
      
      try {
         display = DisplaySystem.getDisplaySystem(settings.getRenderer());
         display.createWindow(width, height, depth, freq, fullscreen);

         cam = display.getRenderer().createCamera(width, height);
      } catch (JmeException e) {
         System.out.println("ERROR: Could not create displaySystem - " + e);
         System.exit(1);
      }

      //set the background to black
      display.getRenderer().setBackgroundColor(ColorRGBA.black.clone());
      
      //initialize the camera
      cam.setFrustumPerspective(45.0f, (float)width / (float)height, 1, 1000);
      Vector3f loc = new Vector3f(0.0f, 0.0f, 25.0f);
      Vector3f left = new Vector3f(-1.0f, 0.0f, 0.0f);
      Vector3f up = new Vector3f(0.0f, 1.0f, 0.0f);
      Vector3f dir = new Vector3f(0.0f, 0f, -1.0f);
      // Move our camera to a correct place and orientation.
      cam.setFrame(loc, left, up, dir);
      // Signal that we've changed our camera's location/frustum.
      cam.update();
      
       // Get a high resolution timer for FPS updates.
       timer = Timer.getTimer();

      display.getRenderer().setCamera(cam);

      KeyBindingManager.getKeyBindingManager().set("exit", KeyInput.KEY_ESCAPE);
   }
   
   protected void reinit() {
      display.recreateWindow(width, height, depth, freq, fullscreen);
   }
   
   protected void initGame() {
      scene = new Node("Root Node");
      cam.update();
      
      MaxToJme md3tojme = new MaxToJme();  //the converter
      Node node = null; //Where to dump mesh.
      ByteArrayOutputStream bytearrayoutputstream = new ByteArrayOutputStream(); //For loading the raw file
      
      // File Path for the model (dump file in toplevel of classpath)
      URL url = this.getClass().getResource("die.3ds");
            
      InputStream is=null;
       try {
         is = url.openStream();
      } catch (IOException e) {
         e.printStackTrace();
      }
      
       // Converts the file into a jme usable file
       try {
         md3tojme.convert(is, bytearrayoutputstream);
      } catch (IOException e) {
         e.printStackTrace();
      }
      
       // Used to convert the jme usable file to a TriMesh
       BinaryImporter binaryImporter = new BinaryImporter();
       ByteArrayInputStream in=new ByteArrayInputStream(bytearrayoutputstream.toByteArray());
      
       //importer returns a Loadable, cast to Node
       try {
         node = (Node)binaryImporter.load(in);
      } catch (IOException e) {
         e.printStackTrace();
      }
      
      node.setModelBound(new BoundingBox());
      node.updateModelBound();
      //attach node to scene graph
      node.updateRenderState();
       
      scene.attachChild(node);
      
        DirectionalLight light = new DirectionalLight();
        light.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
        light.setAmbient(new ColorRGBA(0.5f, 0.5f, 0.5f, .5f));
        light.setDirection(new Vector3f(1,-1,0));
        light.setShadowCaster(true);
        light.setEnabled(true);

        // Attach the light to a lightState and the lightState to rootNode.
        LightState lightState = display.getRenderer().createLightState();
        lightState.setEnabled(true);
        lightState.setGlobalAmbient(new ColorRGBA(.2f, .2f, .2f, 1f));
        lightState.attach(light);
        scene.setRenderState(lightState);
       
        HashMap<String, Object> props = new HashMap<String, Object>();
        props.put(ThirdPersonMouseLook.PROP_MAXROLLOUT, "10");
        props.put(ThirdPersonMouseLook.PROP_MINROLLOUT, "3");
        props.put(ThirdPersonMouseLook.PROP_MAXASCENT, ""+45 * FastMath.DEG_TO_RAD);
        props.put(ChaseCamera.PROP_INITIALSPHERECOORDS, new Vector3f(5, 0, 30 * FastMath.DEG_TO_RAD));
        props.put(ChaseCamera.PROP_DAMPINGK, "4");
        props.put(ChaseCamera.PROP_SPRINGK, "9");
        chaser = new ChaseCamera(cam, node, props);
        chaser.setMaxDistance(12);
        chaser.setMinDistance(2);
   }

   protected void update(float interpolation) {
        timer.update();
        interpolation = timer.getTimePerFrame();
       
        chaser.update(interpolation);
       
      if (KeyBindingManager.getKeyBindingManager().isValidCommand("exit")) {
         finished = true;
      }
   }
   
   protected void render(float interpolation) {
      display.getRenderer().clearBuffers();
      display.getRenderer().draw(scene);
   }
   
   protected void cleanup() {      
   }
}



What I get on my screen is a completely white die. I can spin it, so the chase cam seems to work, and I can see the beveled edges, so I know it's my model. But I can't see the pips because there are no shadows, and it's certainly not red and white like the Blender render shows.

So I guess I did one (or more) of three things wrong:

A) Didn't properly create the thing in Blender. Maybe I missed something about materials or textures.
B) I didn't properly load the object. There are a million things enable on every jME object. :)
C) I didn't properly light the scene. FlagRush looked great, but... see B, above. :)

I know it's a lot of code, but it should be rote for anybody who does jME a lot, and I'm sure I'm just missing something simple. If you'd like me to attach my die.3ds file, just let me know.

Thanks so much! You guys have been a great help. I picked up jME about a week ago and Blender yesterday, so I've been trying to cram a lot into my brain. You've been instrumental is getting it all to fit. :)

As a general suggestion, consider using SimpleGame when you're doing quick tests in isolation (eg. initially checking you can import a model). Reason being it is much quicker and easier to get up and running, and you know all the basic stuff has been set up. I find starting from BaseGame gives you more things to worry about which can cloud the problem you're working on.



On the problem at hand, it looks like you're missing an updateRenderState:



scene.updateRenderState();



after


scene.setRenderState(lightState);

Alric said:
As a general suggestion, consider using SimpleGame when you're doing quick tests in isolation (eg. initially checking you can import a model). Reason being it is much quicker and easier to get up and running, and you know all the basic stuff has been set up. I find starting from BaseGame gives you more things to worry about which can cloud the problem you're working on.


Yea, I originally had it as a SimpleGame, but I didn't have any examples of changing up the camera or adding light in a game of that type. FlagRush goes to BaseGame in only the second tutorial, so I just used that since the early FlagRush code is pretty simple unto itself.

Alric said:
On the problem at hand, it looks like you're missing an updateRenderState:


That did indeed seem to turn the light on! And now I can see the pips and red coloring... but the model looks severely wrong. I'll show a picture, since I'm guessing experienced people will recognize the 'mode of wrongness' in an instant. :)



Thanks for the quick response, Alric! I think I'll play around with putting a lightstate into a SimpleGame and see if that makes the code a little easier...
GorillaNinja said:
I think I'll play around with putting a lightstate into a SimpleGame and see if that makes the code a little easier...


Okay, so... it's trivial. :// New code, same display issue mentioned above:

public class SimpleModelTest extends SimpleGame {
   
   public static void main(String[] args) {
      SimpleModelTest app = new SimpleModelTest();
       app.setConfigShowMode(ConfigShowMode.ShowIfNoConfig);
       app.start();
   }
   
   protected void simpleInitGame() {
      MaxToJme md3tojme = new MaxToJme();
      Node node = null;
      ByteArrayOutputStream bytearrayoutputstream = new ByteArrayOutputStream();
      InputStream is = this.getClass().getResourceAsStream("die.3ds");

       try {
         md3tojme.convert(is, bytearrayoutputstream);
      } catch (IOException e) {
         System.out.println("ERROR: Can't convert model");
         e.printStackTrace();
         System.exit(1);
      }

       BinaryImporter binaryImporter = new BinaryImporter();
       ByteArrayInputStream in=new ByteArrayInputStream(bytearrayoutputstream.toByteArray());

       try {
         node = (Node)binaryImporter.load(in);
      } catch (IOException e) {
         System.out.println("ERROR: Can't load model");
         e.printStackTrace();
         System.exit(1);
      }
      
      node.setModelBound(new BoundingBox());
      node.updateModelBound();
      node.setRenderQueueMode(Renderer.QUEUE_OPAQUE);
      node.updateRenderState();
       
      rootNode.attachChild(node);
      
        DirectionalLight light = new DirectionalLight();
        light.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
        light.setAmbient(new ColorRGBA(0.5f, 0.5f, 0.5f, .5f));
        light.setDirection(new Vector3f(1,-1,0));
        light.setShadowCaster(true);
        light.setEnabled(true);

        // Attach the light to a lightState and the lightState to rootNode.
        LightState lightState = display.getRenderer().createLightState();
        lightState.setEnabled(true);
        lightState.setGlobalAmbient(new ColorRGBA(.2f, .2f, .2f, 1f));
        lightState.attach(light);
        rootNode.setRenderState(lightState);
        rootNode.updateRenderState();
   }
}

Strange that code loaded a 3ds model ok for me, but I tried the original code and got a similar problem to yours.

I don't use 3ds much but if you link the model I'll give it a try.

I think your normals are messed up; go back into Blender (I assume thats what you used), select the mesh (mesh mode) and press ctrl+n to recalculate the normals, then re-export and try again.



You can also set a cullState that has its faces set to None to test; although this would just fix the symptoms not the problem…

Okay, a couple updates.



First of all, basixs, recalculating the normals doesn’t seem to have changed anything. The 3ds file is a bit smaller, so something happened, but the die looks the same.



Second, I’m pretty sure there’s something off with my model. It’s 192KB. You know that giant bike that they use in Flag Rush? bike.3ds is only 112KB, and it’s far, far more complicated. Maybe Blender’s 3ds exporter has something to do with that, or maybe my model is just whack. I’m thinking the latter.



Third, that bike.3ds file loads fine and looks great in my little program. Obviously, the problem is with my model, not my code.



Feel free to tell me to go to a Blender forum if this is too off-topic now. :slight_smile: Here’s my normalized model file. (Sorry for the crazy hosting site, I just did a search and picked the first one. :slight_smile: )



Side note: what modeler / format do you use? I’d like to hear anybody’s response to that, actually. Is there one tool that seems to work better with jME? If so, I’d like to know about it! Thanks!

basixs LOVES his Blender (he thinks its the bees knees)  :smiley: :smiley:



(I spent almost 6 months back in the day working with Maya and 3DS max, and was much more efficient after only one month of learning blender…)

It's the specular on the light (your code threw an error, this works for me):


import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.jme.app.SimpleGame;
import com.jme.light.DirectionalLight;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Node;
import com.jme.util.export.binary.BinaryImporter;
import com.jmex.model.converters.MaxToJme;



public class Test3DSModel extends SimpleGame {

    private static final Logger logger = Logger.getLogger( Test3DSModel.class.getName() );

    public static void main( String[] args ) {

        Test3DSModel app = new Test3DSModel();
        app.setConfigShowMode( ConfigShowMode.AlwaysShow );
        app.start();
    }

    protected void simpleInitGame() {
        try{

            URL modelToLoad = Test3DSModel.class.getClassLoader().getResource(
                    "die2.3ds" );

            MaxToJme C1 = new MaxToJme();
            ByteArrayOutputStream BO = new ByteArrayOutputStream();
            C1.convert( new BufferedInputStream( modelToLoad.openStream() ), BO );
            Node model = (Node) BinaryImporter.getInstance().load( new ByteArrayInputStream( BO.toByteArray() ) );

            rootNode.attachChild( model );
        } catch( IOException e ){
            logger.log( Level.SEVERE, "Failed to load Max file", e );
        }

        lightState.setEnabled( false );

        DirectionalLight light = new DirectionalLight();
        light.setSpecular( new ColorRGBA( 0f, 0f, 0f, 1f ) );
        light.setDiffuse( new ColorRGBA( 0.6f, 0.6f, 0.6f, 1f ) );
        light.setAmbient( new ColorRGBA( 0.4f, 0.4f, 0.4f, 1f ) );
        light.setDirection( new Vector3f( 1, -1, 0 ) );
        light.setEnabled( true );

        // Attach the light to a lightState and the lightState to rootNode.
        lightState = display.getRenderer().createLightState();
        lightState.setEnabled( true );
        lightState.setGlobalAmbient( new ColorRGBA( .2f, .2f, .2f, 1f ) );
        lightState.attach( light );
        rootNode.setRenderState( lightState );

        rootNode.updateRenderState();
    }
}



(Also, I think the file change is from the white 'dots' missing...)

Obviously!



Heh, sorry, sometimes I just amuse myself. What's obvious is that my next step is to figure out what the heck that means and why it was creating such a problem. On the whole, I'm very shaky on lighting and materials; for example, from searching this wiki, it seems both materials and lights can have a specular color, and I have no idea what that means. Does anybody have any good links to a tutorial on this subject, either from a jME/coding perspective or anything I can generalize to this application?



Also, correct about the white dots; I must have mis-selected when I dumped the second 3ds file. My first export, though, die.3ds, works perfectly. What happens if I hit 'a' to select everything in object view and dump that? Will I get the light and camera object, as well, in the 3ds file?



And, from my 48 hours or so of experience, Blender is simply stunning.

When exporting in blender make sure you are in object mode, otherwise select all will just select all the verts in the current mesh…

Me again.



Okay, I've made a lot of progress on understanding coordinate systems; the stuff I put in my scene actual goes where I want and gets rotated how I want! This is a major step for me.



Still working on lighting and materials, but with your help I have something that works, so I'm leaving that for the moment.



My next problem to tackle is the camera. I decided that I want Z to be up and down, with X and Y being the 'ground plane'. However, with the way SimpleGame sets up the camera, it doesn't move around this setup very nicely at all. I'm going to take this opportunity to learn how the camera works and bend it to my will!



SimpleGame essentially sets up the camera like so:


cam.setFrustumPerspective( 45.0f, (float) display.getWidth() / (float) display.getHeight(), 1, 1000 );
cam.setParallelProjection( false );
cam.update();

Vector3f loc = new Vector3f( 0.0f, 0.0f, 25.0f );
Vector3f left = new Vector3f( -1.0f, 0.0f, 0.0f );
Vector3f up = new Vector3f( 0.0f, 1.0f, 0.0f );
Vector3f dir = new Vector3f( 0.0f, 0f, -1.0f );
cam.setFrame( loc, left, up, dir );
cam.update();
display.getRenderer().setCamera( cam );



Okay, so I'm trying to get my camera to react properly to my axis change. Here's what I did in my simpleInitGame:

Vector3f loc = new Vector3f( 0.0f, -25.0f, 0.0f );
Vector3f left = new Vector3f( -1.0f, 0.0f, 0.0f );
Vector3f up = new Vector3f( 0.0f, 0.0f, 1.0f );
Vector3f dir = new Vector3f( 0.0f, 1.0f, 0.0f );
cam.setFrame( loc, left, up, dir );
cam.update();



When the scene displays, we start out okay. It's in the right place, and looking the right direction. When I pan my mouse up and down, it does what it should; the 's' and 'w' keys do what they should. But instead of 'q' and 'z' lifting me up or down along the Z axis, they do the same thing as 'w' and 's'. And when I pan my mouse left and right, the whole scene rotates in front of me, instead of the camera turning left or right.

Any hints? Thanks!

if your using the first person handler (input in SimpleGame), then this should work for the keyboard:

((FirstPersonHandler) input).getKeyboardLookHandler().setUpAxis( Vector3f.UNIT_Z.clone() );




And you may have to change the lock axis for the mouse:

((FirstPersonHandler) input).getMouseLookHandler().setLockAxis( Vector3f.UNIT_Z.clone() );



Out of curiosity why do you want to have the z-xis be the up/down; familiarity?

Basixs, those lines worked just how I'd like. That'll help a ton when I start learning more how to get the camera to behave exactly how I want, thanks.


basixs said:
Out of curiosity why do you want to have the z-xis be the up/down; familiarity?


Mainly that, yea. I like my X and Y plane like they always have been, and Z to be the new on that makes it 3D. Just makes sense to me. Also, in Blender Z seems to be the 'up'; that might totally just be because that's how I see it, but when I import things, that's how it turns out. :)

Basixs, I'm having some issues with getting materials and textures out of Blender and into jME. I know you linked me a tutorial, but I tried it with my own shape and texture and it's not working for me. I just get gray shapes no matter what I try. I'll be going through the wiki pages again tomorrow, but any words of wisdom or experience that might help it sink in for me?

This is essentially the last step for this phase of my learning; once I can make a model, texture it, and put it into jME, all my procedural generation falls into place and I have something much greater than the sum of the pieces. Thanks for any advice.