How do I achieve low poly map generation

I’m making a combat game and I need to generate a new low poly map that consists of mostly triangles every new round.
Although I’ve tried making my own terrain generator and trying out some already made solutions, I was unable to make the terrain as shown in the following picture.


Could anyone please explain how was it achieved in Armored Patrol?
Thank you.

3 Likes

It looks like Armored Patrol generates two landmasses that intersect each other. One is made up of grass and has a lower level of detail and the other one is mountainous, with different colors, a higher level of detail, vegetation and even caves.

I guess that you can achieve something similar if you generate two vertex maps (with some noise function like math.noise for calculating the height of those points). Then you can triangulate those points and draw the triangles.

2 Likes

Hey hey hey, I have also worked on this exact same project to attempt to replicate it.

Yep @Brickman808 is right there are two landmasses which I also find to be the best. The reason why is due to math.noise and the effects of frequency, you could generate smooth landscape using low frequency however there would be no noticeable mountains.

example from red blob games a very good website

You could also increase the frequency however there would be too many mountains:

First is the triangle terrain, props to @okeanskiy for the code and excellent tutorial. However I swapped the noise with @ThanksRoBama Noise module which has advanced parameters to control noise with similar to red blob games.

Then the mountains, which doesn’t exactly use math.noise like the above, it uses a predetermined linear function to ensure I can pick any point on the map and say “this mountain height is equal to 500” and that the slope is equal to 30 degrees and such, additionally these mountains are placed randomly across the map not with math.noise:

Additionally it uses a linear equation to decide the slope of the mountain to ensure that the mountain can be climbable by vehicles.

y is equal to the height of the mountain, x is equal the the magnitude of the vector from mountain center → any XZ position on the grid.

There are multiple mountains and I used math.max to ensure the tallest mountain gets proritized.

Currently there are still a lot of problems but I guess it’s good enuff for now. Ex the result is very hard to control and the mountains don’t “merge” as well, and the mountain tops all look much of the “same” being circular, I believe I will need to work on the mountains a lot more since it uses random generation the mountains are not placed well and should use another distribution method like poisson discs.

4 Likes

This probably isn’t suuuper helpful to OP but I just had to comment on this :smiley:

It’s totally possible to combine two different noise functions, one for “flat plains” and one for “mountains” into a single noise function that has some areas where one is most prevalent and other areas where the other is prevalent. Here’s an example:

The green areas are plains, while the more grey areas are mountains. The key is to use a third noise function to tell the generator “how much mountain-ness” there is at any given point, and blend between the mountain function and the plains function using that mountain-ness function. This image above just linearly interpolates from plains to mountains based on the mountain-ness function. Something like a smootherstep function should give a sharper transition:

Sharper transitions allows higher-frequency mountain-ness noise, i.e. more plains-areas and mountain-areas close together. This also makes each mountain area smaller, so each peak is more solitary / distinct:

Combining that with the “absolute value trick” on the mountain height function you can get really cool mountain ranges / sharp-ish ridges:

(0.1x scale compared to other screenshots to actually render shadows)

Code for the last image. The earlier ones are really just simpler/tweaked version of this.
local v3 = Vector3.new
local _ = nil
local FNG = require(script.FractNoiseGen)

local mapSize = v3(256, 0, 256)

local plainsHeightGen = FNG(_, 1.25, 1/32, 1, _, _)  --Height function for plains
local mountainsHeightGen = FNG(_, 30, 1/64, 3, _, _) --Height function for mountains
local mountaineousnessGen = FNG(_, 1, 1/96, 1, _, _) --Blending function for plains/mountains

function smoothstep5(x)
	-- https://en.wikipedia.org/wiki/Smoothstep#5th-order_equation
	return 6 * math.pow(x, 5) - 15 * math.pow(x, 4) + 10 * math.pow(x, 3)
end

function smoothstep7(x)
	-- https://en.wikipedia.org/wiki/Smoothstep#7th-order_equation
	return -20 * math.pow(x, 7) + 70 * math.pow(x, 6) - 84 * math.pow(x, 5) + 35 * math.pow(x, 4)
end

function mountainsHeight(x, z)
	local h = mountainsHeightGen(x, 0, z)
	
	h = 20 - math.abs(h)
	
	return h
end

for x = 1, mapSize.X do
	for z = 1, mapSize.Z do
		--m = how much is this point in mountains vs plains? (range [0-1])
		local m = math.clamp(smoothstep7(mountaineousnessGen(x, 0, z) + 0.5), 0, 1)
		--Blended height = m * mountain + (1 - m) * plains
		--Although I wanted mountains to always "stick out of" plains, not dip into them, so here
		--	mountain height = plain height + something
		local h = 	m 		* (plainsHeightGen(x, 0, z) + mountainsHeight(x, z)) + 
					(1 - m) * (plainsHeightGen(x, 0, z))
		
		local p = script.Part:Clone()
		p.CFrame = CFrame.new(x * .1, h * .1, z * .1) --* CFrame.Angles(0, math.random() * math.pi * 2, 0)
		p.Color = Color3.fromHSV(0.35, 0.55 - m * 0.55, 0.60)
		p.Parent = game.Workspace
	end
end

I’m not sure this will be directly useful for generating AA2- style maps that are triangle-based and really low resolution, but maybe some of the techniques can be used? They could certainly also be combined with the idea of generating mountains at discrete points, e.g. with Poisson disc sampling like @dthecoolest suggested or even Voronoi diagrams/Delaunay triangulations somehow. And adding more “biomes” might be interesting too.

4 Likes

Although I’ve made a terrain generator, I am unable to get any assistance with the following replies as the only thing I understood is that you need to make a grid then triangulate it

That is right. You need to use a noise function like math.noise to calculate the height of each point in the grid. There are some fancy tricks here and there to make the landscape more fancy, but you can just use the built-in noise function for starters. I recommend to start simple and then look at the examples above for inspiration.

All you need to know about the noise function is that it is very similar to math.random. It generates pseudo-random (seemingly random but not really random) numbers, but it does this in a smoother way than math.random. The noise function returns numbers that gradually move up and down, while math.random produces numbers that look like they have absolutely nothing to do with each other. This makes math.noise perfect for terrain generation as you want it to be sort of random, but not too random.

noise

Random noise (math.random).

perlin-noise

Perlin noise (math.noise).

1 Like

Old post but, could you go a bit more in-depth about how the mountain generation works? I’m trying to replicate something similar to AP as well, but I keep getting perfectly smooth hills just using the linear slope function.

1 Like

Should be simple enough to unsmooth it, I think I just added another math.noise to make it bumpy.

I don’t really know how I can replicate this terrain generation technique. I’ve managed to create a smooth plains with varying elevation but I’m not sure how to replicate the mountainous part of the generation.

Where do I/Where can I use this this linear equation? How do I flatten the tops of the mountains as seen in one of the pictures?

1 Like