The surface of a torus is a set of points that are equidistant from some circle. That gives you the equation for a torus, which is

[math]\left(R - \sqrt{x^2 + y^2}\right)^2 + z^2 = r^2,[/math]

where R is the radius of the circle, and r is the distance from the circle to the surface. Put another way, if you have R and plug x,y, and z into the LHS there, you get the distance to the circle squared.

If you rotate and translate your coordinates around before you plug your values in, you rotate and translate your torus around, though you have to be a bit careful in that the torus is moved with the inverse of the transformations of the coordinates. But, if you're careful, then you end up with a pair of equations/functions, which tell you how far away a point is from two interlocked circles.

So then, you have a voxel grid, and you go through each voxel, calculating the distances. From that, you can work out the largest r that doesn't end up with voxels in the tori that are adjacent. I then spent some time twerking the grid to get what I wanted. Here's the python 2.7 code.

Code: Select all

`import math, png, os`

def interlockedtori(largeradius):

R = int(math.ceil(largeradius))

r2 = largeradius ** 2

dx = 4*R + 1

dy = 4*R + 1

dz = 4*R + 1

cx = dx/2

cy = dy/2

cz = dz/2

rt2o2 = math.sqrt(2)/2

def wtori(x, y, z):

x, y, z = x - cx, y - cy, z - cz

x, y = rt2o2 * (x-y), rt2o2 * (x+y)

y, z = rt2o2 * (y+z), rt2o2 * (z-y)

x -= largeradius/2

return (largeradius - math.sqrt(x**2 + y**2))**2 + z**2

def ltori(x, y, z):

x, y, z = x - cx, y - cy, z - cz

x, y = rt2o2 * (x-y), rt2o2 * (x+y)

y, z = rt2o2 * (y-z), rt2o2 * (y+z)

x += largeradius/2

return (largeradius - math.sqrt(x**2 + y**2))**2 + z**2

# create array of distance pairs.

grid = [[[(ltori(x,y,z),wtori(x,y,z)) for x in range(dx)] for y in range(dy)] for z in range(dz)]

lr2 = r2

neighbours = [ (0,0,0), (1,0,0), (-1,0,0), (0,1,0), (0,-1,0), (0,0,1), (0,0,-1)]

# determine small radius of tori.

for x in range(1,dx-1):

for y in range(1,dy-1):

for z in range(1,dz-1):

ld, wd = grid[z][y][x]

if ld < lr2 or wd < lr2:

for offsets in neighbours:

ox, oy, oz = offsets

xn, yn, zn = x + ox, y + oy, z + oz

lnd, wnd = grid[zn][yn][xn]

if ld < lr2 and wnd < lr2:

lr2 = max(ld, wnd)

if wd < lr2 and lnd < lr2:

lr2 = max(wd, lnd)

print(lr2)

print(2*(math.floor(largeradius*rt2o2 + math.sqrt(lr2))) + 3)

# create array with lava and water placed

mgrid = [[[0 for x in range(dx)] for y in range(dy)] for z in range(dz)]

for x in range(1,dx-1):

for y in range(1,dy-1):

for z in range(1,dz-1):

ld, wd = grid[z][y][x]

if ld < lr2 and wd < lr2:

print("shit")

elif ld < lr2:

mgrid[z][y][x] = 2

elif wd < lr2:

mgrid[z][y][x] = 3

# coat the lava and water with glass.

neighbours = [(1,0,0), (-1,0,0), (0,1,0), (0,-1,0), (0,0,1), (0,0,-1)]

glasscount = 0

for x in range(1,dx-1):

for y in range(1,dy-1):

for z in range(1,dz-1):

if mgrid[z][y][x] == 0:

for offsets in neighbours:

ox, oy, oz = offsets

xn, yn, zn = x + ox, y + oy, z + oz

if mgrid[zn][yn][xn] == 2 or mgrid[zn][yn][xn] == 3:

mgrid[z][y][x] = 1

glasscount += 1

break

print(glasscount)

# hollow out the lava

lavacount = 0

for x in range(1,dx-1):

for y in range(1,dy-1):

for z in range(1,dz-1):

if mgrid[z][y][x] == 2:

hollow = True

for offsets in neighbours:

