terrain-editor/modules/terrain_editor/terrain_mesh_generator.cpp

151 lines
5 KiB
C++

#include "terrain_mesh_generator.h"
#include "core/io/resource_saver.h"
#include "core/math/math_funcs.h"
#include "core/math/rect2.h"
#include "core/object/class_db.h"
#include "core/templates/local_vector.h"
#include "scene/resources/surface_tool.h"
#include "terrain_editor/macros.h"
#include "terrain_editor/terrain_primitive.h"
#include <limits>
String const TerrainMeshGenerator::sig_primitives_changed{ "primitives_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);
ADD_SIGNAL(MethodInfo(sig_primitives_changed));
ClassDB::bind_method(D_METHOD("generate_grid", "area", "out_mesh", "side_points"), &self_type::generate_grid);
}
void TerrainMeshGenerator::on_configuration_changed() {
emit_signal(sig_primitives_changed);
}
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<float>::infinity() };
for (Ref<TerrainPrimitive> primitive : this->primitives) {
if (primitive.is_valid()) {
primitive->evaluate(at, height);
}
}
return height;
}
void face_from(Ref<SurfaceTool> surface, size_t tl, size_t side_points) {
LocalVector<SurfaceTool::Vertex> &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<ArrayMesh> mesh, size_t side_points) {
mesh->clear_surfaces();
surface->clear();
Vector2 point_distance{ area.size / (float)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, height, at.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<TerrainPrimitive> primitive : this->primitives) {
if (primitive.is_valid()) {
primitive->disconnect_changed(this->generation_changed);
}
}
this->primitives.clear();
for (Variant var : primitives) {
Ref<TerrainPrimitive> primitive{ var };
this->primitives.push_back(primitive);
if (primitive.is_valid()) {
primitive->connect_changed(this->generation_changed);
}
}
on_configuration_changed();
}
Array TerrainMeshGenerator::get_primitives() const {
Array a;
for (Ref<TerrainPrimitive> primitive : this->primitives) {
a.push_back(primitive);
}
return a;
}
void TerrainMeshGenerator::set_vertex_color_gradient(Ref<Gradient> 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<Gradient> 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;
}