For those who don’t know, Minecraft is an inspiring indie game that places the gamer into a sandbox 3d world, where everything is made of blocks. Blocks can also be crafted into other blocks through recipes. Besides having over 10 million users and being feature on Techcrunch, what makes it quite unique is what the users have created with it: from a 3d block replica of the enterprise, to giant spaceships and entire 8 bit CPUs. Users even created a real time kinetic world terraformer, a tool that lets you use a 3d printer to bring to physical world creations from minecraft, and tools to import good old fashioned 3d models into minecraft.
As expected, Minecraft has its tools and mods. And even though there are several libs for view the world of a save file, I found that, in spite of its vibrant community, there were no libs for editing the world. Note that MCEdit allows some hacking, but it is mostly a GUI editor (and a very good one in my opinion).
So I hacked together a simple library for manipulating the world files called RubyCraft. To illustrate the simplicity it enables, turning the first chunk completely into gold is a simple one line:
Region.fromFile(filename).chunk(0, 0).block_map { :gold }
And making all blocks into orange wool is as simple as
Region.fromFile(filename).chunk(0, 0).each do |b| b.name = :wool b.color = :orange end
The result:
Image may be NSFW.
Clik here to view.
The issue with this Api is that it leaks a bit the Minecraft abstraction of how the world is divided. In a nutshell, the world is divided into region files, each one is divided into a 32 x 32 matrix of chunks, which is nothing more than a 16x16x128 cube of blocks. To manipulate the chunks inside a region file, you can request a cube, giving its initial point, width, length and height. The same code above could be written ignoring the chunk abstraction like this:
r = Region.fromFile(filename) c = r.cube(0, 0, 0, :width => 16, :length => 16, :height => 128) c.each do |block, z, x, y| block.name = :wool block.color = :orange end
A cube can span several chunks, but at the moment it can’t span several regions. It might not be a big issue, as a Region is a pretty large area (it contains over 33 million blocks), and it can take a while to save an entire region (the time it takes to save a Region is proportional to the changed chunks), even in JRuby (which I found to be 3 times as fast than MRI for this particular task).
A Gnuplot in my Minecraft
Image may be NSFW.
Clik here to view.
Edit: The save file for the resulting world can be found here.
After turning Minecraft world file into a 3d matrix, making a two real function plotter quite simple. The plotting_example.rb mostly contains code that decide the area where the graph will be plotted, centering the function on the xz axis, and more importantly, it plots f(x, y) for a given f:
def plot(function, fillFunction) cube = getCube middlePointX = length / 2 middlePointZ = width / 2 centeredF = proc do |x, z| function.call(x - middlePointX, z - middlePointZ).ceil end points = Set.new yzraster(centeredF, points) yxraster(centeredF, points) modifyBlocks(cube, centeredF, fillFunction, points) end
Quite straightforward. The functions yxraster and yzraster have a mild subtlety: just plotting the points of f(x, y) can prevent a look from looking continuous. In general plotting algorithms you have to find a plane or another elementary surface to approximate a small region. As minecraft only contains blocks, I’ve joined all points by discrete line segments, using Bresenham’s line algorithm (source here). This is done by transversing the plotting cube with xy planes, and then with zy planes (therefore only the 2d version of Bresenham algorithm is needed).
Also note that f(x, y) is coerced into integer values by taking the ceil. This is because Bresenham’s algorithm expects points defined on Z x Z, but is expected, as the resulting points would have to be coerced into a integer y coordinate anyway because of the Minecraft world definition.
With all of this, the following examples are easy to create:
Diamond Cone:
plotWith :diamond_block do |x, z| sqrt((x** 2 + z ** 2) / 3) * 5 + 20 end
Image may be NSFW.
Clik here to view.
Water Hyperbolic Paraboloid
plotWith :water do |x, z| (x** 2 - z ** 2) / 3 + 50 end
Image may be NSFW.
Clik here to view.
Lava Surface 10 of Gnuplot examples
plotWith :lava do |x, z| log(x ** 4 * z ** 2 + 2) + 20 end
Image may be NSFW.
Clik here to view.
Netherrack Surface15 of Gnuplot examples
plotWith :netherrack do |x, z| (sin(sqrt(z ** 2 + z ** 2)) / sqrt(x ** 2 + z ** 2)) * 30 + 30 end
Image may be NSFW.
Clik here to view.
Golden rotated Sine
plotWith :gold do |x, z| sin(sqrt((x** 2 + z ** 2)) / 2) * 10 + 30 end
Image may be NSFW.
Clik here to view.
Ice Sphere (half sphere actually)
plotWith :ice do |x, z| sqrt(18**2 - x**2 - z **2) + 30 end
Image may be NSFW.
Clik here to view.
Wooden Polynomial
plotWith :log do |x, z| x /= 5 z /= 5 (x + z) ** 5 + x**3 + z**2 + 30 end
Image may be NSFW.
Clik here to view.
Obysidan Polynomial Quotient
plotWith :obsidian do |x, z| p1 = (x + z) ** 6 - x ** 3 + z **2 + 50 p2 = x ** 7 + 6* z ** 6 - x **4 - z**2 + 30 p1 / p2 + 10 end
Image may be NSFW.
Clik here to view.
Colorful Paraboloid
plot(proc {|x, z| (x** 2 + z ** 2) / 3}, proc do |b, z, x, y| b.name = :wool; b.data = OrderedColors[y * 16 / 128].data end)
Image may be NSFW.
Clik here to view.
The Ordered Colors of the Colorful Paraboloid are the Wool Colors of minecraft sorted by distance to the black color. The distance definition is the same as the one from the kinetic experiment.
The plotting class cannot plot parametric surfaces at the moment. However, since the graphs are real minecraft objects, they can be manipulated as any other minecraf object. For instance, it is possible to turn the golden rotated sine into a roller coaster (source here):
Image may be NSFW.
Clik here to view.
Image may be NSFW.
Clik here to view.
All the code is open source and can be fount on Github.
Acknowledgements
The examples use an edited version of the Low Dirt Tyken‘s test world. The screenshots were take while flying using Single Player Commands mod. The algorithm for parsing the region file was based on Weeble’s work. Parsing the nbt binary is done through NbtFile gem.