feat: started work on terrain editor tools
This commit is contained in:
parent
a61b52806a
commit
ac139f01b6
11 changed files with 423 additions and 0 deletions
3
modules/terrain/SCsub
Normal file
3
modules/terrain/SCsub
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
Import('env')
|
||||
|
||||
env.add_source_files(env.modules_sources, "*.cpp")
|
||||
5
modules/terrain/config.py
Normal file
5
modules/terrain/config.py
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
def can_build(env, platform):
|
||||
return True;
|
||||
|
||||
def configure(env):
|
||||
pass;
|
||||
22
modules/terrain/register_types.cpp
Normal file
22
modules/terrain/register_types.cpp
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#include "register_types.h"
|
||||
|
||||
#include "core/object/class_db.h"
|
||||
#include "terrain/terrain.h"
|
||||
#include "terrain/terrain_chunk.h"
|
||||
#include "terrain/terrain_modifier.h"
|
||||
|
||||
void initialize_terrain_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
return;
|
||||
}
|
||||
ClassDB::register_class<Terrain>();
|
||||
ClassDB::register_abstract_class<TerrainModifier>();
|
||||
ClassDB::register_class<TerrainModifierDistance>();
|
||||
ClassDB::register_class<TerrainChunkMesh>();
|
||||
}
|
||||
|
||||
void uninitialize_terrain_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
9
modules/terrain/register_types.h
Normal file
9
modules/terrain/register_types.h
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#ifndef TERRAIN_REGISTER_TYPES_H
|
||||
#define TERRAIN_REGISTER_TYPES_H
|
||||
|
||||
#include "modules/register_module_types.h"
|
||||
|
||||
void initialize_terrain_module(ModuleInitializationLevel p_level);
|
||||
void uninitialize_terrain_module(ModuleInitializationLevel p_level);
|
||||
|
||||
#endif // !TERRAIN_REGISTER_TYPES_H
|
||||
80
modules/terrain/terrain.cpp
Normal file
80
modules/terrain/terrain.cpp
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
#include "terrain.h"
|
||||
#include "terrain/terrain_chunk.h"
|
||||
#include "terrain/terrain_modifier.h"
|
||||
|
||||
void Terrain::_bind_methods() {}
|
||||
|
||||
void Terrain::ready() {
|
||||
construct_chunk_grid();
|
||||
generate_meshes();
|
||||
}
|
||||
|
||||
void Terrain::update_modifier_list() {
|
||||
this->modifiers.clear();
|
||||
for (Variant var : get_children()) {
|
||||
if (TerrainModifier * mod{ cast_to<TerrainModifier>(var) }) {
|
||||
this->modifiers.push_back(mod);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Terrain::construct_chunk_grid() {
|
||||
size_t const chunks_per_side{ this->side_length / this->chunk_size };
|
||||
Vector3 const origin{ -(float)chunks_per_side / 2.f * (float)this->chunk_size, 0.f, -(float)chunks_per_side / 2.f * (float)this->chunk_size };
|
||||
for (size_t y{ 0 }; y < chunks_per_side; ++y) {
|
||||
for (size_t x{ 0 }; x < chunks_per_side; ++x) {
|
||||
TerrainChunkMesh *chunk{ memnew(TerrainChunkMesh) };
|
||||
chunk->set_size(this->chunk_size);
|
||||
chunk->set_detail(this->detail);
|
||||
chunk->set_terrain(this);
|
||||
chunk->set_position(origin + Vector3{ (float)this->chunk_size * (float)x, 0.f, (float)this->chunk_size * (float)y });
|
||||
add_child(chunk);
|
||||
chunk->set_owner(this);
|
||||
this->meshes.push_back(chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Terrain::generate_meshes() {
|
||||
for (TerrainChunkMesh *mesh : this->meshes) {
|
||||
mesh->update_mesh();
|
||||
}
|
||||
}
|
||||
|
||||
void Terrain::child_entered(Node *node) {
|
||||
if (cast_to<TerrainModifier>(node)) {
|
||||
update_modifier_list();
|
||||
}
|
||||
}
|
||||
|
||||
void Terrain::child_exiting(Node *node) {
|
||||
if (TerrainModifier * mod{ cast_to<TerrainModifier>(node) }) {
|
||||
this->modifiers.erase(mod);
|
||||
}
|
||||
}
|
||||
|
||||
void Terrain::_notification(int what) {
|
||||
switch (what) {
|
||||
default:
|
||||
return;
|
||||
case NOTIFICATION_ENTER_TREE:
|
||||
if (!is_ready()) {
|
||||
connect("child_entered_tree", callable_mp(this, &self_type::child_entered));
|
||||
connect("child_exiting_tree", callable_mp(this, &self_type::child_exiting));
|
||||
connect("child_order_changed", callable_mp(this, &self_type::update_modifier_list));
|
||||
}
|
||||
return;
|
||||
return;
|
||||
case NOTIFICATION_READY:
|
||||
ready();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
float Terrain::height_at(Vector2 world_coordinate) {
|
||||
float height{ 0 };
|
||||
for (TerrainModifier *mod : this->modifiers) {
|
||||
height = mod->evaluate_at(world_coordinate, height);
|
||||
}
|
||||
return height;
|
||||
}
|
||||
39
modules/terrain/terrain.h
Normal file
39
modules/terrain/terrain.h
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
#pragma once
|
||||
|
||||
#include "core/templates/vector.h"
|
||||
#include "macros.h"
|
||||
#include "scene/main/node.h"
|
||||
class TerrainChunkMesh;
|
||||
class TerrainModifier;
|
||||
|
||||
class Terrain : public Node {
|
||||
GDCLASS(Terrain, Node);
|
||||
static void _bind_methods();
|
||||
void ready();
|
||||
void update_modifier_list();
|
||||
void construct_chunk_grid();
|
||||
void generate_meshes();
|
||||
|
||||
void child_entered(Node *node);
|
||||
void child_exiting(Node *node);
|
||||
|
||||
protected:
|
||||
void _notification(int what);
|
||||
|
||||
public:
|
||||
float height_at(Vector2 world_coordinate);
|
||||
|
||||
private:
|
||||
size_t side_length{ 100 };
|
||||
size_t chunk_size{ 10 };
|
||||
size_t detail{ 1 };
|
||||
|
||||
public:
|
||||
GET_SET_FNS(size_t, side_length);
|
||||
GET_SET_FNS(size_t, chunk_size);
|
||||
GET_SET_FNS(size_t, detail);
|
||||
|
||||
private:
|
||||
Vector<TerrainChunkMesh *> meshes;
|
||||
Vector<TerrainModifier *> modifiers;
|
||||
};
|
||||
69
modules/terrain/terrain_chunk.cpp
Normal file
69
modules/terrain/terrain_chunk.cpp
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
#include "terrain_chunk.h"
|
||||
#include "core/variant/variant.h"
|
||||
#include "scene/resources/surface_tool.h"
|
||||
#include "terrain/terrain.h"
|
||||
|
||||
void TerrainChunkMesh::_bind_methods() {}
|
||||
|
||||
void TerrainChunkMesh::generate_vertices() {
|
||||
ERR_FAIL_COND_EDMSG(this->terrain == nullptr, "TerrainChunkMesh::generate_vertices: no terrain assigned");
|
||||
ERR_FAIL_COND_EDMSG(this->size <= 0.f, "TerrainChunkMesh::generate_vertices: size <= 0");
|
||||
ERR_FAIL_COND_EDMSG(points_per_side() <= 0, "TerrainChunkMesh::generate_vertices: points per side <= 0");
|
||||
float const half_extent{ (float)this->size / 2.f };
|
||||
float const point_distance{ (float)this->size / ((float)points_per_side() - 1) };
|
||||
Vector3 origin{ this->get_global_position() - Vector3{ half_extent, 0, half_extent } };
|
||||
for (size_t x{ 0 }; x < points_per_side(); ++x) {
|
||||
for (size_t y{ 0 }; y < points_per_side(); ++y) {
|
||||
Vector2 const coordinate{ origin.x + point_distance * x, origin.z + point_distance * y };
|
||||
this->surface->set_uv({ (float)x / (float)points_per_side(), (float)y / (float)points_per_side() });
|
||||
this->surface->add_vertex({ coordinate.x - get_global_position().x, this->terrain->height_at(coordinate), coordinate.y - get_global_position().z });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TerrainChunkMesh::generate_faces() {
|
||||
LocalVector<SurfaceTool::Vertex> &verts{ this->surface->get_vertex_array() };
|
||||
ERR_FAIL_COND_EDMSG(verts.size() == 0, "TerrainChunkMesh::generate_faces: no vertices in surface, call generate_vertices first");
|
||||
size_t const faces_per_side{ points_per_side() - 1 };
|
||||
for (size_t x{ 0 }; x < faces_per_side; ++x) {
|
||||
for (size_t y{ 0 }; y < faces_per_side; ++y) {
|
||||
size_t const tl{ x + y * points_per_side() };
|
||||
float tl_br{ verts[tl].vertex.distance_to(verts[tl + points_per_side() + 1].vertex) };
|
||||
float tr_bl{ verts[tl + 1].vertex.distance_to(verts[tl + points_per_side()].vertex) };
|
||||
if (tl_br < tr_bl) {
|
||||
surface->add_index(tl);
|
||||
surface->add_index(tl + points_per_side() + 1);
|
||||
surface->add_index(tl + 1);
|
||||
|
||||
surface->add_index(tl);
|
||||
surface->add_index(tl + points_per_side());
|
||||
surface->add_index(tl + points_per_side() + 1);
|
||||
} else {
|
||||
surface->add_index(tl + points_per_side());
|
||||
surface->add_index(tl + points_per_side() + 1);
|
||||
surface->add_index(tl + 1);
|
||||
|
||||
surface->add_index(tl + 1);
|
||||
surface->add_index(tl);
|
||||
surface->add_index(tl + points_per_side());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TerrainChunkMesh::update_mesh() {
|
||||
ERR_FAIL_COND_EDMSG(this->size <= 0.f, "TerrainChunkMesh::generate: size <= 0");
|
||||
ERR_FAIL_COND_EDMSG(points_per_side() <= 0, "TerrainChunkMesh::generate: points per side <= 0");
|
||||
this->set_mesh(memnew(ArrayMesh));
|
||||
this->surface->clear();
|
||||
this->surface->begin(Mesh::PRIMITIVE_TRIANGLES);
|
||||
generate_vertices();
|
||||
generate_faces();
|
||||
this->surface->generate_normals();
|
||||
this->surface->generate_tangents();
|
||||
this->surface->commit(this->mesh);
|
||||
}
|
||||
|
||||
size_t TerrainChunkMesh::points_per_side() const {
|
||||
return this->size * this->detail;
|
||||
}
|
||||
28
modules/terrain/terrain_chunk.h
Normal file
28
modules/terrain/terrain_chunk.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#include "macros.h"
|
||||
#include "scene/3d/mesh_instance_3d.h"
|
||||
#include "scene/resources/surface_tool.h"
|
||||
class Terrain;
|
||||
|
||||
class TerrainChunkMesh : public MeshInstance3D {
|
||||
GDCLASS(TerrainChunkMesh, MeshInstance3D);
|
||||
static void _bind_methods();
|
||||
void generate_vertices();
|
||||
void generate_faces();
|
||||
|
||||
public:
|
||||
void update_mesh();
|
||||
size_t points_per_side() const;
|
||||
|
||||
private:
|
||||
Ref<SurfaceTool> surface{ memnew(SurfaceTool) };
|
||||
Terrain *terrain{};
|
||||
size_t detail{ 1 };
|
||||
size_t size{ 1 };
|
||||
|
||||
public:
|
||||
GET_SET_FNS(Terrain *, terrain);
|
||||
GET_SET_FNS(size_t, detail);
|
||||
GET_SET_FNS(size_t, size);
|
||||
};
|
||||
82
modules/terrain/terrain_modifier.cpp
Normal file
82
modules/terrain/terrain_modifier.cpp
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
#include "terrain_modifier.h"
|
||||
#include "core/variant/variant.h"
|
||||
#include "macros.h"
|
||||
#include <algorithm>
|
||||
|
||||
void TerrainModifier::_bind_methods() {
|
||||
BIND_HPROPERTY(Variant::INT, blend_mode, PROPERTY_HINT_ENUM, BlendMode_hint());
|
||||
BIND_PROPERTY(Variant::FLOAT, blend_distance);
|
||||
}
|
||||
|
||||
float TerrainModifier::blend(float under, float over, float weight) {
|
||||
if (weight <= 0.0) {
|
||||
return under;
|
||||
}
|
||||
over = Math::lerp(under, over, weight);
|
||||
float const difference{ under - over };
|
||||
float const distance{ Math::abs(difference) };
|
||||
// .25 because we need half of each half of the blend range to be used
|
||||
float const center_distance{
|
||||
this->blend_distance == 0.f
|
||||
? 0.f
|
||||
: this->blend_distance * 0.25f - distance / this->blend_distance
|
||||
};
|
||||
if (center_distance < 0.f) {
|
||||
if (this->blend_mode == Override) {
|
||||
return over;
|
||||
} else if (this->blend_mode == Add) {
|
||||
return under > over ? under : over;
|
||||
} else {
|
||||
return under > over ? over : under;
|
||||
}
|
||||
}
|
||||
float const smooth_center_distance{ center_distance * center_distance };
|
||||
if (this->blend_mode == Override) {
|
||||
return over + smooth_center_distance;
|
||||
} else {
|
||||
return (this->blend_mode == Add
|
||||
? (under > over ? under : over) + smooth_center_distance
|
||||
: (under > over ? over : under) - smooth_center_distance);
|
||||
}
|
||||
}
|
||||
|
||||
float TerrainModifier::evaluate_at(Vector2 world_coordinate, float before) {
|
||||
Vector3 const global_position{ get_global_position() };
|
||||
world_coordinate -= { global_position.x, global_position.z };
|
||||
return blend(before, 0.0, 1.0);
|
||||
}
|
||||
|
||||
void TerrainModifierDistance::_bind_methods() {
|
||||
BIND_HPROPERTY(Variant::OBJECT, distance_weight_curve, PROPERTY_HINT_RESOURCE_TYPE, "Curve");
|
||||
BIND_HPROPERTY(Variant::OBJECT, distance_height_curve, PROPERTY_HINT_RESOURCE_TYPE, "Curve");
|
||||
}
|
||||
|
||||
float TerrainModifierDistance::distance_at(Vector2 const &world_coordinate) {
|
||||
Vector3 const global_position{ get_global_position() };
|
||||
return world_coordinate.distance_to({ global_position.x, global_position.z });
|
||||
}
|
||||
|
||||
float TerrainModifierDistance::evaluate_at(Vector2 world_coordinate, float before) {
|
||||
if (this->distance_weight_curve.is_null() || this->distance_height_curve.is_null()) {
|
||||
return before;
|
||||
}
|
||||
float const distance{ distance_at(world_coordinate) };
|
||||
float const height_offset{
|
||||
std::clamp(distance, this->distance_height_curve->get_min_domain(), this->distance_height_curve->get_max_domain())
|
||||
};
|
||||
float const weight_offset{
|
||||
std::clamp(distance, this->distance_weight_curve->get_min_domain(), this->distance_weight_curve->get_max_domain())
|
||||
};
|
||||
return blend(before, this->distance_height_curve->sample_baked(height_offset) + get_global_position().y, this->distance_weight_curve->sample_baked(weight_offset));
|
||||
}
|
||||
|
||||
PackedStringArray TerrainModifierDistance::get_configuration_warnings() const {
|
||||
PackedStringArray warnings{ super_type::get_configuration_warnings() };
|
||||
if (this->distance_weight_curve.is_null()) {
|
||||
warnings.push_back("distance_weight_curve is invalid, add a valid distance_weight_curve");
|
||||
}
|
||||
if (this->distance_height_curve.is_null()) {
|
||||
warnings.push_back("distance_height_curve is invalid, add a valid distance_height_curve");
|
||||
}
|
||||
return warnings;
|
||||
}
|
||||
51
modules/terrain/terrain_modifier.h
Normal file
51
modules/terrain/terrain_modifier.h
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
#pragma once
|
||||
|
||||
#include "core/object/object.h"
|
||||
#include "core/variant/variant.h"
|
||||
#include "macros.h"
|
||||
#include "scene/3d/node_3d.h"
|
||||
#include "scene/resources/curve.h"
|
||||
|
||||
class TerrainModifier : public Node3D {
|
||||
GDCLASS(TerrainModifier, Node3D);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
GDENUM(BlendMode, Add, Subtract, Override);
|
||||
|
||||
protected:
|
||||
float blend(float under, float over, float weight);
|
||||
|
||||
public:
|
||||
virtual float evaluate_at(Vector2 world_coordinate, float before);
|
||||
|
||||
private:
|
||||
float blend_distance{ 1.0 };
|
||||
BlendMode blend_mode{ Add };
|
||||
|
||||
public:
|
||||
GET_SET_FNS(float, blend_distance);
|
||||
GET_SET_FNS(BlendMode, blend_mode);
|
||||
};
|
||||
|
||||
MAKE_TYPE_INFO(TerrainModifier::BlendMode, Variant::INT);
|
||||
|
||||
class TerrainModifierDistance : public TerrainModifier {
|
||||
GDCLASS(TerrainModifierDistance, TerrainModifier);
|
||||
static void _bind_methods();
|
||||
|
||||
protected:
|
||||
virtual float distance_at(Vector2 const &world_coordinate);
|
||||
|
||||
public:
|
||||
float evaluate_at(Vector2 world_coordinate, float before) override;
|
||||
PackedStringArray get_configuration_warnings() const override;
|
||||
|
||||
private:
|
||||
Ref<Curve> distance_weight_curve{};
|
||||
Ref<Curve> distance_height_curve{};
|
||||
|
||||
public:
|
||||
GET_SET_FNS_EX(Ref<Curve>, distance_weight_curve, update_configuration_warnings());
|
||||
GET_SET_FNS_EX(Ref<Curve>, distance_height_curve, update_configuration_warnings());
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue