Updated loader and random seed handling (#15)

Loader is not a lot more generic and deserialization code moved to generators.

There is now a small dice button on nodes that create random patterns that can be used to freeze the seed. Frozen nodes can thus be moved without affecting the seed.
Graph nodes can also transmit their seed to their children (this behavior can be enabled/disabled using the dice button at the top right of the graph pane).
This commit is contained in:
RodZill4 2019-11-04 07:58:17 +01:00
parent c466d20229
commit c121f7c00a
11 changed files with 178 additions and 76 deletions

View File

@ -34,6 +34,9 @@ var position : Vector2 = Vector2(0, 0)
var model = null var model = null
var parameters = {} var parameters = {}
var seed_locked : bool = false
var seed_value : int = 0
func _ready() -> void: func _ready() -> void:
init_parameters() init_parameters()
@ -49,9 +52,28 @@ func toggle_editable() -> bool:
func is_editable() -> bool: func is_editable() -> bool:
return false return false
func has_randomness(): func has_randomness():
return false return false
func get_seed() -> int:
if !seed_locked:
var s : int = ((int(position.x) * 0x1f1f1f1f) ^ int(position.y)) % 65536
if get_parent().get("transmits_seed") != null and get_parent().transmits_seed:
s += get_parent().get_seed()
return s
else:
return seed_value
func toggle_lock_seed():
if !seed_locked:
seed_value = get_seed()
seed_locked = !seed_locked
return seed_locked
func is_seed_locked():
return seed_locked
func init_parameters() -> void: func init_parameters() -> void:
for p in get_parameter_defs(): for p in get_parameter_defs():
if !parameters.has(p.name): if !parameters.has(p.name):
@ -64,6 +86,9 @@ func init_parameters() -> void:
func set_position(p) -> void: func set_position(p) -> void:
position = p position = p
if has_randomness() and !is_seed_locked():
for i in range(get_output_defs().size()):
notify_output_change(i)
func get_type() -> String: func get_type() -> String:
return "generic" return "generic"
@ -163,6 +188,7 @@ func get_shader_code(uv : String, output_index : int, context : MMGenContext) ->
func _get_shader_code(__, __, __) -> Dictionary: func _get_shader_code(__, __, __) -> Dictionary:
return {} return {}
func _serialize(data: Dictionary) -> Dictionary: func _serialize(data: Dictionary) -> Dictionary:
print("cannot save "+name) print("cannot save "+name)
return data return data
@ -171,8 +197,35 @@ func serialize():
var rv = { name=name, type=get_type(), parameters={}, node_position={ x=position.x, y=position.y } } var rv = { name=name, type=get_type(), parameters={}, node_position={ x=position.x, y=position.y } }
for p in parameters.keys(): for p in parameters.keys():
rv.parameters[p] = MMType.serialize_value(parameters[p]) rv.parameters[p] = MMType.serialize_value(parameters[p])
if seed_locked:
rv.seed_value = seed_value
if model != null: if model != null:
rv.type = model rv.type = model
else: else:
rv = _serialize(rv) rv = _serialize(rv)
return rv return rv
func _deserialize(data : Dictionary) -> void:
pass
func deserialize(data : Dictionary) -> void:
_deserialize(data)
if data.has("name"):
name = data.name
if data.has("node_position"):
position.x = data.node_position.x
position.y = data.node_position.y
if data.has("parameters"):
for p in data.parameters.keys():
set_parameter(p, MMType.deserialize_value(data.parameters[p]))
else:
for p in get_parameter_defs():
if data.has(p.name) and p.name != "type":
set_parameter(p.name, MMType.deserialize_value(data[p.name]))
if data.has("seed_value"):
seed_locked = true
seed_value = data.seed_value
else:
seed_locked = false
_post_load()

View File

@ -33,3 +33,9 @@ func _serialize(data: Dictionary) -> Dictionary:
data.text = text data.text = text
data.size = { x=size.x, y=size.y } data.size = { x=size.x, y=size.y }
return data return data
func _deserialize(data : Dictionary) -> void:
if data.has("text"):
text = data.text
if data.has("size"):
size = Vector2(data.size.x, data.size.y)

View File

@ -175,6 +175,10 @@ func _get_shader_code(uv : String, output_index : int, context : MMGenContext) -
rv[convolution_params.output_type] = "%s_%d" % [ genname, variant_index ] rv[convolution_params.output_type] = "%s_%d" % [ genname, variant_index ]
return rv return rv
func _serialize(data: Dictionary) -> Dictionary: func _serialize(data: Dictionary) -> Dictionary:
data.convolution_params = convolution_params data.convolution_params = convolution_params
return data return data
func _deserialize(data : Dictionary) -> void:
set_convolution_params(data.convolution_params)

View File

@ -5,10 +5,13 @@ class_name MMGenGraph
var label : String = "Graph" var label : String = "Graph"
var connections = [] var connections = []
var editable = false var editable : bool = false
var transmits_seed : bool = true
signal connections_changed(removed_connections, added_connections) signal connections_changed(removed_connections, added_connections)
func fix_remotes() -> void: func fix_remotes() -> void:
for c in get_children(): for c in get_children():
if c is MMGenRemote: if c is MMGenRemote:
@ -17,6 +20,11 @@ func fix_remotes() -> void:
func _post_load() -> void: func _post_load() -> void:
fix_remotes() fix_remotes()
func has_randomness():
return transmits_seed
func get_type() -> String: func get_type() -> String:
return "graph" return "graph"
@ -203,13 +211,6 @@ func _get_shader_code(uv : String, output_index : int, context : MMGenContext) -
return rv return rv
return { globals=[], defs="", code="", textures={} } return { globals=[], defs="", code="", textures={} }
func _serialize(data: Dictionary) -> Dictionary:
data.label = label
data.nodes = []
for c in get_children():
data.nodes.append(c.serialize())
data.connections = connections
return data
func edit(node) -> void: func edit(node) -> void:
node.get_parent().call_deferred("update_view", self) node.get_parent().call_deferred("update_view", self)
@ -300,3 +301,19 @@ func create_subgraph(gens : Array) -> void:
new_graph.add_child(gen_parameters) new_graph.add_child(gen_parameters)
fix_remotes() fix_remotes()
new_graph.fix_remotes() new_graph.fix_remotes()
func _serialize(data: Dictionary) -> Dictionary:
data.label = label
data.nodes = []
for c in get_children():
data.nodes.append(c.serialize())
data.connections = connections
return data
func _deserialize(data : Dictionary) -> void:
if data.has("label"):
label = data.label
var nodes = data.nodes if data.has("nodes") else []
var connections = data.connections if data.has("connections") else []
load("res://addons/material_maker/engine/loader.gd").add_to_gen_graph(self, nodes, connections)

View File

@ -97,3 +97,6 @@ func _serialize(data: Dictionary) -> Dictionary:
data.type = "ios" data.type = "ios"
data.ports = ports data.ports = ports
return data return data
func _deserialize(data : Dictionary) -> void:
ports = data.ports

View File

@ -134,11 +134,6 @@ func set_parameter(p : String, v) -> void:
if name == "gen_parameters": if name == "gen_parameters":
get_parent().parameters[p] = v get_parent().parameters[p] = v
func _serialize(data: Dictionary) -> Dictionary:
data.type = "remote"
data.widgets = widgets
return data
func create_linked_control(label : String) -> String: func create_linked_control(label : String) -> String:
var n = get_next_widget_name() var n = get_next_widget_name()
widgets.push_back({ name=n, label=label, type="linked_control", linked_widgets=[] }) widgets.push_back({ name=n, label=label, type="linked_control", linked_widgets=[] })
@ -219,3 +214,12 @@ func remove_configuration(widget_name : String, config_name : String) -> void:
if widget.type == "config_control": if widget.type == "config_control":
widget.configurations.erase(config_name) widget.configurations.erase(config_name)
emit_signal("parameter_changed", "__update_all__", null) emit_signal("parameter_changed", "__update_all__", null)
func _serialize(data: Dictionary) -> Dictionary:
data.type = "remote"
data.widgets = widgets
return data
func _deserialize(data : Dictionary) -> void:
set_widgets(data.widgets.duplicate(true))

View File

@ -16,6 +16,9 @@ func toggle_editable() -> bool:
func is_editable() -> bool: func is_editable() -> bool:
return editable return editable
func has_randomness():
return uses_seed
func get_type() -> String: func get_type() -> String:
return "shader" return "shader"
@ -45,24 +48,30 @@ func get_output_defs():
func set_shader_model(data: Dictionary): func set_shader_model(data: Dictionary):
shader_model = data shader_model = data
init_parameters() init_parameters()
uses_seed = false
if shader_model.has("outputs"): if shader_model.has("outputs"):
for i in range(shader_model.outputs.size()): for i in range(shader_model.outputs.size()):
var output = shader_model.outputs[i] var output = shader_model.outputs[i]
var output_code = ""
if output.has("rgba"): if output.has("rgba"):
shader_model.outputs[i].type = "rgba" shader_model.outputs[i].type = "rgba"
output_code = output.rgba
elif output.has("rgb"): elif output.has("rgb"):
shader_model.outputs[i].type = "rgb" shader_model.outputs[i].type = "rgb"
else: output_code = output.rgb
elif output.has("f"):
shader_model.outputs[i].type = "f" shader_model.outputs[i].type = "f"
output_code = output.f
func set_position(p) -> void: else:
.set_position(p) print("Unsupported output type")
if uses_seed: if output_code.find("$seed") != -1 or output_code.find("$(seed)") != -1:
source_changed(0) uses_seed = true
if shader_model.has("code"):
func get_seed() -> int: if shader_model.code.find("$seed") != -1 or shader_model.code.find("$(seed)") != -1:
var s = ((int(position.x) * 0x1f1f1f1f) ^ int(position.y)) % 65536 uses_seed = true
return s if shader_model.has("instance"):
if shader_model.instance.find("$seed") != -1 or shader_model.instance.find("$(seed)") != -1:
uses_seed = true
func find_keyword_call(string, keyword): func find_keyword_call(string, keyword):
var search_string = "$%s(" % keyword var search_string = "$%s(" % keyword
@ -156,7 +165,6 @@ func subst(string, context, uv = "") -> Dictionary:
var tmp_string = replace_variable(string, "seed", str(get_seed())) var tmp_string = replace_variable(string, "seed", str(get_seed()))
if tmp_string != string: if tmp_string != string:
string = tmp_string string = tmp_string
uses_seed = true
if uv != "": if uv != "":
string = replace_variable(string, "uv", "("+uv+")") string = replace_variable(string, "uv", "("+uv+")")
if shader_model.has("parameters") and typeof(shader_model.parameters) == TYPE_ARRAY: if shader_model.has("parameters") and typeof(shader_model.parameters) == TYPE_ARRAY:
@ -213,7 +221,6 @@ func subst(string, context, uv = "") -> Dictionary:
return { string=string, globals=required_globals, defs=required_defs, code=required_code, textures=required_textures } return { string=string, globals=required_globals, defs=required_defs, code=required_code, textures=required_textures }
func _get_shader_code(uv : String, output_index : int, context : MMGenContext) -> Dictionary: func _get_shader_code(uv : String, output_index : int, context : MMGenContext) -> Dictionary:
uses_seed = false
var genname = "o"+str(get_instance_id()) var genname = "o"+str(get_instance_id())
var output_info = [ { field="rgba", type="vec4" }, { field="rgb", type="vec3" }, { field="f", type="float" } ] var output_info = [ { field="rgba", type="vec4" }, { field="rgb", type="vec3" }, { field="f", type="float" } ]
var rv = { globals=[], defs="", code="", textures={} } var rv = { globals=[], defs="", code="", textures={} }
@ -278,10 +285,18 @@ func _get_shader_code(uv : String, output_index : int, context : MMGenContext) -
rv.globals.push_back(shader_model.global) rv.globals.push_back(shader_model.global)
return rv return rv
func _serialize(data: Dictionary) -> Dictionary: func _serialize(data: Dictionary) -> Dictionary:
data.shader_model = shader_model data.shader_model = shader_model
return data return data
func _deserialize(data : Dictionary) -> void:
if data.has("shader_model"):
set_shader_model(data.shader_model)
elif data.has("model_data"):
set_shader_model(data.model_data)
func edit(node) -> void: func edit(node) -> void:
if shader_model != null: if shader_model != null:
var edit_window = load("res://addons/material_maker/widgets/node_editor/node_editor.tscn").instance() var edit_window = load("res://addons/material_maker/widgets/node_editor/node_editor.tscn").instance()

View File

@ -40,6 +40,7 @@ static func create_gen(data) -> MMGenBase:
{ keyword="connections", type=MMGenGraph }, { keyword="connections", type=MMGenGraph },
{ keyword="nodes", type=MMGenGraph }, { keyword="nodes", type=MMGenGraph },
{ keyword="shader_model", type=MMGenShader }, { keyword="shader_model", type=MMGenShader },
{ keyword="convolution_params", type=MMGenConvolution },
{ keyword="model_data", type=MMGenShader }, { keyword="model_data", type=MMGenShader },
{ keyword="convolution_params", type=MMGenConvolution }, { keyword="convolution_params", type=MMGenConvolution },
{ keyword="widgets", type=MMGenRemote } { keyword="widgets", type=MMGenRemote }
@ -48,40 +49,20 @@ static func create_gen(data) -> MMGenBase:
material = MMGenMaterial, material = MMGenMaterial,
buffer = MMGenBuffer, buffer = MMGenBuffer,
image = MMGenImage, image = MMGenImage,
ios = MMGenIOs,
switch = MMGenSwitch, switch = MMGenSwitch,
export = MMGenExport, export = MMGenExport,
comment = MMGenComment,
debug = MMGenDebug debug = MMGenDebug
} }
var generator = null var generator = null
if data.has("connections") and data.has("nodes"): for g in guess:
generator = MMGenGraph.new() if data.has(g.keyword):
if data.has("label"): generator = g.type.new()
generator.label = data.label break
add_to_gen_graph(generator, data.nodes, data.connections) if generator == null and data.has("type"):
elif data.has("shader_model"):
generator = MMGenShader.new()
generator.set_shader_model(data.shader_model)
elif data.has("convolution_params"):
generator = MMGenConvolution.new()
generator.set_convolution_params(data.convolution_params)
elif data.has("model_data"):
generator = MMGenShader.new()
generator.set_shader_model(data.model_data)
elif data.has("widgets"):
generator = MMGenRemote.new()
generator.set_widgets(data.widgets.duplicate(true))
elif data.has("type"):
if types.has(data.type): if types.has(data.type):
generator = types[data.type].new() generator = types[data.type].new()
elif data.type == "comment":
generator = MMGenComment.new()
if data.has("text"):
generator.text = data.text
if data.has("size"):
generator.size = Vector2(data.size.x, data.size.y)
elif data.type == "ios":
generator = MMGenIOs.new()
generator.ports = data.ports
else: else:
var file = File.new() var file = File.new()
var gen_paths = [ STD_GENDEF_PATH, OS.get_executable_path().get_base_dir()+"/generators" ] var gen_paths = [ STD_GENDEF_PATH, OS.get_executable_path().get_base_dir()+"/generators" ]
@ -101,22 +82,10 @@ static func create_gen(data) -> MMGenBase:
print("Cannot find description for "+data.type) print("Cannot find description for "+data.type)
if generator != null: if generator != null:
generator.name = data.type generator.name = data.type
else: if generator == null:
print("LOADER: data not supported:"+str(data)) print("LOADER: data not supported:"+str(data))
if generator != null: if generator != null:
if data.has("name"): generator.deserialize(data)
generator.name = data.name
if data.has("node_position"):
generator.position.x = data.node_position.x
generator.position.y = data.node_position.y
if data.has("parameters"):
for p in data.parameters.keys():
generator.set_parameter(p, MMType.deserialize_value(data.parameters[p]))
else:
for p in generator.get_parameter_defs():
if data.has(p.name) and p.name != "type":
generator.set_parameter(p.name, MMType.deserialize_value(data[p.name]))
generator._post_load()
return generator return generator
static func get_generator_list() -> Array: static func get_generator_list() -> Array:

