jPCT is a 3D engine for JAVA. The basic idea behind jPCT is to provide a small, fast and easy to use API for rendering 3D graphics in applets and applications. jPCT is targeted, but not limited to games development.
jPCT supports software rendering using its own software renderer and OpenGL using the LWJGL.
jPCT can be found here: http://www.jpct.net
jPCT runs on any JAVA virtual machine that supports JAVA 1.1 or higher (this includes the Microsoft VM as well as the VM that comes with Netscape 4.x). However, jPCT makes use of advanced features of newer JAVA versions like 1.2 (support for BufferedImages) and 1.4 (support for hardware acceleration via OpenGL/LWJGL).
3D graphics require quite a lot of processing power, so jPCT is a processor demanding API. How fast the processor has to be to be "fast enough" for jPCT is impossible to say, because it depends on what you are doing with the API and if software or hardware rendering should be used (or both). As a rule of thumb, a 500MHz cpu should be fast enough to run simpler applications while the fun starts at around 1GHz...faster is always better of course...:-)
If OpenGL should be used, a graphics card that supports OpenGL in hardware is required.
|
jPCT offers you all the features you need to write a cool looking 3D game or application in Java in a short time. There is no need for an extra library for collision detection or a seperate GUI package to replace Swing/AWT. Engine features Features of the hardware renderer (Java 1.4 or higher required) Features of the software renderer (Java 1.1 or higher required) |
This chapter should illustrate the basic structure of jPCT. This isn't meant to be an object model, just a simplified overview. Don't pay attention to the fact that Object3D is always a rectangle while all other entities are drawn as ellipsoids...that doesn't mean anything (except that i should think about such things before i draw them the next time...:-)
We'll start to show what a "World" in jPCT is. The World is the most important class in jPCT (though not the most powerfull), because without an instance of World, you simply can't do anything usefull. A World is where the scene is stored.

