Six Months of voxel.js

a look back through my git commit history

Welcome to my first blog post, where I discuss my experience developing with voxel.js. For those unfamiliar, voxel.js is an open source platform for creating voxel-style games in your browser using WebGL and JavaScript, founded by @maxogden and @substack in December 2012.

Check out maxogden’s one-month status report Bringing Minecraft-style games to the Open Web (January 2013) and his presentation at NodePDX (May 17th, 2013) for a detailed introduction to voxel.js.

Later in 2013, voxel.js progress sort of slowed down; the end of 2013 and early 2014 is where I got involved. I am a big fan of Minecraft by Mojang Specifications and especially the extensive “modding” community focused on enhancing it to create a richer and more advanced game, such as in the Feed The Beast packs.

The prospect of a completely open source and highly modular system for creating voxel-style games like modded Minecraft, right in your browser, was very appealing to me so I signed up for GitHub and began playing around with voxel.js:

git commit history from http://github.com/deathcap (Yes I know this is not a full 6 months, more like 5 months 11 days, fewer if you exclude days with no commits. So, I rounded up. Alternative title: 142 days of voxel.js)

This article is written based on summarizing my commits over this five or six month period. More details if you are interested are available in the actual commits themselves, all publicly available on GitHub.

Day 1. I began on November 9th, creating a repository named ‘voxpopuli’ (internal codename; subject to change UPDATE 2015/02/04: renamed project to ‘voxelmetaverse) as a testbed for development (live demo). Just for fun I decided to use CoffeeScript, a nice and clean language that compiles down to JavaScript, to replicate the voxel-hello-world demo. Here’s how it first looked with voxel-engine 0.18.4:

voxel.js hello world!

Not much to look at it, but it was a great feeling to see this blank canvas unfurl beneath me, the prospect of countless enhancements on the horizon. It was the start of something, for sure. I initially focused on small miscellaneous tweaks and control improvements (right-click to place blocks, continuous firing, hide avatar in first-person mode, hold tab to sprint, double-tap to exit fly mode, etc.), slowly shaping it up into how I envisioned a voxel game might be.

Artwork. Soon I ran into a problem: what graphics should I use for textures?

There is a painterly-textures module on NPM, used in a few examples, but it hasn’t been updated in a while and I couldn’t find clear licensing terms on the official Painterly Pack website. isabella-texture-pack looks promising, licensed Creative Commons 3.0 Attribution, but it is also outdated (although Isabella II: FTB Edition is actively updated, couldn’t find licensing information either). If anyone knows of an actively developed permissively-licensed set of original textures for voxel-style games, I’d be interested — I also asked on the Minecraft Forums — but I couldn’t find any at the time so I decided to make my own, essentially as a placeholder which could be freely redistributed:

ProgrammerArt 1.0, open source voxel artwork

That’s how it looks when ProgrammerArt, as I call it, is loaded into Minecraft 1.7. It took a while to even haphazardly draw placeholders for everything, but textures for all items and blocks are included. The pack is distributed in several formats: as a Resource Pack (compatible with MC 1.7, 1.6), Texture Pack (MC 1.5), and a pre-built “Stitch Pack” (MC 1.4 and earlier):

Texture atlas (terrain.png) for ProgrammerArt 2.0

The latter format can be read directly into a GL texture atlas, but has the disadvantage of limiting flexibility when combining multiple textures from different sources or of higher resolutions. Zipped archives of individual textures have a clear advantage, so I wrote artpacks to load this format (compatible with MC’s resource packs). The loader cascades, like CSS, allowing multiple packs to be loaded and organized with user-defined priority:

voxel-artpacks showing an embedded artpacks-ui with several packs loaded

This allows the player to easily customize the appearance of the game to their liking, improving from the default included ProgrammerArt pack. Even high-res packs can be loaded, shown here are Faithful32 and Sphax PureBDCraft 64x64, installed simply by downloading and dragging the .zip into the browser.

Meshing. With textures out of the way, another problem became apparent: severe lag spikes when breaking or placing blocks. After some investigation, finally isolated it to the chunk remeshing algorithm, which is used to generate the vertices and faces given voxel data to send to WebGL for rendering.

The voxel module provides a handful of meshers, most notably ‘culled’ and ‘greedy’. The former is an improvement of the simplest possible algorithm, culling the hidden faces, but still providing only one face per voxel; the latter is a bigger improvement, meshing together adjacent voxels into one face therefore significantly reducing the number of vertices to upload. Changing the mesher from culled to greedy fixed the block lag, but introduced a visual problem:

culled mesher
greedy mesher

The effect may be appear subtle in these screenshots, so here’s another block configuration which makes it more obvious:

Undesired texture stretching across greedy meshes with texture atlases

The textures are stretched! Instead of the texture (grass side, in this case) repeating for each voxel, there is one texture across the entire greedily-meshed face.

As a workaround, I tried reverting to an older version of shama’s voxel-texture before it began to use texture atlases and set UV texture coordinates, but then texture upload bandwidth became the new bottleneck. Turns out there is a solution giving the best of both worlds, @mikolalysenko eloquently explained on his seminal 0fps blog posts, Texture atlases, wrapping and mip mapping (highly recommend reading this post and the others on his blog, as they are extremely informative).

Unfortunately the greedy meshing + texture atlases demonstration was not compatible with the three.js library used by voxel-engine, so I forked voxel-texture into voxel-texture-shader to port it. Using a custom three.js ShaderMaterial to set the proper texture coordinates from the fragment position within the voxel and optional four-tap sampling to fix the bleeding seams. voxel-texture-shader also supports artpacks texture loading as described above. Though the three.js integration is a bit of hack, it solved the problem at the time (revisited it in the future with the ndarray/gl-now based voxel-shader and voxel-mesher derived from mikolalysenko’s ao-shader and ao-mesher, but now we’re getting ahead of ourselves).

Mining. Now with rendering down (at least for now), the lack of gameplay elements in the demo became more noticeable. Blocks could be “mined” (broken) instantly, just by left-clicking once, for example. So I added a finite mining time and block break overlay:

voxel-mine, mining in progress

Holding down the left mouse button increases the block break progress over time:

about to break! (note: the block break (and block) texture shown here is retrieved from the artpack module, in this screenshot, it is ProgrammerArt, but other texture packs can be loaded, too)

until it eventually ‘breaks’. The voxel-mine plugin shown here relies on a new module, voxel-reach, to listen for mining events. Later it became possible to use different tools to mine certain blocks faster, and to configure the relative hardness per block type.

Plugins. With the infinite possibilities in a voxel-style game, how to best manage extensibility? The Node Packaged Modules (NPM) system voxel.js is built on (via browserify) encourages small reusable modules, a breath of fresh air in comparison to the monolithic design of other platforms, but NPM itself does not provide a “plugin” system, per se. Modules can specify “peer dependencies”, but it does not provide the full functionality I was looking for in a plugin system (and there is talk about removing it). Hence, voxel-plugins, a simple modular system for loading, enabling, and disabling addons to voxel.js.

voxel-plugins builds on the existing voxel.js convention of exporting a factory constructor which takes (game, opts) arguments, where game is a global game object (voxel-engine instance), and opts is a hash of options (think of it as the plugin configuration file). Modules already supporting this convention can be loaded without changes, and others can be loaded with only minor modification. Furthermore, plugins can optionally implement an API to support runtime enable/disable support, allowing the player to toggle the plugins on or off:

voxel-plugins-ui example, using dat-gui, with a small number of plugins loaded and voxel-fly disabled

Not all plugins would make sense to support enabling/disabling, but many do. For example, disabling the fly plugin completely removes the event handlers making flight possible. With some refactoring, I was able to get voxpopuli down to consist entirely of plugins (~50 at this time).

Terrain. With modularity nailed down, time to get started on writing plugins. voxel-mine etc. were soon refactored into plugins, before tackling chunk generation.

In voxel.js, the world is segmented into 32x32x32 cubic chunks, by default. Interestingly, Minecraft also uses chunking, but in the shape of a rectangular prism instead: 16x256x16 (or 16x128x16 in earlier versions), that is, vertical columns with limited fixed height. The X and Z dimensions in MC are practically infinite (modulo the Far Lands, due to precision limits — voxel.js has a similar issue), but Y cannot normally exceed 256 — though there is an unofficial Cubic Chunks modification to change this. With voxel.js, chunks are already cubic. This may have novel gameplay implications later on.

As for the terrain itself, to move beyond a more interesting landscape than a valley, some kind of continuous noise can be used. maxogden’s voxel-perlin-terrain and voxel-simplex-terrain served as good starting points, showing Perlin and Simplex noise, respectively (very useful algorithms both invented by Ken Perlin in 2001 and earlier). substack’s voxel-trees added some vegetation, and rachel-carvalho’s voxel-mars (which lets you explore a the topology of another planet using data captured by NASA in 1996 using voxel.js, check it out at voxelmars.com) used web workers to efficiently generate the chunks. Combining several of these techniques, we now have:

voxel-land, simplex noise grassy terrain with trees

The surface is all grass, below that is dirt, and further below solid stone, occasionally interspersed with coal ore and iron ore. By this point, we are using quite a few blocks, leading to…

Registry. To keep track of all the block types in the game, you can use voxel-registry. Registering a block allocates a 16-bit numerical voxel index and sets optional properties, which can be later looked up by name, during terrain generation or otherwise. No fancy screenshots here, but this added level of indirection is a huge benefit in avoiding hardcoded static “magic numbers”, providing more flexibility when adding new blocks.

This same plugin also now handles items, which have no numerical index (not needed), and metablocks — blocks with additional state (rotation or so on, analogous to MC’s metadata, except it is not limited to 4-bits (at the time of this writing)), which will become important later.

Inventory. Now that players mine blocks and plugin developers can register their own blocks, having a means to store said blocks is only logical. The itempile module represents items which can group together (pile/stack), and inventory holds a fixed number of slots for these item piles. These modules implement the low-level merging/splitting logic and have no UI, but can be interacted with through an inventory-window:

excerpt from the interactive inventory-window demo

You can click to pick up and drop the items in an inventory window as you would expect (well, not in the above static screenshot, but you can in the interactive demo). An inventory window is used for the player’s hotbar:

voxel-inventory-hotbar

which shows a subset of the items the player is currently carrying, including their currently “held” item; in this screenshot, an iron pickaxe about to mine coal ore. Pressing ‘E’ (can be rebound) will open your full inventory:

voxel-inventory-crafting

Besides letting you rearrange your inventory, this dialog lets you do something else:

Crafting. The built-in inventory screen pictured above provides a 2x2 crafting grid, backed by the low-level craftingrecipes module. Recipe types supported include amorphous (given ingredients in any order) and positional (ingredients in a specified shape). A larger, 3x3 grid can be accessed by building a workbench, and then right-clicking to open the interface:

voxel-workbench GUI (note: screenshot is outdated, block textures are now 3D CSS cubes, and the pixel art is no longer bilinearly interpolated, but you get the idea)

There are a couple things worth noting going on here. voxel-workbench registered a workbench block using voxel-registry, and added a crafting recipe for it (not shown: 4 planks, amorphous) using voxel-recipes. Right-clicking the block triggers voxel-reach, and then voxel-use which recognizes you are attempting to use the block placed in the world (the workbench), so it opens the registered onUse handler. [Note that right-clicking is also used to place blocks from your inventory; but GUI opening overrides this behavior, unless the player is crouching (by holding shift — in case you want to place a block in the world against a workbench, for example, instead of opening the workbench GUI itself).] The recipe pictured above was added by voxel-pickaxe, which in turn registers pickaxe items with the appropriate registry properties to provide a mining speedup for voxel-mine, and when you mine with it the blocks make it to items in your inventory (backed by voxel-carry) using voxel-harvest.

Whew, that’s a lot of plugins! To better support seamlessly integrating numerous plugins, craftingrecipes also supports a crafting thesaurus, where items can be registered as equivalent for crafting purposes. For example, recipes with wooden logs can use oak or birch logs equivalently, not unsimilar to the functionality provided by the Forge ore dictionary.

Chests. Need more space to store your items? A chest is for exactly that:

voxel-chest

Crafted with planks and placed in the world, right-clicking the chest opens up an interface showing both your player inventory (here, it is empty) and the chest contents, 10 by 3. As a handy convenience, shift-clicking the items will quickly transfer them between the chest and player inventory (using the inventory-window concept of ‘linked’ inventories).

How are the items stored within the chest, you might be wondering? The <16-bit address space of voxel-registry metablocks is not nearly enough, so instead the data is stored out-of-band, using voxel-blockdata. Using this plugin, arbitrary data is stored within the voxel chunk format, keyed by the block location. This blockdata scheme can be compared to MC’s concept of a block entity.

Furnaces. Another GUI-enabled block, allows you to smelt ores:

voxel-furnace

Like the workbench, furnaces also use voxel-blockdata and voxel-recipes.

ClientMC. Now for something a bit different. What if you could use voxel.js to connect to an actual Minecraft server?

voxel-clientmc

voxel-clientmc is the start of such a plugin, using node-minecraft-protocol to parse the MC protocol in the browser, and wsmc to proxy the MC server to and from a WebSocket where your browser can reach it. The above screenshot is from voxel.js connecting to a real MC 1.7 server.

Implementing a full MC client is a lot of work, and is somewhat of a moving target; voxel-clientmc is therefore very incomplete, more of a proof-of-concept than anything. See also, the more complete projects: mineflayer (client API using node-minecraft-protocol; mineflayer-voxel), and ThinkMap (3D map viewer using WebGL and haxe). voxel-clientmc may not reach a usable level of completeness, but it was a fun experiment at least.

Console. With the possibility of remote server connectivity, a chat/command console could be useful:

voxel-console plugin (wraps console-widget), bridged over voxel-clientmc, formatting text with tellraw2dom

Plugins can register their own commands, and voxel-commands provides a few handy commands itself, for: teleporting to specific coordinates or to the player home position, cheating in items into the player inventory, setting/getting blocks (including with optional voxel-blockdata tags), and listing or enabling/disabling plugins (CLI alternative voxel-plugins-ui GUI).

Voila. Continuing the trend of providing more debugging/testing/informative functionality, and in addition to the possible difficulty of recognizing my quick-and-dirty ProgrammerArt textures, wouldn’t it be nice if there was a label showing the name of the currently-highlighted block?

voxel-voila

Using voxel-registry to supply the display names, voxel-voilà provides this much-needed feature. Note: the name is phonentically similar to the popular Waila (What Am I Looking At) modification for MC.

In case this and other UI elements become too distracting, you can toggle them off (default F1 key) using voxel-zen (the name inspired by GitHub’s Zen Writing Mode, which similarly removes distractions):

voxel-zen before
voxel-zen after

Server. Separate from the potential for connecting to MC servers, more realistic is the possibility of connecting to native voxel.js servers. NodeJS supports running JavaScript server-side, so much of the JavaScript code used in the browser can be reused.

voxel-server and voxel-client, lately largely refactored by @kumavis for node-warrior, now supports multiple transports including WebSockets and even better, peer-to-peer WebRTC. As part of the client/server split, my voxel-fuel provides a loader for setting up both client and server engines, including loading plugins in each instance. I made some progress integrating these changes with my game but there is still much work to be done to make it generally usable, to be determined.

Meanwhile @mikolalysenko posted an excellent four-part series Replication in networked games, many of the techniques therein could be applicable to voxel.js multiplayer. At least as a start, many of my plugins now load on server, or opt-out of loading if client-only.

As aside, it is an interesting question whether a game should include multiplayer or remain single-player only, and reap the benefits of simplicity single-player-only provides (albeit the downside of lack of cooperative play). @FlowerChildXL, developer of acclaimed Better Than Wolves total conversion modification for MC, also raised this question in the development of his new in-progress independent voxel-style game, Return to Home: The Question of Multiplayer in RTH.

Health. Back to adding usual gameplay elements. Here’s a simple mechanic: health. voxel-health stores the player health and provides an API for hurting/healing; voxel-health-bar shows a primitive health bar:

voxel-health-bar

Health decreases proportionally when a player falls from a minimum height (fall damage). Health can be increased by eating items added by voxel-food:

you know you want to eat that cookie
cookie (added by voxel-food) eaten, restoring your voxel-health as reflected in the voxel-health-bar

When the player receives fall damage, an event is emitted which voxel-sfx listens for to play a hurt sound effect (loaded from the artpacks, cascading just as texture artwork cascades).

Webview. Browsing /r/minecraft, stumbled across a post And this is why I love mods, showing off the Web Displays modification for Minecraft by montoyo, embedding a web browser into the game. The top reply by /u/drasdafy was had an intriguing idea, would it be possible to use Web Displays to play the web-based (Java applet) version of Minecraft within Minecraft? The answered turned out to be no, because Web Displays was using a browser without Java support, but I was intrigued and curious if something similar would be possible with voxel.js.

Jerome Etienne wrote about a clever technique in Mixing HTML Pages Inside Your WebGL (Apr 30th, 2013), punching a hole through the WebGL canvas and shining a 3D CSS rendered DOM element through it. 3D CSS transforms are the same technology used in cube-icon to provide 3D blocks for inventory-window, and they can be matched up to a three.js WebGL scene using the CSS3DRenderer example from three.js. To wit:

voxel-webview showing an embedded iframe with the npmjs.org NPM website

The webpage behaves like a 3D object, in that you can walk around it and it transforms as you would expect (even inverting when viewed from the back), the CSS transforms in line with WebGL. With some added hooks you are also able to interact with the webpage, scrolling or clicking links.

And yes, to answer the original question, you can embed another instance:

voxel-webview loading another voxel.js engine, for no good reason

After all, it is just an <iframe>. Surprisingly runs fairly well. Nesting further is possible, but quickly runs into performance issues:

a completely unnecessary nesting of several voxel.js engines in themselves by abusing voxel-webview

Fluids. Back to more fundamental mechanics, how about water? First I implemented buckets and a fluid registry:

voxel-bucket: right-clicking will empty the bucket, giving you an empty bucket and placing water in the world — uses voxel-fluid

The other water/fluid-related mechanics are trickier. Water is not completely transparent like glass, nor completely opaque, but in between:

voxel-texture-shader rendering alpha transparent water with transgreedy meshing

voxel-texture-shader passes through the alpha channel from the texture .png, but this is not the complete story. The interior faces behind the water must also be rendered, or an x-ray effect will occur. Fortunately, @vogonistic developed a ‘transgreedy’ mesher (originally for mineflayer-voxel, a tool for visualizing mineflayer bots using voxel.js) to handle this, shown above, I was able to integrate it. But the voxels are meshed twice, lowering performance, some faces are unexpectedly rendered (consider chunk boundaries), occasional flashing (may be a voxel-texture-shader issue) and a few other issues (@mikolalysenko’s ao-shader and ao-mesher solve most of these problems, efficiently meshing fully opaque or transparent textures, but do not implement front-to-back sorting for full alpha transparent support). Something to look into in the future.

Flowing was implemented by @shama in voxel-virus, allowing water to spread onto another nearby blocks. Much more could be done with this, expanding to not only include water flow but lava or other liquids, or any kind of growth or spreading, possibly using voxel-registry metablocks for growth state or fluid height.

Skyhook. Another small but useful module. Normally, blocks can only be placed against other blocks. The skyhook can be placed in mid-air:

voxel-skyhook

Inspired by the Angel Block from the Extra Utilities modification for Minecraft by @rwtema. While the voxel-skyhook plugin by itself may not be all that notable, it does help show where I’m come from with the voxel-plugins design. Any new blocks with unique functionality could be implemented in its own plugin, and enabled by the user as desired.

Wool. Lets add more blocks, with different variations. “Wool” is a good choice, since it can come in different colors, and I already drew textures for it when completing the ProgrammerArt resource pack for MC 1.7:

voxel-wool

Of course, my artwork isn’t the best, but since textures are loaded via artpacks, different texture packs can be used, such as Faithful32:

voxel-wool with Faithful32, high-resolution 32x32

But something else is going on here. All of the blocks are fundamentally the same, except for their color. As alluded to earlier, voxel-registry supports metablocks to help out in this situation. Instead of registering each block individually, you call registerBlocks() and give it the number of “states” your block needs, in this case 16. Then you can set your block property values to a function, and it is called with the state value as an argument, when read with getProp(). The textures and display names for wool as shown above are set dynamically (dynamic properties).

It is worth noting, the number of states is not limited to 16. Plugins can request as many or as few states as they need, as long as it fits within the 16-bit global block index limit. Internally, a block is registered for each state, and its state value is calculated by subtracting from the base index. With this strategy, it is possible to efficiently attach small amounts of data to blocks for various purposes (the wool color in this case).

Pumpkins. A more complex test of metablocks:

voxel-pumpkin

The pumpkin’s carved “face” (pardon my textures) is controlled by the block’s meta value, and can be facing in any cardinal direction, or not present at all. Shears (or scissors) are used to carve the pumpkin, creating its face, and a lighter can be used to light it up, turning it into a jack-o’-lantern. A better screenshot using Sphax PureBDCraft 64x64 textures, to show it is possible:

voxel-pumpkin with Sphax PureBDCraft 64x64

These pumpkin blocks have a few more interesting properties. Since they can either be uncarved (no face), or face north/south/east/west, and either lit up or dark, there are 9 possible states. Shears and lighters alter these states. However, unlike wool, you don’t want the state necessarily to be preserved when the block is mined and drops an item in your inventory.