View File

@ -15,6 +15,7 @@ var generator = null
onready var timer : Timer = $Timer onready var timer : Timer = $Timer
onready var subgraph_ui : HBoxContainer = $GraphUI/SubGraphUI onready var subgraph_ui : HBoxContainer = $GraphUI/SubGraphUI
onready var button_transmits_seed : Button = $GraphUI/SubGraphUI/ButtonTransmitsSeed
signal save_path_changed signal save_path_changed
signal graph_changed signal graph_changed
@ -127,6 +128,12 @@ func update_view(g) -> void:
subgraph_ui.visible = generator != top_generator subgraph_ui.visible = generator != top_generator
subgraph_ui.get_node("Label").text = generator.label subgraph_ui.get_node("Label").text = generator.label
center_view() center_view()
if generator.get_parent() is MMGenGraph:
button_transmits_seed.visible = true
button_transmits_seed.pressed = generator.transmits_seed
else:
button_transmits_seed.visible = false
func clear_material() -> void: func clear_material() -> void:
if top_generator != null: if top_generator != null:
@ -325,3 +332,8 @@ func edit_subgraph(g : MMGenGraph) -> void:
if !g.is_editable(): if !g.is_editable():
g.toggle_editable() g.toggle_editable()
update_view(g) update_view(g)
func _on_ButtonTransmitsSeed_toggled(button_pressed):
if button_pressed != generator.transmits_seed:
generator.transmits_seed = button_pressed

