392 lines
12 KiB
C++
392 lines
12 KiB
C++
#include "terrain_modifier.h"
|
|
#include "core/config/engine.h"
|
|
#include "core/math/math_funcs.h"
|
|
#include "core/variant/variant.h"
|
|
#include "macros.h"
|
|
#include "terrain/terrain.h"
|
|
#include <algorithm>
|
|
|
|
void TerrainModifier::_bind_methods() {}
|
|
|
|
void TerrainModifier::_notification(int what) {
|
|
switch (what) {
|
|
default:
|
|
return;
|
|
case NOTIFICATION_ENTER_TREE:
|
|
if (Engine::get_singleton()->is_editor_hint()) {
|
|
set_notify_transform(true);
|
|
}
|
|
this->thread_safe_global_position = get_global_position();
|
|
case NOTIFICATION_TRANSFORM_CHANGED:
|
|
this->thread_safe_global_position = get_global_position();
|
|
return;
|
|
}
|
|
}
|
|
|
|
void TerrainModifier::push_changed(Rect2 area) {
|
|
if (this->terrain) {
|
|
this->terrain->push_changed(area);
|
|
}
|
|
}
|
|
|
|
float TerrainModifier::evaluate_at(Vector2 world_coordinate, float before) {
|
|
Vector3 const global_position{ get_thread_safe_global_position() };
|
|
return global_position.y;
|
|
}
|
|
|
|
void TerrainModifier::set_bounds(Rect2 bounds) {
|
|
if (this->bounds != bounds) {
|
|
push_changed(bounds);
|
|
push_changed(this->bounds);
|
|
this->bounds = bounds;
|
|
}
|
|
}
|
|
|
|
Rect2 TerrainModifier::get_bounds() const {
|
|
return this->bounds;
|
|
}
|
|
|
|
Vector3 TerrainModifier::get_thread_safe_global_position() const {
|
|
return this->thread_safe_global_position;
|
|
}
|
|
|
|
void SharedMutex::lock_shared() {
|
|
this->lock.lock();
|
|
this->shared_count++;
|
|
this->lock.unlock();
|
|
}
|
|
|
|
void SharedMutex::unlock_shared() {
|
|
this->lock.lock();
|
|
this->shared_count--;
|
|
this->lock.unlock();
|
|
}
|
|
|
|
void SharedMutex::lock_exclusive() {
|
|
while (true) {
|
|
this->lock.lock();
|
|
if (this->shared_count == 0) {
|
|
return;
|
|
}
|
|
this->lock.unlock();
|
|
}
|
|
}
|
|
|
|
void SharedMutex::unlock_exclusive() {
|
|
this->lock.unlock();
|
|
}
|
|
|
|
void TerrainModifierDistance::_bind_methods() {
|
|
BIND_HPROPERTY(Variant::OBJECT, distance_weight_curve, PROPERTY_HINT_RESOURCE_TYPE, "Curve");
|
|
}
|
|
|
|
void TerrainModifierDistance::curves_changed() {
|
|
if (!update_bounds()) {
|
|
push_changed(get_bounds());
|
|
}
|
|
}
|
|
|
|
bool TerrainModifierDistance::update_bounds() {
|
|
Rect2 const before{ get_bounds() };
|
|
Rect2 bounds{};
|
|
Vector3 position{ get_thread_safe_global_position() };
|
|
bounds.position = { position.x, position.z };
|
|
bounds.size = { 0, 0 };
|
|
this->lock.lock_shared();
|
|
if (this->distance_weight_curve.is_valid()) {
|
|
float const max_radius{ this->distance_weight_curve->get_max_domain() };
|
|
float const max_diameter{ 2.f * max_radius };
|
|
bounds.size = { max_diameter, max_diameter };
|
|
bounds.position -= { max_radius, max_radius };
|
|
}
|
|
this->lock.unlock_shared();
|
|
this->lock.lock_exclusive();
|
|
bool const changed{ before != bounds };
|
|
set_bounds(bounds);
|
|
this->lock.unlock_exclusive();
|
|
return changed;
|
|
}
|
|
|
|
void TerrainModifierDistance::_notification(int what) {
|
|
switch (what) {
|
|
default:
|
|
return;
|
|
case NOTIFICATION_READY:
|
|
update_bounds();
|
|
set_notify_transform(true);
|
|
return;
|
|
case NOTIFICATION_TRANSFORM_CHANGED:
|
|
if (is_inside_tree()) {
|
|
if (!update_bounds()) {
|
|
push_changed(get_bounds());
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
float TerrainModifierDistance::distance_at(Vector2 const &world_coordinate) {
|
|
Vector3 const global_position{ get_thread_safe_global_position() };
|
|
return world_coordinate.distance_to({ global_position.x, global_position.z });
|
|
}
|
|
|
|
float TerrainModifierDistance::evaluate_at(Vector2 world_coordinate, float before) {
|
|
this->lock.lock_shared();
|
|
if (this->distance_weight_curve.is_null()) {
|
|
this->lock.unlock_shared();
|
|
return before;
|
|
}
|
|
float const distance{ distance_at(world_coordinate) };
|
|
if (distance >= this->distance_weight_curve->get_max_domain()) {
|
|
this->lock.unlock_shared();
|
|
return before;
|
|
}
|
|
float const weight_offset{
|
|
std::clamp(distance, this->distance_weight_curve->get_min_domain(), this->distance_weight_curve->get_max_domain())
|
|
};
|
|
float const weight{ this->distance_weight_curve->sample(weight_offset) };
|
|
float out{ weight <= 0.f ? before : Math::lerp(before, get_thread_safe_global_position().y, weight) };
|
|
this->lock.unlock_shared();
|
|
|
|
return out;
|
|
}
|
|
|
|
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");
|
|
}
|
|
return warnings;
|
|
}
|
|
|
|
void TerrainModifierDistance::set_distance_weight_curve(Ref<Curve> curve) {
|
|
this->lock.lock_exclusive();
|
|
if (Engine::get_singleton()->is_editor_hint()) {
|
|
if (this->distance_weight_curve.is_valid()) {
|
|
this->distance_weight_curve->disconnect_changed(callable_mp(this, &self_type::curves_changed));
|
|
}
|
|
if (curve.is_valid()) {
|
|
curve->connect_changed(callable_mp(this, &self_type::curves_changed));
|
|
}
|
|
}
|
|
this->distance_weight_curve = curve;
|
|
this->lock.unlock_exclusive();
|
|
curves_changed();
|
|
update_configuration_warnings();
|
|
}
|
|
|
|
Ref<Curve> TerrainModifierDistance::get_distance_weight_curve() const {
|
|
return this->distance_weight_curve;
|
|
}
|
|
|
|
void TerrainModifierPathPoint::_bind_methods() {}
|
|
|
|
void TerrainModifierPathPoint::_notification(int what) {
|
|
switch (what) {
|
|
default:
|
|
return;
|
|
case NOTIFICATION_ENTER_TREE:
|
|
if ((this->path = cast_to<TerrainModifierPath>(get_parent()))) {
|
|
this->path->path_changed();
|
|
}
|
|
return;
|
|
case NOTIFICATION_READY:
|
|
set_notify_transform(true);
|
|
return;
|
|
case NOTIFICATION_TRANSFORM_CHANGED:
|
|
if (this->path) {
|
|
this->path->path_changed();
|
|
}
|
|
return;
|
|
case NOTIFICATION_EXIT_TREE:
|
|
this->path = nullptr;
|
|
return;
|
|
}
|
|
}
|
|
|
|
void TerrainModifierPath::_bind_methods() {
|
|
BIND_HPROPERTY(Variant::OBJECT, curve_left, PROPERTY_HINT_RESOURCE_TYPE, "Curve");
|
|
BIND_HPROPERTY(Variant::OBJECT, curve_right, PROPERTY_HINT_RESOURCE_TYPE, "Curve");
|
|
}
|
|
|
|
void TerrainModifierPath::curves_changed() {
|
|
if (!update_bounds()) {
|
|
push_changed(get_bounds());
|
|
}
|
|
}
|
|
|
|
bool TerrainModifierPath::update_bounds() {
|
|
Vector2 min{}, max{};
|
|
this->lock.lock_shared();
|
|
if (this->points.is_empty() || this->curve_left.is_null() || this->curve_right.is_null()) {
|
|
Vector3 point{ this->get_thread_safe_global_position() };
|
|
min.x = point.x;
|
|
min.y = point.z;
|
|
max = min;
|
|
} else {
|
|
max = min = { this->points[0].x, this->points[0].y };
|
|
for (Vector3 const &point : this->points) {
|
|
max.x = max.x > point.x ? max.x : point.x;
|
|
max.y = max.y > point.z ? max.y : point.z;
|
|
min.x = min.x < point.x ? min.x : point.x;
|
|
min.y = min.y < point.z ? min.y : point.z;
|
|
}
|
|
float max_distance_left{ this->curve_left->get_max_domain() };
|
|
float max_distance_right{ this->curve_right->get_max_domain() };
|
|
float max_distance{ max_distance_left > max_distance_right ? max_distance_left : max_distance_right };
|
|
min -= { max_distance, max_distance };
|
|
max += { max_distance, max_distance };
|
|
}
|
|
Rect2 bounds{ min, max - min };
|
|
bool const changed{ bounds != get_bounds() };
|
|
this->lock.unlock_shared();
|
|
set_bounds(bounds);
|
|
return changed;
|
|
}
|
|
|
|
void TerrainModifierPath::_notification(int what) {
|
|
switch (what) {
|
|
default:
|
|
return;
|
|
case NOTIFICATION_READY:
|
|
update_bounds();
|
|
set_notify_transform(true);
|
|
return;
|
|
case NOTIFICATION_TRANSFORM_CHANGED:
|
|
if (is_inside_tree()) {
|
|
if (!update_bounds()) {
|
|
push_changed(get_bounds());
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
float TerrainModifierPath::evaluate_line(Vector3 a, bool a_end, Vector3 b, bool b_end, Vector2 world_coordinate, float &out_dot, float &out_distance) {
|
|
Vector2 a2{ a.x, a.z }, b2{ b.x, b.z };
|
|
Vector2 const relative_coordinate{ world_coordinate - a2 };
|
|
Vector2 const difference2{ b2 - a2 };
|
|
float w{ difference2.normalized().dot(relative_coordinate) / difference2.length() };
|
|
Vector3 const difference{ b - a };
|
|
Vector3 const closest_on_line{ a + difference * (w > 0 ? (w < 1 ? w : 1) : 0) };
|
|
Vector2 const right{ -difference.z, difference.x };
|
|
out_dot = right.normalized().dot(relative_coordinate);
|
|
out_distance = world_coordinate.distance_to({ closest_on_line.x, closest_on_line.z });
|
|
if (a_end) {
|
|
w = w > 0 ? w : 0;
|
|
}
|
|
if (b_end) {
|
|
w = w < 1 ? w : 1;
|
|
}
|
|
return a.y + (b.y - a.y) * w;
|
|
}
|
|
|
|
float TerrainModifierPath::evaluate_at(Vector2 world_coordinate, float before) {
|
|
this->lock.lock_shared();
|
|
if (this->curve_left.is_null() || this->curve_right.is_null() || this->points.size() <= 1) {
|
|
this->lock.unlock_shared();
|
|
return before;
|
|
}
|
|
float const max_distance{ this->curve_left->get_max_domain() > this->curve_right->get_max_domain() ? this->curve_left->get_max_domain() : this->curve_right->get_max_domain() };
|
|
float out_score{ -INFINITY };
|
|
float out_height{ 0 };
|
|
long const count{ this->closed ? this->points.size() : this->points.size() - 1 };
|
|
for (int i{ 0 }; i < count; i++) {
|
|
float dot, distance;
|
|
float const height{ evaluate_line(this->points[i], !this->closed && i == 0, this->points[Math::wrapi(i + 1, 0, this->points.size())], !this->closed && i == count - 1, world_coordinate, dot, distance) };
|
|
float const left{ this->curve_left->sample(distance) };
|
|
float const right{ this->curve_right->sample(distance) };
|
|
float const ndot{ dot / distance };
|
|
float separation{ ndot / 2.f + 0.5f };
|
|
if (closed || (i > 0 && i < count - 1)) {
|
|
separation = Math::round(separation);
|
|
}
|
|
float const weight{ left * (1 - separation) + right * separation };
|
|
float const blended_height{ Math::lerp(before, height, weight) };
|
|
float const score{ weight - (1 - Math::abs(ndot)) * 2.f - (distance / max_distance) };
|
|
if (score > out_score) {
|
|
out_score = score;
|
|
out_height = blended_height;
|
|
}
|
|
}
|
|
this->lock.unlock_shared();
|
|
return out_height;
|
|
}
|
|
|
|
void TerrainModifierPath::path_changed() {
|
|
this->lock.lock_exclusive();
|
|
this->points.clear();
|
|
this->min_height = INFINITY;
|
|
this->max_height = -INFINITY;
|
|
Vector3 last{ INFINITY, INFINITY, INFINITY };
|
|
for (Variant var : get_children()) {
|
|
if (TerrainModifierPathPoint * point{ cast_to<TerrainModifierPathPoint>(var) }) {
|
|
Vector3 position{ point->get_global_position() };
|
|
if (position != last) {
|
|
this->points.push_back(position);
|
|
if (position.y > this->max_height) {
|
|
this->max_height = position.y;
|
|
}
|
|
if (position.y < this->min_height) {
|
|
this->min_height = position.y;
|
|
}
|
|
}
|
|
}
|
|
last = var;
|
|
}
|
|
this->lock.unlock_exclusive();
|
|
if (!update_bounds()) {
|
|
push_changed(get_bounds());
|
|
}
|
|
}
|
|
|
|
PackedStringArray TerrainModifierPath::get_configuration_warnings() const {
|
|
PackedStringArray warnings{ super_type::get_configuration_warnings() };
|
|
if (this->curve_left.is_null()) {
|
|
warnings.push_back("distance_weight_curve is invalid, add a valid curve_left");
|
|
}
|
|
if (this->curve_right.is_null()) {
|
|
warnings.push_back("distance_weight_curve is invalid, add a valid curve_right");
|
|
}
|
|
return warnings;
|
|
}
|
|
|
|
void TerrainModifierPath::set_curve_left(Ref<Curve> curve) {
|
|
this->lock.lock_exclusive();
|
|
if (Engine::get_singleton()->is_editor_hint()) {
|
|
if (this->curve_left.is_valid()) {
|
|
this->curve_left->disconnect_changed(callable_mp(this, &self_type::curves_changed));
|
|
}
|
|
if (curve.is_valid()) {
|
|
curve->connect_changed(callable_mp(this, &self_type::curves_changed));
|
|
}
|
|
}
|
|
this->curve_left = curve;
|
|
this->lock.unlock_exclusive();
|
|
curves_changed();
|
|
update_configuration_warnings();
|
|
}
|
|
|
|
Ref<Curve> TerrainModifierPath::get_curve_left() const {
|
|
return this->curve_left;
|
|
}
|
|
|
|
void TerrainModifierPath::set_curve_right(Ref<Curve> curve) {
|
|
this->lock.lock_exclusive();
|
|
if (Engine::get_singleton()->is_editor_hint()) {
|
|
if (this->curve_right.is_valid()) {
|
|
this->curve_right->disconnect_changed(callable_mp(this, &self_type::curves_changed));
|
|
}
|
|
if (curve.is_valid()) {
|
|
curve->connect_changed(callable_mp(this, &self_type::curves_changed));
|
|
}
|
|
}
|
|
this->curve_right = curve;
|
|
this->lock.unlock_exclusive();
|
|
curves_changed();
|
|
update_configuration_warnings();
|
|
}
|
|
|
|
Ref<Curve> TerrainModifierPath::get_curve_right() const {
|
|
return this->curve_right;
|
|
}
|