So you want to build a maze

First off, two things you need to know:

This tutorial was made for Cinema 4D with MoGraph, so having a copy of that is important. Mazes are awesome and tedious to build by hand.

So, how do you make a maze? You don’t. That’s what computers are for. So, how do you make a maze maker? Read on.

A bit of theory

There are several algorithms that create perfect mazes (ie, that have no loops). The one used here (indeed, the most widely-used) is the depth-first algorithm, which is explained on the Wikipedia page, so I won’t get into that.

My method uses what I’ve termed “cells”, square rooms that can have up to 4 walls. The idea is that each cell is assigned a number, and this number determines where an exit goes, as follows: The north exit is given the value 1, the south exit is given the value 2, the east exit is given the value 4 and the west exit is given the value of 8. The final value of a cell is thus a number between 0 (no exits) and 15 (no walls), and is the result of simple addition. Thus, if your cell has a value of 7, it means that it’s open to the north, the south and the east (7 = 1 + 2 + 4).

Now on to the good bit.

Setup

Create a Matrix object, and set it to Grid. Add some User Data to this Matrix. You want to add Width (Integer, between 2 and 50), Height (same), Random seed (Integer, between 0 and 99999) and Cell list (In-/Exclude list).

Then, add an XPresso tag to it. The remarks should make it obvious what’s what, but just in case: Green is the Matrix object, gold are Python scripts, and blue are generic XPresso tags.

XPresso

First, get the Width and Height custom values and plug them in the Matrix Sizer script. The code is below:

# Matrix Sizer import c4d def main(): global Output1 global Output2 # Lower the width/height by 1 so the cells are right next to eachother vx = Input1 - 1 vz = Input2 - 1 # The count on X, Y and Z Output1 = c4d.Vector(Input1, 1, Input2) # The size on X, Y, Z # The cells are 200 x 200 cm, hence the value Output2 = c4d.Vector(vx * 200, 0, vz * 200) # End Matrix Sizer

The resulting values get plugged back into the Matrix object, into Count and Size, respectively.

Then, get those values again and plug them into the Instance Maker script.

# Instance Maker import c4d def main(): global Output1 # Get the object named Holder holder = doc.SearchObject("Holder") # No holder? Create it if holder == None: # Make a Null holder = c4d.BaseObject(c4d.Onull) # Call it Holder holder[c4d.ID_BASELIST_NAME] = "Holder" # Insert it into the document doc.InsertObject(holder) c4d.EventAdd() # Does the holder have fewer objects inside it than the grid size? if len(holder.GetChildren()) < Input1 * Input2: # Create a new Instance object clone = c4d.BaseObject(c4d.Oinstance) # Make sure it's not a Render Instance (ie, it creates actual geometry) clone[c4d.INSTANCEOBJECT_RENDERINSTANCE] = 0 # Stick it in the holder clone.InsertUnder(holder) c4d.EventAdd() # Are there more objects in the holder than there should be? if len(holder.GetChildren()) > Input1 * Input2: # See how many more... diff = len(holder.GetChildren()) - (Input1 * Input2) kids = holder.GetChildren() # ...then KILL THEM for i in range(diff): kids[i].Remove() c4d.EventAdd() # Use this holder object Output1 = holder # End Instance Maker

Using the Hierarchy and Object Index nodes, loop through each of the newly-created Instance objects. The Width, Height and Random seed of the Matrix, along with the Index of the current Instance, all go together into the Maze Generator script.

# Maze Generator # Shamelessly lifted from http://rosettacode.org/wiki/Maze_generation#Python import c4d import random # Magic goes here def makeMaze(w, h, seed): # Set a random seed, to make sure each cell is part of the same maze # Otherwise, it'll generate a new maze for each cell, so they won't link random.seed(seed) # The values for each exit, as per the introduction dd = {'n': 1, 's': 2, 'e': 4, 'v': 8} # Movement directions dx = {'n': 0, 's': 0, 'e': 1, 'v': -1} dy = {'n': -1, 's': 1, 'e': 0, 'v': 0} # The opposite directions op = {'n': dd['s'], 's': dd['n'], 'e': dd['v'], 'v': dd['e']} # Blank 2d array, as big as the grid grid = [[0] * w for _ in range(h)] # The actual output out = [] def carve(x, y): dirs = ['n', 's', 'e', 'v'] # Pick a direction at random, repeat random.shuffle(dirs) for dr in dirs: # The coordinates of the new cell (nx, ny) = (x + dx[dr], y + dy[dr]) # It must be inside the grid, and unvisited if (0 <= ny <= h - 1) and (0 <= nx <= w - 1) and grid[ny][nx] == 0: # Mark the new cell as visited, and recurse from here grid[y][x] += dd[dr] grid[ny][nx] += op[dr] carve(nx, ny) # We're starting at 0, 0 carve(0, 0) # Turn the 2d array into a 1d array for i in range(h): for j in range(w): out.append(grid[i][j]) # out will contain all the cells return out def main(): global Output1 # Invoke previous function, making a maze maze = makeMaze(Input1, Input2, Input3) # See which cell we're at on the grid, and what type it is Output1 = maze[Input4] # End Maze Generator

So now we have a grid of the proper size, a bunch of empty Instances, and we know what each Instance is supposed to be an instance of. Let’s put it all together.

Pipe the current Instance, the Cell list (I’ll get back to that), the output of the previous script, and the position of the current Matrix point into the Maze Cells script.

# Maze Cells import c4d def main(): # Get the current cell from the list oo = Input2.ObjectFromIndex(doc, Input3) # Name it, so we know what's what Input1[c4d.ID_BASELIST_NAME] = "Instance of " + oo[c4d.ID_BASELIST_NAME] # Hook up the cell to the instance Input1[c4d.INSTANCEOBJECT_LINK] = oo # Position it Input1.SetRelPos(Input4) # End Maze Cells

Modeling

The following list will tell you how the cell exits should be laid out:

0 : none

: none 1 : -Z

: -Z 2 : +Z

: +Z 3 : +Z, -Z

: +Z, -Z 4 : +X

: +X 5 : +X, -Z

: +X, -Z 6 : +X, +Z

: +X, +Z 7 : +X, +Z, -Z

: +X, +Z, -Z 8 : -X

: -X 9 : -X, -Z

: -X, -Z 10 : -X, +Z

: -X, +Z 11 : -X, +Z, -Z

: -X, +Z, -Z 12 : +X, -X

: +X, -X 13 : +X, -X, -Z

: +X, -X, -Z 14 : +X, -X, +Z

: +X, -X, +Z 15 : +X, -X, +Z, -Z

Pay attention to this list. It took me the better part of a week to figure it out, and it’ll save you a lot of trouble.

The actual modelling will be left as an exercise to the reader.

When you’re done with that, drag them all in that exact order into the Cell list user data field on the Matrix. The maze should update automatically.

Finish

Throw some lights, some materials, fiddle with the values, and brag to your friends. They’ll be impressed.

I hope you’ve enjoyed this thing, and let me know what you make with it.

/r/rockeh ~ reasonoptional.tumblr.com