I just threw this little generator together tonight and I thought it was pretty fun to watch! Enjoy!
Hey! That was really cool, I’ve never seen anything like that before!
I’m glad you like it!
Yeah pretty interesting, whats going on here? Is it all c frames?
It’s rather inefficient actually haha. I generate a 2D matrix for my vertices and draw two triangles to create a box. Then I use a .Changed on the vertices to recalculate the polygons. I use perlin noise to generate a height map Then I set the vertices y position accordingly. I use tweenservice to get that smooth effect but it is very laggy when the map is big.
Pretty cool. Are you using Roblox’s built-in math.noise()
function or your own implementation?
A few things I’d like to point out:
-
Keeping the terrain static but generating a larger area would probably do better to demonstrate the shape of the terrain. Or maybe try to generate terrain on a sphere instead; these algorithms lend themselves well to generating terrain on spherical bodies.
-
Try playing around with the numbers passed into and out of your noise function. You can do some pretty interesting stuff by adding a noise offset to the coordinates you’re putting in, and also by manipulating the value you’re getting out.
-
Try layering noise of different frequencies and amplitudes on top of eachother. This allows you to keep decent large-scale terrain while also generating smaller details.
I did something like this a while ago, and while I used a relatively small amount of polygons, the results did look pretty nice:
For instance, the following generates long mountain crests with craters in between:
height = 1 - math.abs(math.noise(x, y, seed))
You could multiply this by another noise function to get mountain crests that aren’t connected:
local baseHeight = 1 - math.abs(math.noise(x, y, seed1))
height = baseHeight * math.pow(math.noise(x/2, y/2, seed2), 2)
Or you could “stretch” features randomly like so:
local offsetX = math.noise(x*2, y*2, seed1)
local offsetY = math.noise(x*2, y*2, seed2)
height = math.noise(x + offsetX, y + offsetY, seed3)
Using these techniques should allow you to generate much more interesting terrain and if you do it correctly, even stuff like very steep cliffs and overhangs. The farther you think outside the box, the better results you can get!
LOVE the ideas and feedback! I would love to create polygon planets using this method that’s actually what I was going to try next!
Thanks, when I get home from work I’m going to play with these concepts!
Also yes I am using Roblox’s noise Function
Cool! Just a few quick pointers for planets:
-
I suggest you use an icosphere (also known as a geodesic polyhedron) as your base sphere. The advantage of these shapes is that they have consistent detail throughout, instead of being more detailed near the poles. I have some code that can do this for you if you need it.
-
You can use the direction of your vertices from the center of the sphere as the input coordinates to your noise function. Doing this generates perfectly wrapping noise, so you won’t generate any “seams” on your planets.
Hopefully this helps!
Woah! I just did a bit of research and that looks very interesting, do you have any good resources in order to learn how it works in code? Like a good resource that describes generation of its vertices
Typing “generate icosphere” in Google yields some pretty good results, albeit not in Lua. Here’s a pretty good resource that explains how the algorithm works.
The gist of it is that you start with an icosahedron (hence "ico"sphere) and then sub-divide each triangle some number of times and normalize the new vertices to the same distance to the sphere’s center as the original ones. When I originally implemented my icosphere generator, I did this recursively because I found it easier to do that way at the time, but there’s actually a better way of doing it. I’ll get into this in a moment.
The first step is to generate your icosphere. It has 12 vertices, and I define them as follows:
Vertices
--Constant needed in several vertices
local t = (1 + math.sqrt(5)) / 2
--Create vertices
local vtx00 = Vertex.new(Vector3.new(-1, t, 0).unit, 1)
local vtx01 = Vertex.new(Vector3.new( 1, t, 0).unit, 2)
local vtx02 = Vertex.new(Vector3.new(-1,-t, 0).unit, 3)
local vtx03 = Vertex.new(Vector3.new( 1,-t, 0).unit, 4)
local vtx04 = Vertex.new(Vector3.new( 0,-1, t).unit, 5)
local vtx05 = Vertex.new(Vector3.new( 0, 1, t).unit, 6)
local vtx06 = Vertex.new(Vector3.new( 0,-1,-t).unit, 7)
local vtx07 = Vertex.new(Vector3.new( 0, 1,-t).unit, 8)
local vtx08 = Vertex.new(Vector3.new( t, 0,-1).unit, 9)
local vtx09 = Vertex.new(Vector3.new( t, 0, 1).unit, 10)
local vtx10 = Vertex.new(Vector3.new(-t, 0,-1).unit, 11)
local vtx11 = Vertex.new(Vector3.new(-t, 0, 1).unit, 12)
Then you define the triangles. There’s 20 of them:
Triangles
--Create faces
--5 faces around point 0
triangles[ 1] = Triangle.new(vtx00, vtx11, vtx05)
triangles[ 2] = Triangle.new(vtx00, vtx05, vtx01)
triangles[ 3] = Triangle.new(vtx00, vtx01, vtx07)
triangles[ 4] = Triangle.new(vtx00, vtx07, vtx10)
triangles[ 5] = Triangle.new(vtx00, vtx10, vtx11)
--5 adjacent faces
triangles[ 6] = Triangle.new(vtx01, vtx05, vtx09)
triangles[ 7] = Triangle.new(vtx05, vtx11, vtx04)
triangles[ 8] = Triangle.new(vtx11, vtx10, vtx02)
triangles[ 9] = Triangle.new(vtx10, vtx07, vtx06)
triangles[10] = Triangle.new(vtx07, vtx01, vtx08)
--5 faces around point 3
triangles[11] = Triangle.new(vtx03, vtx09, vtx04)
triangles[12] = Triangle.new(vtx03, vtx04, vtx02)
triangles[13] = Triangle.new(vtx03, vtx02, vtx06)
triangles[14] = Triangle.new(vtx03, vtx06, vtx08)
triangles[15] = Triangle.new(vtx03, vtx08, vtx09)
--5 adjacent faces
triangles[16] = Triangle.new(vtx04, vtx09, vtx05)
triangles[17] = Triangle.new(vtx02, vtx04, vtx11)
triangles[18] = Triangle.new(vtx06, vtx02, vtx10)
triangles[19] = Triangle.new(vtx08, vtx06, vtx07)
triangles[20] = Triangle.new(vtx09, vtx08, vtx01)
Vertex
and Triangle
are custom classes I made, but this should give you the information needed to do the work yourself.
Then, you sub-divide the triangles. My implementation always divides one triangle into 4 sub-triangles, like so:
I then normalize the 3 new vertices to a length of 1 and repeat that process for all new triangles, up to some point. The more often you do this, the more triangles (and the more detail) you end up with, but the longer it takes.
However, for an icosphere, it actually doesn’t matter where you divide your triangles. You could just as well divide a triangle at one third of their side lengths:
(Forgive the slightly janky angles, I hand-drew these on the spot. You get the idea )
It works the same for dividing at quarter length, one 1/5th length or even more. The result will feature perfectly identical triangles, both before and after you normalize the vertices out to generate a round shape.
After a good amount of tries, I finally got it working! Here is a version you can join.
Although, I’m struggling to implement Perlin noise on these vertices because they have no set order. Does that even matter for implementing random terrain?
Thank you!
The order of the vertices doesn’t matter, only their direction from the center of the sphere. You can do this:
local terrainFrequency = 5
newVertex = vertex * (1 + math.noise(vertex.x * terrainFrequency, vertex.y * terrainFrequency , vertex.z * terrainFrequency)/2)
This is what I meant when I said this:
This is because you’re actually sampling points from 3D space. You’re not basing it on 2-dimensional coordinates somewhere on the surface but the literal 3D coordinates of that surface vertex. In other words, no matter which direction you’re approaching a point from, you’re always approaching the same point, and math.noise
will approach the same output value (math.noise()
is actually deterministic and the same input will always generate the same output).
This wouldn’t be the case with angular coordinates, for instance, where you have to wrap around from -180 to +180 degrees, or somehow have to reconcile your terrain at the poles even though it might be at wildly different heights.