Pesquisar este blog

gerador de terreno godot com colisor , arvore e textura por bioma

 @tool

extends MeshInstance3D


# ==============================================================================

# PROPRIEDADES DO INSPECTOR

# ==============================================================================


@export_category("Configurações do Terreno")

@export var terrain_size: Vector2 = Vector2(40, 40):

set(value):

terrain_size = value

if is_node_ready(): _generate_terrain()


@export var subdivision: int = 40:

set(value):

subdivision = clamp(value, 2, 250)

if is_node_ready(): _generate_terrain()


@export var height_scale: float = 8.0:

set(value):

height_scale = value

if is_node_ready(): _generate_terrain()


@export var generate_collision: bool = true:

set(value):

generate_collision = value

if is_node_ready(): _update_collision()


@export_category("Configuração das Bordas (Suavização e Profundidade)")

@export var enable_edge_falloff: bool = true:

set(value):

enable_edge_falloff = value

if is_node_ready(): _generate_terrain()


@export_range(0.05, 0.5, 0.01) var edge_falloff_margin: float = 0.2:

set(value):

edge_falloff_margin = value

if is_node_ready(): _generate_terrain()


@export var edge_target_height: float = -2.0: # Altura/Profundidade final das bordas (Valores negativos afundam o terreno)

set(value):

edge_target_height = value

if is_node_ready(): _generate_terrain()


@export var edge_falloff_radial: bool = false:

set(value):

edge_falloff_radial = value

if is_node_ready(): _generate_terrain()


@export_category("Configurações do Ruído Base")

@export var noise_seed: int = 1337:

set(value):

noise_seed = value

_update_noise_seeds()

if is_node_ready(): _generate_terrain()


@export var noise_frequency: float = 0.04:

set(value):

noise_frequency = value

if noise: noise.frequency = value

if is_node_ready(): _generate_terrain()


@export var gerar_novo_terreno_aleatorio: bool = false:

set(value):

if value:

noise_seed = randi()

_update_noise_seeds()

if is_node_ready(): _generate_terrain()

gerar_novo_terreno_aleatorio = false


@export_category("Texturas dos Biomas")

@export var texture_sand: Texture2D:

set(value): texture_sand = value; _update_shader_parameters()

@export var texture_grass: Texture2D:

set(value): texture_grass = value; _update_shader_parameters()

@export var texture_rock: Texture2D:

set(value): texture_rock = value; _update_shader_parameters()

@export var texture_snow: Texture2D:

set(value): texture_snow = value; _update_shader_parameters()

@export var texture_trail: Texture2D:

set(value): texture_trail = value; _update_shader_parameters()


@export var texture_scale: float = 10.0:

set(value): texture_scale = value; _update_shader_parameters()


@export_category("Configuração de Trilhas (Caminhos Conectados)")

@export var enable_trails: bool = true:

set(value):

enable_trails = value

if is_node_ready(): _generate_terrain()


@export var trail_frequency: float = 0.03:

set(value):

trail_frequency = value

if trail_noise: trail_noise.frequency = value

if is_node_ready(): _generate_terrain()


@export var trail_width: float = 0.06:

set(value):

trail_width = value

if is_node_ready(): _generate_terrain()


@export var trail_warp_strength: float = 12.0:

set(value):

trail_warp_strength = value

if is_node_ready(): _generate_terrain()


@export_category("Configuração de Árvores (Bioma Grama)")

@export var spawn_trees: bool = true:

set(value):

spawn_trees = value

if is_node_ready(): _generate_trees()


@export var tree_mesh: Mesh:

set(value):

tree_mesh = value

_update_tree_mesh()


@export var tree_density: float = 0.2:

set(value):

tree_density = value

if is_node_ready(): _generate_trees()


# ==============================================================================

# VARIÁVEIS INTERNAS E RUÍDOS

# ==============================================================================


var noise: FastNoiseLite = FastNoiseLite.new()

var trail_noise: FastNoiseLite = FastNoiseLite.new()

var warp_noise: FastNoiseLite = FastNoiseLite.new()


var tree_multimesh_inst: MultiMeshInstance3D

var static_body: StaticBody3D

var collision_shape: CollisionShape3D


# ==============================================================================

# CICLO DE VIDA E INICIALIZAÇÃO

# ==============================================================================


func _ready() -> void:

_init_noises()

_setup_tree_multimesh()

_generate_terrain()


func _init_noises() -> void:

noise.noise_type = FastNoiseLite.TYPE_SIMPLEX

trail_noise.noise_type = FastNoiseLite.TYPE_SIMPLEX

warp_noise.noise_type = FastNoiseLite.TYPE_SIMPLEX

warp_noise.frequency = 0.015

_update_noise_seeds()


func _update_noise_seeds() -> void:

if noise:

noise.seed = noise_seed

noise.frequency = noise_frequency

if trail_noise:

trail_noise.seed = noise_seed + 123

trail_noise.frequency = trail_frequency

if warp_noise:

warp_noise.seed = noise_seed + 456


# ==============================================================================

# CÁLCULOS PROCEDURAIS (ALTURA, BORDAS E TRILHAS)

# ==============================================================================


func _get_trail_weight(x: float, z: float) -> float:

if not enable_trails: return 0.0

var warp_x = warp_noise.get_noise_2d(x, z) * trail_warp_strength

var warp_z = warp_noise.get_noise_2d(x + 500.0, z + 500.0) * trail_warp_strength

var raw_trail = abs(trail_noise.get_noise_2d(x + warp_x, z + warp_z))

if raw_trail < trail_width:

return smoothstep(trail_width, 0.0, raw_trail)

return 0.0


func _get_height_at(x: float, z: float, trail_weight: float) -> float:

var n_val = noise.get_noise_2d(x, z)

var h = n_val * height_scale


# Aplicação da suavização e profundidade nas bordas

if enable_edge_falloff:

var half_x = terrain_size.x * 0.5

var half_z = terrain_size.y * 0.5

var nx = abs(x) / half_x

var nz = abs(z) / half_z

var dist: float

if edge_falloff_radial:

dist = sqrt(nx * nx + nz * nz)

else:

dist = max(nx, nz)

var start_margin = clamp(1.0 - edge_falloff_margin, 0.0, 0.99)

# falloff varia de 1.0 (centro) até 0.0 (extremidade da borda)

var falloff = 1.0 - smoothstep(start_margin, 1.0, dist)

# Interpola da altura original até a altura alvo da borda

h = lerp(edge_target_height, h, falloff)


return h - (trail_weight * 0.4)


# ==============================================================================

# GERAÇÃO DA MALHA DO TERRENO

# ==============================================================================


func _generate_terrain() -> void:

_init_noises()


var st = SurfaceTool.new()

st.begin(Mesh.PRIMITIVE_TRIANGLES)


var half_x = terrain_size.x / 2.0

var half_z = terrain_size.y / 2.0

var step_x = terrain_size.x / float(subdivision)

var step_z = terrain_size.y / float(subdivision)


for z in range(subdivision):

for x in range(subdivision):

var x0 = -half_x + x * step_x

var z0 = -half_z + z * step_z

var x1 = x0 + step_x

var z1 = z0 + step_z

var tw0 = _get_trail_weight(x0, z0)

var tw1 = _get_trail_weight(x1, z0)

var tw2 = _get_trail_weight(x0, z1)

var tw3 = _get_trail_weight(x1, z1)


var v0 = Vector3(x0, _get_height_at(x0, z0, tw0), z0)

var v1 = Vector3(x1, _get_height_at(x1, z0, tw1), z0)

var v2 = Vector3(x0, _get_height_at(x0, z1, tw2), z1)

var v3 = Vector3(x1, _get_height_at(x1, z1, tw3), z1)


