diff --git a/modules/terrain_editor/register_types.cpp b/modules/terrain_editor/register_types.cpp index cc1f0e9b..1fe23e00 100644 --- a/modules/terrain_editor/register_types.cpp +++ b/modules/terrain_editor/register_types.cpp @@ -21,7 +21,6 @@ void initialize_terrain_editor_module(ModuleInitializationLevel p_level) { ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); - ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); diff --git a/modules/terrain_editor/terrain_primitive.cpp b/modules/terrain_editor/terrain_primitive.cpp index d5a0a548..c3642025 100644 --- a/modules/terrain_editor/terrain_primitive.cpp +++ b/modules/terrain_editor/terrain_primitive.cpp @@ -7,6 +7,8 @@ void TerrainPrimitive::_bind_methods() { BIND_HPROPERTY(Variant::INT, blend_mode, PROPERTY_HINT_ENUM, BlendMode_hint()); 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 @@ -54,12 +56,42 @@ float TerrainPrimitive::get_blend_range() const { 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() { BIND_PROPERTY(Variant::FLOAT, baseline); } -void PlanePrimitive::evaluate(Vector2, float &io_height) const { - io_height = blend(io_height, this->baseline); +void PlanePrimitive::_parse_new_expression(String expression) { + 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) { @@ -77,9 +109,17 @@ void PointPrimitive::_bind_methods() { 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 { 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) { @@ -115,6 +155,10 @@ void NoisePrimitive::_bind_methods() { 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 { if (this->noise.is_valid()) { 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) }; 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 { 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; -} diff --git a/modules/terrain_editor/terrain_primitive.h b/modules/terrain_editor/terrain_primitive.h index f0cb41da..486c3ec2 100644 --- a/modules/terrain_editor/terrain_primitive.h +++ b/modules/terrain_editor/terrain_primitive.h @@ -18,6 +18,7 @@ public: protected: float blend(float under, float over) const; + virtual void _parse_new_expression(String expression) = 0; 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 @@ -27,10 +28,18 @@ public: BlendMode get_blend_mode() const; void set_blend_range(float blend_range); float get_blend_range() const; + void set_expression(String expression); + String get_expression() const; + String get_expression_error() const; private: float blend_range{ 4.f }; BlendMode blend_mode{ Peak }; + String expr_text{}; + +protected: + bool expression_valid{}; + Ref expression{ memnew(Expression) }; }; MAKE_TYPE_INFO(TerrainPrimitive::BlendMode, Variant::INT); @@ -39,6 +48,9 @@ class PlanePrimitive : public TerrainPrimitive { GDCLASS(PlanePrimitive, TerrainPrimitive); static void _bind_methods(); +protected: + void _parse_new_expression(String expression) override; + public: void evaluate(Vector2 at, float &io_height) const override; void set_baseline(float value); @@ -52,6 +64,9 @@ class PointPrimitive : public TerrainPrimitive { GDCLASS(PointPrimitive, TerrainPrimitive); static void _bind_methods(); +protected: + void _parse_new_expression(String expression) override; + public: void evaluate(Vector2 at, float &io_height) const override; void set_center(Vector2 center); @@ -71,6 +86,9 @@ class NoisePrimitive : public TerrainPrimitive { GDCLASS(NoisePrimitive, TerrainPrimitive); static void _bind_methods(); +protected: + void _parse_new_expression(String expression) override; + public: void evaluate(Vector2 at, float &io_height) const override; void set_noise(Ref noise); @@ -85,18 +103,3 @@ private: float noise_scale{ 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{ memnew(Expression) }; - String expression_string{ "height" }; - bool valid{ false }; -}; diff --git a/project/scenes/editor.tscn b/project/scenes/editor.tscn index 1c6dcc45..2240c075 100644 --- a/project/scenes/editor.tscn +++ b/project/scenes/editor.tscn @@ -95,8 +95,10 @@ func _unhandled_input(event: InputEvent) -> void: load_path = "res://.godot/imported/point.svg-e68fd7c1e788d2c48d769cc58eba6e98.ctex" [sub_resource type="PointPrimitive" id="PointPrimitive_5lcyj"] +expression = "sloped_height" [sub_resource type="PlanePrimitive" id="PlanePrimitive_5lcyj"] +expression = "baseline" [sub_resource type="FastNoiseLite" id="FastNoiseLite_3vi5u"] frequency = 0.0336 @@ -107,6 +109,7 @@ domain_warp_fractal_lacunarity = 5.512 domain_warp_fractal_gain = 0.662 [sub_resource type="NoisePrimitive" id="NoisePrimitive_5lcyj"] +expression = "previous_height + noise_sample" noise = SubResource("FastNoiseLite_3vi5u") [sub_resource type="GDScript" id="GDScript_74j0u"] diff --git a/project/ui/primitive_inspectors/base_primitive_inspector.tscn b/project/ui/primitive_inspectors/base_primitive_inspector.tscn index c4307c02..46a1a65f 100644 --- a/project/ui/primitive_inspectors/base_primitive_inspector.tscn +++ b/project/ui/primitive_inspectors/base_primitive_inspector.tscn @@ -35,6 +35,36 @@ func _pressed() -> void: 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] offset_right = 302.0 offset_bottom = 230.0 @@ -91,6 +121,24 @@ icon_alignment = 1 expand_icon = true 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"] [editable path="VBoxContainer/FloatEditor3"] diff --git a/test-terrains/hills.terrain.res b/test-terrains/hills.terrain.res new file mode 100644 index 00000000..5fbef3b1 Binary files /dev/null and b/test-terrains/hills.terrain.res differ