ox, oy, oz = offsets

xn, yn, zn = x + ox, y + oy, z + oz

if mgrid[zn][yn][xn] == 1:

hollow = False

break

if hollow:

mgrid[z][y][x] = 0

else:

lavacount += 1

print(lavacount)

# coat the inside of the lava with cobble

for x in range(1,dx-1):

for y in range(1,dy-1):

for z in range(1,dz-1):

if mgrid[z][y][x] == 0 and grid[z][y][x][0]<lr2:

for offsets in neighbours:

ox, oy, oz = offsets

xn, yn, zn = x + ox, y + oy, z + oz

if mgrid[zn][yn][xn] == 2:

mgrid[z][y][x] = 4

break

# create printable array

pgrid = [[[0 for x in range(dx)] for y in range(dy)] for z in range(dz)]

for x in range(1,dx-1):

for y in range(1,dy-1):

for z in range(1,dz-1):

if mgrid[z][y][x] != 0:

pgrid[z][y][x] = mgrid[z][y][x]

if (mgrid[z][y][x] == 1 or mgrid[z][y][x] == 4) and pgrid[z-1][y][x] %5 == pgrid[z][y][x]:

pgrid[z][y][x] += 5

if mgrid[z][y][x] == 1 and mgrid[z-1][y][x] == 3:

pgrid[z-1][y][x] = 11

if mgrid[z][y][x] == 1 and mgrid[z-1][y][x] == 2:

pgrid[z-1][y][x] = 12

if (mgrid[z][y][x] % 5) == 4 and mgrid[z-1][y][x] == 2:

pgrid[z-1][y][x] = 7

else:

if pgrid[z-1][y][x] % 5 != 0:

pgrid[z][y][x] = 10

elif (x-cx)%5 == 0 and (y-cy)%5 == 0:

pgrid[z][y][x] = 5

d = './interlockedtorus'+str(largeradius)

if not os.path.exists(d):

os.mkdir(d)

palette = [(255,255,255), (0,255,0), (255,0,0), (0,0,255), (85,85,85), (255,255,0), (0,85,0), (85,0,0), (0,0,85), (0,0,0), (170,170,170), (255,0,255), (255,127,0)]

w = png.Writer(dx, dy, palette = palette, bitdepth=4)

for z in range(1, dz-1):

f = open(d+'/lvl'+str(z).zfill(3)+'.png', 'wb')

w.write(f,pgrid[z])

f.close()

If you run the command "interlockedtori(R)" where R is some number, it will create a folder called "interlockedtori[R]", where [R] is the string representing R, full of pngs that represent what you need to build, like this.

- lvl018.png (440 Bytes) Viewed 4683 times

note: there's a slightly tricky thing going on when you make stuff like this, which I call inner and outer round. To illustrate what I'm talking about, imagine that you made a sphere, such as are described in

this thread. Then you put a skin of dirt on it, then a skin of cobble on that, then dirt, then cobble, and say you did this five or ten times. Would the resulting figure be a sphere such as described in that thread? Answer: no, and if you put a lot of skins on it, it would look like a rounded octahedron. This is because the figure that encloses the most volume for least surface area in minecraft geometry is an octahedron. What this means is that that first skin you put on the sphere had a round inner surface, but its outer surface isn't really round, just approximately so. As you add more skins, that approximation gets less and less exact.

The relevance of this is that the layer of glass in the tori is "inner round", and the outer layer of the fluids is "outer round". If you favour a skin that renders glass as much more visible, you would probably want to make the glass outer round.

If you wanted to make the tori out of stuff that doesn't react when it comes in contact, not only would you make the outer layer outer round, you could make them a little larger if you used a grid with a voxel vertex, face or edge at the centre (I think edge would work best), rather than a voxel. That would give you slightly bigger r, and would leave you without holes,

like this.Also, there are probably better and worse input values for the code, some which will give you figures that look quite round, and others where flat spots, at the top or bottom or easternmost etc planes, are larger and more visible. Because there are several flat spots on the tori, and its not clear what priority they should have, its not really possible to give a straightforward rule for what are good input values (for spheres with a central voxel, you use an integer +1/2, but here its a lot more tricky). Regardless, at about 10 it looks pretty good no matter what.