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.
This commit is contained in:
RodZill4 2018-09-09 12:09:05 +02:00
parent 95a737029a
commit 9dc6d4b18a
11 changed files with 207 additions and 85 deletions

View File

@ -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()

View File

@ -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()

View File

@ -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"]

View File

@ -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

View File

@ -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))

View File

@ -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

View File

@ -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)

View File

@ -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"]

View File

@ -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()

View File

@ -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()

View File

@ -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: