• Projects
  • OpenSimplexNoise tool for procedural generation

Hello,

Beginning on Godot, I started to create a world based on procedural generation.

I use OpenSimplexNoise to generate the relief but when I wanted to use it to generate the elements of nature (tree, plants, rocks) I realized that it was not so easy to configure or visualize the result.

The need is to have a way to give me the biome, the type of ground or vegetation that I have to use for a given coordinate.

So I created a tool to find the right values ​​to use according to my needs.

How to start this tool: 1. Create a new 3D scene 2. Add the following script to the spacial node 3. Play

How to use it: A color representant an element (biome, ground, population .. etc) you can add/remove colors in the node configuration. Select the quality (1-10) according to your computer speed (use 10 for screenshot) Mode 0: provide the result of the OpenSimplexNoise as is. As you can see all colors are not displayed. Mode 1: Use an exponential function to evenly distribute colors Mode 2: Use multiple layers (one by color) and keep the highest value. This avoids the "ordered color in circle" effect. Play with OpenSimplexNoise parameters


extends Spatial

enum eModeList {SimpleNoise, SimpleNoiseExp, MultipleNoise}
export(eModeList) var Mode = eModeList.MultipleNoise
export var map_size = Vector2(2500, 1500)
export(int, 1, 10) var quality = 2
export var noiseParam = {"seed":0, "period":400, "octaves":7, "persistence":0.3, "lacunarity":2.0}
export(int, 2, 20) var exp_coef = 5 # exponential coefficient used for SimpleNoiseExp mode
export var colors = [Color(0,0,0), Color(0,0,1), Color(0,1,0),Color(0,1,1), Color(1,0,0), Color(1,0,1), Color(1,1,0), Color(1,1,1)]

const CAMERA_HEIGH_MAP_RATIO = 2.4
const NOISE_OFFSET = 1009

var g_spinBoxCount = 0
var g_noiseColor

func _ready():
	g_noiseColor = OpenSimplexNoise.new()
	g_noiseColor.seed = noiseParam.seed
	g_noiseColor.octaves = noiseParam.octaves
	g_noiseColor.period = noiseParam.period
	g_noiseColor.persistence = noiseParam.persistence
	g_noiseColor.lacunarity = noiseParam.lacunarity
	add_camera()
	add_gui()
	genSurface(false)

func genSurface(update):	
	var plane_mesh = PlaneMesh.new()
	plane_mesh.size = map_size
	plane_mesh.subdivide_depth = quality * 100
	plane_mesh.subdivide_width = quality * 100
	plane_mesh.material = shaderMat()
	
	var surface_tool = SurfaceTool.new()
	surface_tool.create_from(plane_mesh, 0)
	
	var mesh_instance = MeshInstance.new()
	mesh_instance.mesh = surface_tool.commit()
	mesh_instance.name = "meshInst"
	
	if update:
		$meshInst.free()
	
	add_child(mesh_instance)
	updateVertex()

func updateVertex():
	var colCount = []
	for i in range(colors.size()):
		colCount += [0]
	
	var mdt = MeshDataTool.new()
	mdt.create_from_surface($meshInst.mesh, 0)
	
	for i in range(mdt.get_vertex_count()):
		var colPos = getColor(mdt.get_vertex(i), g_noiseColor)
		mdt.set_vertex_color(i, colors[colPos])
		colCount[colPos] += 1

	update_stats(colCount, mdt.get_vertex_count())
	
	$meshInst.mesh.surface_remove(0)
	mdt.commit_to_surface($meshInst.mesh)
	mdt.clear()

func getColor(vertex, g_noiseColor):
	# This is the default SimpleNoise usage
	if Mode == eModeList.SimpleNoise:
		var color = g_noiseColor.get_noise_2d(vertex.x, vertex.z)
		color = .5 * (color+1) # convert [-1,1] range to [0,1]
		return int((colors.size())*color)
	
	# Use an exponential function to evenly distribute colors
	if Mode == eModeList.SimpleNoiseExp:
		var color = g_noiseColor.get_noise_2d(vertex.x, vertex.z)
		color = 1/(1+exp(-color*exp_coef))
		return int((colors.size())*color)
	
	# Computes a noise for each color and keeps the highest value
	if Mode == eModeList.MultipleNoise:
		var colPos = 0
		var best = -1
		for i in range(colors.size()):
			var cur = g_noiseColor.get_noise_3d(vertex.x, vertex.z, i*NOISE_OFFSET)
			if (best < cur):
				best = cur
				colPos = i
		return colPos

func shaderMat():
	var mat = ShaderMaterial.new()
	mat.shader = Shader.new()
	mat.shader.code = "shader_type spatial; void fragment() {ALBEDO.r=COLOR.r;ALBEDO.g=COLOR.g;ALBEDO.b=COLOR.b;}"
	return mat

func add_camera():
	var cam = Camera.new()
	cam.far = 0
	cam.translation.y = max(map_size.x, map_size.y) / CAMERA_HEIGH_MAP_RATIO
	cam.rotation.x = -PI/2
	cam.current = true
	add_child(cam)

func add_gui():
	addSpinBox("quality",     quality, 1, 10, 1, "onQualityChanged")
	addSpinBox("seed",        noiseParam.seed, 0, 0, 1, "onSeedChanged")
	addSpinBox("period",      noiseParam.period, 10, 0, 10, "onPeriodChanged")
	addSpinBox("octaves",     noiseParam.octaves, 1, 0, 1, "onOctavesChanged")
	addSpinBox("persistence", noiseParam.persistence, 0, 0, 0.1, "onPersistenceChanged")
	addSpinBox("lacunarity",  noiseParam.lacunarity, 0, 0, 0.1, "onLacunarityChanged")
	addSpinBox("Mode",        Mode, 0, 2, 1, "onModeChanged")
	addSpinBox("ExpCoef",     exp_coef, 1, 100, 1, "onExpCoefChanged")
	
	$ExpCoef.visible = (Mode==1)
	
	var label = Label.new()
	label.name = "label"
	label.margin_left = 300
	add_child(label)

func addSpinBox(prefix, value, vMin, vMax, step, cbk):
	var spinBox = SpinBox.new()
	spinBox.margin_top = g_spinBoxCount* 28
	spinBox.margin_right = 200
	spinBox.prefix = prefix
	spinBox.min_value = vMin
	spinBox.max_value = vMax
	spinBox.allow_greater = (vMax==0)
	spinBox.step = step
	spinBox.value = value
	spinBox.name = prefix
	spinBox.connect("value_changed", self, cbk)
	add_child(spinBox)
	g_spinBoxCount += 1

func update_stats(colCount, total):
	for i in range(colCount.size()):
		colCount[i] = str(100 * colCount[i] / total) + "%"
	$label.text = str(colCount)

func onQualityChanged(value):
	quality = value
	genSurface(true)

func onSeedChanged(value):
	g_noiseColor.seed = value
	updateVertex()

func onPeriodChanged(value):
	g_noiseColor.period = value
	updateVertex()
	
func onOctavesChanged(value):
	g_noiseColor.octaves = value
	updateVertex()
	
func onPersistenceChanged(value):
	g_noiseColor.persistence = value
	updateVertex()

func onLacunarityChanged(value):
	g_noiseColor.lacunarity = value
	updateVertex()
	
func onModeChanged(value):
	$ExpCoef.visible = (value==1)
	Mode = value
	updateVertex()
	
func onExpCoefChanged(value):
	exp_coef = value
	updateVertex()

Here some results:


Have fun in godot! Traquila

It looks great, but dang! That's a huge wall of text. ?<-(on mobile) Do you think you could put it in like a repository, like GitHub, for convenience?