@tool
extends MeshInstance3D
@export_category("Configurações do Terreno")
@export var terrain_size: Vector2 = Vector2(40, 40):
set(value):
terrain_size = value
_generate_terrain()
@export var subdivision: int = 40:
set(value):
subdivision = value
_generate_terrain()
@export var height_scale: float = 8.0:
set(value):
height_scale = value
_generate_terrain()
@export_category("Configurações do Ruído")
@export var noise_seed: int = 1337:
set(value):
noise_seed = value
noise.seed = value
_generate_terrain()
@export var noise_frequency: float = 0.04:
set(value):
noise_frequency = value
noise.frequency = value
_generate_terrain()
@export var gerar_novo_terreno_aleatorio: bool = false:
set(value):
if value:
noise.seed = randi()
noise_seed = noise.seed
_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_scale: float = 10.0:
set(value):
texture_scale = value
_update_shader_parameters()
@export_category("Configuração de Árvores (Bioma Grama)")
@export var spawn_trees: bool = true:
set(value):
spawn_trees = value
_generate_terrain()
# Campo exposto explicitamente para escolher a malha da árvore no Inspector
@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
_generate_terrain()
var noise: FastNoiseLite = FastNoiseLite.new()
var tree_multimesh_inst: MultiMeshInstance3D
func _ready() -> void:
noise.noise_type = FastNoiseLite.TYPE_SIMPLEX
noise.seed = noise_seed
noise.frequency = noise_frequency
_setup_tree_multimesh()
_generate_terrain()
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():
tree_multimesh_inst.owner = self
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:
# Malha padrão temporária (um cilindro simples) caso nenhum mesh seja selecionado
var default_cyl = CylinderMesh.new()
default_cyl.top_radius = 0.1
default_cyl.bottom_radius = { "value": 0.3 }.value if false else 0.3
default_cyl.height = 1.5
tree_multimesh_inst.multimesh.mesh = default_cyl
func _generate_terrain() -> void:
var plane_mesh = PlaneMesh.new()
plane_mesh.size = terrain_size
plane_mesh.subdivide_width = subdivision
plane_mesh.subdivide_depth = subdivision
var st = SurfaceTool.new()
st.begin(Mesh.PRIMITIVE_TRIANGLES)
st.create_from(plane_mesh, 0)
var st_data = st.commit_to_arrays()
var mesh = ArrayMesh.new()
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, st_data)
var mdt = MeshDataTool.new()
mdt.create_from_surface(mesh, 0)
for i in range(mdt.get_vertex_count()):
var vertex = mdt.get_vertex(i)
var n_val = noise.get_noise_2d(vertex.x, vertex.z)
vertex.y = n_val * height_scale
mdt.set_vertex(i, vertex)
mesh.clear_surfaces()
mdt.commit_to_surface(mesh)
self.mesh = mesh
_setup_terrain_shader()
_generate_trees()
print("Terreno e árvores atualizados!")
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 / subdivision
var step_z = terrain_size.y / 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 n_val = noise.get_noise_2d(px, pz)
var norm_val = (n_val * 0.5) + 0.5
# Faixa de altura reservada para a grama
if norm_val >= 0.3 and norm_val <= 0.55:
if rng.randf() <= tree_density:
var py = n_val * height_scale
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])
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;
uniform sampler2D tex_grass : source_color, filter_linear_mipmap;
uniform sampler2D tex_rock : source_color, filter_linear_mipmap;
uniform sampler2D tex_snow : source_color, filter_linear_mipmap;
uniform float uv_scale = 10.0;
uniform float max_height = 8.0;
varying float height_factor;
void vertex() {
height_factor = (VERTEX.y / max_height) * 0.5 + 0.5;
}
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 final_color = col_sand;
float blend1 = smoothstep(0.2, 0.35, height_factor);
final_color = mix(final_color, col_grass, blend1);
float blend2 = smoothstep(0.5, 0.65, height_factor);
final_color = mix(final_color, col_rock, blend2);
float blend3 = smoothstep(0.75, 0.85, height_factor);
final_color = mix(final_color, col_snow, blend3);
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)
mat.set_shader_parameter("uv_scale", texture_scale)
mat.set_shader_parameter("max_height", height_scale)