var u0 = float(x) / subdivision

var u1 = float(x + 1) / subdivision

var w0 = float(z) / subdivision

var w1 = float(z + 1) / subdivision


# Triângulo 1

st.set_color(Color(tw0, 0, 0, 1)); st.set_uv(Vector2(u0, w0)); st.add_vertex(v0)

st.set_color(Color(tw1, 0, 0, 1)); st.set_uv(Vector2(u1, w0)); st.add_vertex(v1)

st.set_color(Color(tw2, 0, 0, 1)); st.set_uv(Vector2(u0, w1)); st.add_vertex(v2)


# Triângulo 2

st.set_color(Color(tw1, 0, 0, 1)); st.set_uv(Vector2(u1, w0)); st.add_vertex(v1)

st.set_color(Color(tw3, 0, 0, 1)); st.set_uv(Vector2(u1, w1)); st.add_vertex(v3)

st.set_color(Color(tw2, 0, 0, 1)); st.set_uv(Vector2(u0, w1)); st.add_vertex(v2)


st.generate_normals()

self.mesh = st.commit()

_setup_terrain_shader()

_generate_trees()

_update_collision()


# ==============================================================================

# COLISÃO FÍSICA

# ==============================================================================


func _update_collision() -> void:

static_body = get_node_or_null("TerrainCollider") as StaticBody3D

if not generate_collision or not self.mesh:

if static_body: static_body.queue_free()

return


if not static_body:

static_body = StaticBody3D.new()

static_body.name = "TerrainCollider"

add_child(static_body)

collision_shape = CollisionShape3D.new()

collision_shape.name = "Shape"

static_body.add_child(collision_shape)

else:

collision_shape = static_body.get_node_or_null("Shape") as CollisionShape3D


if collision_shape and self.mesh:

collision_shape.shape = self.mesh.create_trimesh_shape()


# ==============================================================================

# VEGETAÇÃO (MULTIMESH)

# ==============================================================================


func _setup_tree_multimesh() -> void:

tree_multimesh_inst = get_node_or_null("TreeMultiMesh") as MultiMeshInstance3D

if not tree_multimesh_inst:

tree_multimesh_inst = MultiMeshInstance3D.new()

tree_multimesh_inst.name = "TreeMultiMesh"

add_child(tree_multimesh_inst)

if Engine.is_editor_hint() and get_tree():

tree_multimesh_inst.owner = get_tree().edited_scene_root

if not tree_multimesh_inst.multimesh:

var mm = MultiMesh.new()

mm.transform_format = MultiMesh.TRANSFORM_3D

tree_multimesh_inst.multimesh = mm

_update_tree_mesh()


func _update_tree_mesh() -> void:

if not tree_multimesh_inst or not tree_multimesh_inst.multimesh:

return

if tree_mesh:

tree_multimesh_inst.multimesh.mesh = tree_mesh

else:

var default_cyl = CylinderMesh.new()

default_cyl.top_radius = 0.1

default_cyl.bottom_radius = 0.3

default_cyl.height = 1.5

tree_multimesh_inst.multimesh.mesh = default_cyl


func _generate_trees() -> void:

_setup_tree_multimesh()

if not spawn_trees or not tree_multimesh_inst.multimesh:

if tree_multimesh_inst and tree_multimesh_inst.multimesh:

tree_multimesh_inst.multimesh.instance_count = 0

return


var valid_transforms: Array[Transform3D] = []

var step_x = terrain_size.x / float(subdivision)

var step_z = terrain_size.y / float(subdivision)

var rng = RandomNumberGenerator.new()

rng.seed = noise_seed + 999

var start_x = -terrain_size.x / 2.0

var start_z = -terrain_size.y / 2.0

for x in range(subdivision + 1):

for z in range(subdivision + 1):

var px = start_x + (x * step_x) + rng.randf_range(-step_x * 0.3, step_x * 0.3)

