Skip to content

Commit

Permalink
Merge pull request #737 from Jowan-Spooner/gradient-tools
Browse files Browse the repository at this point in the history
Add gradient effects and fix interpolation in gradient.get_color()
  • Loading branch information
RodZill4 authored Jan 16, 2025
2 parents a06cd17 + 5d3c055 commit da3397e
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 27 deletions.
149 changes: 126 additions & 23 deletions addons/material_maker/types/gradient.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -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()):
Expand All @@ -201,19 +212,111 @@ 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)
else:
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:
add_point(p.v, p.c)
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)
1 change: 1 addition & 0 deletions material_maker/panels/common/menu_bar_button_with_panel.gd
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func _draw() -> void:

func _on_toggled(pressed:bool) -> void:
panel.visible = pressed
panel.size = Vector2()

if panel.visible:
position_panel()
Expand Down
2 changes: 1 addition & 1 deletion material_maker/widgets/gradient_editor/gradient_edit.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uid://d1a1bkn1evvxv
16 changes: 15 additions & 1 deletion material_maker/widgets/gradient_editor/gradient_popup.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
Expand Down Expand Up @@ -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
30 changes: 28 additions & 2 deletions material_maker/widgets/gradient_editor/gradient_popup.tscn
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit da3397e

Please sign in to comment.