Hello World

From JPCT
Jump to: navigation, search

Hello World

Welcome to the Hello World tutorial. It's based on the HelloWorld-sources that you can find in the examples-directory of the distribution. Our goal here is to create a textured box spinning around in a window.

We'll start with the first jPCT related line in the source code, the import:

import com.threed.jpct.*;

This imports all basic classes of jPCT. There are a few more sub-packages like util and procs, but for this example, we don't need them.

Now we'll play god and create a world:

world = new World();
world.setAmbientLight(0, 255, 0);

These two lines are creating a new instance of World and set the ambient light for this world to green. A World in jPCT is the container, that accumulates everything that is needed to describe a 3D scene, like light sources, the 3D objects and the camera. Without an instance of World, you can't do much in jPCT.

We'll leave our world alone for a while now and load a texture instead. Loading a texture to texture an object requires two steps in jPCT, which are both combined in this line:

TextureManager.getInstance().addTexture("box", new Texture("box.jpg"));

At first, we load create a new texture by simply creating a new instance of Texture. We create it from a file, "box.jpg" in this case. You can also create textures out of images, inputstreams or just create a single colored texture out of thin air.

The texture alone can't be assigned to our object (we'll create that later). We have to add it to the TextureManager first. The TextureManager is a singleton, i.e. it exists only once per VM/classloader and can't be instantiated from outside. To obtain it, there's a getInstance()-method in TextureManager like shown above. To add our new texture, we are using the addTexture()-method. To identify the texture later, we give it a unique name. In this case, that's simply "box".

We now have a world and a texture, it's time to create an object. An object in jPCT is an instance of Object3D. You can either load one, create one yourself triangle by triangle or pick one from a set of predefined primitives. We choose the latter option for this simple application:

box = Primitives.getBox(13f, 2f);
box.setTexture("box");
box.setEnvmapped(Object3D.ENVMAP_ENABLED);

getBox() has two parameters, scale and scaleHeight. Please refer to the java docs for a more detailed explanation. We are using 13 and 2 here simply because it gives us the box that we want. Now the box needs a texture. We have already added one to the TextureManager and because the manager is a singleton, we just have to give the object the name of the texture in the manager, which is "box". This is not all. Most primitives aren't created with proper texture coordinates, so we won't see our texture. To keep things simple, we let jPCT create coordinates on the fly by using environment mapping. This is enabled in the third line of that code snippet.

The next step is pretty important, because it's something that has to be done to all object in jPCT: We have to build it, if we are ready to render it. Building an object does some internal work on the object like creating normals, calculating a bounding box, calculating a rotation pivot etc... Doing so, is pretty straight forward:

box.build();

There's also an option in the Config-class to turn on automatic building of object, but i prefer it this way. Otherwise, building the objects on demand may cause stuttering.

Are we done now? No, not yet. We now have the world and the object, they just don't known from each other. But that's pretty easy, we simply make the world know our object by doing:

world.addObject(box);

Please note that an object can only belong to one instance of World. If you want to use the same object in more than one World instance, you have to create a clone of it.

However, we are ready to render our object now...almost. Because we haven't done anything to move the object or ourselves around in the world, we both occupy the same space, i.e. we are located inside the object. This is perfectly fine from a technical point of view but it just doesn't look good. So we move the camera now. We don't have to create a camera ourselves, the world already brings one with it:

world.getCamera().setPosition(50, -50, -5);
world.getCamera().lookAt(box.getTransformedCenter());

This code moves the camera 50 units to the right, 50 units up and 5 units out of the screen. This also shows another fact: In jPCT, the x-axis goes to the right, the positive z-axis goes INTO the screen and the negative y-axis goes UP. This isn't a very common setup, but it really doesn't matter...it's just something you have to know. To make our camera look at the object, we use the camera's lookAt-method.

Now we are ready to render that thing...

...the question is into what!? We need something like a frame buffer to render into. The class FrameBuffer will provide one. In this application, we are going to use the hardware renderer for this. Here's how:

buffer = new FrameBuffer(800, 600, FrameBuffer.SAMPLINGMODE_NORMAL);
buffer.disableRenderer(IRenderer.RENDERER_SOFTWARE);
buffer.enableRenderer(IRenderer.RENDERER_OPENGL);

800*600 is the size of the frame buffer, we aren't using anti-aliasing, hence the sampling mode is normal. In the second line, we disable the software renderer, which is jPCT's default renderer that is active when creating a FrameBuffer. Now, we enable the OpenGL based hardware renderer in the third line. The hardware renderer will open a native OpenGL window when enabled, which is why we don't need a Frame, JFrame or some other component to render into in this case.

We are using active rendering in this example, which means that we have something that's called "render loop" or "game loop". it looks like this:

while (!org.lwjgl.opengl.Display.isCloseRequested()) {
        box.rotateY(0.01f);
	buffer.clear(java.awt.Color.BLUE);
	world.renderScene(buffer);
	world.draw(buffer);
	buffer.update();
	buffer.displayGLOnly();
	Thread.sleep(10);
}

This is almost the most basic render loop there is. The condition in the while-clause uses LWJGL directly to determine if the native OpenGL windows has received a close request. Other possibilities are to wait for a specific key to be pressed, for some time to pass or whatever... The first line in the loop simply rotates the box, so that there is some action going on. Then comes the basic command sequence in a typical render loop:

  • clear the frame buffer
  • process the geometry
  • draw the processed geometry
  • call update on the frame buffer...this is mandatory before calling display
  • display the rendered image

In the last line, we sleep for 10ms so that the box doesn't rotate too fast and doesn't use too much cpu power. In a game or any other application that needs maximum performance, you'll most likely not do this. However, a Thread.yield() at that point is always nice.

That's it...almost. If the loop exits, we quit our application this way:

buffer.disableRenderer(IRenderer.RENDERER_OPENGL);
buffer.dispose();
System.exit(0);

On some systems, calling dispose() on the frame buffer causes the VM to crash for an unknown reason. If this is the case, omit it and simply do a System.exit(0);...terminating the task will free the resources anyway.