feat: implemented smooth paths

This commit is contained in:
Sara Gerretsen 2026-03-02 23:29:32 +01:00
parent 9ea66df220
commit fac1f4c733
3 changed files with 101 additions and 30 deletions

View file

@ -84,6 +84,9 @@ void TerrainModifierDistance::curves_changed() {
if (!update_bounds()) {
push_changed(get_bounds());
}
this->lock.lock_exclusive();
this->distance_weight_curve_buffer = this->distance_weight_curve.is_valid() ? this->distance_weight_curve->duplicate(true) : nullptr;
this->lock.unlock_exclusive();
}
bool TerrainModifierDistance::update_bounds() {
@ -99,9 +102,9 @@ bool TerrainModifierDistance::update_bounds() {
bounds.size = { max_diameter, max_diameter };
bounds.position -= { max_radius, max_radius };
}
bool const changed{ before != bounds };
this->lock.unlock_shared();
this->lock.lock_exclusive();
bool const changed{ before != bounds };
set_bounds(bounds);
this->lock.unlock_exclusive();
return changed;
@ -132,19 +135,17 @@ float TerrainModifierDistance::distance_at(Vector2 const &world_coordinate) {
float TerrainModifierDistance::evaluate_at(Vector2 world_coordinate, float before) {
this->lock.lock_shared();
if (this->distance_weight_curve.is_null()) {
if (this->distance_weight_curve_buffer.is_null()) {
this->lock.unlock_shared();
return before;
}
float const distance{ distance_at(world_coordinate) };
if (distance >= this->distance_weight_curve->get_max_domain()) {
if (distance >= this->distance_weight_curve_buffer->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 const weight_offset{ std::clamp(distance, this->distance_weight_curve_buffer->get_min_domain(), this->distance_weight_curve_buffer->get_max_domain()) };
float const weight{ this->distance_weight_curve_buffer->sample(weight_offset) };
float out{ weight <= 0.f ? before : Math::lerp(before, get_thread_safe_global_position().y, weight) };
this->lock.unlock_shared();
@ -213,6 +214,10 @@ void TerrainModifierPath::curves_changed() {
if (!update_bounds()) {
push_changed(get_bounds());
}
this->lock.lock_exclusive();
this->curve_left_buffer = this->curve_left.is_valid() ? this->curve_left->duplicate(true) : nullptr;
this->curve_right_buffer = this->curve_right.is_valid() ? this->curve_right->duplicate(true) : nullptr;
this->lock.unlock_exclusive();
}
bool TerrainModifierPath::update_bounds() {
@ -240,7 +245,9 @@ bool TerrainModifierPath::update_bounds() {
Rect2 bounds{ min, max - min };
bool const changed{ bounds != get_bounds() };
this->lock.unlock_shared();
this->lock.lock_exclusive();
set_bounds(bounds);
this->lock.unlock_exclusive();
return changed;
}
@ -284,10 +291,14 @@ float TerrainModifierPath::evaluate_line(Vector3 a, bool a_end, Vector3 b, bool
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) {
if (this->curve_left_buffer.is_null() || this->points.size() <= 1) {
this->lock.unlock_shared();
return before;
}
Ref<Curve> right_curve{ this->curve_right_buffer };
if (right_curve.is_null()) {
right_curve = this->curve_left_buffer;
}
float out_score{ 0.f };
float out_delta{ 0.f };
long const count{ this->closed ? this->points.size() : this->points.size() - 1 };
@ -296,29 +307,30 @@ float TerrainModifierPath::evaluate_at(Vector2 world_coordinate, float before) {
float dot, distance, percentage;
bool const is_start{ !this->closed && i == 0 }, is_end{ !this->closed && i == count - 1 };
float const height{ evaluate_line(ipos, is_start, this->points[Math::wrapi(i + 1, 0, this->points.size())], is_end, world_coordinate, dot, distance, percentage) };
float const left{ this->curve_left->sample(distance) };
float const right{ this->curve_right->sample(distance) };
float const left{ this->curve_left_buffer->sample(distance) };
float const right{ right_curve->sample(distance) };
float const ndot{ dot / distance };
float separation{ ndot / 2.f + 0.5f };
float turn{ ndot };
Vector3 const right_direction{ (this->points[Math::wrapi(i + 1, 0, this->points.size())] - ipos).cross({ 0, 1, 0 }) };
float turn{ 0.f };
Vector3 const right_direction{ (this->points[Math::wrapi(i + 1, 0, this->points.size())] - ipos).normalized().cross({ 0, 1, 0 }) };
if (!is_end && percentage >= 1.f) {
turn = (this->points[Math::wrapi(i + 2, 0, this->points.size())] - ipos).dot(right_direction);
separation = turn <= 0 ? 0 : 1;
separation = turn >= 0 ? 0 : 1;
} else if (!is_start && percentage <= 0.f) {
turn = (this->points[Math::wrapi(i - 1, 0, this->points.size())] - ipos).dot(right_direction);
separation = turn <= 0 ? 0 : 1;
separation = turn >= 0 ? 0 : 1;
} else if (!is_start && !is_end) {
separation = Math::round(separation);
turn = -dot;
}
if (turn / turn == dot / dot) {
if ((percentage > 0.f && percentage < 1.f) || Math::abs(turn) / turn != Math::abs(dot) / dot) {
float const weight{ left * (1 - separation) + right * separation };
float const blended_height{ Math::lerp(before, height, weight) };
float const delta{ blended_height - before };
float const score{ weight };
float const score{ Math::abs(delta) };
if (score > out_score) {
out_score = score;
out_delta = delta * score;
out_delta = delta;
}
}
}
@ -326,7 +338,7 @@ float TerrainModifierPath::evaluate_at(Vector2 world_coordinate, float before) {
if (out_score == 0.f) {
return before;
}
return before + (out_delta / out_score);
return before + out_delta;
}
void TerrainModifierPath::path_changed() {
@ -369,6 +381,14 @@ PackedStringArray TerrainModifierPath::get_configuration_warnings() const {
void TerrainModifierPath::set_curve_left(Ref<Curve> curve) {
this->lock.lock_exclusive();
if (curve.is_valid() && curve == this->curve_right) {
curve = curve->duplicate();
} else if (!curve.is_valid() && this->curve_right.is_valid()) {
curve = this->curve_right;
this->lock.unlock_exclusive();
set_curve_right(nullptr);
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));
@ -389,6 +409,9 @@ Ref<Curve> TerrainModifierPath::get_curve_left() const {
void TerrainModifierPath::set_curve_right(Ref<Curve> curve) {
this->lock.lock_exclusive();
if (curve.is_valid() && curve == this->curve_left) {
curve = curve->duplicate();
}
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));

View file

@ -61,6 +61,7 @@ public:
private:
SharedMutex lock{};
Ref<Curve> distance_weight_curve{};
Ref<Curve> distance_weight_curve_buffer{};
public:
void set_distance_weight_curve(Ref<Curve> curve);
@ -99,7 +100,9 @@ private:
float max_height{};
bool closed{ false };
Vector<Vector3> points{};
Ref<Curve> curve_left_buffer{};
Ref<Curve> curve_left{};
Ref<Curve> curve_right_buffer{};
Ref<Curve> curve_right{};
public: