3D mazes with Lua, OpenSCAD, and Blender
Way back in 2015, I posted a language review about Lua. In that post, I ported an even older 2D maze generator I implemented in Python when I was in secondary school on a Raspberry Pi (this was one of the first experiences I had with the Raspberry Pi). I talked about how Lua was easy to get started with, but difficult do anything serious because everything starts from 1, not 0 - and that immutable strings are awkward.
Since then, I've gained lots more experience with the language. As an aside, I discovered a nice paradigm for building strings:
local function string_example()
local parts = {} -- Create a table
table.insert(parts, "This is ") -- Add some strings
table.insert(parts, "a ")
table.insert(parts, "string")
return table.concat(result, "") -- Concatenate them all at once and return
end
Anyway, before I get too distracted, I think the best way to continue this post is with a picture:
Fair warning: This blog post is pretty media heavy. If you are viewing on your mobile device with a limited data connection, you might want to continue reading on another device later.
Pretty cool, right? Perhaps I should explain a little about how I got here. A month or two ago, I rediscovered the above blog post and the Lua port of my Python 2d maze generator. It outputs mazes like this:
#################
# # # #
### ##### ##### #
# # # # #
# # # # # ##### #
# # # # #
# ### # ### #####
# # # #
#################
(I can't believe I didn't include example output in my previous blog post!)
My first thought was that I could upgrade it to support 3d mazes as well. One thing led to another, and I ended up with a 3D maze generator that output something like this:
#################
#################
#################
#################
#################
#################
#################
#################
#################
#################
# # # #
# ### ###########
# # # #
# ####### ### # #
# # # # #
# ### ####### # #
# # # # #
#################
#################
##### ### #######
#################
############### #
#################
############# # #
#################
# ########### ###
#################
#################
# #
# ### ###########
# # # #
# ###############
# #
##### # ####### #
# # # # # #
#################
#################
#################
#################
#################
#################
#################
#################
#################
#################
Each block of hash (#
) symbols is a layer of the maze. It's a bit hard to visualise though, so I decided to do something about it. For my masters project, I used OpenSCAD to design a housing for an Internet of Things project I did. Since it's essentially a programming language for expressing 3D models, I realised that it would be perfect for representing my 3D mazes - and since said mazes use a grid, I can simply generate an OpenSCAD file full of cubes for all the locations at which I have a hash symbol in the output (the data itself is stored in a nested table setup, which I then process).
This is much better. We can clearly see the maze now and navigate around it. OpenSCAD's preview controls are really quite easy to pick up. What you see in the above screenshot is an 'inverted' version of the maze - i.e. instead of carving out a solid block, the algorithm walks around an empty space inside a defined region.
The algorithm that generates the maze itself is pretty much the same as the original algorithm I devised myself in Python (which I've now lost, sadly - as I didn't use Git back then).
It starts in the top left corner, and then does a random walk around the defined area. It keeps track of where it has been in a node list (basically a list of coordinates), and every time it takes a step forwards, there's a chance it will jump back to a previous position in the nodes list. Once it can't jump anywhere from a position, that position is considered complete and is removed from the nodes list. Once the node list is empty, the maze is considered complete and it returns the output.
As soon as I saw the STL export function though, I knew I could do better. I've used Blender before a little bit - it's a production-grade free open-source rendering program. You can model things in it and apply textures to them, and then render the result. It is using a program like this that many CGI pictures (and films!) are created.
Crucially for my case, I found the STL import function. With that, I could import the STL I exported from OpenSCAD, and then have some fun playing around with the settings to get some cool renders of some mazes:
(Above: Some renders of some of the outputs of the maze generator. See the full size image [3 MiB])
The sizes of the above are as follows, in grid squares as generated by the Lua 3d maze generator:
- Blue: 15 x 15 x 15
- Orange: 7 x 7 x 7
- Purple: 17 x 15 x 11, with a path length of 4 (i.e. the generator jumps forwards by 4 spaces instead of 2 during the random walk)
- Green: 21 x 21 x 7
Somehow it's quite satisfying to watch it render, with the little squares gradually spiralling their way out from the centre in a hilbert curve - so I looked into how to create a glass texture, and how to setup volumetric rendering. It was not actually too difficult to do (the most challenging part was getting the lights in the right place with the right strength). Here's a trio of renders that show the iterative process to getting to the final image you see at the top of this post:
(Above: Some renders of some of the blue 15x15x15 above in the previous image with a glass texture. See the full size image [3.4 MiB])
From left to right:
- My initial attempt using clear glass
- Frosting the glass made it look better
- Adding volumetric lighting makes it look way cooler!
I guess that you could give the same treatment to any STL file you like.
Anyway, the code for my maze generator can be found here on my private git server: sbrl/multimaze
The repository README contains instructions on how to use it. I won't duplicate that here, because it will probably change over time, and then this blog post would be out of date.
Before I go, I'll leave you with some animations of some mazes rotating. This whole experience of generating and rendering mazes has been really fun - it's quite far outside what I've been doing recently. I think I'd like to do some more of this in the future!
Update: I've re-rendered a new version at a lower quality. This should help mobile devices! The high-quality version can still be accessed via the links below.
(High-quality version: webm - vp9, ogv - ogg theora, mp4 - h264)