material-maker/addons/material_maker/engine/gen_graph.gd
RodZill4 c121f7c00a 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).
2019-11-04 07:58:17 +01:00

320 lines
9.6 KiB
GDScript

tool
extends MMGenBase
class_name MMGenGraph
var label : String = "Graph"
var connections = []
var editable : bool = false
var transmits_seed : bool = true
signal connections_changed(removed_connections, added_connections)
func fix_remotes() -> void:
for c in get_children():
if c is MMGenRemote:
c.fix()
func _post_load() -> void:
fix_remotes()
func has_randomness():
return transmits_seed
func get_type() -> String:
return "graph"
func get_type_name() -> String:
return label
func toggle_editable() -> bool:
editable = !editable
if editable:
model = null
return true
func is_editable() -> bool:
return editable
func get_parameter_defs() -> Array:
if has_node("gen_parameters"):
return get_node("gen_parameters").get_parameter_defs()
return []
func set_parameter(p, v):
if has_node("gen_parameters"):
return get_node("gen_parameters").set_parameter(p, v)
func get_input_defs() -> Array:
if has_node("gen_inputs"):
return get_node("gen_inputs").get_output_defs()
return []
func get_output_defs() -> Array:
if has_node("gen_outputs"):
return get_node("gen_outputs").get_input_defs()
return []
func source_changed(input_index : int):
if has_node("gen_inputs"):
return get_node("gen_inputs").source_changed(input_index)
func get_port_source(gen_name: String, input_index: int) -> OutputPort:
if gen_name == "gen_inputs":
var parent = get_parent()
if parent != null and parent.get_script() == get_script():
return parent.get_port_source(name, input_index)
else:
for c in connections:
if c.to == gen_name and c.to_port == input_index:
var src_gen = get_node(c.from)
if src_gen != null:
if src_gen.get_script() == get_script():
return src_gen.get_port_source("gen_outputs", c.from_port)
return OutputPort.new(src_gen, c.from_port)
return null
func get_port_targets(gen_name: String, output_index: int) -> Array:
var rv = []
for c in connections:
if c.from == gen_name and c.from_port == output_index:
var tgt_gen = get_node(c.to)
if tgt_gen != null:
rv.push_back(InputPort.new(tgt_gen, c.to_port))
return rv
func add_generator(generator : MMGenBase) -> bool:
var name = generator.name
var index = 1
while has_node(name):
index += 1
name = generator.name + "_" + str(index)
generator.name = name
add_child(generator)
return true
func remove_generator(generator : MMGenBase) -> bool:
if !generator.can_be_deleted():
return false
var new_connections = []
for c in connections:
if c.from != generator.name and c.to != generator.name:
new_connections.append(c)
connections = new_connections
remove_child(generator)
fix_remotes()
generator.queue_free()
return true
func replace_generator(old : MMGenBase, new : MMGenBase) -> void:
new.name = old.name
new.position = old.position
remove_child(old)
old.free()
add_child(new)
func get_connected_inputs(generator) -> Array:
var rv : Array = []
for c in connections:
if c.to == generator.name:
rv.push_back(c.to_port)
return rv
func get_connected_outputs(generator) -> Array:
var rv : Array = []
for c in connections:
if c.from == generator.name:
rv.push_back(c.from_port)
return rv
func connect_children(from, from_port : int, to, to_port : int) -> bool:
# check the new connection does not create a loop
var spreadlist = [ InputPort.new(to, to_port) ]
while !spreadlist.empty():
var input : InputPort = spreadlist.pop_front()
for o in input.generator.follow_input(input.input_index):
if o.generator == from and o.output_index == from_port:
return false
for t in o.generator.get_parent().get_port_targets(o.generator.name, o.output_index):
spreadlist.push_back(t)
# disconnect target
while true:
var remove = -1
for i in connections.size():
if connections[i].to == to.name and connections[i].to_port == to_port:
remove = i
break
if remove == -1:
break
connections.remove(remove)
# create new connection
connections.append({from=from.name, from_port=from_port, to=to.name, to_port=to_port})
to.source_changed(to_port)
return true
func disconnect_children(from, from_port : int, to, to_port : int) -> bool:
var new_connections : Array = []
for c in connections:
if c.from != from.name or c.from_port != from_port or c.to != to.name or c.to_port != to_port:
new_connections.push_back(c)
else:
to.source_changed(to_port)
connections = new_connections
return true
func reconnect_inputs(generator, reconnects : Dictionary) -> bool:
var new_connections : Array = []
var added_connections : Array = []
var removed_connections : Array = []
for c in connections:
if c.to == generator.name and reconnects.has(c.to_port):
removed_connections.push_back(c.duplicate(true))
if reconnects[c.to_port] < 0:
continue
c.to_port = reconnects[c.to_port]
added_connections.push_back(c.duplicate(true))
new_connections.push_back(c)
connections = new_connections
if !removed_connections.empty():
emit_signal("connections_changed", removed_connections, added_connections)
return true
func reconnect_outputs(generator, reconnects : Dictionary) -> bool:
var new_connections : Array = []
var added_connections : Array = []
var removed_connections : Array = []
for c in connections:
if c.from == generator.name and reconnects.has(c.from_port):
removed_connections.push_back(c.duplicate(true))
if reconnects[c.from_port] < 0:
continue
c.from_port = reconnects[c.from_port]
added_connections.push_back(c.duplicate(true))
new_connections.push_back(c)
connections = new_connections
if !removed_connections.empty():
emit_signal("connections_changed", removed_connections, added_connections)
return true
func _get_shader_code(uv : String, output_index : int, context : MMGenContext) -> Dictionary:
var outputs = get_node("gen_outputs")
if outputs != null:
var rv = outputs._get_shader_code(uv, output_index, context)
while rv is GDScriptFunctionState:
rv = yield(rv, "completed")
return rv
return { globals=[], defs="", code="", textures={} }
func edit(node) -> void:
node.get_parent().call_deferred("update_view", self)
func create_subgraph(gens : Array) -> void:
# Remove material, gen_inputs and gen_outputs nodes
var generators = []
var center = Vector2(0, 0)
var left_bound = 65535
var right_bound = -65536
var upper_bound = 65536
var count = 0
# Filter group nodes and calculate boundin box
for g in gens:
if g.name != "Material" and g.name != "gen_inputs" and g.name != "gen_outputs":
generators.push_back(g)
var p = g.position
center += p
count += 1
if left_bound > p.x: left_bound = p.x
if right_bound < p.x: right_bound = p.x
if upper_bound > p.y: upper_bound = p.y
if count == 0:
return
center /= count
# Create a new graph and add it to the current one
var new_graph = get_script().new()
new_graph.name = "graph"
add_generator(new_graph)
new_graph.position = center
# Add grouped generators and keep their names
var names : Array = []
for g in generators:
names.push_back(g.name)
remove_child(g)
new_graph.add_generator(g)
# Create inputs and outputs generators
var gen_inputs = MMGenIOs.new()
gen_inputs.name = "gen_inputs"
gen_inputs.position = Vector2(left_bound-300, center.y)
new_graph.add_generator(gen_inputs)
var gen_outputs = MMGenIOs.new()
gen_outputs.name = "gen_outputs"
gen_outputs.position = Vector2(right_bound+300, center.y)
new_graph.add_generator(gen_outputs)
# Process connections
var new_graph_connections = []
var my_new_connections = []
var inputs = []
var outputs = []
for c in connections:
var src_name = c.from+"."+str(c.from_port)
if names.find(c.from) != -1 and names.find(c.to) != -1:
new_graph_connections.push_back(c)
elif names.find(c.from) != -1:
var port_index = outputs.find(src_name)
if port_index == -1:
port_index = outputs.size()
outputs.push_back(src_name)
gen_outputs.ports.push_back( { name="port"+str(port_index), type="rgba" } )
print(gen_outputs.ports)
my_new_connections.push_back( { from=new_graph.name, from_port=port_index, to=c.to, to_port=c.to_port } )
new_graph_connections.push_back( { from=c.from, from_port=c.from_port, to="gen_outputs", to_port=port_index } )
elif names.find(c.to) != -1:
var port_index = inputs.find(src_name)
if port_index == -1:
port_index = inputs.size()
inputs.push_back(src_name)
gen_inputs.ports.push_back( { name="port"+str(port_index), type="rgba" } )
my_new_connections.push_back( { from=c.from, from_port=c.from_port, to=new_graph.name, to_port=port_index } )
new_graph_connections.push_back( { from="gen_inputs", from_port=port_index, to=c.to, to_port=c.to_port } )
else:
my_new_connections.push_back(c)
connections = my_new_connections
new_graph.connections = new_graph_connections
var found_remote = false
var remote_script = load("res://addons/material_maker/engine/gen_remote.gd")
for g in generators:
if g.get_script() == remote_script:
g.name = "gen_parameters"
found_remote = true
new_graph.parameters = g.parameters.duplicate(true)
break
if !found_remote:
var gen_parameters = remote_script.new()
gen_parameters.name = "gen_parameters"
gen_parameters.position = Vector2(center.x - 200, upper_bound-300)
new_graph.add_child(gen_parameters)
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)