View File

@ -1,4 +1,4 @@
[gd_scene load_steps=6 format=2] [gd_scene load_steps=7 format=2]
[ext_resource path="res://addons/material_maker/graph_edit.gd" type="Script" id=1] [ext_resource path="res://addons/material_maker/graph_edit.gd" type="Script" id=1]
[ext_resource path="res://addons/material_maker/icons/icons.svg" type="Texture" id=2] [ext_resource path="res://addons/material_maker/icons/icons.svg" type="Texture" id=2]
@ -9,11 +9,16 @@ bg_color = Color( 0.6, 0.6, 0.6, 0 )
[sub_resource type="AtlasTexture" id=2] [sub_resource type="AtlasTexture" id=2]
flags = 4 flags = 4
atlas = ExtResource( 2 ) atlas = ExtResource( 2 )
region = Rect2( 15.4016, 47.1451, 16.7512, 17.8319 ) region = Rect2( 64, 32, 16, 16 )
[sub_resource type="AtlasTexture" id=3] [sub_resource type="AtlasTexture" id=3]
flags = 4 flags = 4
atlas = ExtResource( 2 ) atlas = ExtResource( 2 )
region = Rect2( 15.4016, 47.1451, 16.7512, 17.8319 )
[sub_resource type="AtlasTexture" id=4]
flags = 4
atlas = ExtResource( 2 )
region = Rect2( 0, 48, 16, 16 ) region = Rect2( 0, 48, 16, 16 )
[node name="GraphEdit" type="GraphEdit"] [node name="GraphEdit" type="GraphEdit"]
@ -37,7 +42,7 @@ margin_bottom = 24.0
alignment = 2 alignment = 2
[node name="SubGraphUI" type="HBoxContainer" parent="GraphUI"] [node name="SubGraphUI" type="HBoxContainer" parent="GraphUI"]
margin_left = 362.0 margin_left = 330.0
margin_right = 544.0 margin_right = 544.0
margin_bottom = 24.0 margin_bottom = 24.0
size_flags_horizontal = 9 size_flags_horizontal = 9
@ -48,23 +53,33 @@ margin_bottom = 24.0
rect_min_size = Vector2( 150, 0 ) rect_min_size = Vector2( 150, 0 )
size_flags_horizontal = 9 size_flags_horizontal = 9
[node name="ButtonUp" type="Button" parent="GraphUI/SubGraphUI"] [node name="ButtonTransmitsSeed" type="Button" parent="GraphUI/SubGraphUI"]
margin_left = 154.0 margin_left = 154.0
margin_right = 182.0 margin_right = 182.0
margin_bottom = 24.0 margin_bottom = 24.0
hint_tooltip = "Affect children seeds"
size_flags_horizontal = 9
toggle_mode = true
icon = SubResource( 2 )
[node name="ButtonUp" type="Button" parent="GraphUI/SubGraphUI"]
margin_left = 186.0
margin_right = 214.0
margin_bottom = 24.0
hint_tooltip = "Back to parent" hint_tooltip = "Back to parent"
size_flags_horizontal = 9 size_flags_horizontal = 9
icon = SubResource( 2 ) icon = SubResource( 3 )
[node name="ButtonShowTree" type="Button" parent="GraphUI"] [node name="ButtonShowTree" type="Button" parent="GraphUI"]
margin_left = 548.0 margin_left = 548.0
margin_right = 576.0 margin_right = 576.0
margin_bottom = 24.0 margin_bottom = 24.0
hint_tooltip = "Show hierarchy" hint_tooltip = "Show hierarchy"
icon = SubResource( 3 ) icon = SubResource( 4 )
[connection signal="connection_request" from="." to="." method="connect_node"] [connection signal="connection_request" from="." to="." method="connect_node"]
[connection signal="disconnection_request" from="." to="." method="disconnect_node"] [connection signal="disconnection_request" from="." to="." method="disconnect_node"]
[connection signal="timeout" from="Timer" to="." method="do_send_changed_signal"] [connection signal="timeout" from="Timer" to="." method="do_send_changed_signal"]
[connection signal="text_changed" from="GraphUI/SubGraphUI/Label" to="." method="_on_Label_text_changed"] [connection signal="text_changed" from="GraphUI/SubGraphUI/Label" to="." method="_on_Label_text_changed"]
[connection signal="toggled" from="GraphUI/SubGraphUI/ButtonTransmitsSeed" to="." method="_on_ButtonTransmitsSeed_toggled"]
[connection signal="pressed" from="GraphUI/SubGraphUI/ButtonUp" to="." method="on_ButtonUp_pressed"] [connection signal="pressed" from="GraphUI/SubGraphUI/ButtonUp" to="." method="on_ButtonUp_pressed"]
[connection signal="pressed" from="GraphUI/ButtonShowTree" to="." method="_on_ButtonShowTree_pressed"] [connection signal="pressed" from="GraphUI/ButtonShowTree" to="." method="_on_ButtonShowTree_pressed"]

