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