Oh fun! May I ask what you plan on doing with this? I’ve created an isocohedron and subdivided it to create dodecahedron and further divided it to create geometric spheres which I’ve used as a planet for a hexagonal maze. Check out the video demo here:
Here is the code for it. It includes not only the isocohedron creation (function getShape) and the subdivision algorithm (function divideShape) but also the code to create the triangles for the surface and the walls (createShape), a function to find a path between two tiles (getPath), functions to test if a position is above a cell, and a function to figure out where a position is starting the search from a close cell. Maybe some of them will be useful to you? I’ve had this code just laying around for a good five or six years now.
local phi = (math.sqrt(5) + 1) / 2
local values = {[0] = 0, 1, phi}
local scale = 200
local wallHeight = 30
local columnMesh = game.ServerStorage.ColumnMesh
local floorColor = BrickColor.new("Br. yellowish orange")
local wallColor = BrickColor.new(2, 2, 2)
local floorMaterial = Enum.Material.Granite
local wallMaterial = Enum.Material.Slate
local MazeWorld = {}
function createShape(shape, wallParent, floorParent)
local function createPart(type)
local part = Instance.new(type or "Part")
part.TopSurface = "Smooth"
part.BottomSurface = "Smooth"
part.Anchored = true
part.FormFactor = "Custom"
return part
end
local function createColumn(pos, parent)
pos = pos + pos.unit * wallHeight/2
local zAxis = Vector3.new(pos.y, -pos.x, pos.z)
local yAxis = pos
local xAxis = yAxis:Cross(zAxis)
local part = createPart()
part.Size = Vector3.new(5, wallHeight, 5)
part.CFrame = CFrame.new(
pos.x, pos.y, pos.z,
xAxis.x, yAxis.x, zAxis.x,
xAxis.y, yAxis.y, zAxis.y,
xAxis.z, yAxis.z, zAxis.z
)
columnMesh:Clone().Parent = part
part.BrickColor = wallColor
part.Material = wallMaterial
part.Parent = parent
end
local function createSurface(surface, parent)
local a = surface[1][1]
local b = surface[1][2]
local c = surface[2][1]
if c == a or c == b then
c = surface[2][2]
end
-- Anaminus's triangle creator used in his terrain generator
-- split triangle into two right angles on longest edge:
local len_AB = (b - a).magnitude
local len_BC = (c - b).magnitude
local len_CA = (a - c).magnitude
if (len_AB > len_BC) and (len_AB > len_CA) then
a,c = c,a
b,c = c,b
elseif (len_CA > len_AB) and (len_CA > len_BC) then
a,b = b,a
b,c = c,b
end
local dot = (a - b):Dot(c - b)
local split = b + (c - b).unit*dot/(c - b).magnitude
-- get triangle sizes:
local xA = 1
local yA = (split - a).magnitude
local zA = (split - b).magnitude
local xB = 1
local yB = (split - a).magnitude
local zB = (split - c).magnitude
-- get unit directions:
local diry = (a - split).unit
local dirz = (c - split).unit
local dirx = diry:Cross(dirz).unit
-- get triangle centers:
local posA = split + diry*yA/2 - dirz*zA/2
local posB = split + diry*yB/2 + dirz*zB/2
-- place parts:
local partA = createPart("WedgePart")
partA.Name = "TrianglePart"
partA.Size = Vector3.new(xA,yA,zA)
partA.CFrame = CFrame.new(posA.x,posA.y,posA.z,
dirx.x,diry.x,dirz.x,
dirx.y,diry.y,dirz.y,
dirx.z,diry.z,dirz.z
)
partA.BrickColor = floorColor
partA.Material = floorMaterial
partA.Parent = parent
dirx = dirx * -1
dirz = dirz * -1
local partB = createPart("WedgePart")
partB.Name = "TrianglePart"
partB.Size = Vector3.new(xB,yB,zB)
partB.CFrame = CFrame.new(posB.x,posB.y,posB.z,
dirx.x,diry.x,dirz.x,
dirx.y,diry.y,dirz.y,
dirx.z,diry.z,dirz.z
)
partB.BrickColor = floorColor
partB.Material = floorMaterial
partB.Parent = parent
end
local function createColumn(pos, parent)
pos = pos + pos.unit * wallHeight/2
local zAxis = Vector3.new(pos.y, -pos.x, pos.z)
local yAxis = pos
local xAxis = yAxis:Cross(zAxis)
local part = createPart()
part.Size = Vector3.new(5, wallHeight, 5)
part.CFrame = CFrame.new(
pos.x, pos.y, pos.z,
xAxis.x, yAxis.x, zAxis.x,
xAxis.y, yAxis.y, zAxis.y,
xAxis.z, yAxis.z, zAxis.z
)
columnMesh:Clone().Parent = part
part.BrickColor = wallColor
part.Material = wallMaterial
part.Parent = parent
end
local function createWall(line, parent)
local pos = (line[2] + line[1]) / 2
pos = pos + pos.unit * wallHeight/2
local zAxis = line[2] - line[1]
local yAxis = pos
local xAxis = yAxis:Cross(zAxis)
local part = createPart()
part.Size = Vector3.new(3, wallHeight, zAxis.magnitude)
part.CFrame = CFrame.new(
pos.x, pos.y, pos.z,
xAxis.x, yAxis.x, zAxis.x,
xAxis.y, yAxis.y, zAxis.y,
xAxis.z, yAxis.z, zAxis.z
)
part.BrickColor = wallColor
part.Material = wallMaterial
part.Parent = parent
local part = createPart()
part.FormFactor = Enum.FormFactor.Custom
part.Size = Vector3.new(0.2, 2, zAxis.magnitude * 8.5/pos.Magnitude)
pos = pos.unit * 8.5
part.CFrame = CFrame.new(
pos.x, pos.y, pos.z,
xAxis.x, yAxis.x, zAxis.x,
xAxis.y, yAxis.y, zAxis.y,
xAxis.z, yAxis.z, zAxis.z
)
part.BrickColor = wallColor
part.Material = wallMaterial
part.Parent = parent
end
local function getGroup(node)
if node.group then
node.group = getGroup(node.group)
return node.group
else
return node
end
end
local walls = {}
for _, tile in next, shape.tiles do
for otherTile, line in next, tile.connections do
walls[#walls + 1] = {line, tile, otherTile}
tile.connections[otherTile] = nil
tile.walls[otherTile] = line
otherTile.connections[tile] = nil
otherTile.walls[tile] = line
end
end
for _, tile in next, shape.tiles do
local normals = {}
for key, value in next, tile.walls do
local normal = value[1]:Cross(value[1] - value[2])
if normal:Dot(value[1] - tile.center) < 0 then
normals[key] = {value[1], -normal}
else
normals[key] = {value[1], normal}
end
end
tile.normals = normals
end
local num = #walls
for i = 1, num do
local j = math.random(i, num)
walls[i], walls[j] = walls[j], walls[i]
end
for i = 1, num do
local j = math.random(i, num)
walls[i], walls[j] = walls[j], walls[i]
end
local i = 1
while walls[i] do
local wall = walls[i]
local node1 = wall[2]
local node2 = wall[3]
if getGroup(node1) ~= getGroup(node2) then
getGroup(node1).group = getGroup(node2)
node1.connections[node2] = wall[1]
node2.connections[node1] = wall[1]
walls[i] = nil
else
createWall(wall[1], wallParent)
wait()
end
i = i + 1
end
for _, point in next, shape.points do
createColumn(point, wallParent)
wait()
end
if floorParent then
for i, surface in next, shape.surfaces do
createSurface(surface, floorParent)
wait()
end
end
wait()
end
local function newPointFromVector3(vec, final)
return final and vec or vec.unit * scale
end
local function newPointFromXYZ(x, y, z)
local point = Vector3.new(x, y, z)
return point.unit * scale
end
local function newLine(p1, p2)
return {p1, p2}
end
local function newSurface(l1, l2, l3)
return {l1, l2, l3}
end
local function getShape()
local points = {
newPointFromXYZ( phi, 0 , 1 ); --1
newPointFromXYZ( phi, 0 , -1 ); --2
newPointFromXYZ(-phi, 0 , 1 ); --3
newPointFromXYZ(-phi, 0 , -1 ); --4
newPointFromXYZ( 1 , phi, 0 ); --5
newPointFromXYZ( 1 , -phi, 0 ); --6
newPointFromXYZ(-1 , phi, 0 ); --7
newPointFromXYZ(-1 , -phi, 0 ); --8
newPointFromXYZ( 0 , 1 , phi); --9
newPointFromXYZ( 0 , 1 , -phi); --10
newPointFromXYZ( 0 , -1 , phi); --11
newPointFromXYZ( 0 , -1 , -phi); --12
}
local lines = {
newLine(points[1], points[2]);--1
newLine(points[1], points[5]);--2
newLine(points[1], points[6]);--3
newLine(points[1], points[9]);--4
newLine(points[1], points[11]);--5
newLine(points[2], points[5]);--6
newLine(points[2], points[6]);--7
newLine(points[2], points[10]);--8
newLine(points[2], points[12]);--9
newLine(points[3], points[4]);--10
newLine(points[3], points[7]);--11
newLine(points[3], points[8]);--12
newLine(points[3], points[9]);--13
newLine(points[3], points[11]);--14
newLine(points[4], points[7]);--15
newLine(points[4], points[8]);--16
newLine(points[4], points[10]);--17
newLine(points[4], points[12]);--18
newLine(points[5], points[7]);--19
newLine(points[5], points[9]);--20
newLine(points[5], points[10]);--21
newLine(points[6], points[8]);--22
newLine(points[6], points[11]);--23
newLine(points[6], points[12]);--24
newLine(points[7], points[9]);--25
newLine(points[7], points[10]);--26
newLine(points[8], points[11]);--27
newLine(points[8], points[12]);--28
newLine(points[9], points[11]);--29
newLine(points[10], points[12]);--30
}
local surfaces = {
newSurface(lines[1], lines[2], lines[6]);
newSurface(lines[1], lines[3], lines[7]);
newSurface(lines[4], lines[2], lines[20]);
newSurface(lines[4], lines[5], lines[29]);
newSurface(lines[5], lines[3], lines[23]);
newSurface(lines[6], lines[8], lines[21]);
newSurface(lines[8], lines[9], lines[30]);
newSurface(lines[9], lines[7], lines[24]);
newSurface(lines[10], lines[11], lines[15]);
newSurface(lines[10], lines[12], lines[16]);
newSurface(lines[13], lines[11], lines[25]);
newSurface(lines[13], lines[14], lines[29]);
newSurface(lines[14], lines[12], lines[27]);
newSurface(lines[15], lines[17], lines[26]);
newSurface(lines[17], lines[18], lines[30]);
newSurface(lines[18], lines[16], lines[28]);
newSurface(lines[19], lines[20], lines[25]);
newSurface(lines[19], lines[21], lines[26]);
newSurface(lines[22], lines[23], lines[27]);
newSurface(lines[22], lines[24], lines[28]);
}
return setmetatable({
points = points;
lines = lines;
surfaces = surfaces;
}, MazeWorld)
end
local function divideShape(shape, final)
local function divideLine(shape, line, tile)
if line[6] then
if tile then
line[8].connections[tile] = line[4]
tile.connections[line[8]] = line[4]
end
return line[3], line[4], line[5], line[6], line[7]
else
local vector = line[2] - line[1]
local pnts = shape.points
local p1 = newPointFromVector3(line[1] + vector/3, tile ~= nil)
local p2 = newPointFromVector3(line[1] + vector * 2/3, tile ~= nil)
pnts[#pnts + 1] = p1
pnts[#pnts + 1] = p2
local lns = shape.lines
local l1 = newLine(line[1], p1)
local l2 = newLine(p1, p2)
local l3 = newLine(p2, line[2])
lns[#lns + 1] = l1
lns[#lns + 1] = l2
lns[#lns + 1] = l3
if tile then
local pentCons = shape.pentagonalConnections
pentCons[line[1]][#pentCons[line[1]] + 1] = l1
pentCons[line[2]][#pentCons[line[2]] + 1] = l3
end
line[3] = l1
line[4] = l2
line[5] = l3
line[6] = p1
line[7] = p2
line[8] = tile
return l1, l2, l3, p1, p2
end
end
local function divideSurface(shape, surface, final)
local pnts = shape.points
local l1 = surface[1]
local l2 = surface[2]
local l3 = surface[3]
local c1 = l1[1]
local c2 = l1[2]
local c3 = l2[1]
if c3 == c1 or c3 == c2 then
c3 = l2[2]
end
local middle = newPointFromVector3((c1 + c2 + c3)/3, final)
local tile
if not final then
pnts[#pnts + 1] = middle
pnts[#pnts + 1] = c1
pnts[#pnts + 1] = c2
pnts[#pnts + 1] = c3
else
tile = {connections = {}, center = middle, walls = {}}
end
local l4, l5, l6, p1, p2 = divideLine(shape, surface[1], tile)
local l7, l8, l9, p3, p4
local l10, l11, l12, p5, p6
if l1[2] == l2[1] then
l7, l8, l9, p3, p4 = divideLine(shape, l2, tile)
if l2[2] == l3[2] then
l12, l11, l10, p6, p5 = divideLine(shape, l3, tile)
else
l10, l11, l12, p5, p6 = divideLine(shape, l3, tile)
end
elseif l1[2] == l2[2] then
l9, l8, l7, p4, p3 = divideLine(shape, l2, tile)
if l2[1] == l3[2] then
l12, l11, l10, p6, p5 = divideLine(shape, l3, tile)
else
l10, l11, l12, p5, p6 = divideLine(shape, l3, tile)
end
elseif l1[2] == l3[1] then
l7, l8, l9, p3, p4 = divideLine(shape, l3, tile)
if l3[2] == l2[2] then
l12, l11, l10, p6, p5 = divideLine(shape, l2, tile)
else
l10, l11, l12, p5, p6 = divideLine(shape, l2, tile)
end
else
l9, l8, l7, p4, p3 = divideLine(shape, l3, tile)
if l3[1] == l2[2] then
l12, l11, l10, p6, p5 = divideLine(shape, l2, tile)
else
l10, l11, l12, p5, p6 = divideLine(shape, l2, tile)
end
end
local lns = shape.lines
local l13 = newLine(p1, p6)
local l14 = newLine(p2, p3)
local l15 = newLine(p4, p5)
local l16 = newLine(p1, middle)
local l17 = newLine(p2, middle)
local l18 = newLine(p3, middle)
local l19 = newLine(p4, middle)
local l20 = newLine(p5, middle)
local l21 = newLine(p6, middle)
lns[#lns + 1] = l13
lns[#lns + 1] = l14
lns[#lns + 1] = l15
lns[#lns + 1] = l16
lns[#lns + 1] = l17
lns[#lns + 1] = l18
lns[#lns + 1] = l19
lns[#lns + 1] = l20
lns[#lns + 1] = l21
local srfcs = shape.surfaces
local s1 = newSurface(l5, l16, l17)
local s2 = newSurface(l8, l18, l19)
local s3 = newSurface(l11, l20, l21)
local s4 = newSurface(l13, l16, l21)
local s5 = newSurface(l14, l17, l18)
local s6 = newSurface(l15, l19, l20)
local s7 = newSurface(l4, l12, l13)
local s8 = newSurface(l6, l7, l14)
local s9 = newSurface(l9, l10, l15)
srfcs[#srfcs + 1] = s1
srfcs[#srfcs + 1] = s2
srfcs[#srfcs + 1] = s3
srfcs[#srfcs + 1] = s4
srfcs[#srfcs + 1] = s5
srfcs[#srfcs + 1] = s6
srfcs[#srfcs + 1] = s7
srfcs[#srfcs + 1] = s8
srfcs[#srfcs + 1] = s9
if final then
local t = shape.tiles
t[#t + 1] = tile
t[c1].connections[tile] = l13
t[c2].connections[tile] = l14
t[c3].connections[tile] = l15
tile.connections[t[c1]] = l13
tile.connections[t[c2]] = l14
tile.connections[t[c3]] = l15
end
end
local srfcs = shape.surfaces
local pnts = shape.points
local pentCons
local tiles
if final then
tiles = {}
pentCons = {}
for p, point in next, pnts do
pentCons[point] = {}
tiles[point] = {
walls = {};
lines = {};
connections = {};
}
end
shape.pentagonalConnections = pentCons
shape.tiles = tiles
end
shape.surfaces = {}
shape.points = {}
shape.lines = {}
for i = 1, #srfcs do
divideSurface(shape, srfcs[i], final)
end
if final then
for p, lines in next, pentCons do
local newP = Vector3.new()
for _, line in next, lines do
if line[1] == p then
newP = newP + line[2]
else
newP = newP + line[1]
end
end
newP = newP / #lines
for _, line in next, lines do
if line[1] == p then
line[1] = newP
else
line[2] = newP
end
end
pnts[#pnts+1] = newP
tiles[p].center = newP
end
shape.pentCons = nil
end
end
local function getPath(start, finish)
local curLvl
local nxtLvl = {finish}
local parent = {}
while #nxtLvl > 0 do
curLvl = nxtLvl
nxtLvl = {}
for i = 1, #curLvl do
local node = curLvl[i]
if node == start then
local path = {}
while node do
path[#path + 1] = node
node = parent[node]
end
return path
end
for child, _ in next, node.connections do
if child ~= parent[node] then
parent[child] = node
nxtLvl[#nxtLvl + 1] = child
end
end
end
end
print "No Solution"
return {}
end
local function insideTile(tile, pos)
local function insideLine(line, center, pos)
local a = line[1]
local normal = a:Cross(line[2]).unit
if (center - a):Dot(normal) <= 0 then
return (pos - a):Dot(normal) <= 0
else
return (pos - a):Dot(normal) > 0
end
end
for otherTile, line in next, tile.walls do
if not insideLine(line, tile.center, pos) then
return false
end
end
for otherTile, line in next, tile.connections do
if not insideLine(line, tile.center, pos) then
return false
end
end
return true
end
local function getTile(self, pos)
for _, tile in next, self.tiles do
if insideTile(tile, pos) then
return tile
end
end
end
local function getTileGuess(self, pos, guess)
local fringe = {guess}
local closed = {[guess] = true}
local front = 1
local back = 2
while front < back do
local tile = fringe[front]
front = front + 1
if insideTile(tile, pos) then
return tile
end
for child, _ in next, tile.connections do
if not closed[child] then
fringe[back] = child
closed[child] = true
back = back + 1
end
end
end
end
local function getValidPath(self)
local tiles = self.tiles
local num = #tiles
for i = 1, num do
local j = math.random(i, num)
tiles[i], tiles[j] = tiles[j], tiles[i]
end
for i = 1, num do
local j = math.random(i, num)
tiles[i], tiles[j] = tiles[j], tiles[i]
end
for i = 2, num do
local path = getPath(tiles[1], tiles[i])
if #path >= 20 then
return path
end
end
end
MazeWorld.__index = MazeWorld
MazeWorld.new = getShape
MazeWorld.subdivide = divideShape
MazeWorld.create = createShape
MazeWorld.isInside = insideTile
MazeWorld.getPath = getPath
MazeWorld.getTile = getTile
MazeWorld.getTileGuess = getTileGuess
MazeWorld.getRandomPath = getValidPath
return MazeWorld
The maze algorithm is Randomized Kruskal’s.