From 9dc6d4b18a181acbf7cc37ce4019de6fa01596d2 Mon Sep 17 00:00:00 2001 From: RodZill4 Date: Sun, 9 Sep 2018 12:09:05 +0200 Subject: [PATCH] Gradient and serialization related updates Added a class for gradient (that handles serialization and shader generation), and updated the GradientEditor. Added a "Types" class that handles values serialization. Updated linked_control and config_control to link to GradientEditors. --- addons/material_maker/node_base.gd | 23 ++++- addons/material_maker/nodes/colorize.gd | 19 ++-- addons/material_maker/nodes/colorize.tscn | 6 +- addons/material_maker/nodes/remote.tscn | 2 +- addons/material_maker/types/gradient.gd | 86 ++++++++++++++++++ addons/material_maker/types/types.gd | 26 ++++++ .../material_maker/widgets/gradient_editor.gd | 89 +++++++------------ .../widgets/gradient_editor.tscn | 8 +- .../widgets/linked_widgets/config_control.gd | 18 ++-- .../widgets/linked_widgets/linked_control.gd | 8 ++ .../linked_widgets/linked_control_base.gd | 7 +- 11 files changed, 207 insertions(+), 85 deletions(-) create mode 100644 addons/material_maker/types/gradient.gd create mode 100644 addons/material_maker/types/types.gd diff --git a/addons/material_maker/node_base.gd b/addons/material_maker/node_base.gd index c559ca2..77b5683 100644 --- a/addons/material_maker/node_base.gd +++ b/addons/material_maker/node_base.gd @@ -20,6 +20,8 @@ var generated_variants = [] var property_widgets = [] +const Types = preload("res://addons/material_maker/types/types.gd") + func _ready(): pass @@ -46,6 +48,11 @@ func initialize_properties(object_list): elif o is ColorPickerButton: set(o.name, o.color) o.connect("color_changed", self, "_on_color_changed", [ o.name ]) + elif o is Control and o.filename == "res://addons/material_maker/widgets/gradient_editor.tscn": + set(o.name, o.value) + o.connect("updated", self, "_on_gradient_changed", [ o.name ]) + else: + print("unsupported widget "+str(o)) func get_seed(): return int(offset.x)*3+int(offset.y)*5 @@ -64,6 +71,10 @@ func update_property_widgets(): o.pressed = get(o.name) elif o is ColorPickerButton: o.color = get(o.name) + elif o is Control and o.filename == "res://addons/material_maker/widgets/gradient_editor.tscn": + o.value = get(o.name) + else: + print("Failed to update "+o.name) func update_shaders(): get_parent().send_changed_signal() @@ -80,6 +91,10 @@ func _on_color_changed(new_color, variable): set(variable, new_color) update_shaders() +func _on_gradient_changed(new_gradient, variable): + set(variable, new_gradient) + update_shaders() + func get_source(index = 0): for c in get_parent().get_connection_list(): if c.to == name and c.to_port == index: @@ -153,6 +168,10 @@ func deserialize_element(e): if typeof(e) == TYPE_DICTIONARY: if e.has("type") and e.type == "Color": return Color(e.r, e.g, e.b, e.a) + elif typeof(e) == TYPE_ARRAY: + var gradient = preload("res://addons/material_maker/types/gradient.gd").new() + gradient.deserialize(e) + return gradient return e func generate_shader(slot = 0): @@ -169,7 +188,7 @@ func serialize(): var data = { name=name, type=type, node_position={x=offset.x, y=offset.y} } for w in property_widgets: var v = w.name - data[v] = serialize_element(get(v)) + data[v] = Types.serialize_value(get(v)) # serialize_element(get(v)) return data func deserialize(data): @@ -178,7 +197,7 @@ func deserialize(data): for w in property_widgets: var variable = w.name if data.has(variable): - var value = deserialize_element(data[variable]) + var value = Types.deserialize_value(data[variable]) #deserialize_element(data[variable]) set(variable, value) update_property_widgets() diff --git a/addons/material_maker/nodes/colorize.gd b/addons/material_maker/nodes/colorize.gd index 88c8d87..749aa25 100644 --- a/addons/material_maker/nodes/colorize.gd +++ b/addons/material_maker/nodes/colorize.gd @@ -1,6 +1,11 @@ tool extends "res://addons/material_maker/node_base.gd" +var gradient + +func _ready(): + initialize_properties([ $gradient ]) + func _get_shader_code(uv): var rv = { defs="", code="" } var src = get_source() @@ -8,7 +13,7 @@ func _get_shader_code(uv): return rv var src_code = src.get_shader_code(uv) if generated_variants.empty(): - rv.defs = src_code.defs+$Control.get_shader("%s_gradient" % name); + rv.defs = src_code.defs+gradient.get_shader("%s_gradient" % name); var variant_index = generated_variants.find(uv) if variant_index == -1: variant_index = generated_variants.size() @@ -17,12 +22,6 @@ func _get_shader_code(uv): rv.rgb = "%s_%d_rgb" % [ name, variant_index ] return rv -func serialize(): - var data = .serialize() - data.gradient = $Control.serialize() - return data - -func deserialize(data): - if data.has("gradient"): - $Control.deserialize(data.gradient) - .deserialize(data) +func _on_Control_updated(v): + gradient = v + update_shaders() diff --git a/addons/material_maker/nodes/colorize.tscn b/addons/material_maker/nodes/colorize.tscn index bc4fe7b..0175558 100644 --- a/addons/material_maker/nodes/colorize.tscn +++ b/addons/material_maker/nodes/colorize.tscn @@ -6,7 +6,7 @@ [sub_resource type="Theme" id=1] -[node name="Colorize" type="GraphNode" index="0"] +[node name="Colorize" type="GraphNode"] anchor_left = 0.0 anchor_top = 0.0 @@ -39,13 +39,13 @@ slot/0/right_color = Color( 0.5, 0.5, 1, 1 ) script = ExtResource( 1 ) _sections_unfolded = [ "Theme", "slot", "slot/0" ] -[node name="Control" parent="." index="0" instance=ExtResource( 2 )] +[node name="gradient" parent="." index="0" instance=ExtResource( 2 )] margin_left = 16.0 margin_top = 24.0 margin_right = 136.0 margin_bottom = 54.0 -[connection signal="updated" from="Control" to="." method="update_shaders"] +[connection signal="updated" from="gradient" to="." method="_on_Control_updated"] diff --git a/addons/material_maker/nodes/remote.tscn b/addons/material_maker/nodes/remote.tscn index e19728c..d7a9ce0 100644 --- a/addons/material_maker/nodes/remote.tscn +++ b/addons/material_maker/nodes/remote.tscn @@ -7,7 +7,7 @@ [sub_resource type="Theme" id=1] -[node name="Remote" type="GraphNode"] +[node name="Remote" type="GraphNode" index="0"] anchor_left = 0.0 anchor_top = 0.0 diff --git a/addons/material_maker/types/gradient.gd b/addons/material_maker/types/gradient.gd new file mode 100644 index 0000000..2827b09 --- /dev/null +++ b/addons/material_maker/types/gradient.gd @@ -0,0 +1,86 @@ +extends Object + +class CustomSorter: + static func compare(a, b): + return a.v < b.v + +var points = [ { v=0.0, c=Color(0.0, 0.0, 0.0) }, { v=1.0, c=Color(1.0, 1.0, 1.0) } ] +var sorted = true + +func _ready(): + pass + +func duplicate(): + var copy = get_script().new() + copy.clear() + for p in points: + copy.add_point(p.v, p.c) + return copy + +func clear(): + points.clear() + sorted = true + +func add_point(v, c): + points.append({ v=v, c=c }) + sorted = false + +func sort(): + if !sorted: + points.sort_custom(CustomSorter, "compare") + sorted = true + +func get_color(x): + sort() + if x < points[0].v: + return points[0].c + var s = points.size()-1 + 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 + +# get_color_in_shader +func gcis(color): + return "vec3(%.9f,%.9f,%.9f)" % [color.r, color.g, color.b] + +func get_shader(name): + sort() + var shader + shader = "vec3 "+name+"(float x) {\n" + shader += " if (x < %.9f) {\n" % points[0].v + shader += " return "+gcis(points[0].c)+";\n" + var s = points.size()-1 + for i in range(s): + var p0 = points[i].v + var c0 = points[i].c + var p1mp0 = points[i+1].v-p0 + var c1mc0 = points[i+1].c-c0 + if p1mp0 > 0: + shader += " } else if (x < %.9f) {\n" % points[i+1].v + shader += " return %s+x*%s;\n" % [gcis(c0-c1mc0*(p0/p1mp0)), gcis(c1mc0/p1mp0)] + shader += " }\n" + shader += " return "+gcis(points[s].c)+";\n" + shader += "}\n" + return shader + +func serialize(): + sort() + var rv = [] + for p in points: + rv.append({ pos=p.v, r=p.c.r, g=p.c.g, b=p.c.b }) + rv = { type="Gradient", points=rv } + return rv + +func deserialize(v): + clear() + if typeof(v) == TYPE_ARRAY: + for i in v: + add_point(i.pos, Color(i.r, i.g, i.b)) + elif typeof(v) == TYPE_DICTIONARY && v.has("type") && v.type == "Gradient": + for i in v.points: + add_point(i.pos, Color(i.r, i.g, i.b)) diff --git a/addons/material_maker/types/types.gd b/addons/material_maker/types/types.gd new file mode 100644 index 0000000..ce4181c --- /dev/null +++ b/addons/material_maker/types/types.gd @@ -0,0 +1,26 @@ +extends Node + +const Gradient = preload("res://addons/material_maker/types/gradient.gd") + +static func serialize_value(value): + if typeof(value) == TYPE_COLOR: + return { type= "Color", r=value.r, g=value.g, b=value.b, a=value.a } + elif typeof(value) == TYPE_OBJECT && value.has_method("serialize"): + return value.serialize() + return value + +static func deserialize_value(data): + if typeof(data) == TYPE_DICTIONARY: + if data.has("type"): + if data.type == "Color": + return Color(data.r, data.g, data.b, data.a) + elif data.type == "Gradient": + var gradient = Gradient.new() + gradient.deserialize(data) + return gradient + # in previous releases, Gradients were serialized as arrays + elif typeof(data) == TYPE_ARRAY: + var gradient = Gradient.new() + gradient.deserialize(data) + return gradient + return data diff --git a/addons/material_maker/widgets/gradient_editor.gd b/addons/material_maker/widgets/gradient_editor.gd index fd952f3..e304f40 100644 --- a/addons/material_maker/widgets/gradient_editor.gd +++ b/addons/material_maker/widgets/gradient_editor.gd @@ -8,7 +8,7 @@ class GradientCursor: func _ready(): rect_position = Vector2(0, 15) - rect_size = Vector2(WIDTH, 20) + rect_size = Vector2(WIDTH, 15) func _gui_input(ev): if ev is InputEventMouseButton: @@ -17,43 +17,63 @@ class GradientCursor: elif ev.button_index == BUTTON_RIGHT && get_parent().get_sorted_cursors().size() > 2: var parent = get_parent() parent.remove_child(self) - parent.update_shader() + parent.update_value() queue_free() elif ev is InputEventMouseMotion && (ev.button_mask & 1) != 0: rect_position.x += ev.relative.x rect_position.x = min(max(0, rect_position.x), get_parent().rect_size.x-rect_size.x) - get_parent().update_shader() + get_parent().update_value() func get_position(): return rect_position.x / (get_parent().rect_size.x - WIDTH) func set_color(c): color = c - get_parent().update_shader() + get_parent().update_value() static func sort(a, b): if a.get_position() < b.get_position(): return true return false +var value = null setget set_value + +const Gradient = preload("res://addons/material_maker/types/gradient.gd") + signal updated func _ready(): $Gradient.material = $Gradient.material.duplicate(true) - add_cursor(0, Color(0, 0, 0)) - add_cursor(rect_size.x-GradientCursor.WIDTH, Color(1, 1, 1)) + set_value(Gradient.new()) + +func set_value(v): + value = v + for c in get_children(): + if c != $Gradient: + remove_child(c) + c.free() + for p in value.points: + add_cursor(p.v*(rect_size.x-GradientCursor.WIDTH), p.c) + update_shader() + +func update_value(): + value.clear() + for p in get_children(): + if p != $Gradient: + value.add_point(p.rect_position.x/(rect_size.x-GradientCursor.WIDTH), p.color) + update_shader() func add_cursor(x, color): var cursor = GradientCursor.new() add_child(cursor) cursor.rect_position.x = x cursor.color = color - update_shader() func _gui_input(ev): if ev is InputEventMouseButton && ev.button_index == 1 && ev.doubleclick && ev.position.y > 15: var p = max(0, min(ev.position.x, rect_size.x-GradientCursor.WIDTH)) add_cursor(p, get_color(p)) + update_value() # Showing a color picker popup to change a cursor's color @@ -78,59 +98,12 @@ func get_sorted_cursors(): return array func get_color(x): - var array = get_sorted_cursors() - x = x / (rect_size.x - array[0].rect_size.x) - if x < array[0].get_position(): - return array[0].color - for i in range(array.size()-1): - if x < array[i+1].get_position(): - var p0 = array[i].get_position() - var c0 = array[i].color - var p1 = array[i+1].get_position() - var c1 = array[i+1].color - return c0 + (c1-c0) * (x-p0) / (p1-p0) - return array[array.size()-1].color - -# get_color_in_shader -func gcis(color): - return "vec3(%.9f,%.9f,%.9f)" % [color.r, color.g, color.b] - -func get_shader(name): - var array = get_sorted_cursors() - var shader - shader = "vec3 "+name+"(float x) {\n" - shader += " if (x < %.9f) {\n" % array[0].get_position() - shader += " return "+gcis(array[0].color)+";\n" - for i in range(array.size()-1): - var p0 = array[i].get_position() - var c0 = array[i].color - var p1mp0 = array[i+1].get_position()-p0 - var c1mc0 = array[i+1].color-c0 - if p1mp0 > 0: - shader += " } else if (x < %.9f) {\n" % array[i+1].get_position() - shader += " return %s+x*%s;\n" % [gcis(c0-c1mc0*(p0/p1mp0)), gcis(c1mc0/p1mp0)] - shader += " }\n" - shader += " return "+gcis(array[array.size()-1].color)+";\n" - shader += "}\n" - return shader + return value.get_color(x / (rect_size.x - GradientCursor.WIDTH)) func update_shader(): var shader shader = "shader_type canvas_item;\n" - shader += get_shader("gradient") - shader += "void fragment() { COLOR = vec4(gradient((UV.x-%.9f)*%.9f), 1.0); }" % [ float(GradientCursor.WIDTH)*0.5/float(rect_size.x), rect_size.x/(rect_size.x-GradientCursor.WIDTH) ] + shader += value.get_shader("gradient") + shader += "void fragment() { COLOR = vec4(gradient(UV.x), 1.0); }" $Gradient.material.shader.set_code(shader) - emit_signal("updated") - -func serialize(): - var rv = [] - for c in get_sorted_cursors(): - rv.append({ pos= c.get_position(), r= c.color.r, g= c.color.g, b= c.color.b }) - return rv - -func deserialize(v): - for c in get_sorted_cursors(): - remove_child(c) - c.free() - for i in v: - add_cursor(i.pos*(rect_size.x-GradientCursor.WIDTH), Color(i.r, i.g, i.b)) + emit_signal("updated", value) diff --git a/addons/material_maker/widgets/gradient_editor.tscn b/addons/material_maker/widgets/gradient_editor.tscn index 71ad827..7a2b5f3 100644 --- a/addons/material_maker/widgets/gradient_editor.tscn +++ b/addons/material_maker/widgets/gradient_editor.tscn @@ -26,10 +26,8 @@ anchor_left = 0.0 anchor_top = 0.0 anchor_right = 0.0 anchor_bottom = 0.0 -margin_left = 24.0 -margin_top = 14.0 -margin_right = 144.0 -margin_bottom = 44.0 +margin_right = 120.0 +margin_bottom = 30.0 rect_min_size = Vector2( 120, 30 ) rect_pivot_offset = Vector2( 0, 0 ) rect_clip_content = false @@ -57,7 +55,7 @@ mouse_default_cursor_shape = 0 size_flags_horizontal = 1 size_flags_vertical = 1 color = Color( 1, 1, 1, 1 ) -_sections_unfolded = [ "Material" ] +_sections_unfolded = [ "Material", "Rect" ] [node name="Popup" type="Popup" parent="Gradient" index="0"] diff --git a/addons/material_maker/widgets/linked_widgets/config_control.gd b/addons/material_maker/widgets/linked_widgets/config_control.gd index dd9562c..36179a2 100644 --- a/addons/material_maker/widgets/linked_widgets/config_control.gd +++ b/addons/material_maker/widgets/linked_widgets/config_control.gd @@ -6,6 +6,8 @@ var configurations = {} var current = null onready var button = null +const Types = preload("res://addons/material_maker/types/types.gd") + func _ready(): update_options() @@ -41,10 +43,16 @@ func update_options(): func add_linked(node, widget): linked_widgets.append({ node=node, widget=widget }) +func duplicate_value(value): + if typeof(value) == TYPE_OBJECT and value.has_method("duplicate"): + value = value.duplicate() + return value + func apply_configuration(c): for w in configurations[c]: - w.widget.set(WIDGETS[get_widget_type(w.widget)].value_attr , w.value) - w.node.set(w.widget.name, w.value) + var value = duplicate_value(w.value) + w.widget.set(WIDGETS[get_widget_type(w.widget)].value_attr, value) + w.node.set(w.widget.name, value) var graph_node = get_parent() while !(graph_node is GraphNode): graph_node = graph_node.get_parent() @@ -53,7 +61,7 @@ func apply_configuration(c): func do_update_configuration(name): var configuration = [] for w in linked_widgets: - configuration.append({ node=w.node, widget=w.widget, value=w.node.get(w.widget.name) }) + configuration.append({ node=w.node, widget=w.widget, value=duplicate_value(w.node.get(w.widget.name)) }) configurations[name] = configuration current = name update_options() @@ -90,7 +98,7 @@ func serialize(): var c = configurations[k] var data_configuration = [] for e in c: - data_configuration.append({ node=e.node.name, widget=e.widget.name, value=e.value }) + data_configuration.append({ node=e.node.name, widget=e.widget.name, value=Types.serialize_value(e.value) }) data_configurations[k] = data_configuration data.configurations = data_configurations return data @@ -113,7 +121,7 @@ func deserialize(data): if w.name == e.widget: widget = w break - configuration.append({ node=node, widget=widget, value=e.value }) + configuration.append({ node=node, widget=widget, value=Types.deserialize_value(e.value) }) configurations[k] = configuration update_options() diff --git a/addons/material_maker/widgets/linked_widgets/linked_control.gd b/addons/material_maker/widgets/linked_widgets/linked_control.gd index 852a49a..1b06eb3 100644 --- a/addons/material_maker/widgets/linked_widgets/linked_control.gd +++ b/addons/material_maker/widgets/linked_widgets/linked_control.gd @@ -11,6 +11,9 @@ func add_linked(node, widget): elif widget is ColorPickerButton: new_widget = ColorPickerButton.new() type = "ColorPickerButton" + elif widget is Control && widget.filename == "res://addons/material_maker/widgets/gradient_editor.tscn": + new_widget = preload("res://addons/material_maker/widgets/gradient_editor.tscn").instance() + type = "GradientEditor" elif widget is HSlider: new_widget = HSlider.new() type = "HSlider" @@ -45,6 +48,11 @@ func _on_item_selected(i): for l in linked_widgets: l.widget.selected = i l.node.set(l.widget.name, i) + +func _on_gradient_updated(i): + for l in linked_widgets: + l.widget.value = i + l.node.set(l.widget.name, i) func serialize(): var data = .serialize() diff --git a/addons/material_maker/widgets/linked_widgets/linked_control_base.gd b/addons/material_maker/widgets/linked_widgets/linked_control_base.gd index 827dca8..7e6ea9f 100644 --- a/addons/material_maker/widgets/linked_widgets/linked_control_base.gd +++ b/addons/material_maker/widgets/linked_widgets/linked_control_base.gd @@ -13,7 +13,8 @@ const WIDGETS = { SpinBox={ attrs=[ "min_value", "max_value", "step", "value" ], value_attr="value", sig="value_changed", sig_handler="_on_value_changed" }, HSlider={ attrs=[ "min_value", "max_value", "step", "value" ], value_attr="value", sig="value_changed", sig_handler="_on_value_changed" }, ColorPickerButton={ attrs=[ "edit_alpha", "color" ], value_attr="color", sig="color_changed", sig_handler="_on_color_changed" }, - OptionButton={ attrs=[ "selected" ], value_attr="selected", sig="item_selected", sig_handler="_on_item_selected" } + OptionButton={ attrs=[ "selected" ], value_attr="selected", sig="item_selected", sig_handler="_on_item_selected" }, + GradientEditor={ attrs=[ "value" ], value_attr="value", sig="updated", sig_handler="_on_gradient_updated" } } func get_widget_type(widget): @@ -21,6 +22,10 @@ func get_widget_type(widget): return "SpinBox" elif widget is ColorPickerButton: return "ColorPickerButton" + elif widget is Control && widget.filename == "res://addons/material_maker/widgets/gradient_editor.tscn": + return "GradientEditor" + elif widget is HSlider: + return "HSlider" elif widget is OptionButton: return "OptionButton" else: