tool extends MMGenBase class_name MMGenShader var shader_model : Dictionary = {} var uses_seed = false var editable = false func toggle_editable() -> bool: editable = !editable if editable: model = null return true func is_editable() -> bool: return editable func has_randomness() -> bool: return uses_seed func get_type() -> String: return "shader" func get_type_name() -> String: if shader_model.has("name"): return shader_model.name return .get_type_name() func get_parameter_defs() -> Array: if shader_model == null or !shader_model.has("parameters"): return [] else: return shader_model.parameters func get_input_defs() -> Array: if shader_model == null or !shader_model.has("inputs"): return [] else: return shader_model.inputs func get_output_defs() -> Array: if shader_model == null or !shader_model.has("outputs"): return [] else: return shader_model.outputs func set_shader_model(data: Dictionary) -> void: shader_model = data init_parameters() uses_seed = false if shader_model.has("outputs"): for i in range(shader_model.outputs.size()): var output = shader_model.outputs[i] var output_code = "" for f in mm_io_types.types.keys(): if output.has(f): shader_model.outputs[i].type = f output_code = output[f] break if output_code == "": print("Unsupported output type") if output_code.find("$seed") != -1 or output_code.find("$(seed)") != -1: uses_seed = true if shader_model.has("code"): if shader_model.code.find("$seed") != -1 or shader_model.code.find("$(seed)") != -1: uses_seed = true 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): var search_string = "$%s(" % keyword var position = string.find(search_string) if position == -1: return null var parenthesis_level = 0 var parameter_begin = position+search_string.length() for i in range(parameter_begin, string.length()): if string[i] == '(': parenthesis_level += 1 elif string[i] == ')': if parenthesis_level == 0: return string.substr(parameter_begin, i-parameter_begin) parenthesis_level -= 1 return "" func replace_input_with_function_call(string : String, input : String) -> String: var genname = "o"+str(get_instance_id()) while true: var uv = find_keyword_call(string, input) if uv == null: break elif uv == "": print("syntax error") break string = string.replace("$%s(%s)" % [ input, uv ], "%s_input_%s(%s)" % [ genname, input, uv ]) return string func replace_input(string : String, context, input : String, type : String, src : OutputPort, default : String) -> Dictionary: var required_globals = [] var required_defs = "" var required_code = "" var required_textures = {} var new_pass_required = false while true: var uv = find_keyword_call(string, input) if uv == null: break elif uv == "": print("syntax error") break elif uv.find("$") != -1: new_pass_required = true break var src_code if src == null: src_code = subst(default, context, "(%s)" % uv) else: src_code = src.generator.get_shader_code(uv, src.output_index, context) while src_code is GDScriptFunctionState: src_code = yield(src_code, "completed") if src_code.has(type): src_code.string = src_code[type] else: src_code.string = "*error missing "+type+"*\n"+JSON.print(src_code) # Add global definitions if src_code.has("globals"): for d in src_code.globals: if required_globals.find(d) == -1: required_globals.push_back(d) # Add generated definitions if src_code.has("defs"): required_defs += src_code.defs # Add generated code if src_code.has("code"): required_code += src_code.code # Add textures if src_code.has("textures"): required_textures = src_code.textures string = string.replace("$%s(%s)" % [ input, uv ], src_code.string) return { string=string, globals=required_globals, defs=required_defs, code=required_code, textures=required_textures, new_pass_required=new_pass_required } func is_word_letter(l) -> bool: return "azertyuiopqsdfghjklmwxcvbnAZERTYUIOPQSDFGHJKLMWXCVBN1234567890_".find(l) != -1 func replace_variable(string : String, variable : String, value : String) -> String: string = string.replace("$(%s)" % variable, value) var keyword_size = variable.length()+1 var new_string = "" while !string.empty(): var pos = string.find("$"+variable) if pos == -1: new_string += string break new_string += string.left(pos) string = string.right(pos) if string.length() > keyword_size and is_word_letter(string[keyword_size]): new_string += string.left(keyword_size) string = string.right(keyword_size) continue if string.empty() or !is_word_letter(string[0]): new_string += value else: new_string += "$"+variable string = string.right(keyword_size) return new_string func subst(string : String, context : MMGenContext, uv : String = "") -> Dictionary: var genname = "o"+str(get_instance_id()) var required_globals = [] var required_defs = "" var required_code = "" var required_textures = {} string = replace_variable(string, "name", genname) if uv != "": var genname_uv = genname+"_"+str(context.get_variant(self, uv)) string = replace_variable(string, "name_uv", genname_uv) var tmp_string = replace_variable(string, "seed", "seed_"+genname) if tmp_string != string: string = tmp_string if shader_model.has("parameters") and typeof(shader_model.parameters) == TYPE_ARRAY: for p in shader_model.parameters: if !p.has("name") or !p.has("type"): continue var value = parameters[p.name] var value_string = null if p.type == "float": value_string = "p_%s_%s" % [ genname, p.name ] elif p.type == "size": value_string = "%.9f" % pow(2, value) elif p.type == "enum": if value >= p.values.size(): value = 0 value_string = p.values[value].value elif p.type == "color": value_string = "vec4(%.9f, %.9f, %.9f, %.9f)" % [ value.r, value.g, value.b, value.a ] elif p.type == "gradient": value_string = genname+"_p_"+p.name+"_gradient_fct" elif p.type == "boolean": value_string = "true" if value else "false" else: print("Cannot replace parameter of type "+p.type) if value_string != null: string = replace_variable(string, p.name, value_string) if uv != "": string = replace_variable(string, "uv", "("+uv+")") if shader_model.has("inputs") and typeof(shader_model.inputs) == TYPE_ARRAY: var cont = true while cont: var changed = false var new_pass_required = false for i in range(shader_model.inputs.size()): var input = shader_model.inputs[i] var source = get_source(i) if input.has("function") and input.function: string = replace_input_with_function_call(string, input.name) else: var result = replace_input(string, context, input.name, input.type, source, input.default) while result is GDScriptFunctionState: result = yield(result, "completed") if string != result.string: changed = true if result.new_pass_required: new_pass_required = true string = result.string # Add global definitions for d in result.globals: if required_globals.find(d) == -1: required_globals.push_back(d) # Add generated definitions required_defs += result.defs # Add generated code required_code += result.code for t in result.textures.keys(): required_textures[t] = result.textures[t] cont = changed and new_pass_required 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: var genname = "o"+str(get_instance_id()) var rv = { globals=[], defs="", code="", textures={} } if shader_model != null and shader_model.has("outputs") and shader_model.outputs.size() > output_index: var output = shader_model.outputs[output_index] if !context.has_variant(self): # Generate functions for gradients if has_randomness(): rv.defs += "uniform int seed_%s = %d;\n" % [ genname, get_seed() ] for p in shader_model.parameters: if p.type == "float": rv.defs += "uniform float p_%s_%s = %.9f;\n" % [ genname, p.name, parameters[p.name] ] elif p.type == "gradient": var g = parameters[p.name] if !(g is MMGradient): g = MMGradient.new() g.deserialize(parameters[p.name]) rv.defs += g.get_shader(genname+"_p_"+p.name+"_gradient_fct") # Generate functions for inputs if shader_model.has("inputs"): for i in range(shader_model.inputs.size()): var input = shader_model.inputs[i] if input.has("function") and input.function: var source = get_source(i) var string = "$%s(%s)" % [ input.name, mm_io_types.types[input.type].params ] var local_context = MMGenContext.new(context) var result = replace_input(string, local_context, input.name, input.type, source, input.default) while result is GDScriptFunctionState: result = yield(result, "completed") # Add global definitions for d in result.globals: if rv.globals.find(d) == -1: rv.globals.push_back(d) # Add generated definitions rv.defs += result.defs # Add textures for t in result.textures.keys(): rv.textures[t] = result.textures[t] rv.defs += "%s %s_input_%s(%s) {\n" % [ mm_io_types.types[input.type].type, genname, input.name, mm_io_types.types[input.type].paramdefs ] rv.defs += "%s\n" % result.code rv.defs += "return %s;\n}\n" % result.string if shader_model.has("instance"): var subst_output = subst(shader_model.instance, context, "") while subst_output is GDScriptFunctionState: subst_output = yield(subst_output, "completed") rv.defs += subst_output.string # process textures for t in subst_output.textures.keys(): rv.textures[t] = subst_output.textures[t] # Add inline code if shader_model.has("code"): var variant_index = context.get_variant(self, uv) if variant_index == -1: variant_index = context.get_variant(self, uv) var subst_code = subst(shader_model.code, context, uv) while subst_code is GDScriptFunctionState: subst_code = yield(subst_code, "completed") # Add global definitions for d in subst_code.globals: if rv.globals.find(d) == -1: rv.globals.push_back(d) # Add generated definitions rv.defs += subst_code.defs # Add generated code rv.code += subst_code.code rv.code += subst_code.string # Add output_code var variant_string = uv+","+str(output_index) var variant_index = context.get_variant(self, variant_string) if variant_index == -1: variant_index = context.get_variant(self, variant_string) for f in mm_io_types.types.keys(): if output.has(f): var subst_output = subst(output[f], context, uv) while subst_output is GDScriptFunctionState: subst_output = yield(subst_output, "completed") # Add global definitions for d in subst_output.globals: if rv.globals.find(d) == -1: rv.globals.push_back(d) # Add generated definitions rv.defs += subst_output.defs # Add generated code rv.code += subst_output.code rv.code += "%s %s_%d_%d_%s = %s;\n" % [ mm_io_types.types[f].type, genname, output_index, variant_index, f, subst_output.string ] for t in subst_output.textures.keys(): rv.textures[t] = subst_output.textures[t] for f in mm_io_types.types.keys(): if output.has(f): rv[f] = "%s_%d_%d_%s" % [ genname, output_index, variant_index, f ] rv.type = output.type if shader_model.has("global") && rv.globals.find(shader_model.global) == -1: rv.globals.push_back(shader_model.global) return rv func _serialize(data: Dictionary) -> Dictionary: data.shader_model = shader_model 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: if shader_model != null: var edit_window = load("res://material_maker/widgets/node_editor/node_editor.tscn").instance() node.get_parent().add_child(edit_window) edit_window.set_model_data(shader_model) edit_window.connect("node_changed", node, "update_generator") edit_window.popup_centered()