Have a look at Fig.1: As you can see, the World contains Object3Ds. These Object3Ds (or short: objects) are what will be rendered. After creating an instance of World, this instance is empty...it doesn't contain any user defined objects (it does contain two internal objects, but that's not important in this context)
Every object that should be rendered has to be added to an (and only one) instance of World. In Fig.1, the four objects inside the World-oval are such objects. These four objects will be processed the next time this World will be rendered. To do so, some more information is required. First, we have to know how we are looking at these objects. This is what the Camera tells us. Every World has a Camera attached to it. If it hasn't been modified, it's placed in the origin (0,0,0) looking down the positive z-axis (See Fig1.1 to learn more about the coordinate system jPCT is using. It may be a bit unsual that Y is going down, but really...it doesn't matter...:-)).

Second, we need some lights. Every World has a number of lights attached to it. This is done automatically, so you don't have to touch the Lights directly. They are added to and removed from the scene via the World.
In addition to the Camera and the Lights, every World may have some Portals. If it doesn't, the instance of Portals is empty but still there. Don't bother with Portals right now...
In Fig.1, there are two additional things that may need explanation: The dotted lines between some objects and the object outside of the World. The dotted lines are child/parent-relationships between objects. An object can be a child of another object, which means that you can build hierarchies among them. A child inherits all transformations from its parent. The object "outside" the World is a parent of the object "inside" the World, i.e. it won't be rendered but it may "lend" its transformations to its child. While this is possible with every kind of object, it's most usefull to be done using so called dummy-objects. A dummy-object is an empty object that does only serve as a container for transformations. In Fig.1, the object outside the World is such a dummy-object.
Now that we know what a World basically is and that we need Object3Ds to fill it with life, we'll have a closer look at...

Again, the dotted lines stand for things an object may have while the other lines are standing for things an object has to have. As you can see, an object has at least one Texture, some Vectors and a Matrix. The Vectors are not so interesting from a developer's point of view, because they are almost totally hidden from the user of the API. They contain object specific (in Vectors itself) and shared geometry data (in Mesh)...more about that later.
Back to the Texture: A texture has to belong to the one and only instance of the TextureManager (a singleton) there is (per VM). If a new object has been loaded/generated/constructed, its texture is a bright white dummy texture. This is important because it shows that jPCT always does texture mapping. It's impossible not to. Because a texture that's all white isn't very appealing, every texture known to the TextureManager may to assigned to an object. An object always has a "normal" texture, an optional bumpmap and an optional basemap. These optional textures are only of interest if bumpmapping will be used, so they won't be covered any further here.
The Matrix stands for the transformations of the object (rotation, translation, scaling), so even if Fig.2 shows it like that, it doesn't mean that an object has only one Matrix but a couple of them. Some of these matrices can be modified from outside of jPCT but that's another topic and therefore not covered here.
There are a few other optional things an object might have: As mentioned in the World-section, an object may have child and/or parent objects. This is shown in Fig.2 by the dotted line to the other Object3D. It may have an Animation assigned to it. An Animation in this context is a keyframe animation, not an animation achieved by transforming the object or its geometry data itself. And finally, it may have an OcTree. This is usefull for objects that are quite large like landscapes...we'll cover that later.
So basically, an Object3D is a textured bunch of geometry data with additional transformation information and therefore, we'll continue with these three things, starting with the geometry:
What is Vectors? First of all, it's a stupid name. Despite of this, it's a container for all the object specific geometry data like transformed vertices and normals (both not very interesting in this case) and texture coords. Basically, you don't have to care about the Vectors. jPCT does this for you. That's why this class is no longer public since jPCT 0.97. What is more interesting is the Mesh. Have a look at Fig.3 to see why:

The Mesh contains the raw geometry data of an object and multiple objects can share the same instance of Mesh (via their different instances of Vectors). This is important, because it means that manipulating a Mesh will change the look of every object that uses this Mesh. The Mesh can be manipulated in two ways and Fig.3 shows them: Using an implementation of IVertexController and using an Animation. Last things first: The Animation. An Animation in jPCT is a keyframe animation, i.e. it is defined by a load of keyframes. These keyframes are in fact Meshes, but usually not Meshes that belong to an object. But an Animation has to be assigned to an object to make any sense and therefore the outcome of the Animation is stored in the object's Mesh instance (this is what the arrow from Animation to Mesh tries to visualize). This allows to animate a lot of objects at a time by applying the animation to just one of them as long as they share the same Mesh.
The second way to manipulate a Mesh is a more direct one and requires an in-deep knowledge of the Mesh, so i'll only mention this possiblity here and go ahead: With an implementation of the IVertexController-interface, it's possible to change the vertices directly in object-space. This is required for some effects like a flag fluttering in the wind. On to the Textures:
As mentioned, the TextureManager is a singleton, i.e. only one instance may exist in a VM. The TextureManager is basically a container for Textures. You can't assign a texture to an object unless it's known to the TextureManager. As usual, here's a Fig.:

Some words about textures: They have to be 24bit deep and their width and height should be a power of 2. If width and/or height are not a power of 2, jPCT will do a resize of the texture. The resize is done in a very rough and naive way, so you might be better off doing this yourself in your favourite image manipulation software.
Something is still missing and that's the ITextureEffect-interface. An implementation of this interface can be assigned to a texture to manipulate the texture's pixel data during runtime. That's helpful to do some texture effects (hence the name...:-)). But this is a more advanced topic, so i'll leave it out for now.
It's of course possible to assign the same texture to different objects. That's why the TextureManager is there in the first place (and to handle the OpenGL conversion stuff...).
No Fig. for Matrix, because there's nothing to visualize. A Matrix is a 4x4 matrix that can be used for all the stuff matrices are good for. jPCT uses them as rotation and translation matrices, so if you are going to manipulate these matrices directly, the methods in Matrix are your friends.
To complete this chapter: Matrices in jPCT are row major.
Now that we've covered what's required to render a scene, the remaining question is: Where do we actually draw the rendered stuff into? The answer is simple: into a FrameBuffer. jPCT distinguishes between rendering (that's what has been described in 2.1 to 2.5) and scan-converting the rendered scene. Scan-conversion describes the process of drawing the actual scene into the FrameBuffer. Before we are going into details, here's the Fig.:

The fact that jPCT supports software and hardware rendering (and that the hardware rendering has been added later to the engine) makes the FrameBuffer act a little bit unintuitive at first glance. However, it's actually not that hard. If you are doing nothing but getting yourself a new FrameBuffer instance in the resolution and using the settings you want, you don't have to care about the internals. Problem is that for the sake of compatibility with older versions of jPCT, this FrameBuffer is a software one in legacy mode. Everything else has to be changed after the creation of the buffer.
The ISomeImageBuffer is usually a no-brainer. Java2 will use a BufferedImage as backbuffer while Java1.1 will use a MemoryImageSource. You don't have to care about it, it happens automatically for you.
The IRenderer implementation the FrameBuffer will use is a bit more tricky. As mentioned, the FrameBuffer always starts of as a software/legacy buffer (i.e. using the LegacyRenderer). Both, software and hardware renderers, support two modes: legacy and OpenGL. OpenGL means lighting in the way OpenGL does it (i.e. multiplicative). Legacy means lighting in the way older jPCT versions are doing it (i.e. additive). This lighting mode is not really supported by OpenGL hardware...the legacy mode of the GLRenderer is an ugly hack and should be avoided.
If you want to change the renderer a FrameBuffer is using, you may add and remove Renderers to/from it. Usually, you'll remove the software and add the OpenGL renderer in case you want to use OpenGL. It's also possible to add OpenGL without removing software (or vice versa). This means that jPCT will draw the scene in both ways, which is the slowest and most useless combination there is...:-). If the renderer is OpenGL only, some additional optimizations will be used. However, it's not possible to add more than one renderer of a kind (software/hardware). If you try to do so, the existing renderer of that kind will be disposed.
To summarize this: The FrameBuffer is the one who allows you to switch between software and hardware rendering. Have a look at the following picture. It shows how software and hardware renderer compare:

Now we are able to render and scan-convert a scene...but how do we change jPCT's configuration? Well, there's a class called Config and it has a lot of public members that can be modified to change jPCT's behaviour. jPCT uses this approach because it's fast and small...it may not be very OO-ish, but who cares? I for sure don't...:-)
It's impossible to mention every possible setting here. You just have to know that there are settings that need to be set before World/FrameBuffer are being instantiated to show an effect and some can be changed at runtime and will show an immediate effect.
But don't worry, this is mentioned in the Javadoc of the settings.
jPCT supports the 3DS-format used by the popular 3D-Studio. jPCT only uses the polygonal models from these files, all other information is ignored. The loader will return an array of Object3D that contains all polygonal models from the file. Texture coordinates including texture tiling is supported (tiling only by the OpenGL and the SoftGL-renderer. The legacy renderer can't do it and will clamp the coordinates to [0...1]).
3DS-files usually use the ".3ds"-extension (not the *.max extension...that's another, more recent format from 3DS that isn't supported).
MD2 is the model (not level!) format of Quake II by ID-Software. MD2 supports keyframe-animations and so this format is the easiest way to use animations in jPCT. jPCT ignores the texture name of the model, so you have to load
and assign an appropiate texture yourself.
MD2-files usually use the ".md2"-extension.
ASC is an older but still popular (and simple) ASCII-format from 3D-Studio. ASC-files are quite big but because the
format is easy to save/load and understand, a lot of converters are supporting it very well. Texture coordinates are supported.
ASC-files usually use the ".asc"-extension. Note that Netscape4's VM has some trouble loading ASC-files with this extension because it thinks of them as TXT-files and screws them up somehow because of this.
This format is almost unknown. It's a very simple ASCII-based format that has been used by the (also unknown, i assume) JAW-3D-engine. There's nothing special about this format. jPCT still supports it, because it's very very easy, so one may use it as a simple exchange format (that, and i still own some models in this format...:-)).
JAW-files usually use the ".jaw"-extension.
This is jPCT's proprietary XML-based format. It allows you to load an (almost) entire scene from a single XML-file. Do we need another proprietary format, you may ask...i think we do! The reason why is, that this format supports portals which none of the others does and that it's very readable and easy to understand (like XML always is...:-)). It's a "flat" format, i.e. no fancy vertex-indexing is used. This may increase file size, but allows for an easier processing of the XML via XSLT (for example). XML files are quite big, because they include a lot of meta-data. On the other hand, they can be compressed very well, so put them in a JAR and size shouldn't be an issue anymore.
XML-files usually use the ".xml"-extension.
XML files need to match this DTD to be valid (for jPCT): jpct.dtd (You'll most likely have to open this DTD in a seperate editor).
"Premature optimization is the root of all evil"...had to say that...and now on to where the fun is: Optimizing things...:-) Seriously, this chapter is in here to cover some aspects that are documented in the Javadocs as well, but which could be a bit hard to figure out if you don't know where to look exactly.
So this is basically a chapter about how to improve the performance of your application using the means jPCT offers. Actually, there are two different kinds of optimizations when using jPCT: The ones that are applied automatically and the ones that have to be activated/tweaked in one way or another.
Being fillrate limited means, that your application's performance highly depends on the speed with which the renderer can plot the pixels onto the screen. If you are fillrate limited when using the OpenGL-renderer depends on the underlying hardware. If you have a fast graphics card, you'll most likely not be that much limited by its fillrate as if you own a rather slow card. When using the software renderer, you are fillrate limited in almost every case. So what can you do to speed up things?
Well, if your renderer is not fast enough to plot all the pixels in a reasonable time, just try to draw less pixels, i.e. try to reduce the resolution of the framebuffer. While this works very well most of the time, it's not always what you want to do. Other options are:
jPCT comes with its own T&L implementation in pure Java. Although it's quite optimized, it can become a bottleneck for larger, more complex scenes (i.e. scenes with a high polygon count). Here are some options to consider in this case:
Apart from the two well known potential bottlenecks T&L and fillrate, there are some others. The first one is your own code. Whatever you are doing in your own code may become a bottleneck, but the good thing (for me) is, that it's your task to find and fix it. :-)
Back to jPCT: There is blitting and there is collision detection. Both can cause performance problems if not used right. Blitting is most critical if the OpenGL renderer is in use, because it may cause texture-uploads to the card which are quite slow. If blitting should be used together with OpenGL, it's highly recommended to use the blit(Texture...)-method in favour of the blit(int[]...)-method. If this isn't an option, have a look at Config.glUseIgnorantBlits...maybe it's possible to use that to speed up things.
Collision detection can be tweaked in many ways. Adjusting Config.collideOffset to an appropiate value is one of them. You'll most likely have to play around with this setting when implementing collision detection. In addition, you can tell jPCT to use an optimization which will automatically decrease this value on a per object base if this is possible. However, this optimization is off by default (Object3D.setCollisionOptimization(boolean)).
If you are using OcTrees (and if you don't maybe this is the time to do so...), you can make jPCT to use them for collision detection too (OcTree.setCollisionUse(boolean)). This option is also off by default (just because the outcome of the collision detection can be slightly different in some rare cases...to adjust this, play around with OcTree.setRadiusMultiplier(float)).
Another, not so obvious, optimization is to tell jPCT which objects are static or at least static during the collision detection (objects that are moving seldom or not at all). This can be done by using Object3D.enableLazyTransformations()...but be carefull with this option as it can result in a screwed up rendering if used on the wrong objects.
jPCT does a lot of internal work to optimize the rendering process. Almost every optimization can be turned off somehow, but you shouldn't do that until you are absolutely sure that this optimization is either buggy (in that case, please let me know) or doesn't do anything good in your special case. If you are disabling them with no need, you may turn jPCT's rendering pipeline into a brute-force polygon pusher, which isn't what you want (trust me...).
So leave things enabled that are enabled by default until you have a very good reason not to!
Collision detection is very important for most projects. A 3D engine without collision detection is almost useless. That's why there are currently 3 (and a half) approaches implemented in jPCT. This section will give you short introduction so that you can pick the appropiate method for your particular
problem.
All kinds of collision detection can be applied to the camera and to objects or, if that's not enough, you can define a position in worldspace to be used for this.
This is easy to understand but not very powerfull. It basically casts a ray from the camera/object center/position in worldspace (short: entity) into the entity's direction and tests if this ray intersects a polygon on its way and if this intersection lies within the lenght of the requested translation. If it does, we have a collision between the entity and the polygon.
This algorithm is more a kind of picking than collision detection. It's usefull for things like laser beams or very small, moving objects like bullets. It's definitly not very usefull to implement collision detection for the camera, albeit the method to do so is still in jPCT.
Here's a Fig.:

Sphere-polygon collision detection is bit slower but more powerfull than ray-polygon. The idea is to give the collider (i.e. the player or an object) a size, so that it's no longer an infinitly small point in space like it was in ray-polygon. Of course, putting the player into a sphere is a simplyfication too. In the real world, no one moves "protected" by a sphere around him. But it's a quite good approximation in some cases.
Sphere-polygon collision detection can be implemented in various flavors. The one used in jPCT works like this: The algorithm tries to place the sphere at the end of its requested translation, i.e. if you want to move your camera (0,0,10) in space, the sphere will be placed at (cx, cy, cz+10). Then, the algorithm checks if the sphere intersects with any polygon at this position and if it does, it's pushed back into the direction of the polygons normal vector until no intersection (with this polygon) takes place anymore. This is done for every polygon in question and finally, the sphere will end up in a position where (hopefully) not a single polygon intersects with it anymore.
Sounds too good to be true? Well, it isn't that good in some cases. At first, this algorithm is not using recursion, i.e. a once checked polygon will never be checked again in this run. This may cause problems, because the sphere could be pressed back into that polygon by a collision with another one and we won't notice that. That said, another problem comes to mind: Order (not size...:-)) matters! That means that the sphere may end up in different positions depending on which polygon it will be tested against first. Fig.8: Tries to illustrate the problem:

Please note that this is a problem with this specific implementation, not with the idea of a sphere based collision detection in general (we'll see this later...). So depending on the order in this example, you could either be pressed up the step or stuck in front of it...you simply don't know it before trying.
This is for sure a problem when moving the camera, but it's not necessarily for objects (depending on their purpose). What makes this approach appealing anyway is, that it can adjust the sphere's position automatically if it finds itself in a collision situation which hasn't been caused by the sphere's movement itself. The sphere doesn't even have to move to do this. Imagine an object hanging around in your level doing nothing. Now another, much bigger object, collides with it. This object's movement shouldn't be affected by this collision. It should rather press the smaller one out of its way. You may check the bigger object for a collision, but what would you do then? Adjust the smaller object based on the result from the bigger one? That's possible for sure, but it's much easier than that: You just have to do a sphere-polygon collision detection on the smaller object without moving it at all. The algorithm will detect that the object is already part of the collision and will try to move it out of it.
The sphere-polygon collision detection in jPCT can be seen a kind of collision response algorithm while ray-polygon and the one in the next chapter are more like collision avoidance algorithms. This difference doesn't always matter, but it may...
So you are running around in your level using the famous sphere-polygon collision detection and finally, you get stuck in front of a stair. Ouch! And as if this weren't enough, you are not very happy with the fact that you have to represent your virtual alter ego with a sphere too. You are actually not that small (or big...depending on the view of things...:-)).
Want a solution? Here is one: Ellipsoid-polygon collision detection. An ellipsoid is much more suitable to represent humans, animals, spaceships etc. than a sphere is. Fine, so you'll get stuck with an ellipsoid in front of that stair? No, you don't! jPCT uses a swept algorithm, i.e. it will detect the first collision of the ellipsoid on its way from start to end and it will adjust the movement direction accordingly. This means that it tries to avoid collisions where the sphere approach tries to handle them after they occured. In addition, the collision detection will call itself after detecting the first collision to test if there are other collisions on the now adjusted way of the ellipsoid.
Long story short, the result will be an ellipsoid that climbs stairs, rocks or other small obstacles and that will still be blocked by larger objects...just like you would expect it from the "real world" (well, at least similar to this).
If you want to read more about this topic, i suggest to visit Kasper Fauerby's website. He offers a nice white-paper about this topic for download.
However, this approach may be the most realistic one when it comes to camera movement, but it's also the slowest one. Under normal conditions (i.e. not using it for dozens if objects at a time), this shouldn't be an issue though.
This section covers some advanced possibilities the engine offers. You may not need them during your first steps. In fact, you may never need them. But if you do, here they are.
jPCT supports OpenGL in two flavours: Integrated into Swing/AWT and using a native window of its own. Both renderers support multi texturing, i.e. the possibility to apply more then one texture (and maybe with different texture coordinates) per polygon. The software renderes can't do that, they will ignore the additional texture layers and simply render the first layer.
To apply multiple textures to a polygon, jPCT uses the TextureInfo-class. Each method that sets a texture (addTriangle(...), setTexture(...), etc) exists in a TextureInfo-variant. Unlike in similar engines, you can combine layers freely on a single Object3D in jPCT, i.e. one polygon may have one layer, the next one two layers using blending, the next one six layers using addition. If you are using more layers than the hardware natively supports, jPCT will fall back to multi pass rendering, i.e. it reverts to one texture stage which will render the polygon multiple times with different blending modes applied. Apart from some special cases like transparency and fogging, this should remain unnoticed (but may cause a performance drop due to the higher demand for fillrate).
Using the PolygonManager (can be obtained from any Object3D by calling getPolygonManager()), you may even change a polygon's texturing information on the fly for special effects.

This is another OpenGL only option. By default, you don't have to care about transparency and transluceny. You can set a transparency level for an object (0...x), whose outcome can be fine tuned by using Config.glTransparencyXXX. Any black (or almost black) part of the texture/object will be transparent (alpha value is 0), all other texels will be translucent according to the setting (alpha value is 255). The alpha channel that is needed for this is internally created by jPCT. However, as simple as this is, it's not always sufficient. Therefore, you may add an alpha channel to a texture. This can be done by using the appropriate contructors in Texture and a texture with alpha information (PNG supports alpha channels for example). Such a texture (when used on a transparent object) will provide you with a finer grained way to specify what is tranlucent and how much. You may even use such a texture in combination with 2D blitting (Framebuffer.blit(....)).

jPCT is not thread safe, i.e. it's not advised to manipulate Object3Ds, Worlds...whatever from inside another thread than the rendering/main loop thread. Sometimes, the fact that you are actually in another thread may be hidden. For example, every mouse or GUI event happens in the awt event thread. If this is not where your actual application logic/rendering happens (and it most likely isn't), it's advised to queue such events (or the operations they are triggering) and execute them in the main loop of your game/application.
However, jPCT itself can benefit from multi cpu/core setups when using either the AWTGLRenderer instead of the "normal" OpenGL renderer or when using the "normal" one with Config.useMultipleThreads=true. In case of the AWTGLRenderer, there is no other choice for JPCT than to work with multiple threads under the hood. In case of Config.useMultipleThreads=true, you should evaluate (or benchmark) if it helps or hurts your application on the underlying hardware. It may increase performance up to 80% but it may also hurt performance, especially on single core setups.
For simpler applications, working with one World may be sufficient, but for more complex ones, the need for multiple instances of World may arise. There are a few things to consider when doing this. First, an Object3D can only belong to one instance of World. If you want to render the "same" Objects in different Worlds, they have to be different Objects (but which can share the same Mesh). Second, each World has its own instance of VisList by default. This isn't always needed, so you may set Config.shareVisibilityList=true to save some memory. You may also have a look at World.decoupleVisibilityList(), which covers a special case when using multiple threads.

By default, jPCT does collision detection with the mesh of an Object3D. I.e. if object X is a potential collider, collision detection and response happens on this object based on it's mesh data. While this is easy to use, it may decrease performance if the mesh is quite complex. If you run into such problems (or just want to unify the collision shape of your entities for gameplay reasons), you may consider to use a collision mesh instead. A collision mesh is not supported by jPCT directly but can easily added when extending Object3D (or in another way if that isn't an option). A collision mesh is only visible while performing the collision detection and invisible afterwards (you have to ensure this yourself, jPCT doesn't do this for you). So instead of checking for collision with the actual entity's mesh, you are checking for collision with it's collision mesh.

This short introduction left out a few classes like SimpleVector, Interact2D, Logger... That's because it isn't meant to be a complete how-to and an answer to all questions. Its purpose is to provide a basic overview about how jPCT's general design looks like. Furthermore, it skips a detailed description of the rendering pipeline. You don't have to know that but it could be interesting in one case or another (to understand why things happen the way they do sometimes).
Anyway, all these topics may follow in a later chapter...