Implementing Minecraft in WebGL
WebGL is an amazing piece of technology that enables browsers to natively render hardware accelerated 3d creations (yay, no o3d plugin needed!). I’ve always been specially amazed by what Mr Doob has been doing with his Three.js framework for quite a while (in particular his participation on the ROME project, which I briefly talked about recently). Nonetheless, there are some other amazing WebGL creations around, such as those featured on Chrome Experiments, those crafted by OOS and the projects recently presented on WebGLCamp (not to mention the amazing Team Fortress 2 level vizualizer).
This seemed to be from the fact the 3d collision is quite a bit more involved than 2d (MIT’s lecture notes on Computational Geometry, and even on Doom 3′s recently released source code, can give you an idea of how much involved it can get). And since this is considered to be one of the important things that 3d games need, I was not happy with my hands off answer of this topic on Hacker News. Thus I took the challenge of making a a 3d game in WebGL with collision detection.
Implementing Minecraft Classic (which is playable online for free) seemed like a good candidate for such project, as its mechanics are simple, and yet meaningful (not to mention that I am big fan of Notch’s creation). If you don’t know anything about Minecraft, I wrote a small intro about it a few months ago.
Three.js was selected as the rendering lib not only because I really like Mr Doob’s work, but also because it has is quite mature, is open source, has lots of examples and provides a very promising starting point: a visualization of a Minecraft world, which includes a noise function for generating it.
So all that was left was just adding Collision Detection. Well… this is where things started getting interesting.
Minecraft Collision Detection Attempt 1: JigLibJS
Jiglibjs is a port of Jiglibflash, which in itself is a port of Jiglib for c++. It seemed pretty promising, since there was some people using it (along with three.js), and I had great success with using physics libs in the past for 2d games, so it seemed like a natural choice.
After some fun 4 dimension matrix hacking, and failing to get around the bugs, I was ready to move on. Thankfully I found another port of JigLibJS which seemed to correct such issues…
Minecraft Collision Detection Attempt 2: JigLibJS2
The fact that someone tried to make a more complex example on JigLibJS, failed, but was tenacious enough to write its own AS3 -> JS compiler to make another port (which worked!) made a very positive impression.
It was also very positive the fact that the interface was almost the same to JigLibJS, making the change to the existing code very small. The problems started to come when trying to make the player cube jump: the cube would not always collide with the bottom plane. This was solved by making more collision iterations. Soon enough another problem came up: when trying to move inside an adjacent static cube (even when both the player and the cube were in the same height), sometimes the player would get a rotation. Trying to set it to no rotation every iteration did not actually help, as the player would still sometimes get a vertical velocity when trying to penetrate the block.
Still, rolling my own Physics Engine seemed a bit daunting, and I decided to try out another lib:
Minecraft Collision Detection Attempt 3: Ammo.js
But then it came the time to try to get to more Minecrafty world dimensions. It was a bit disappointing when mere 400 cubes made the physics engine go to a crawl (even when using static cubes). It became clear that I needed O(1) collision algorithms, which is very doable for Minecraft Classic, as only the player can move, and there is always a constant amount of cubes the player can collide with at any given time. And now there were no more libs left to evaluate.
Minecraft Collision Detection Attempt 4: Rays!
Rays are the standard way of detecting line/Object collision in Three.js. A very simple interactive demo by OOS seemed like it could do the trick. It had the advantage of being very simple, and constant time (given that I selected the possible blocks to collide with, as the traditional Ray.intersectObjects actually tries to intersect all objects on the Scene).
The OOS example had some issues (like trapping the player cube when jumping up and down, while hodling the forward key). This was solvable by using 12 rays correspoding to the edges of the cube that represented the player. Actually, this did not help all the time, as some rays would not collide with the world blocks if the ray’s origin inside a blocks’s face. This is a bug yet to be solved, but I got around it by using 24 rays (two directions for every edge of the player cube).
Things were going well enough that I finally moved into adding textures to the game. However, after making the player cuboid have size dimensions to Minecraft’s, instead of having the same size of the world’s cube (which was what I was experimenting with so far), I noticed that the ray would give false positives depending on the height position of the cube. This only happened when the numbers were all too exact, instead of with minor deltas, as you’d expect from using rays cast from the mouse pointer (as it is usually used on Three.js’ demos).
This, and the fact that collision was taking about half the time of every tick (which was constant, due the improved collision algorithm) made me move on to…
Minecraft Collision Detection Attempt 5: Cube Projection
Up until now I had hopes that I would be able to eventually rotate the player cuboid according to its camera. After having so many troubles with so many collision systems, I simplified the problem: collision of unrotated cuboids. This is a really simple problem: from the Separating Plane Theorem, it is easy to see that I only need to see if the orthogonal projections of the cuboids, which, from the Separating Axis Theorem, means I only need to check out if the unrotated rectangles from the projected faces collide, which finally means I only need to check if the projected intervals collide. All of this amounts to 13 lines of Coffeescript:
CollisionUtils = # The two intervals are [s1, f1] and [s2, f2] testIntervalCollision: (s1, f1, s2, f2) -> !(s2 > f1 || s1 > f2) #Cubes are objects with vmax, vmin (the vertices with greatest/smallest values) #properties. Assumes unrotated cubes. testCubeCollision: (cube1, cube2) -> fcol = CollisionUtils.testIntervalCollision for axis in ['x', 'y', 'z'] collides = fcol cube1.vmin[axis], cube1.vmax[axis] , cube2.vmin[axis], cube2.vmax[axis] return false unless collides return true window.CollisionUtils = CollisionUtils
Which in retrospect is what I should have tried in the first place. At least I learned some stuff in the process.
Not quite finished yet: Camera
Paul Irish did a pretty amazing job with the Three.js FirstPersonControls.js, which is the one that powers the Three.js Minecraft visualizer. The problem with this camera is that it makes hard to actually play Minecraft, as its default mode is to be always moving, making hard to place/remove blocks. Real Minecraft uses first person shooter camera which cannot be achieved with current browsers, as there is no way to trap the user’s mouse. Nevertheless, Minecraft on Android uses a touch and drag camera that can easily be implemented in JS. This camera works they same way as the one on Brandon Jones’ Quake 3 WebGL implementation.
This is where Rays worked really well. In fact, Mr. Doob even have a voxel editor example which shows really well how to make an app that adds/removes cubes on a 3d grid:
The only issue I’ve found was that my floor plane was too big, which messed up the Ray/plane collision in certain angles. So I coded this intersection directly:
getCubeOnFloorPosition: (ray) -> return null if ray.direction.y >= 0 ret = vec() o = ray.origin v = ray.direction t = (-o.y) / v.y ret.y = 0 ret.x = o.x + t * v.x ret.z = o.z + t * v.z return @addHalfCube ret
Granted, it starts skipping frames quite often as you add more blocks. The original example from Dr. Doob’s didn’t because he created a single Mesh (aka object scene) composed of all blocks. Doing such would make adding/removing blocks a lot more involved (or force me to handle some area loading/unloading), which is a project in itself. Note that the rendering, and not the collision system, is the real bottleneck at this point.
All of this made me admire Notch’s work on Minecraft a lot more, as Minecraft can handle over 21×21 loaded chunks of 16x16x128 (over 14 million blocks!) in any given time. The Three.js community had some great insights on how to achieve such performance over WebGL, which requires the use of shaders, which are quite low level (even if you use the respective TDL Google Library for this, or GPipe to write the shaders in Haskell), and would probably require a lot of collision code to be also written in also a very low level language (slash GPipe’s Haskell). I found it also interesting that shaders can be used in some clever ways to improve JS performance.
And finally, it is important to note that a lot of very people a lot smarter than me have been doing some great work to make working with WebGL and making 3d games much simpler.
There are also other renderer libraries besides Three.js: Scene.js, PhiloGL, A3 (recently presented on the WebGL Camp), Coppercube (which is not open source, but can use flash for 3d rendering as well) and GLGE.