feat: added expression value to every primitive

This commit is contained in:
Sara Gerretsen 2026-01-07 17:23:49 +01:00
parent 63c391593d
commit 2b04362ecc
6 changed files with 121 additions and 48 deletions

View file

@ -21,7 +21,6 @@ void initialize_terrain_editor_module(ModuleInitializationLevel p_level) {
ClassDB::register_class<PlanePrimitive>(); ClassDB::register_class<PlanePrimitive>();
ClassDB::register_class<PointPrimitive>(); ClassDB::register_class<PointPrimitive>();
ClassDB::register_class<NoisePrimitive>(); ClassDB::register_class<NoisePrimitive>();
ClassDB::register_class<ExpressionPrimitive>();
ClassDB::register_class<PointPrimitiveNode>(); ClassDB::register_class<PointPrimitiveNode>();
ClassDB::register_class<TerrainMeshEditor>(); ClassDB::register_class<TerrainMeshEditor>();
ClassDB::register_class<SaveData>(); ClassDB::register_class<SaveData>();

View file

@ -7,6 +7,8 @@
void TerrainPrimitive::_bind_methods() { void TerrainPrimitive::_bind_methods() {
BIND_HPROPERTY(Variant::INT, blend_mode, PROPERTY_HINT_ENUM, BlendMode_hint()); BIND_HPROPERTY(Variant::INT, blend_mode, PROPERTY_HINT_ENUM, BlendMode_hint());
BIND_PROPERTY(Variant::FLOAT, blend_range); BIND_PROPERTY(Variant::FLOAT, blend_range);
BIND_HPROPERTY(Variant::STRING, expression, PROPERTY_HINT_EXPRESSION);
ClassDB::bind_method(D_METHOD("get_expression_error"), &self_type::get_expression_error);
} }
// by default does not modify height // by default does not modify height
@ -54,12 +56,42 @@ float TerrainPrimitive::get_blend_range() const {
return this->blend_range; return this->blend_range;
} }
void TerrainPrimitive::set_expression(String expression) {
this->expr_text = expression;
if (expression.is_empty()) {
this->expression_valid = false;
} else {
_parse_new_expression(expression);
}
emit_changed();
}
String TerrainPrimitive::get_expression() const {
return this->expr_text;
}
String TerrainPrimitive::get_expression_error() const {
if (this->expression_valid) {
return "Valid Expression";
} else {
return this->expression->get_error_text();
}
}
void PlanePrimitive::_bind_methods() { void PlanePrimitive::_bind_methods() {
BIND_PROPERTY(Variant::FLOAT, baseline); BIND_PROPERTY(Variant::FLOAT, baseline);
} }
void PlanePrimitive::evaluate(Vector2, float &io_height) const { void PlanePrimitive::_parse_new_expression(String expression) {
io_height = blend(io_height, this->baseline); this->expression_valid = this->expression->parse(expression, { "previous_height", "at", "baseline" }) == OK;
}
void PlanePrimitive::evaluate(Vector2 at, float &io_height) const {
float height{ this->baseline };
if (this->expression_valid) {
height = this->expression->execute({ io_height, at, this->baseline }, nullptr, false, true);
}
io_height = blend(io_height, height);
} }
void PlanePrimitive::set_baseline(float value) { void PlanePrimitive::set_baseline(float value) {
@ -77,9 +109,17 @@ void PointPrimitive::_bind_methods() {
BIND_PROPERTY(Variant::FLOAT, height); BIND_PROPERTY(Variant::FLOAT, height);
} }
void PointPrimitive::_parse_new_expression(String expression) {
this->expression_valid = this->expression->parse(expression, { "previous_height", "at", "center", "height", "sloped_height", "distance", "slope" }) == OK;
}
void PointPrimitive::evaluate(Vector2 at, float &io_height) const { void PointPrimitive::evaluate(Vector2 at, float &io_height) const {
float distance{ at.distance_to(this->center) }; float distance{ at.distance_to(this->center) };
io_height = blend(io_height, this->height + distance * this->slope); float height{ this->height + distance * this->slope };
if (this->expression_valid) {
height = this->expression->execute({ io_height, at, this->center, this->height, height, distance, this->slope }, nullptr, false, true);
}
io_height = blend(io_height, height);
} }
void PointPrimitive::set_center(Vector2 center) { void PointPrimitive::set_center(Vector2 center) {
@ -115,6 +155,10 @@ void NoisePrimitive::_bind_methods() {
BIND_PROPERTY(Variant::FLOAT, noise_amplitude); BIND_PROPERTY(Variant::FLOAT, noise_amplitude);
} }
void NoisePrimitive::_parse_new_expression(String expression) {
this->expression_valid = this->expression->parse(expression, { "previous_height", "at", "noise", "amplitude", "scale" }) == OK;
}
void NoisePrimitive::evaluate(Vector2 at, float &io_height) const { void NoisePrimitive::evaluate(Vector2 at, float &io_height) const {
if (this->noise.is_valid()) { if (this->noise.is_valid()) {
if (Math::is_nan(io_height) || Math::is_inf(io_height)) { if (Math::is_nan(io_height) || Math::is_inf(io_height)) {
@ -122,7 +166,11 @@ void NoisePrimitive::evaluate(Vector2 at, float &io_height) const {
} }
float noise_sample{ this->noise->get_noise_2dv(at / this->noise_scale) }; float noise_sample{ this->noise->get_noise_2dv(at / this->noise_scale) };
noise_sample *= this->noise_amplitude; noise_sample *= this->noise_amplitude;
io_height = blend(io_height, io_height + noise_sample); float height{ noise_sample + io_height };
if (this->expression_valid) {
height = this->expression->execute({ io_height, at, noise_sample, this->noise_amplitude, this->noise_scale }, nullptr, false, true);
}
io_height = blend(io_height, height);
} }
} }
@ -158,31 +206,3 @@ void NoisePrimitive::set_noise_amplitude(float value) {
float NoisePrimitive::get_noise_amplitude() const { float NoisePrimitive::get_noise_amplitude() const {
return this->noise_amplitude; return this->noise_amplitude;
} }
void ExpressionPrimitive::_bind_methods() {
BIND_HPROPERTY(Variant::STRING, expression, PROPERTY_HINT_EXPRESSION);
}
void ExpressionPrimitive::evaluate(Vector2 at, float &io_height) const {
if (!this->valid) {
return;
}
Variant result{ this->expression->execute({ io_height, at }, nullptr, false, true) };
if (!this->expression->has_execute_failed()) {
io_height = blend(io_height, float(result.get(0)));
}
}
void ExpressionPrimitive::set_expression(String expression) {
this->expression_string = expression;
this->expression.unref();
this->expression = memnew(Expression);
Error error{ this->expression->parse(this->expression_string, { "height", "at" }) };
if ((this->valid = error == OK)) {
emit_changed();
}
}
String ExpressionPrimitive::get_expression() const {
return this->expression_string;
}

View file

@ -18,6 +18,7 @@ public:
protected: protected:
float blend(float under, float over) const; float blend(float under, float over) const;
virtual void _parse_new_expression(String expression) = 0;
public: public:
// evaluate the height of this primitive at point, returns the weight of the effect, out_height will be set to the closest point on the primitive // evaluate the height of this primitive at point, returns the weight of the effect, out_height will be set to the closest point on the primitive
@ -27,10 +28,18 @@ public:
BlendMode get_blend_mode() const; BlendMode get_blend_mode() const;
void set_blend_range(float blend_range); void set_blend_range(float blend_range);
float get_blend_range() const; float get_blend_range() const;
void set_expression(String expression);
String get_expression() const;
String get_expression_error() const;
private: private:
float blend_range{ 4.f }; float blend_range{ 4.f };
BlendMode blend_mode{ Peak }; BlendMode blend_mode{ Peak };
String expr_text{};
protected:
bool expression_valid{};
Ref<Expression> expression{ memnew(Expression) };
}; };
MAKE_TYPE_INFO(TerrainPrimitive::BlendMode, Variant::INT); MAKE_TYPE_INFO(TerrainPrimitive::BlendMode, Variant::INT);
@ -39,6 +48,9 @@ class PlanePrimitive : public TerrainPrimitive {
GDCLASS(PlanePrimitive, TerrainPrimitive); GDCLASS(PlanePrimitive, TerrainPrimitive);
static void _bind_methods(); static void _bind_methods();
protected:
void _parse_new_expression(String expression) override;
public: public:
void evaluate(Vector2 at, float &io_height) const override; void evaluate(Vector2 at, float &io_height) const override;
void set_baseline(float value); void set_baseline(float value);
@ -52,6 +64,9 @@ class PointPrimitive : public TerrainPrimitive {
GDCLASS(PointPrimitive, TerrainPrimitive); GDCLASS(PointPrimitive, TerrainPrimitive);
static void _bind_methods(); static void _bind_methods();
protected:
void _parse_new_expression(String expression) override;
public: public:
void evaluate(Vector2 at, float &io_height) const override; void evaluate(Vector2 at, float &io_height) const override;
void set_center(Vector2 center); void set_center(Vector2 center);
@ -71,6 +86,9 @@ class NoisePrimitive : public TerrainPrimitive {
GDCLASS(NoisePrimitive, TerrainPrimitive); GDCLASS(NoisePrimitive, TerrainPrimitive);
static void _bind_methods(); static void _bind_methods();
protected:
void _parse_new_expression(String expression) override;
public: public:
void evaluate(Vector2 at, float &io_height) const override; void evaluate(Vector2 at, float &io_height) const override;
void set_noise(Ref<Noise> noise); void set_noise(Ref<Noise> noise);
@ -85,18 +103,3 @@ private:
float noise_scale{ 1.f }; float noise_scale{ 1.f };
float noise_amplitude{ 1.f }; float noise_amplitude{ 1.f };
}; };
class ExpressionPrimitive : public TerrainPrimitive {
GDCLASS(ExpressionPrimitive, TerrainPrimitive);
static void _bind_methods();
public:
void evaluate(Vector2 at, float &io_height) const override;
void set_expression(String expression);
String get_expression() const;
private:
Ref<Expression> expression{ memnew(Expression) };
String expression_string{ "height" };
bool valid{ false };
};

View file

@ -95,8 +95,10 @@ func _unhandled_input(event: InputEvent) -> void:
load_path = "res://.godot/imported/point.svg-e68fd7c1e788d2c48d769cc58eba6e98.ctex" load_path = "res://.godot/imported/point.svg-e68fd7c1e788d2c48d769cc58eba6e98.ctex"
[sub_resource type="PointPrimitive" id="PointPrimitive_5lcyj"] [sub_resource type="PointPrimitive" id="PointPrimitive_5lcyj"]
expression = "sloped_height"
[sub_resource type="PlanePrimitive" id="PlanePrimitive_5lcyj"] [sub_resource type="PlanePrimitive" id="PlanePrimitive_5lcyj"]
expression = "baseline"
[sub_resource type="FastNoiseLite" id="FastNoiseLite_3vi5u"] [sub_resource type="FastNoiseLite" id="FastNoiseLite_3vi5u"]
frequency = 0.0336 frequency = 0.0336
@ -107,6 +109,7 @@ domain_warp_fractal_lacunarity = 5.512
domain_warp_fractal_gain = 0.662 domain_warp_fractal_gain = 0.662
[sub_resource type="NoisePrimitive" id="NoisePrimitive_5lcyj"] [sub_resource type="NoisePrimitive" id="NoisePrimitive_5lcyj"]
expression = "previous_height + noise_sample"
noise = SubResource("FastNoiseLite_3vi5u") noise = SubResource("FastNoiseLite_3vi5u")
[sub_resource type="GDScript" id="GDScript_74j0u"] [sub_resource type="GDScript" id="GDScript_74j0u"]

View file

@ -35,6 +35,36 @@ func _pressed() -> void:
terrain.current_selected = null terrain.current_selected = null
" "
[sub_resource type="GDScript" id="GDScript_2i6ni"]
resource_name = "ExpressionEditor"
script/source = "extends TextEdit
@onready var terrain : TerrainMeshEditor = $\"../../..\".terrain
@onready var primitive : TerrainPrimitive = terrain.current_selected
var pushing_change : bool = false
func _ready():
primitive.changed.connect(_primitive_changed)
text_changed.connect(_text_changed)
$ExpressionTimerBuffer.timeout.connect(_timeout)
_primitive_changed()
func _primitive_changed():
if not pushing_change and not has_focus():
pushing_change = true
self.text = primitive.expression
pushing_change = false
$\"../ExpressionError\".text = primitive.get_expression_error()
func _text_changed():
if not pushing_change:
$ExpressionTimerBuffer.start(2)
func _timeout():
primitive.expression = text
"
[node name="Primitive" type="MarginContainer" unique_id=905749607] [node name="Primitive" type="MarginContainer" unique_id=905749607]
offset_right = 302.0 offset_right = 302.0
offset_bottom = 230.0 offset_bottom = 230.0
@ -91,6 +121,24 @@ icon_alignment = 1
expand_icon = true expand_icon = true
script = SubResource("GDScript_ivj30") script = SubResource("GDScript_ivj30")
[node name="Expression" type="TextEdit" parent="VBoxContainer" unique_id=1154146381]
layout_mode = 2
size_flags_vertical = 3
placeholder_text = "expression"
backspace_deletes_composite_character_enabled = true
caret_blink = true
caret_move_on_right_click = false
draw_tabs = true
draw_spaces = true
script = SubResource("GDScript_2i6ni")
[node name="ExpressionTimerBuffer" type="Timer" parent="VBoxContainer/Expression" unique_id=1376932514]
wait_time = 2.0
[node name="ExpressionError" type="Label" parent="VBoxContainer" unique_id=1840569033]
layout_mode = 2
text = "Error Text"
[connection signal="item_selected" from="VBoxContainer/BlendModeSelector" to="VBoxContainer/BlendModeSelector" method="_on_item_selected"] [connection signal="item_selected" from="VBoxContainer/BlendModeSelector" to="VBoxContainer/BlendModeSelector" method="_on_item_selected"]
[editable path="VBoxContainer/FloatEditor3"] [editable path="VBoxContainer/FloatEditor3"]

Binary file not shown.