How to place objects based on procedural generated noise?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By randbo

I’m new to godot and enjoying learning about it.

I am trying to understand more about procedural generated noise. I have followed a Youtube video by Codat showing how to get procedural terrain generated in chunks. It does ok for what i need it to do right now. The issue I am facing is when it comes to placing something like trees on the surface of the generated noise. As there is no set height map to use and when i try to print vertex.y above a number to console it lags badly.

The idea of my project is a star fox type of game. the player is stationary and the terrain moves toward the player on the z axis. the terrain continuously generates and unloads as it leaves behind the camera view. I am not sure about proceeding with this as the programming concepts are out of my league at the moment. but before starting over from scratch i thought i would ask anyway. Thanks for your time.

Here is the code to create chunks and world generation.
Chunk.gd

# Original Source: Codat Youtube 
extends Spatial
class_name Chunk

var mesh_instance
var noise
var x
var z
var chunk_size
var should_remove = true

func _init(noise, x, z, chunk_size):
	self.noise = noise
	self.x = x
	self.z = z
	self.chunk_size = chunk_size
	
func _ready():
	generate_chunk()
	generate_water()
	
func generate_chunk():
	var plane_mesh = PlaneMesh.new()
	plane_mesh.size = Vector2(chunk_size, chunk_size)
	plane_mesh.subdivide_depth = chunk_size * 0.5
	plane_mesh.subdivide_width = chunk_size * 0.5
	plane_mesh.material = preload("res://asset/material/terrain.material")
	
	var surface_tool = SurfaceTool.new()
	var data_tool = MeshDataTool.new()
	surface_tool.create_from(plane_mesh, 0)
	var array_plane = surface_tool.commit()
	var error = data_tool.create_from_surface(array_plane, 0)
	
	for i in range(data_tool.get_vertex_count()):
		var vertex = data_tool.get_vertex(i)
		# Chunk Height
		vertex.y = noise.get_noise_3d(vertex.x + x, vertex.y, vertex.z +z) * 10 #80
		
		data_tool.set_vertex(i, vertex)
		
	for s in range(array_plane.get_surface_count()):
		array_plane.surface_remove(s)
	
	data_tool.commit_to_surface(array_plane)
	surface_tool.begin(Mesh.PRIMITIVE_TRIANGLES)
	surface_tool.create_from(array_plane, 0)
	surface_tool.generate_normals()
	
	mesh_instance = MeshInstance.new()
	mesh_instance.mesh = surface_tool.commit()
	mesh_instance.create_trimesh_collision()
	mesh_instance.cast_shadow = GeometryInstance.SHADOW_CASTING_SETTING_OFF
	add_child(mesh_instance)
	
func generate_water():
	var plane_mesh = PlaneMesh.new()
	plane_mesh.size = Vector2(chunk_size, chunk_size)
	
	# To Do give it a material
	plane_mesh.material = preload("res://asset/material/water.material")
	var mesh_instance = MeshInstance.new()
	mesh_instance.mesh = plane_mesh
	add_child(mesh_instance)

World.gd

extends Spatial

const chunk_size = 32 #64 default
const chunk_amount = 16 #16 default

var noise
var chunks = {}
var unready_chunks = {}
var thread


func _ready():
	randomize()
	noise = OpenSimplexNoise.new()
	noise.seed = randi()
	noise.octaves = 6
	noise.period = 80

	thread = Thread.new()

func add_chunk(x, z):
	var key = str(x) + "," + str(z)
	if chunks.has(key) or unready_chunks.has(key):
		return

	if not thread.is_active():
		thread.start(self, "load_chunk", [thread, x, z])
		unready_chunks[key] = 1

func load_chunk(arr):
	var thread = arr[0]
	var x = arr[1]
	var z = arr[2]

	var chunk = Chunk.new(noise, x * chunk_size, z * chunk_size, chunk_size)
	# Chunk Y axis Location
	chunk.translation = Vector3(x * chunk_size, 0, z * chunk_size)

	call_deferred("load_done", chunk, thread)

func load_done(chunk, thread):
	add_child(chunk)
	var key = str(chunk.x / chunk_size) + "," + str(chunk.z / chunk_size)
	chunks[key] = chunk
	unready_chunks.erase(key)
	thread.wait_to_finish()

func get_chunk(x, z):
	var key = str(x) + "," + str(z)
	if chunks.has(key):
		return chunks.get(key)

	return null

func _process(delta):
	update_chunks()
	clean_up_chunks()
	reset_chunks()

func update_chunks():

	var player_translation = $Player.translation
	var p_x = int(player_translation.x) / chunk_size
	var p_z = int(player_translation.z) / chunk_size
# Chunk Location To Player
	for x in range(p_x - chunk_amount * 1, p_x + chunk_amount * 1):
		for z in range(p_z - chunk_amount, p_z + 1): #(p_z - chunk_amount * 0.5, p_z + chunk_amount * 0.5):
			add_chunk(x, z)
			var chunk = get_chunk(x, z)
			if chunk != null:
				chunk.should_remove = false

func clean_up_chunks():
	for key in chunks:
		var chunk = chunks[key]
		if chunk.should_remove:
			chunk.queue_free()
			chunks.erase(key)

func reset_chunks():
	for key in chunks:
		chunks[key].should_remove = true

pic

:bust_in_silhouette: Reply From: Saitodepaula

You can use a RayCast.

  1. You will need to create a Collision Object (probably a Static Body) to your terrain mesh, and after that, a Collision Shape.

  2. Once you have that, the RayCast will be able to detect your terrain.

  3. Be sure your raycast is enabled.

  4. Be sure to place your terrain in a PhysicsLayer your raycast is checking.

  5. Inside a loop, randomly position your raycast, check for your terrain, and using the raycast function GetCollisionPoint, you will have a Vector3 with the point in space the raycast intersected your terrain.

  6. With this point in hand, place a tree or whatever object you want.

  7. Be sure to use raycast function ForceRayCastUpdate after each loop iteration.

  8. Be sure to ramdomize inside the loop, so that you get a random value in each iteration.