diff --git a/addons/material_maker/types/gradient.gd b/addons/material_maker/types/gradient.gd index 37871ba9f..80db1a8ed 100644 --- a/addons/material_maker/types/gradient.gd +++ b/addons/material_maker/types/gradient.gd @@ -13,11 +13,13 @@ class CustomSorter: return a.v < b.v var points = [ Point.new(0.0, Color(0.0, 0.0, 0.0, 0.0)), { v=1.0, c=Color(1.0, 1.0, 1.0, 1.0) } ] -var interpolation = 1 -var sorted = true + +enum {CONSTANT=0, LINEAR=1, SMOOTHSTEP=2, CUBIC=3} +var interpolation := LINEAR +var sorted := true func to_string() -> String: - var rv = PackedStringArray() + var rv := PackedStringArray() for p in points: rv.append("("+str(p.v)+","+str(p.c)+")") return ",".join(rv) @@ -30,51 +32,60 @@ func duplicate() -> Object: copy.interpolation = interpolation return copy + func clear() -> void: points.clear() sorted = true + func add_point(v : float, c : Color) -> void: points.append(Point.new(v, c)) sorted = false + func get_point_count() -> int: return points.size() + func get_point_position(i : int) -> float: return points[i].v + func set_point_position(i : int, v : float) -> void: points[i].v = v sorted = false + func sort() -> void: - if ! sorted: + if not sorted: points.sort_custom(Callable(CustomSorter, "compare")) for i in range(points.size()-1): if points[i].v+0.0000005 >= points[i+1].v: points[i+1].v = points[i].v+0.000001 sorted = true -func get_color(x) -> Color: + +func get_color(x:float) -> Color: sort() if points.size() > 0: + # x is before the first point if x < points[0].v: return points[0].c - var s = points.size()-1 + + # x is after the last point + var s := points.size()-1 + if x > points[s].v: + return points[s].c + for i in range(s): if x < points[i+1].v: - var p0 = points[i].v - var c0 = points[i].c - var p1 = points[i+1].v - var c1 = points[i+1].c - return c0 + (c1-c0) * (x-p0) / (p1-p0) - return points[s].c - else: - return Color(0.0, 0.0, 0.0, 1.0) + return get_color_between_points(i, i+1, x) + + return Color(0.0, 0.0, 0.0, 1.0) + func get_shader_params(parameter_name : String, attribute : String = "uniform") -> String: - var rv = "" + var rv := "" for p : MMGenBase.ShaderUniform in get_parameters(parameter_name): rv += p.to_str(attribute) return rv @@ -112,10 +123,10 @@ func pc(parameter_name : String, i : int) -> String: func get_shader(parameter_name : String) -> String: sort() - var shader + var shader: String shader = "vec4 "+parameter_name+"_gradient_fct(float x) {\n" match interpolation: - 0: + CONSTANT: if points.size() > 0: shader += " if (x < %s) {\n" % pv(parameter_name, 1) shader += " return "+pc(parameter_name, 0)+";\n" @@ -127,20 +138,20 @@ func get_shader(parameter_name : String) -> String: shader += " return "+pc(parameter_name, s)+";\n" else: shader += " return vec4(0.0, 0.0, 0.0, 1.0);\n" - 1, 2: + LINEAR, SMOOTHSTEP: if points.size() > 0: shader += " if (x < %s) {\n" % pv(parameter_name, 0) shader += " return "+pc(parameter_name, 0)+";\n" var s = points.size()-1 for i in range(s): shader += " } else if (x < %s) {\n" % pv(parameter_name, i+1) - var function = "(" if interpolation == 1 else "0.5-0.5*cos(3.14159265359*" + var function = "(" if interpolation == LINEAR else "0.5-0.5*cos(3.14159265359*" shader += " return mix(%s, %s, %s(x-%s)/(%s-%s)));\n" % [ pc(parameter_name, i), pc(parameter_name, i+1), function, pv(parameter_name, i), pv(parameter_name, i+1), pv(parameter_name, i) ] shader += " }\n" shader += " return "+pc(parameter_name, s)+";\n" else: shader += " return vec4(0.0, 0.0, 0.0, 1.0);\n" - 3: + CUBIC: if points.size() > 0: shader += " if (x < %s) {\n" % pv(parameter_name, 0) shader += " return "+pc(parameter_name, 0)+";\n" @@ -174,7 +185,7 @@ func get_shader(parameter_name : String) -> String: func serialize() -> Dictionary: sort() var rv = [] - if interpolation == 0: + if interpolation == CONSTANT: var p : Point = points[0] rv.append({ pos=0, r=p.c.r, g=p.c.g, b=p.c.b, a=p.c.a }) for i in range(1, points.size()): @@ -201,7 +212,7 @@ func deserialize(v) -> void: add_point(i.pos, Color(i.r, i.g, i.b, i.a)) if v.has("interpolation"): interpolation = int(v.interpolation) - if interpolation == 0: + if interpolation == CONSTANT: for i in range(points.size()-1, 0, -1): if points[i].c == points[i-1].c: points.remove_at(i) @@ -209,7 +220,7 @@ func deserialize(v) -> void: points[i].v = 0.5*(points[i-1].v+points[i].v) points[0].v = 0 else: - interpolation = 1 + interpolation = LINEAR elif typeof(v) == TYPE_OBJECT and v.get_script() == get_script(): clear() for p in v.points: @@ -217,3 +228,95 @@ func deserialize(v) -> void: interpolation = v.interpolation else: print("Cannot deserialize gradient "+str(v)) + + +func effect_reverse() -> void: + for i in range(get_point_count()): + set_point_position(i, 1-get_point_position(i)) + + +func effect_evenly_distribute() -> void: + for i in range(get_point_count()): + set_point_position(i, 1.0/(get_point_count()-1)*i) + + +func effect_simplify(threshold := 0.05) -> void: + sort() + while true: + var most_useless_point := -1 + var most_useless_strength := 10.0 + + for point in range(get_point_count()): + var uselessness := 10.0 + var point_color: Color = points[point].c + if point == 0: + uselessness = get_color_difference(point_color, points[point+1].c) + elif point == get_point_count()-1: + uselessness = get_color_difference(point_color, points[point-1].c) + + else: + uselessness = get_color_difference(point_color, + get_color_between_points(point-1, point+1, points[point].v)) + + if uselessness < most_useless_strength: + most_useless_point = point + most_useless_strength = uselessness + + if most_useless_strength > threshold or get_point_count()<3: + break + + points.remove_at(most_useless_point) + + +func get_color_difference(color1: Color, color2: Color) -> float: + return abs((color1.r-color2.r)+(color1.g-color2.g) + (color1.b-color2.b) + (color1.a-color2.a)) + + +func get_color_between_points(point1:int, point2:int, global_offset:float) -> Color: + # point left to the offset + var pl: Point = points[point1] + # point right to the offset + var pr: Point = points[point2] + + # offset relative between the two points + var offset := local_offset(point1, point2, global_offset)#(global_offset-pl.v)/(pr.v-pl.v) + + if interpolation == CONSTANT: + return pl.c + # LINEAR + elif interpolation == LINEAR: + return pl.c.lerp(pr.c, offset) + # SMOOTHSTEP + elif interpolation == SMOOTHSTEP: + return pl.c.lerp(pr.c, 0.5-0.5*cos(3.14159265359*offset)) + # CUBIC + elif interpolation == CUBIC: + if point1 == 0: + var factor := 1.0 - 0.5 * offset + var next_section_lerp := pr.c.lerp(points[point2+1].c, local_offset(point2, point2+1, global_offset)) + var this_section_lerp := pl.c.lerp(pr.c, offset) + + return next_section_lerp.lerp(this_section_lerp, factor) + + elif point2 == len(points)-1: + var factor := 0.5 + 0.5 * offset + var this_section_lerp := pl.c.lerp(pr.c, offset) + var prev_section_lerp: Color = points[point1-1].c.lerp(pl.c, local_offset(point1-1, point1, global_offset)) + + return prev_section_lerp.lerp(this_section_lerp, factor) + + else: + var this_section_lerp := pl.c.lerp(pr.c, offset) + + var prev_section_lerp: Color = points[point1-1].c.lerp(pl.c, local_offset(point1-1, point1, global_offset)) + var next_section_lerp: Color = pr.c.lerp(points[point2+1].c, local_offset(point2, point2+1, global_offset)) + + var final := 0.5 * (this_section_lerp + prev_section_lerp.lerp(next_section_lerp, 0.5-0.5*cos(3.14159265359 * offset))) + + return final + + return pl.c + + +func local_offset(point1:int, point2:int, global_offset:float) -> float: + return (global_offset-points[point1].v)/(points[point2].v-points[point1].v) diff --git a/material_maker/panels/common/menu_bar_button_with_panel.gd b/material_maker/panels/common/menu_bar_button_with_panel.gd index f97d9c9a6..49fb54727 100644 --- a/material_maker/panels/common/menu_bar_button_with_panel.gd +++ b/material_maker/panels/common/menu_bar_button_with_panel.gd @@ -28,6 +28,7 @@ func _draw() -> void: func _on_toggled(pressed:bool) -> void: panel.visible = pressed + panel.size = Vector2() if panel.visible: position_panel() diff --git a/material_maker/widgets/gradient_editor/gradient_edit.gd b/material_maker/widgets/gradient_editor/gradient_edit.gd index 27c5f2e74..0027bc6e7 100644 --- a/material_maker/widgets/gradient_editor/gradient_edit.gd +++ b/material_maker/widgets/gradient_editor/gradient_edit.gd @@ -322,7 +322,7 @@ func remove_popup_button() -> void: func update_visuals() -> void: - if %PopupButton: + if has_node("%PopupButton"): %PopupButton.icon = get_theme_icon("dropdown", "MM_Icons") var is_hovered := Rect2(Vector2(), size).has_point(get_local_mouse_position()) if is_hovered != hovered: diff --git a/material_maker/widgets/gradient_editor/gradient_effects_menu.gd b/material_maker/widgets/gradient_editor/gradient_effects_menu.gd new file mode 100644 index 000000000..6c1d16229 --- /dev/null +++ b/material_maker/widgets/gradient_editor/gradient_effects_menu.gd @@ -0,0 +1,9 @@ +extends PanelContainer + + +func add_effects_button(text:String, callable: Callable) -> Button: + var button := Button.new() + button.text = text + button.pressed.connect(callable) + $VBox.add_child(button) + return button diff --git a/material_maker/widgets/gradient_editor/gradient_effects_menu.gd.uid b/material_maker/widgets/gradient_editor/gradient_effects_menu.gd.uid new file mode 100644 index 000000000..2d505afe4 --- /dev/null +++ b/material_maker/widgets/gradient_editor/gradient_effects_menu.gd.uid @@ -0,0 +1 @@ +uid://d1a1bkn1evvxv diff --git a/material_maker/widgets/gradient_editor/gradient_popup.gd b/material_maker/widgets/gradient_editor/gradient_popup.gd index a2b5401b3..14f863b09 100644 --- a/material_maker/widgets/gradient_editor/gradient_popup.gd +++ b/material_maker/widgets/gradient_editor/gradient_popup.gd @@ -11,6 +11,11 @@ func _ready() -> void: %Previous.icon = get_theme_icon("arrow_left", "MM_Icons") %Next.icon = get_theme_icon("arrow_right", "MM_Icons") + %EffectsMenuPanel.add_effects_button("Reverse", gradient_effect.bind("effect_reverse")) + %EffectsMenuPanel.add_effects_button("Evenly Distribute", gradient_effect.bind("effect_evenly_distribute")) + %EffectsMenuPanel.add_effects_button("Simplify", gradient_effect.bind("effect_simplify")) + %EffectsMenuPanel.add_effects_button("Strong Simplify", gradient_effect.bind("effect_simplify", [0.2])) + func set_gradient(value:MMGradient, cursor_index := 0) -> void: %GradientEdit.value = value @@ -25,7 +30,7 @@ func set_gradient(value:MMGradient, cursor_index := 0) -> void: # Handle closing the popup func _input(event:InputEvent) -> void: if event is InputEventMouseButton and event.pressed: - if not get_global_rect().has_point(get_global_mouse_position()): + if not get_global_rect().has_point(get_global_mouse_position()) and not %EffectsMenuPanel.visible: if not %Pin.button_pressed: close() accept_event() @@ -71,3 +76,12 @@ func _on_previous_pressed() -> void: func _on_next_pressed() -> void: %GradientEdit.active_cursor += 1 + + +func gradient_effect(effect_method:String, args := []) -> void: + if not %GradientEdit.value.has_method(effect_method): + return + %GradientEdit.value.callv(effect_method, args) + %GradientEdit.set_value(%GradientEdit.value) + _on_gradient_edit_updated(%GradientEdit.value, false) + $Panel/VBox/HBox/EffectsMenu.button_pressed = false diff --git a/material_maker/widgets/gradient_editor/gradient_popup.tscn b/material_maker/widgets/gradient_editor/gradient_popup.tscn index de7edab75..70b74465f 100644 --- a/material_maker/widgets/gradient_editor/gradient_popup.tscn +++ b/material_maker/widgets/gradient_editor/gradient_popup.tscn @@ -1,9 +1,11 @@ -[gd_scene load_steps=5 format=3 uid="uid://in4lqr3eetvc"] +[gd_scene load_steps=7 format=3 uid="uid://in4lqr3eetvc"] -[ext_resource type="Script" path="res://material_maker/widgets/gradient_editor/gradient_popup.gd" id="1"] +[ext_resource type="Script" uid="uid://h85iv7e2rlbv" path="res://material_maker/widgets/gradient_editor/gradient_popup.gd" id="1"] [ext_resource type="PackedScene" uid="uid://rflulhsuy3ax" path="res://material_maker/widgets/float_edit/float_edit.tscn" id="2_fotv7"] [ext_resource type="PackedScene" uid="uid://cp6ft7qbucfam" path="res://material_maker/widgets/gradient_editor/gradient_edit.tscn" id="2_uait3"] [ext_resource type="PackedScene" uid="uid://dj5q8sxvd3gci" path="res://material_maker/widgets/option_edit/option_edit.tscn" id="3_2wy2b"] +[ext_resource type="Script" uid="uid://c37lcka7r53wk" path="res://material_maker/panels/common/menu_bar_button_with_panel.gd" id="4_6ujyx"] +[ext_resource type="Script" uid="uid://d1a1bkn1evvxv" path="res://material_maker/widgets/gradient_editor/gradient_effects_menu.gd" id="5_sd4bi"] [node name="GradientPopup" type="MarginContainer"] top_level = true @@ -60,6 +62,30 @@ popup/item_2/id = 2 popup/item_3/text = "Cubic" popup/item_3/id = 3 +[node name="EffectsMenu" type="Button" parent="Panel/VBox/HBox"] +custom_minimum_size = Vector2(40, 25) +layout_mode = 2 +tooltip_text = "Tools" +theme_type_variation = &"MM_PanelMenuButton" +toggle_mode = true +button_mask = 3 +script = ExtResource("4_6ujyx") +icon_name = "settings" + +[node name="EffectsMenuPanel" type="PanelContainer" parent="Panel/VBox/HBox/EffectsMenu"] +unique_name_in_owner = true +top_level = true +layout_mode = 0 +offset_left = 493.0 +offset_top = 9.0 +offset_right = 817.0 +offset_bottom = 75.0 +theme_type_variation = &"MM_PanelMenuSubPanel" +script = ExtResource("5_sd4bi") + +[node name="VBox" type="VBoxContainer" parent="Panel/VBox/HBox/EffectsMenu/EffectsMenuPanel"] +layout_mode = 2 + [node name="Pin" type="Button" parent="Panel/VBox/HBox"] unique_name_in_owner = true layout_mode = 2