var pz = start_z + (z * step_z) + rng.randf_range(-step_z * 0.3, step_z * 0.3)

var tw = _get_trail_weight(px, pz)

var py = _get_height_at(px, pz, tw)

var norm_val = (py / height_scale) * 0.5 + 0.5

# Só instancia se estiver fora do caminho e na altitude da grama

if tw == 0.0 and norm_val >= 0.25 and norm_val <= 0.55:

if rng.randf() <= tree_density:

var t = Transform3D()

t = t.rotated_local(Vector3.UP, rng.randf_range(0, TAU))

var sc = rng.randf_range(0.7, 1.3)

t = t.scaled_local(Vector3(sc, sc, sc))

t.origin = Vector3(px, py, pz)

valid_transforms.append(t)


var mm = tree_multimesh_inst.multimesh

mm.instance_count = valid_transforms.size()

for i in range(valid_transforms.size()):

mm.set_instance_transform(i, valid_transforms[i])


# ==============================================================================

# SHADER DO TERRENO

# ==============================================================================


func _setup_terrain_shader() -> void:

var mat = self.material_override as ShaderMaterial

if not mat:

mat = ShaderMaterial.new()

var shader = Shader.new()

shader.code = """

shader_type spatial;

render_mode blend_mix, depth_draw_opaque, cull_back, diffuse_lambert, specular_schlick_ggx;


uniform sampler2D tex_sand : source_color, filter_linear_mipmap, hint_default_white;

uniform sampler2D tex_grass : source_color, filter_linear_mipmap, hint_default_white;

uniform sampler2D tex_rock : source_color, filter_linear_mipmap, hint_default_white;

uniform sampler2D tex_snow : source_color, filter_linear_mipmap, hint_default_white;

uniform sampler2D tex_trail : source_color, filter_linear_mipmap, hint_default_white;

uniform float uv_scale = 10.0;

uniform float max_height = 8.0;

uniform bool enable_trails = true;


varying float height_factor;

varying float slope;


void vertex() {

height_factor = (VERTEX.y / max_height) * 0.5 + 0.5;

slope = NORMAL.y;

}


void fragment() {

vec2 base_uv = UV * uv_scale;

vec4 col_sand = texture(tex_sand, base_uv);

vec4 col_grass = texture(tex_grass, base_uv);

vec4 col_rock = texture(tex_rock, base_uv);

vec4 col_snow = texture(tex_snow, base_uv);

vec4 col_trail = texture(tex_trail, base_uv);

vec4 final_color = col_sand;

float blend_grass = smoothstep(0.2, 0.35, height_factor);

final_color = mix(final_color, col_grass, blend_grass);

float blend_snow = smoothstep(0.75, 0.85, height_factor);

final_color = mix(final_color, col_snow, blend_snow);


float rock_blend = 1.0 - smoothstep(0.65, 0.85, slope);

final_color = mix(final_color, col_rock, rock_blend);


if (enable_trails && COLOR.r > 0.0) {

if (height_factor < 0.75 && height_factor > 0.15) {

final_color = mix(final_color, col_trail, COLOR.r);

}

}

ALBEDO = final_color.rgb;

ROUGHNESS = 0.8;

}

"""

mat.shader = shader

self.material_override = mat

_update_shader_parameters()


func _update_shader_parameters() -> void:

var mat = self.material_override as ShaderMaterial

if not mat: return

if texture_sand: mat.set_shader_parameter("tex_sand", texture_sand)

if texture_grass: mat.set_shader_parameter("tex_grass", texture_grass)

if texture_rock: mat.set_shader_parameter("tex_rock", texture_rock)

if texture_snow: mat.set_shader_parameter("tex_snow", texture_snow)

if texture_trail: mat.set_shader_parameter("tex_trail", texture_trail)

mat.set_shader_parameter("uv_scale", texture_scale)

mat.set_shader_parameter("max_height", height_scale)

mat.set_shader_parameter("enable_trails", enable_trails)