terrain-module/terrain_modifier_composite.cpp
2026-04-22 22:48:20 +02:00

121 lines
3.6 KiB
C++

#include "terrain_modifier_composite.h"
#include "core/math/math_funcs.h"
#include "terrain/terrain.h"
#include <cmath>
void TerrainModifierComposite::_bind_methods() {
BIND_HPROPERTY(Variant::INT, mode, PROPERTY_HINT_ENUM, CompositeMode_hint());
}
void TerrainModifierComposite::push_all_changes() {
for (TerrainModifier *mod : this->sub_modifiers) {
push_changed(mod->get_bounds());
}
}
void TerrainModifierComposite::update_sub_modifiers() {
for (TerrainModifier *mod : this->sub_modifiers) {
push_changed(mod->get_bounds());
}
this->sub_modifiers.clear();
for (Variant var : get_children()) {
if (TerrainModifier * mod{ cast_to<TerrainModifier>(var) }) {
this->sub_modifiers.push_back(mod);
mod->set_terrain(get_terrain());
push_changed(mod->get_bounds());
}
}
}
void TerrainModifierComposite::terrain_changed(Terrain *terrain) {
for (TerrainModifier *mod : this->sub_modifiers) {
mod->set_terrain(terrain);
}
}
void TerrainModifierComposite::_notification(int what) {
switch (what) {
default:
return;
case NOTIFICATION_ENTER_TREE:
set_notify_transform(true);
if (!is_ready()) {
connect(sig_terrain_changed, callable_mp(this, &self_type::terrain_changed));
}
return;
case NOTIFICATION_CHILD_ORDER_CHANGED:
if (!is_ready()) {
return;
}
// fall through
case NOTIFICATION_READY:
update_sub_modifiers();
return;
}
}
float TerrainModifierComposite::evaluate_at(Vector2 world_coordinate, float before) {
if (this->sub_modifiers.is_empty()) {
return before;
}
switch (this->mode) {
case Normal: { // evaluate as if the sub-modifiers are part of the parent directly, with no modification
float height{ before };
for (TerrainModifier *mod : this->sub_modifiers) {
height = mod->evaluate_at(world_coordinate, height);
}
return height;
}
case Multiply: { // evaluate independently, multiplying the /change/ each modifier makes together
float result_delta{ 1.f };
for (TerrainModifier *mod : this->sub_modifiers) {
result_delta *= mod->evaluate_at(world_coordinate, before) - before;
}
return result_delta + before;
}
case Add: { // evaluate independently, adding together the /change/ each modifier makes
float result{ 0.f };
for (TerrainModifier *mod : this->sub_modifiers) {
result += mod->evaluate_at(world_coordinate, before) - before;
}
return before + result;
}
case Max: { // always select the largest change
float result_delta{ 0.f };
for (TerrainModifier *mod : this->sub_modifiers) {
float delta{ mod->evaluate_at(world_coordinate, before) - before };
if (Math::abs(delta) > Math::abs(result_delta)) {
result_delta = delta;
}
}
return before + result_delta;
}
case Min: { // always select the smallest change
float result_delta{ INFINITY };
for (TerrainModifier *mod : this->sub_modifiers) {
float delta{ mod->evaluate_at(world_coordinate, before) - before };
if (Math::abs(delta) < Math::abs(result_delta)) {
result_delta = delta;
}
}
return before + result_delta;
}
case Average: {
float total_delta{ 0.f };
for (TerrainModifier *mod : this->sub_modifiers) {
total_delta += mod->evaluate_at(world_coordinate, before) - before;
}
return before + total_delta / (float)this->sub_modifiers.size();
}
case NormalMultiply: {
float add_result{ 0.f };
float multiply_result{ 1.f };
for (TerrainModifier *mod : this->sub_modifiers) {
float evaluated{ mod->evaluate_at(world_coordinate, before) - before };
add_result += evaluated;
multiply_result *= evaluated;
}
return before + add_result + multiply_result;
}
}
}