To see why, consider if the full state was preserved. A pumpkin oriented north for example, would drop a different item than a pumpkin oriented south, annoyingly clogging up the inventory slots.

To solve this, voxel-pumpkin overrides the itemDrop property, and drops an appropriate non-directional item depending on the block state. Similarly, when the item is placed, the overridden onUse property sets the block orientation appropriately such that it faces towards the player.

(Why pumpkins, of all things, for demonstrating metablocks and orientation? First of all, since I have a complete MC texture pack already I wanted to use an existing MC block. Doors, signs, ladders, stairs, hatches, fences, or other blocks may seem like a better choice to implement first, at first, but there is a problem: they are not fully solid, so custom block models may be required. Pumpkins satisfy the requirement of directionality and they are also completely solid.)

Decorative. More blocks the better, and these were easy to add:

voxel-decorative

They are not anything special, but add more variety to the game. The storage blocks are craftable using 9 of their constitutent item (9 coal to a coal block), uncraftable by themselves (a coal block to 9 coal), and the bricks are craftable with 2x2 smooth stones.

Creative. Now we’re accumulating quite a few items and blocks, would be nice if there was an easy means to access them all. Here you go:

voxel-inventory-creative

The “creative inventory” dialog replaces the normal survival inventory crafting dialog when in creative mode. Game mode toggling is handled by voxel-gamemode, adding the .creative and .survival commands to voxel-commands. That is, you can type “.creative” (the period brings up the console), return, then hit “E” to open the creative inventory shown above.

All the registered items and blocks are iterated (over voxel-registry) to populate this inventory. To help organize, items/blocks can be grouped together with the creativeTab property, which creates the different sections pictured above (“decorative” is selected in this case, showing voxel-wool and voxel-decorative).

Drop. voxel-drop uses the HTML5 File API to literally allow the player to drag and drop files from their disk onto the game in their browser. As previously mentioned, .zip archives can be dropped to load texture packs, but you can also drop .js or .coffee files to dynamically load plugins, or even .dat to load a subset of MC player.dat inventory files, for some reason. They are loaded using playerdat, which uses nbt-js for parsing this data. This was mainly an experiment in parsing MC’s Named Binary Tags (NBT) — what else could one conceivably drop to load into a voxel.js game in the future?

Keys. voxel-keys provides keyboard events for plugins. Supports bindings from both kb-bindings and game-shell:

This is actually kb-bindings-ui, a dat-gui to allow reconfiguring the bindings to the user’s liking

three.js. Then I updated to three.js version r66, from the previous r58 series. Slightly non-trivial since there were a few major changes, though they provide a migration guide to help updating. The most noticeable was the removal of Face4 (quads, removed in r60), so you need to use two Face3 (triangles) in its place.

ndarray/gl-modules: While three.js is a pretty good library and it has served us well, it does have some important limitations. I first ran into these limitations quite early on, developing voxel-texture-shader with a custom shader. @mikolalysenko had already implemented an voxel shader and mesher in ao-shader and ao-mesher, but in a custom voxel format not compatible with three.js. voxel-texture-shader ported mikolalysenko’s GLSL code for use with three.js ShaderMaterial, but the fit was awkward (or maybe I’m just not too experienced with three.js), so it was high time to start looking into using the original.

Much earlier, @shama also posted Where I have failed voxel.js (Oct 31, 2013) describing the problems inherent in peer dependencies. And later, voxel/issue#4 ndarray/gl-now integration:

That and realizing having three.js part of voxel-engine is the reason most voxel plugins have a horrible peer dependency to the game instance. I think all focus should be towards integrating ndarrays and gl-now into voxel-engine (or better yet, as separate rendering modules).

Intrigued, I started looking into ndarray. Start here, another classic blog post by @mikolalysenko: Implementing Multidimensional Arrays in JavaScript. ndarray implements strided arrays, a superior alternative to arrays-of-arrays for multidimensional array performance in JavaScript:

Strided arrays are much faster than arrays-of-arrays, and only slightly slower than raw typed arrays (source: Implementing Multidimensional Arrays in JavaScript)

The emerging module ecosystem of ndarray is quite nice, with small modules implementing various useful functionality, especially focused around graphics. The ndarray modules work well with gl-now, game-shell, and the related gl-modules suite as demoed on http://modules.gl/. Images can be represented as 2-dimensional arrays or more commonly 3-dimensional if taking the color channel as another dimension, for example. Specific parts of WebGL are wrapped in little modules like gl-buffer, gl-vao, gl-fbo, gl-texture2d, rather than a huge monolithic library like three.js. And they follow semantic versioning.

So I fired up voxel-mipmap-demo and began to dig in. It uses an orbit camera, where the user can drag to rotate the 3d model — useful for demos, less so for first-person games. Swapped it out with @hughsk basic-camera, then added WASD and mouse look controls in game-shell-fps-camera. Would like more advanced movement as with @chrisdickinson’s voxel-control, but this would good enough for testing. Next up: textures again.

Stitching. voxel-mipmap-demo uses a prebuilt texture atlas (isabella-texture-pack), loaded as one image, mipmapped using tile-mip-map, then uploaded to the GPU. To support loading from artpacks, I started building a compatible array (5-dimensional, two extra for the tiles) for tile-mip-map using voxel-stitch.

However this design turned out to be problematic for a few reasons. With the tiles in an array, a mipmap would be computed for each, even if there was no texture in that tile slot, leading to noticeable performance degradation with large texture buffers (2048x2048 is supported by 100% of WebGL users according to WebGLstats, so I figured it would be a reasonable default atlas size). Secondly, textures of different sizes could not be mixed, as the tile size has to be defined a priori.

voxel-texture solved both problems in the past by instead packing textures as arbitrary rectangles into an atlas using atlaspack. So voxel-stitch now uses atlaspack, and generates mipmaps using rect-mip-map — iterating over the packed rects to generate each mipmap.

Sides. ao-mesher originally used the voxel ID as the texture ID, for all faces of the voxel; it now optionally indexes the voxel ID and side through a voxelSideTextureIDs array to support distinct textures on each side:

ao-mesher with voxelSideTextureIDs

This allows the grass block to be rendered as expected (grass on top, grass/dirt on sides, dirt on bottom). voxel-stitch computes the side texture array from the voxel-registry texture properties for each block, specified in a convenient array format from voxel-texture expandName(): a singular value to use the same texture on all six faces; two-element array for top/bottom and sides; three-element for top, bottom, and sides; and so on.

High-res. To support high-resolution and mixed low-resolution textures, in addition to the aforementioned atlas packing change, the voxel format also needed to be enhanced. The ao-mesher demo uses 8 bytes as follows:

x, y, z, ambient occlusion
normal_x, normal_y, normal_z, tex_id

In my enhanced fork, voxel-mesher, I changed this to:

x, y, z, ambient occlusion
packed_normal, tex_size, tex_id_hi, tex_id_lo

The normal is packed into two bits per axis (6 bits total), so it can fit into a byte (with room to spare for possible future expansion to other purposes). The texture ID is expanded from 8-bit to 16-bit to support larger atlases, and a tex_size byte is added, for the base-two logarithm of the texture dimensions (so it only needs 3 or 4 bits).

The ao-shader fork, voxel-shader, reads this corresponding voxel format and renders it accordingly. With these changes, the ndarray/gl-based platform is about at parity with the three.js-based voxel-engine in terms of voxel texturing.

game-shell-voxel. Putting it all together into game-shell-voxel (live demo):

game-shell-voxel

The terrain is the same as mikolalysenko’s voxel-mipmap-demo, but now supports 16-bit block and texture indices, high-res textures (more screenshots), FPS-style movement, a keybinding UI, plugin system, and loading custom texture packs by drag-and-drop.

OK, so the game-shell-voxel experiment works standalone. But many voxel.js plugins rely on the voxel-engine API, it would seem a good idea to integrate it for compatibility. deathcap/voxel-engine#ndarray has my progress on this effort thus far.

Chunking. The ndarray demos only render one “chunk”, a cubic section of terrain meshed and rendered together. To support infinite voxel worlds we need multiple chunks; the ‘voxel’ module used by voxel-engine includes a chunker for this purpose. @shama is working on a new chunker to help avoid peer dependencies, and @hughsk wrote an ndarray-continuous module which may help out too, but I decided to look into integrating the existing voxel/chunker.js implementation with ndarray.

voxel-example (live demo), a testbed built off deathcap/voxel-engine#ndarray, can now render chunks from the valley terrain from day 1:

voxel-example chunking

And we’re almost back where we started =).