View File

@ -3,15 +3,19 @@ extends GraphNode
class_name MMGraphNodeBase class_name MMGraphNodeBase
var generator : MMGenBase = null setget set_generator var generator : MMGenBase = null setget set_generator
var fixed_seed = false
func _ready() -> void: func _ready() -> void:
connect("offset_changed", self, "_on_offset_changed") connect("offset_changed", self, "_on_offset_changed")
func _draw(): func _draw():
if generator.has_randomness(): if generator != null and generator.has_randomness():
var icon = preload("res://addons/material_maker/icons/randomness_unlocked.tres") if fixed_seed else preload("res://addons/material_maker/icons/randomness_locked.tres") var icon = preload("res://addons/material_maker/icons/randomness_locked.tres") if generator.is_seed_locked() else preload("res://addons/material_maker/icons/randomness_unlocked.tres")
draw_texture_rect(icon, Rect2(rect_size.x-48, 4, 16, 16), false) draw_texture_rect(icon, Rect2(rect_size.x-48, 4, 16, 16), false)
if !is_connected("gui_input", self, "_on_gui_input"):
connect("gui_input", self, "_on_gui_input")
else:
if is_connected("gui_input", self, "_on_gui_input"):
disconnect("gui_input", self, "_on_gui_input")
func set_generator(g) -> void: func set_generator(g) -> void:
generator = g generator = g
@ -21,5 +25,5 @@ func _on_offset_changed() -> void:
func _on_gui_input(event): func _on_gui_input(event):
if event is InputEventMouseButton and event.pressed and event.button_index == BUTTON_LEFT and Rect2(rect_size.x-48, 4, 16, 16).has_point(event.position): if event is InputEventMouseButton and event.pressed and event.button_index == BUTTON_LEFT and Rect2(rect_size.x-48, 4, 16, 16).has_point(event.position):
fixed_seed = !fixed_seed generator.toggle_lock_seed()
update() update()