#include "terrain_mesh_generator.h" #include "core/math/math_funcs.h" #include "core/math/rect2.h" #include "core/object/class_db.h" #include "core/object/object.h" #include "core/templates/local_vector.h" #include "scene/resources/surface_tool.h" #include "terrain_editor/macros.h" #include "terrain_editor/terrain_chunk.h" #include "terrain_editor/terrain_primitive.h" #include String const TerrainMeshGenerator::sig_primitives_changed{ "primitives_changed" }; String const TerrainMeshGenerator::sig_primitive_list_changed{ "primitive_list_changed" }; void TerrainMeshGenerator::_bind_methods() { BIND_HPROPERTY(Variant::ARRAY, primitives, PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:TerrainPrimitive", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE)); BIND_HPROPERTY(Variant::OBJECT, vertex_color_gradient, PROPERTY_HINT_RESOURCE_TYPE, "Gradient"); BIND_PROPERTY(Variant::FLOAT, color_gradient_start_height); BIND_PROPERTY(Variant::FLOAT, color_gradient_end_height); BIND_PROPERTY(Variant::FLOAT, chunk_size); BIND_PROPERTY(Variant::INT, lod1_detail); BIND_PROPERTY(Variant::INT, chunk_count); BIND_HPROPERTY(Variant::OBJECT, material, PROPERTY_HINT_RESOURCE_TYPE, "Material"); ADD_SIGNAL(MethodInfo(sig_primitives_changed)); ADD_SIGNAL(MethodInfo(sig_primitive_list_changed, PropertyInfo(Variant::ARRAY, "array", PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:TerrainPrimitive", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE)))); ClassDB::bind_method(D_METHOD("generate_grid", "area", "out_mesh", "side_points"), &self_type::generate_grid); } void TerrainMeshGenerator::ready() { if (this->chunks.is_empty()) { rebuild_chunks(); } } void TerrainMeshGenerator::on_configuration_changed() { emit_signal(sig_primitives_changed); } void TerrainMeshGenerator::_notification(int what) { switch (what) { default: return; case NOTIFICATION_READY: ready(); return; } } void TerrainMeshGenerator::rebuild_chunks() { for (TerrainChunk *chunk : this->chunks) { chunk->queue_free(); } this->chunks.clear(); Vector3 origin{ -Vector3{ (float)chunks_per_side, 0, (float)chunks_per_side } * this->chunk_size / 2 }; for (size_t chunk_y{ 0 }; chunk_y < this->chunks_per_side; ++chunk_y) { for (size_t chunk_x{ 0 }; chunk_x < this->chunks_per_side; ++chunk_x) { TerrainChunk *chunk{ memnew(TerrainChunk) }; chunk->set_size(this->chunk_size); chunk->set_lod1_detail(this->lod1_detail); chunk->set_material_override(this->material); this->chunks.push_back(chunk); Vector3 offset{ Vector3{ (float)chunk_x, 0.f, (float)chunk_y } * this->chunk_size }; chunk->set_position(origin + offset); add_child(chunk); } } } Color TerrainMeshGenerator::color_at_height(float height) const { float const mapped{ Math::remap(height, this->color_gradient_start_height, this->color_gradient_end_height, 0.f, 1.f) }; return this->vertex_color_gradient.is_valid() ? this->vertex_color_gradient->get_color_at_offset(mapped) : Color{ mapped, mapped, mapped, 1.f }; } float TerrainMeshGenerator::evaluate_point(Vector2 at) const { float height{ -std::numeric_limits::infinity() }; for (Ref primitive : this->primitives) { if (primitive.is_valid()) { primitive->evaluate(at, height); } } return height; } void face_from(Ref surface, size_t tl, size_t side_points) { LocalVector &vertices{ surface->get_vertex_array() }; float d1{ vertices[tl].vertex.distance_to(vertices[tl + side_points + 1].vertex) }; float d2{ vertices[tl + 1].vertex.distance_to(vertices[tl + side_points].vertex) }; if (d1 < d2) { surface->add_index(tl); surface->add_index(tl + side_points + 1); surface->add_index(tl + 1); surface->add_index(tl); surface->add_index(tl + side_points); surface->add_index(tl + side_points + 1); } else { surface->add_index(tl + side_points); surface->add_index(tl + side_points + 1); surface->add_index(tl + 1); surface->add_index(tl + 1); surface->add_index(tl); surface->add_index(tl + side_points); } } void TerrainMeshGenerator::generate_grid(Rect2 area, Ref mesh, size_t side_points) { mesh->clear_surfaces(); surface->clear(); Vector2 point_distance{ area.size / (float)side_points }; ++side_points; Vector2 at{ area.position }; surface->begin(Mesh::PRIMITIVE_TRIANGLES); for (size_t xpoint{ 0 }; xpoint < side_points; ++xpoint) { for (size_t ypoint{ 0 }; ypoint < side_points; ++ypoint) { float const height{ evaluate_point(at) }; surface->set_color(color_at_height(height)); surface->set_uv({ at / area.size }); surface->add_vertex({ at.x - area.position.x, height, at.y - area.position.y }); at.y += point_distance.y; } at.y = area.position.y; at.x += point_distance.x; } for (size_t xface{ 0 }; xface < side_points - 1; ++xface) { for (size_t yface{ 0 }; yface < side_points - 1; ++yface) { face_from(surface, xface + yface * (size_t)side_points, side_points); } } surface->generate_normals(); surface->generate_tangents(); surface->commit(mesh); } void TerrainMeshGenerator::set_primitives(Array primitives) { for (Ref primitive : this->primitives) { if (primitive.is_valid()) { primitive->disconnect_changed(this->generation_changed); } } this->primitives.clear(); for (Variant var : primitives) { Ref primitive{ var }; this->primitives.push_back(primitive); if (primitive.is_valid()) { primitive->connect_changed(this->generation_changed); } } emit_signal(sig_primitive_list_changed, get_primitives()); on_configuration_changed(); } Array TerrainMeshGenerator::get_primitives() const { Array a; for (Ref primitive : this->primitives) { a.push_back(primitive); } return a; } void TerrainMeshGenerator::set_vertex_color_gradient(Ref gradient) { if (this->vertex_color_gradient.is_valid()) { this->vertex_color_gradient->disconnect_changed(this->generation_changed); } this->vertex_color_gradient = gradient; if (gradient.is_valid()) { this->vertex_color_gradient->connect_changed(this->generation_changed); } on_configuration_changed(); } Ref TerrainMeshGenerator::get_vertex_color_gradient() const { return this->vertex_color_gradient; } void TerrainMeshGenerator::set_color_gradient_start_height(float value) { this->color_gradient_start_height = value; on_configuration_changed(); } float TerrainMeshGenerator::get_color_gradient_start_height() const { return this->color_gradient_start_height; } void TerrainMeshGenerator::set_color_gradient_end_height(float value) { this->color_gradient_end_height = value; on_configuration_changed(); } float TerrainMeshGenerator::get_color_gradient_end_height() const { return this->color_gradient_end_height; } void TerrainMeshGenerator::set_chunk_size(float size) { this->chunk_size = size; rebuild_chunks(); } float TerrainMeshGenerator::get_chunk_size() const { return this->chunk_size; } void TerrainMeshGenerator::set_lod1_detail(int detail) { if (detail != this->lod1_detail) { this->lod1_detail = detail; rebuild_chunks(); } } int TerrainMeshGenerator::get_lod1_detail() const { return this->lod1_detail; } void TerrainMeshGenerator::set_chunk_count(int num) { if (num != this->chunks_per_side) { this->chunks_per_side = num; rebuild_chunks(); } } int TerrainMeshGenerator::get_chunk_count() const { return this->chunks_per_side; } void TerrainMeshGenerator::set_material(Ref material) { this->material = material; for (TerrainChunk *chunk : this->chunks) { chunk->set_material_override(material); } } Ref TerrainMeshGenerator::get_material() const { return this->material; }