Release candidate 2 has been published, and contains several fixes and improvements over RC1. Many thanks go out to the community for reporting the issues.
As always, see the changelog for details.
You can install the pre-release version with: gem install jax –pre or by adding gem ‘jax’, ’3.0.0.rc2′ to your Gemfile.
Be sure to run jax install in your project root!
]]>What does this mean, exactly? It means that the latest version of Jax has progressed far enough along that the developers think it’s ready to go. Now here’s a chance for the users to agree. Download it, run it, see if you can make it break. Raise issues if you encounter any, but hopefully you won’t.
If the release candidate goes over smoothly, I’ll be able to move it to Release status — a big win indeed!
If not, I’ll address the major issues that are uncovered during the Release Candidate phase, and then publish RC2.
Bear in mind that this release candidate is for a major version change, so backward compatibility is not guaranteed.
I’m going to save the post where I ramble on about the list of wonderful new features for the actual release; for now, to see what’s changed, take a look at the Changelog.
The documentation is still a work-in-progress. For now, if you have questions, you are encouraged to post them to the forum, where they will be promptly reviewed and responded to.
]]>This little project started out with me wanting to render wireframes (and support doing so in the framework) for debugging purposes. It rapidly evolved into a brand-new material that, as you will see, can serve a variety of purposes even in production applications!
There are two common ways to render wireframes in WebGL, and both of them are kind of a cheat. First, you can just change the draw mode from GL_TRIANGLES to GL_LINE_STRIP; this gives a close approximation of what the wireframe should be, but doesn’t look that great because triangles (and even triangle strips) just don’t translate that cleanly into lines. You end up with extra lines or missing lines all over the place. The other method is to actually rebuild the mesh to use bona-fide lines (or line strips) instead of triangles. Both of these are too heavy-handed, in my opinion, and the results are usually not that great — either it runs or initializes too slowly, or it doesn’t look very good, or both. Plus, it usually requires some added up-front work by the developer, which in Jax is against the rules.
In standard desktop OpenGL, rendering a wireframe is a simple process as far as the developer is concerned. One line of code changes the polygon mode from GL_FRONT_AND_BACK (or whatever) to GL_LINE, and you’re done. Unfortunately, this doesn’t translate so well to WebGL, where you can’t control the polygon mode.
This led me into the world of shader-based wireframes. The problem with doing wireframes in the shader is that you only ever have information about a single vertex at a time — and with information about only a single vertex, you can’t hope to draw a line between two or more of them.
Apparently, one of the most common techniques for drawing “correct” wireframes has been to draw objects in two passes, using polygon offsetting to shift one version forward just a tiny bit. This is not only slow (obviously, twice the renders takes twice the time), but it can also produce visual artifacts that don’t quite look right. So instead, I implemented a single-pass algorithm as discussed in this paper.
As you can see, the result is quite easy on the eyes — much more so than the old OpenGL polygon mode would have been, I have to admit!
What’s nice about Wire is that it is a material, and as such gains all of the benefits and perks of being one. The Wire layer can be used on its own, or merged into a series of other layers to produce a very nice combination of them all. As a result, the Wire material can be textured; it can receive ambient, diffuse and specular shading from local light sources; it can cast and receive shadows; and it can do basically anything you’d expect any other material to be able to do.
In short, Jax.Material.Wire is now a first-class material that can be used for much more than just debugging!
This is as far as I’m going to take the Wire material for Jax v3. However, I’m not done with wires in general. In a future release of Jax, I’d like to see Wire become even more versatile, allowing you to style the lines themselves: for example, it’d be neat to support tapering lines so that they are thicker at intersections and thinner in the middle, or becoming thinner as they get farther away from the camera to give a better sense of depth. There’s a lot that can be done with Wire, and we’re just getting warmed up!
]]>One of the most fundamental areas of Jax is the matrix stack. The matrix stack is very helpful because it provides a way to save and restore state throughout the rendering process, without having to rebuild the various matrices at every stage.
Today I made a fairly minor change to how the matrix stack manages its matrices. It was a small change that led to a big difference in performance!
When it was originally coded, the matrix stack would just recalculate the ModelView matrix whenever either the Model or View matrices were updated, and that was acceptable because the ModelView is central to the rendering process; but the stack evolved over time in order to support more complicated rendering tricks like normal mapping, shadow mapping, and so on. Eventually, the stack was maintaining numerous matrices (about a dozen of them!) that needed to be recalculated whenever some other matrix that they depended upon changed.
The change, then, was to simply not update some matrices whenever others changed. Instead, when the key matrices change, the matrix stack will simply flag its siblings as “tainted”, and recalculate them lazily — that is, only recalculate matrices when they are actually being used. What a concept!
I couldn’t resist doing some benchmarks after making this change, even though it was pretty much a given that performance would be better. Of course, the results are subjective: if, for example, you have an application which makes use of every matrix that the matrix stack provides, then you’re not going to see any improvement whatsoever; if, however, you have an application that only uses a few matrices, then you’re going to see a massive improvement. Most apps are somewhere in between these two extremes.
So, for my benchmark, I took a very common scenario:
Old matrix stack - 57,507 ops/sec ±0.58% (55 runs sampled) New matrix stack - 1,217,220 ops/sec ±0.41% (61 runs sampled)
In other words, the new matrix stack proved to be over 21 times faster! Awesome!
It’s worth reiterating that this is only one particular use case; for example, loading a new projection matrix while leaving the view matrix unmodified yields “only” a 13x performance increase. The improvements will be completely dependent upon the application environment, but I think the vast majority of users will see a significant benefit — and that, in my book, spells “WIN.”
(Disclaimer: I’m not saying that all of Jax is about to get 2100% faster. I’m just saying that its matrix stack is getting faster. But yes, this will probably improve performance throughout Jax to one degree or another, in combination with numerous other optimizations I’ve been working on. I’ll benchmark the new version of Jax against v2.x in the coming weeks. Stay tuned…)
]]>I’m trying to break that habit, starting now. Fighting the urge to move on, I’m breaking just long enough to write a bit about what it is I’ve just completed: a fully dynamic, fully automated octree!
If you’re not aware of what an octree is, that’s not surprising because a good one will never let you know it exists. To understand octrees, we should first back up and see what the world looks like without them.
Imagine that you’re piloting a space ship in a game world. When you look around your ship, you see all sorts of objects in the distance: stars, planets, space stations, asteroids, and of course, other space ships. There are thousands of such objects. In fact, there are so many objects being rendered every single frame, that the game is unplayable because the graphics pipeline has been clogged with objects being rendered, most of which are too far away to be seen or are off-screen entirely.
The best way to speed up rendering is not to render at all, and one common approach to this is frustum culling. That’s an article in itself which I won’t go into here, but essentially, the frustum is the viewing extent of the camera, and the idea behind frustum culling is to simply not render any object that’s not actually within the bounds of the camera. A simple concept, to be sure, but not quite so simple in practice.
Even with frustum culling, the game can be unplayably slow. If there are many thousands of objects, the simple act of iterating through all of them can have a devastating impact on performance — especially in JavaScript, where brute-force iteration through all the objects in a scene just isn’t fast enough to be practical.
This is where space partitioning comes in, separating objects into more manageable groups. Instead of checking each object against the frustum individually, we just check whether a region of objects is inside the frustum, either partially or completely. If so, we can intuit that most or all of those objects will also be inside the frustum. In many cases we can even skip checking those objects entirely, in favor of just rendering them outright.
Octrees are a form of space partitioning, dividing vast quantities of geometry in a scene into more manageable chunks. An octree forms a giant cube around the scene. Then, the cube is subdivided into eight (oct-) equal sub-cubes. Those sub-cubes are themselves subdivided (-tree) into equal children, and so on, until some terminating condition is reached.
Because an octree consists of ever-shrinking cubes, they are an ideal solution to the problem of rendering many objects in a scene.
There are about as many octree implementations out there as there are uses for them, and they all vary somewhat depending on those uses. So before writing my own, I laid out some ground rules that the tree had to follow in order to be considered successful:
I’m pleased to say that this octree succeeds at following all 3 rules! (Of course, if it hadn’t, it wouldn’t have made its way into the framework and I wouldn’t be writing about it now.) My next task is to get it integrated into the Jax scene manager, so that it can start doing its thang without a second thought starting in the next release!
Now I’ll move on to some implementation details, in case any of you out there are working on your own octrees and are looking to see how others have done it. If you’d like to skip the theory and jump straight into the source, look no further than the Jax source code repository!
First, the basics: when an octree is created, what happens? Some octrees are initialized and then immediately subdivided to a set number of levels, but this was not an option in our case because we had no way of knowing the size of the tree. Without knowing the eventual size of the root node, we can’t create child nodes, whose own positions and sizes must be calculated based on some knowledge of their parent’s size and position.
Instead, our octree uses a split threshold representing the maximum number of objects in any given node. If the number of objects added to a node exceeds the threshold, the octree is subdivided and its objects are redistributed to the node’s children. If an object doesn’t fit inside of any of the node’s children, it stays where it is.
This is our terminating condition. If there are too few objects in a node, it won’t subdivide, which prevents a recursion error. In the event two objects occupy the same space, they should eventually exceed the bounds of the child node, which also prevents a recursion error. The only thing left, then, is objects with a size of 0 (because empty or missing meshes are allowed in Jax). It’s quite easy to let “size” revert to 0.1 (or some other arbitrary small number), and that rounds out our termination.
Deciding whether an object fits into a particular node is easy, but different than most implementations. Most octrees calculate the object’s position and size, and compare that to the position and size of the octree node. In our case, we compare the object’s size with the size of the node without taking into account its position, ensuring that the object itself is no more than half the size of the node. Then we compare the object’s position, making sure that it occupies a point somewhere within the node, regardless of the object’s size. If both of these tests pass separately, the object is deemed to “fit” within the node.
The benefit to this approach is that objects can be cleanly sorted into sub-nodes, even when they get close to the edge of the sub-node, which would ordinarily cause the object not to “fit”, forcing it to be placed at a higher, less efficient level in the octree. There is one caveat to remember about this approach, though. When doing the frustum calculations, we need to remember to double the size of any given node, because we’ve allowed objects to be positioned close enough to the edge of a node that they might “hang over” its border by as much as 1/2 the node’s size on either side. There is a small amount of extra overhead incurred by the larger (and therefore, more numerous) frustum checks, but I think that’s fairly negated by the improved efficiency of the tree itself.
In order to see if a node can contain an object, we must know the size of the node itself. So how do we decide on a node’s size if we don’t know anything about the scene? Well, we might not know the eventual size of the scene, but we can adapt to fit it. The root node of the octree is initialized with a size set to 1 unit. This number could actually be any positive number, but I chose 1 for simplicity. If objects are smaller, then there’s nothing more to be done; we go straight into distributing the objects as already explained. For objects that are larger than the root node, the root node and its children (if it has any) grow exponentially in size, doubling until the object fits; each time the size is doubled, the bottom-most node is subdivided, so that the deepest level doesn’t actually change in size at all. An important note is that the root node’s position never changes: it’s always centered at the origin, and the tree will grow to encompass both the object’s position and its size.
This obviously produces a ton of empty nodes at all levels of the octree. To prevent unnecessary recursion through these empty nodes, I decided to maintain two lists of objects in each node: objects that are contained directly within the node itself, and a list of all objects contained in both the node in question, and all of its children and grandchildren combined. Since objects are added to the tree in a top-down manner, all this means is we have to build up the second list before distributing objects to child nodes; it’s not that difficult, and it affords us one great optimization: not only can we instantly check at any level of the octree whether a particular node contains any objects (thereby rejecting a ton of empty nodes early on), but we can actually access a list of those objects without recursing any deeper into the tree. This is a huge boon for cases where, for example, we’ve determined that a node is entirely within the view frustum and therefore further recursion would be redundant.
So now we’ve got an octree that can enlarge itself to suit the scene, subdivide itself when necessary, and place objects at their ideal depths based on the octree’s splitting threshold and the size and position of each individual object. What we haven’t talked about is how to make it dynamic. First, when an object is moved, the octree needs to be updated so that it remains accurate. Second, when the object moves outside of a given node, that node needs to be checked. If it doesn’t have enough children, it needs to be un-subdivided, or merged, so that we can avoid extra iterations through nearly-empty nodes.
This is actually a lot simpler than it sounds. Jax models already act as event emitters, so it’s trivial to hook into them to receive an event whenever the object moves or rotates. When this happens, we tell the tree to update the particular object in question. To update it, the tree merely checks to see whether the object still fits in its current node. If so, nothing more needs to be done; if not, then the object is removed from its current node and re-added to whichever parent or grandparent node it does fit into. Since the root node will always grow to fit the object’s new orientation, there’s no risk of leaving the bounds of the octree.
Then, whenever an object is removed from a node, that node needs to check the total number of objects contained within it and its children. If it’s dropped below the merging threshold, then it’s time to un-subdivide the node and move its objects back into the node itself.
In the Jax implementation, we don’t actually remove the references to merged children. This would lead to costly garbage collection, which is bad enough on its own but even worse when we already know we’ll probably need those nodes again. So instead, we keep the reference “alive” and just set a “subdivided = false” flag so that the octree knows not to use the nodes in question. For memory’s sake, this could be improved by setting a timestamp on the nodes, so that if they go too long without being used, they are opened up for garbage collection. I’m not going to implement that now, but if it becomes an issue, it’s an option that’s not too difficult to add.
And that’s it! This fairly simple construct is going to net huge performance gains for those of you who have a lot of objects to manage, while having a negligible impact on anybody else. The best part is, it’s completely invisible — as it should be!
]]>First of all, I’d like to again tout its awesomeness. The framework was always designed with sheer productivity and application time-to-market at the forefront, but I never realized just how much time it saves. Now that it integrates seamlessly with Ruby on Rails, the things I’ve been able to knock out in the past few days alone have been staggering! The application that I’d previously hoped, optimistically, to enter beta in late January is now looking feasible by late December — and I only started coding it on the 11th!
Having said all that, Jax is admittedly not perfect. There is still room for improvement, and I’m constantly tackling bugs and considering feature improvements — now from the point of view of a person using the framework instead of someone developing it. This means I’m discovering all sorts of areas where Jax really could do better. Hence the rapidly-expanding list of objectives for v2.1.0!
Additionally, I’m shaking out the bugs by using Jax in ways I hadn’t originally planned around. I’ve tried to fix most of them as soon as they were discovered; check out the changelog for details!
As always, if you’ve found an issue you’d like to report or would just like to contribute some code, head on over to Github.
Upgrading
Thanks to Rails 3, upgrading Jax is a cinch (even if you’re using “vanilla” Jax, it uses Rails internally):
bundle update jax
All done!
]]>As you can see, a lot of work has gone into this release. Expect more posts in the near future that will overview how to make the nuances of Jax work for you.
In the meantime, you should take a look at the Getting Started Guide if you haven’t done so already! It’s been updated to reflect Jax v2.0.
If you’d rather see Jax in action, check out the live demos page.
As always, if you have any questions or comments, feel free to leave them on the forum. Issues can be raised at the issue tracker on Github.
If you’d like to get involved with Jax development, head on over to the respository at Github!
For those of you using Jax v1.x, there are a few steps you’ll need to take in order to make the upgrade happen:
Follow these instructions if you are developing Jax and are using Ruby on Rails:
Follow these instructions if you are developing Jax and not using Ruby on Rails:
Regardless of whether you are or aren’t using Rails, follow these instructions after generating the new application:
If your old code has a lot of //= require‘s in them (controllers were generated with them by default), then the require path is now relative to app/assets/jax. For instance, to require the ApplicationController (which is no longer necessary, but as an example) the path would be “controllers/application_controller” instead of just “application_controller”.
If you have written your own shaders, the setUniforms and setAttributes functions within the material.js file were generated with calls to $super. Remove this call, and also remove $super from the functions’ argument lists.
]]>Jax was not originally intended to be the incarnation of the Rails adapter I’ve been looking for. The way Jax is written today, it can’t interface directly with Rails applications, and trying to force it into doing so would, in all likelihood, end in catastrophe. Jax was more an attempt to bring Rails-like productivity into the JavaScript world, with particular emphasis on WebGL.
However, if you’ve ever taken a look at the Jax internals, you’ve undoubtedly seen numerous similarities to the Rails backend. Indeed, the Jax gem actually relies on railties, one of the main dependencies of Rails itself.
Fanboy though I may be, these similarities did not arise out of a blind adherence to any “Rails or bust” philosophy. Jax is written the way that it is because the railties system actually implements the vast majority of what Jax itself requires out of necessity. It sets up load paths; finds source files and provides hooks into managing them; interfaces with plugins; and is modular enough to coexist with other Rubygems that might be able to add more to the Jax experience. Why reinvent the wheel?
Still, though, in the back of my mind has always been that dream. WebGL in Rails today is not by any means an impossibility, but it’s not very Rails-like, either. A modular, extensible, MVC-based WebGL framework running in Rails does not exist today, and that is the void I’d like to eventually fill.
As it turns out, “eventually” isn’t all that far away. I’ve spent the whole of the last week experimenting with Rails 3.1, the new asset pipeline it introduces, and Jax. It’s taken a significant development effort (read: a complete rewrite!) of the Ruby side of Jax, but the result is nothing short, in my humble opinion, of impressive.
The best part is that even with the new structure, Jax still offers the ability to write static applications completely outside of Rails! This is a big deal to me, because I want people who don’t want to use Rails (for whatever reason) to keep being able to use Jax, as they are today.
At this point, it’s safe to say that Rails integration is inevitable, and it’s going to happen soon. The only thing I can’t decide on is whether it’s going to be in Jax v2.0 or Jax v1.2.
There’s lots more going on than “just” Rails integration, but that’s all I’ve got time to talk about right now. If you’d like to follow my progress, (or help me along), see the rails31 branch on Github!
]]>
Today’s JavaScript engines are dramatically faster than they were even a few years ago, but they still have a long way to go. WebGL applications can’t afford to sacrifice even a few milliseconds; if you can gain them, you need to do so in any way possible.
One of the major techniques introduced in DOOM in 1993 was the application of binary space partitioning, or BSP, trees. These allowed the engine to predict with consistent accuracy which geometry was going to be rendered to the screen before the rendering actually took place. If the engine didn’t need to render a particular set of geometry, it could completely omit it from the rendering phase. This saved a huge amount of time.
The performance issues of JavaScript-based graphics engines today aren’t entirely different from the same issues of machines in the early 1990s. CPU speed is a major bottleneck; in fact, the JavaScript interpreter is generally far slower than the graphics processor, and the vast majority of your bottlenecks are going to be here — on the CPU.
BSP trees work by splitting the geometry to be rendered into two sections. Each section has a bounding box associated with it, which occupies half of the total area of the BSP. The geometry is then split again, with each half of the BSP containing two more halves, and so on. The last level of the BSP tree contains leaf nodes, which are the actual objects to be processed.
In this implementation, we’ll continue subdividing the tree until we have bounding boxes which only contain a single triangle each. It’s worth noting that this is usually not ideal for rendering in WebGL (in which case it’s usually best to stop on a per-object basis), but it’s very helpful for accurate collision detection, which usually involves figuring out exactly which triangles are intersecting.
In googling for other implementations, I found that most BSPs are constructed in a top-down manner. In other words, you start with the root node and work your way down to the individual leaf nodes. However, I decided to try something a bit different with my tree.
A particular sticking point in generating top-down BSP trees is how to efficiently divide the node to create two new sub-nodes. Intuition might suggest simply dividing the node down the center, but this is often insufficient. If most of your triangles are in one half and only a few are in the other half, then dividing them down the center will result in a huge waste of CPU power in each frame as you iterate through empty or nearly-empty BSP nodes.
Another approach is to iterate through each triangle in the scene, decide how many triangles lie in front of and behind its plane, and choose the one that balances the node most appropriately. I’m concerned with this approach, however, due to the inherent slowness of JavaScript interpreters today. At a minimum, this approach would require the BSP to iterate through the number of triangles in the scene squared, and do so again at each level of the BSP. I believe the majority of sizable 3D scenes would cause the browser to hang using this approach, making it unacceptable for any 3D scene that is large enough to actually make use of a BSP tree in the first place.
Instead, I opted for a completely different solution: a bottom-up algorithm that starts with the leaf nodes (the triangles) and moves upward toward a single root node. This allows us to completely skip choosing a dividing plane; all we have to do is choose two nodes from a pool of nodes and recurse upward from there. If we choose nodes that are very close to each other, our leaf nodes will be tightly packed together, resulting in a BSP tree that is very efficient all the way up to its root.
This approach may not be well suited to other languages like C, but as you’ll see, JavaScript certainly has no issue with building the tree in such a dynamic manner.
Assumptions
Before we jump into some code, I’m going to assume you have some basic supporting objects in place, namely Triangle, Plane, and Box. (If not, you can get implementations of each of these from the source code for this project, linked to at the top of this article.) Their signatures look like this:
new Plane([a, b, c]):
If a, b, and c are given, they are passed to #set.
Otherwise, a plane whose extents are undefined is created.
set(a, b, c):
resets this plane according to the vertices
whereis(point):
returns whether the given point is in FRONT, BACK or
INTERSECT-ing the plane
new Triangle([a, b, c]):
If a, b, and c are given, they are passed to #set.
Otherwise, a triangle whose extents are undefined is created.
set(a, b, c):
sets up a triangle from the given 3 vertices
center:
a vec3 assigned by #set(a,b,c) representing the
calculated center of this triangle
new Box(position, size):
Creates an axis-aligned bounding box (AABB) with the given
position (a vec3) and size (also a vec3).
intersectOBB(other_box, transform):
Tests the other box with this one as an oriented
bounding box (OBB). Uses the given matrix to transform
the other box into the coordinate space of this one.
In addition to these, I’m assuming you are making use of a matrix library like glMatrix. I’ve also added the following new functions to glMatrix, which you can feel free to copy-and-paste for convenience:
/**
* vec3.min(a, b[, dest]) -> vec3
* – a (vec3): first vector
* – b (vec3): second vector
* – dest (vec3): optional destination vector
*
* Stores the minimum value for each element of a and b
* within dest. If dest is omitted, a new vec3 is created.
**/
vec3.min = function(a, b, dest) {
if (!dest) dest = vec3.create();
dest[0] = Math.min(a[0], b[0]);
dest[1] = Math.min(a[1], b[1]);
dest[2] = Math.min(a[2], b[2]);
return dest;
};
/**
* vec3.max(a, b[, dest]) -> vec3
* – a (vec3): first vector
* – b (vec3): second vector
* – dest (vec3): optional destination vector
*
* Stores the maximum value for each element of a and b
* within dest. If dest is omitted, a new vec3 is created.
**/
vec3.max = function(a, b, dest) {
if (!dest) dest = vec3.create();
dest[0] = Math.max(a[0], b[0]);
dest[1] = Math.max(a[1], b[1]);
dest[2] = Math.max(a[2], b[2]);
return dest;
};
Now that we’ve gotten those formalities out of the way, let’s start building a BSP!
We’ll store the resultant function in a variable called, creatively, BSP:
var BSP = (function() {
/* CODE GOES HERE */
})();
This code creates a temporary function, immediately calls it, and then assigns the return value of the function to the BSP variable. This gives us the flexibility of defining “private” helper functions that can only be used internally within the BSP object.
We will define two such helper functions: one to build the tree from the bottom up, and another for calculating a bounding box around a single triangle. First I’ll show the functions in their entirety, and then I’ll explain them line-by-line.
The buildLevel Function
// +level+ is an array, containing either Triangles or BSP nodes.
// This function replaces every 2 elements in the array with a single
// parent BSP node. The array is modified in-place and the size of the
// array will be cut in half, to a minimum of 1.
function buildLevel(level) {
var nextLevel = [];
var plane = new Jax.Geometry.Plane();
while (level.length > 0) {
var front = level.shift(), back = null;
var dist = vec3.create();
var closest = null, closest_index;
var result = front;
if (level.length > 0) {
for (var j = 0; j level.length; j++) {
var len = vec3.length(
vec3.subtract(front.center, level[j].center, dist));
if (closest == null || closest > len) {
closest = len;
closest_index = j;
}
}
back = level[closest_index];
level.splice(closest_index, 1);
// See if back and front are accurate. If not, swap them.
// If triangle, use the plane created by the current triangle.
// If node, use the first triangle in the box for a plane.
if (front instanceof Jax.Geometry.Triangle)
plane.set(front.a, front.b, front.c);
else {
var tri = front.front;
while (tri instanceof BSP) tri = tri.front;
plane.set(tri.a, tri.b, tri.c);
}
if (plane.whereis(back.center) == Jax.Geometry.Plane.FRONT)
result = new BSP(back, front);
else result = new BSP(front, back);
}
nextLevel.push(result);
}
for (var i = 0; i nextLevel.length; i++)
level.push(nextLevel[i]);
}
The buildLevel function is the meat of the BSP tree, and is responsible for building up the tree hierarchy, so it’s important that you understand what it’s doing. Let’s go through it a few lines at a time.
var nextLevel = [];
var plane = new Jax.Geometry.Plane();
We’re creating a few local variables to act as buffers. The nextLevel array contains the parent nodes we’re about to create; we don’t want to stick them right back into level just yet because level has to reach an empty state in order for our function to execute properly.
The plane variable will be used to check whether a given triangle or BSP node is in front of or behind another.
while (level.length > 0) {
var front = level.shift(), back = null;
Here we’re starting a loop that won’t end until we’ve exhausted the entire level array. Once within the loop, the first thing we do is shift the first element out of the array and store it in the front variable. We’re also creating a back variable; for now we’re just setting it to null.
var dist = vec3.create();
var closest = null, closest_index;
We’ll use the dist variable to check the distance from the front element to all other elements, and we’ll use the closest and closest_index variables to make a note of the closest elements.
var result = front;
if (level.length > 0) {
for (var j = 0; j level.length; j++) {
var len = vec3.length(
vec3.subtract(front.center, level[j].center, dist));
if (closest == null || closest > len) {
closest = len;
closest_index = j;
}
}
Here’s where things start to get interesting, and maybe a bit confusing. We’re creating yet another variable called result. The result will ideally be a parent instance of BSP, but we don’t want to create parents that have only 1 child, because this would just make the BSP tree slower to traverse. If the parent is only going to contain a single child, then the parent’s dimensions are going to be equal to the dimensions of the child, and it’s faster to optimize the parent out of the equation. Therefore, we’re initially setting result to the child front element.
Next, we’re making sure the level array isn’t empty yet. This is necessary because the previous call to shift may have emptied the array; we have no guarantee that the number of triangles (or BSP nodes) is divisible by 2, so we need to be aware of that possibility.
Assuming the level array isn’t empty, we’ll iterate through all of its elements. Each time we find an element closer to front than the previous, we store its distance and the array index of the element itself.
back = level[closest_index];
level.splice(closest_index, 1);
At this point, we have a front element and we’ve noted the closest element to it in the array. We’re going to assign the closest element to the back variable, and then remove it from the array using splice.
It’s important to note that the names front and back are, at this point, misnomers. We have no guarantee that they are actually the front and back elements; the only thing we know is that they were two elements from the same soup, and that they are closer to each other than they are to any other element.
if (front instanceof Jax.Geometry.Triangle)
plane.set(front.a, front.b, front.c);
else {
var tri = front.front;
while (tri instanceof BSP) tri = tri.front;
plane.set(tri.a, tri.b, tri.c);
}
In order for the BSP tree to be truly effective, we want to make front and back correspond to true “front” and “back” elements. That’s what the above code does.
First, we need to check whether we’re currently working with a Triangle or a BSP node. If the former, the plane is very simply constructed from the triangle’s 3 vertices.
If, however, we’re dealing with a BSP node, we need to find a plane within the BSP node to test against. I don’t think it matters which node we select at this point, so I just selected the front-most triangle using a while loop. The loop exits as soon as it encounters a node which is not a BSP; it is assumed to be a Triangle, and the plane is set from those vertices.
if (plane.whereis(back.center) == Jax.Geometry.Plane.FRONT)
result = new BSP(back, front);
else result = new BSP(front, back);
The last step to constructing the parent node is to test whether the second element, stored in back, is in front the plane. Since the plane is itself assumed to correspond to the front element, we must ensure that the back element is indeed behind the plane.
This condition simply performs that test; if the back element tests to be in front of the front plane, then we’ve got the two reversed, and we just switch the order of the elements so that back is front and front is back.
If the condition fails, then our ordering is correct and we construct the parent as planned.
It’s important to make a distinction here. Traditional BSP trees also test for an intersect result from the plane. If the plane intersects a triangle, the BSP should probably consider splitting it into two triangles and adding the new triangles to the array before starting again. I don’t do this here for a number of reasons:
If you’re performing alpha blending, in which case you want to ensure that the back-most polygons are always drawn first or else the blending won’t come out right. However, going back to the WebGL argument above, you’re likely going to be rendering an entire object at one time (in which case you can’t dynamically control the order triangles appear in), so this argument breaks down.
With that, let’s move on to the remainder of the function:
}
nextLevel.push(result);
}
for (var i = 0; i nextLevel.length; i++)
level.push(nextLevel[i]);
}
This last chunk of code is pretty simple; we close out the condition we started earlier, push the result (which is now either a parent BSP or the last element in the level array) into the local nextLevel array, and close out the while loop.
The last thing this function does is iterate through nextLevel and push its contents into the now-empty level array so that the original array can be used in a loop.
The calcTriangleDimensions Function
// Calculates the dimensions of a bounding box around
// the given Triangle. If the triangle is axis-aligned,
// one of the dimensions will be 0; in this case,
// that dimension of the bounding box will be set to a
// very small positive value, instead.
function calcTriangleDimensions(tri) {
var result = vec3.create();
var min = vec3.create(), max = vec3.create();
vec3.min(vec3.min(tri.a, tri.b, min), tri.c, min);
vec3.max(vec3.max(tri.a, tri.b, max), tri.c, max);
min_size = Math.EPSILON * 2;
for (var i = 0; i 3; i++) {
result[i] = max[i] - min[i];
if (result[i] min_size) result[i] = min_size;
}
return vec3.scale(result, 0.5);
}
Leaf nodes in the BSP tree are triangles. However, its immediate parents will wrap around the triangle and need some information about the dimensions of the triangle in order to form an accurate bounding box around it. That’s what this function takes care of.
var result = vec3.create();
var min = vec3.create(), max = vec3.create();
vec3.min(vec3.min(tri.a, tri.b, min), tri.c, min);
vec3.max(vec3.max(tri.a, tri.b, max), tri.c, max);
First we create some local variables to work with, all of which are vec3s, and then we store within them the maximum and minimum extents of the triangle. Together, these extents describe a bounding box.
min_size = Math.EPSILON * 2;
for (var i = 0; i 3; i++) {
result[i] = max[i] - min[i];
if (result[i] min_size) result[i] = min_size;
}
To get the dimensions of the bounding box without its world-space position, we subtract the minimum extents from the maximum extents.
There’s an additional consideration, however: if the triangle is axis-aligned, it will have a height, width or depth equal to 0. The problem is, if we create a bounding box with a dimension of 0, it’ll be impossible for that box to intersect another! In order to work around this caveat, here we program a special case to handle a near-0-dimension triangle. If the size in any of the 3 dimensions is less than some very small value (called epsilon), we simply set it to that value. It’s multiplied by 2 to account for the final line in the function:
return vec3.scale(result, 0.5);
Here we simply return half of the size of the triangle. Each node of the BSP tree is defined with a center point, and a half-dimension.
The BSP Object
Now we need to create a constructor which, when instantiated, will make use of these helper functions to build and traverse the BSP tree:
var bsp = function(front, back) {
this.front = null;
this.back = null;
this.triangles = [];
if (front || back) this.set(front, back);
};
/* MORE FUNCTIONS HERE */
return bsp;
Before we go further, note that we’re returning the bsp object. This wraps up our anonymous function and assigns the bsp object to the BSP variable that we defined at the beginning of this tutorial. This allows BSP to be instantiated directly, like so:
var myBSP = new BSP(front, back);
This very simple constructor merely creates a few variables and an array to hold triangles as they are added to the BSP tree. If the node was constructed with a front and/or back element, they are passed into the set() function, which we’ll go over next:
bsp.prototype.set = function(nodeFront, nodeBack) {
this.front = nodeFront;
this.back = nodeBack;
this.center = vec3.create();
var c = 0;
if (nodeFront) {
vec3.add(this.center, nodeFront.center,this.center);
c++;
}
if (nodeBack) {
vec3.add(this.center, nodeBack.center, this.center);
c++;
}
if (c > 0) vec3.scale(this.center, 1.0 / c);
var halfSize = this.calcHalfSize();
var boxPosition = vec3.subtract(this.center, halfSize, vec3.create());
var boxDimensions = vec3.scale(halfSize, 2, vec3.create());
this.box = new Box(boxPosition, boxDimensions);
};
The set() function is responsible for storing the front and back nodes, and then recalculating its center property, which is done by taking the average of the centers of this node’s children. It also recalculates the half-size of this node by calling calcHalfSize() and then rebuilds the bounding box for this node.
bsp.prototype.getHalfSize = function() {
return this.halfSize || this.calcHalfSize();
};
This very simple function simply returns the half-size of this node without recalculating it; an exception is if the half-size hasn’t been calculated yet, in which case it calls calcHalfSize().
bsp.prototype.calcHalfSize = function() {
this.halfSize = this.halfSize || vec3.create();
var min, max;
function calcSide(side) {
var size, cmin, cmax;
if (side instanceof BSP) size = side.getHalfSize();
else size = calcTriangleDimensions(side);
cmin = vec3.subtract(side.center, size, vec3.create());
cmax = vec3.add(side.center, size, vec3.create());
if (min) {
vec3.min(min, cmin, min);
vec3.max(max, cmax, max);
} else {
min = vec3.create(cmin);
max = vec3.create(cmax);
}
}
if (this.front) calcSide(this.front);
if (this.back) calcSide(this.back);
vec3.subtract(max, min, this.halfSize);
vec3.scale(this.halfSize, 0.5);
return this.halfSize;
};
The calcHalfSize() function looks more daunting at first glance than it really is; all it does is get the minimum and maximum extents of each sub-node, and then calculate the minimum and maximum extents of both nodes combined. By subtracting the minimum from the maximum, we’re left with the overall size of the node; scaling that down by half yields the half-size, and we’re done.
bsp.prototype.getTreeDepth = function() { return this.treeDepth; };
The getTreeDepth() function simply returns the maximum depth of the tree starting from the current node, which is calculated by the finalize() function.
bsp.prototype.addTriangle = function(triangle) {
this.triangles.push(triangle);
};
The addTriangle() function simply adds the given triangle to the list of triangles. If you’re using something other than triangles (for instance, an entire vertex buffer object), youll want to add a function to correspond with whatever you plan to use. You’ll also want to tweak the other triangle-related functions to taste.
bsp.prototype.finalize = function() {
var level = [];
for (var i = 0; i this.triangles.length; i++)
level.push(this.triangles[i]);
this.treeDepth = 1;
while (level.length > 2) {
buildLevel(level);
this.treeDepth++;
}
this.set(level[0], level[1]);
this.finalized = true;
};
This function, finalize(), is the last function which will be called when we are constructing a BSP tree. It builds a single, flat level of triangles, then starts a loop. Until the list of triangles has length 2 or less, the helper function buildLevel() is called repeatedly on the list. By the time the list is complete, it’s guaranteed to only have a maximum of 2 elements. We don’t really care whether those elemenst are Triangles or BSP nodes, as all of the special handling is taken care of elsewhere.
Finally, the finalize() function calls set() with the remaining elements in the array, thus making this instance of BSP the ultimate root node. A further optimization might be to check whether level has one or two elements; if it only has one, then we can remove it and assume its children, thus making the tree one node smaller and that much quicker to traverse.
Traversing the Tree
Now that we have a BSP tree, we need to think about how to traverse it. Each node has at most two sub-nodes, front and back, associated with it. That means that if we have a point in space, we can always tell which direction to traverse first: the one closest to the point. In this way, we can guarantee that all of our objects are rendered back-to-front by first traversing the tree down to its leaf nodes, and then rendering as the stack unravels. Here’s an example:
/**
* BSP#traverse(point, renderCallback) -> undefined
* – point (vec3): the position of the camera, used for polygon sorting
* – renderCallback (Function): a callback function to call with each
* leaf node as an argument
*
* Traverses the BSP tree using the given point as a reference; leaf
* nodes will be sent to the +renderCallback+ function in back-to-front
* order.
**/
bsp.prototype.traverse = function(point, renderCallback) {
// handle the (hopefully infrequent!) special
// case of having only 1 child
if (!this.front || !this.back) {
var result = this.front || this.back;
if (result instanceof BSP) result.traverse(point, callback);
else renderCallback(result);
return;
}
// find the distance from front to point, and from back to point
var dist = vec3.create(), frontLen, backLen;
vec3.subtract(this.front.center, point, dist);
frontLen = vec3.dot(dist, dist);
vec3.subtract(this.back.center, point, dist);
backLen = vec3.dot(dist, dist);
if (frontLen backLen) {
// closer to front, traverse back first
if (this.back instanceof BSP) this.back.traverse(point, callback);
else renderCallback(this.back);
if (this.front instanceof BSP) this.front.traverse(point, callback);
else renderCallback(this.front);
} else {
// closer to back, traverse front first
if (this.front instanceof BSP) this.front.traverse(point, callback);
else renderCallback(this.front);
if (this.back instanceof BSP) this.back.traverse(point, callback);
else renderCallback(this.back);
}
};
This is the simplest example I could think of to demonstrate the approach; however, simplicity adds waste, and there are quite a few optimizations that could be made to it. Most notably, it’s a bad idea to recursively call functions during a render pass in JavaScript, because there’s a significant amount of overhead created by function calls. To keep the function calls to a minimum, this should be expanded into a flat loop using some sort of stack. That’s the approach I took in my collision detection algorithm, demonstrated below:
/**
* BSP#collide(other, transform) -> Boolean | Object
* – other (BSP): the potentially-colliding BSP model
* – transform (mat4): a transformation matrix which is used to convert
* +other+ into this BSP’s coordinate space.
*
* Applies the given transformation matrix to +other+; if any triangle
* within +other+ is intersecting any triangle in this BSP tree, then
* a generic object containing the properties +first+, +second+ and
* +second_transformed+ is returned. They have the following meanings:
*
* * +first+ : the colliding triangle in this BSP tree
* * +second+: the colliding triangle in the +other+ BSP tree
* * +second_transformed+: a copy of the colliding triangle in the
* +other+ BSP tree, transformed by the matrix
* to be in this BSP tree’s coordinate space.
*
* If no collision has occurred, +false+ is returned.
*
**/
bsp.prototype.collide = function(other, transform) {
if (!this.finalized) this.finalize();
if (!other.finalized) other.finalize();
// buffer checks for GC optimization
var checks = this.checks = this.checks || [{}];
var check_id = 1;
checks[0][0] = this;
checks[0][1] = other;
var tri = new Jax.Geometry.Triangle(),
a = vec3.create(), b = vec3.create(), c = vec3.create();
while (check_id > 0) {
var check = checks[--check_id];
var first = check[0], second = check[1];
if (first instanceof BSP && second instanceof BSP) {
// both elements are nodes, if they intersect move to the next level;
// if they don’t intersect, let them disappear.
if (first.box.intersectOBB(second.box, transform)) {
while (checks.length - check_id 4) checks.push([{}]);
checks[check_id ][0] = first.front;
checks[check_id ][1] = second.front;
checks[check_id+1][0] = first.back;
checks[check_id+1][1] = second.front;
checks[check_id+2][0] = first.front;
checks[check_id+2][1] = second.back;
checks[check_id+3][0] = first.back;
checks[check_id+3][1] = second.back;
check_id += 4;
}
} else if (first instanceof Jax.Geometry.Triangle &&
second instanceof BSP) {
// first is a tri, keep it to retest against second’s children
while (checks.length - check_id 2) checks.push([{}]);
checks[check_id ][0] = first;
checks[check_id ][1] = second.front;
checks[check_id+1][0] = first;
checks[check_id+1][1] = second.back;
check_id += 2;
} else if (first instanceof BSP &&
second instanceof Jax.Geometry.Triangle) {
// second is a tri, keep it to retest against first’s children
while (checks.length - check_id 2) checks.push([{}]);
checks[check_id ][0] = first.front;
checks[check_id ][1] = second;
checks[check_id+1][0] = first.back;
checks[check_id+1][1] = second;
check_id += 2;
} else {
// dealing with 2 triangles, perform intersection test
// transform second into first’s coordinate space
mat4.multiplyVec3(transform, second.a, a);
mat4.multiplyVec3(transform, second.b, b);
mat4.multiplyVec3(transform, second.c, c);
tri.set(a, b, c);
if (first.intersectTriangle(tri)) {
return {
first: first,
second: second,
second_transformed: new Jax.Geometry.Triangle(
tri.a, tri.b, tri.c)
};
}
}
}
return false;
};
The collision detection is made a little more complicated by the addition of a second BSP, but hopefully it’s not too difficult to understand.
First, we create a stack, which is just an array with a single element. That first element is used to test the root node of this BSP with the root node of the potentially-colliding BSP. If the root nodes don’t intersect, we’re done.
Within the loop, we check whether the nodes to be tested are both BSPs. If they are, and if their bounding boxes are colliding, then each subnode of the first BSP must be compared with each subnode of the second, so 4 more checks are added onto the stack. If they are not colliding, then there’s nothing else to do except move on to the next check in the stack.
If one node is a BSP and the other is a triangle, then we only need to work our way down the BSP’s children and hold onto the triangle for testing against those children.
Finally, when both nodes are triangles, we can convert the second triangle into the first triangle’s coordinate space using the matrix that was passed into the function, and then do a simple triangle-triangle intersection test. If the triangles intersect, then obviously we have a collision.
There are a lot of ways you could code the collision response. I chose to create and return a generic object which contains information about the collision. Since the object evaluates to a non-false value, I can use this to simultaneously check for collisions and then respond to the collision without an extra function call. If you have the CPU performance to spare, you could just as easily fire a callback function or emit a ‘collided’ event instead.
Using the BSP Tree
We’re done coding the BSP tree, so let’s use it!
Let’s say, for sake of argument, that we have a mesh object with a getTriangles() function. Then, we could construct the BSP tree quite simply:
var bsp = new BSP();
var triangles = mesh.getTriangles();
for (var i = 0; i triangles.length; i++)
bsp.addTriangle(triangles[i]);
bsp.finalize();
This will create a new BSP, iterate through the mesh’s triangles, add them one-by-one to the tree, and then finalize it. Finalization causes the single root node to become, well, a tree.
Now that we’ve created the tree, let’s make use of it. Here’s an example of rendering it using our (unoptimized!) traverse() function:
bsp.traverse(camera.getPosition(), function(leaf) {
// render the node
});
And of course, here’s a demonstration of checking for collisions using a mythical second BSP, presumably created in an identical manner to above:
var collision;
var xform = mat4.inverse(mesh.getTransform(), mat4.create());
mat4.multiply(otherMesh.getTransform(), xform, xform);
if (collision = bsp.collide(other_bsp, xform)) {
// collision detected! it’s stored in +collision+
}
In case you’re not very familiar with matrix operations, the inverse of any given matrix is effectively its opposite; since an object’s transformation matrix is used to convert any given point from that object’s local space into world space, the inverse of an object’s transformation matrix will do the opposite, converting from world space into object space. So to test otherMesh against mesh, we combine the transform matrix of otherMesh, which converts otherMesh into world space, with the inverse transform matrix of mesh, which converts world space into mesh’s space. The combination of the two will go straight from otherMesh’s space into mesh’s space, and that’s exactly what we need to perform collision detection. If that was confusing, see this illustration:
Conclusion
Hopefully, this tutorial was informative for you. If you have any questions or comments, feel free to leave them here or on the Jax forums. There are other approaches to segregating scene data, such as using octrees or k-trees, though the others have more trouble replicating one of the major gains to using a BSP tree: the ability to render the scene back-to-front without having to sort the geometry first.
As a side note, octrees seem to have more-or-less replaced BSPs in C programs; I’m still unconvinced that they’re fast enough to replace BSPs just yet in JavaScript, however. In any case, BSPs are still used today for most collision detection, in which they supposedly outperform octrees in most cases. At least, that’s what Google says; I hope to do some benchmarking to come up with more definitive, JavaScript-specific results sometime in the near future.
As I was adding release notes to the Changelog, I realized that I’d doubled the size of the log with this release! So instead of overviewing them all here, I’ll highlight what I think are the greatest improvements and leave it to you to read through the individual updates, if you wish.
Without further ado, here are some of the highlights of this release:
In addition to everything else, I’ve added a new quick-start page to help new developers get started. It’s a lot less daunting than the Getting Started Guide but you’ll still want to read the guide if you want to dig into anything more involved than a teapot.
Finally, I also updated the Getting Started Guide with some details. Out with the deprecated, in with the awesome. Besides minor changes, I added a section on redirection, which explains how the controllers can communicate with one another. It should have been there from the start, but it slipped my mind with everything else I’ve been working on!
A few of the improvements deal with how applications are generated, so those tweaks will naturally only be applied to new applications. Existing ones aren’t left in the dark, however. As always, the update process for an existing app is as follows:
bundle update
.rake jax:update
.